Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 04 Oct 2017 14:57:59 -0700
changeset 675194 c3b7759671deae73e40ebca01d7f23a326a4b8c2
parent 674983 ee1c41cf306df0043a8e68af042f133acf2ef94e (current diff)
parent 675193 1d9045285aa9a481f4d115153991178a8485fc53 (diff)
child 675195 463fc741a109bc8109f8bb496efc2bc8bec6951a
child 675205 dedd9a48da6982467b8b4b635eec59521168ec19
child 675210 dc9c94098596adb757afd1eff05192a2aea7d055
child 675246 00a6cb94ad70d28b101f3dd5b73af7d47d200d0f
child 675556 183c0e03a22917c3e4ea3b8631ab998f61ffa106
child 675600 4aa9e0a67dff574f0ae2e9fba9cd0c6c6678554c
child 675751 06fce0f95f19b43ebb635f7ba293608dca9809a3
child 675863 e7933bdfe64d6ffd016f77e3aea939f1f37d9966
child 685670 24add0311f223def7a000d97d8eca3dc054da3d0
push id83058
push userbwerth@mozilla.com
push dateWed, 04 Oct 2017 23:14:13 +0000
reviewersmerge
milestone58.0a1
Merge autoland to central, a=merge MozReview-Commit-ID: 5q3B4i0wpSI
devtools/client/jsonview/components/headers-panel.js
devtools/client/jsonview/components/headers.js
devtools/client/jsonview/components/json-panel.js
devtools/client/jsonview/components/main-tabbed-area.js
devtools/client/jsonview/components/reps/toolbar.js
devtools/client/jsonview/components/search-box.js
devtools/client/jsonview/components/text-panel.js
devtools/server/actors/performance-entries.js
devtools/shared/fronts/performance-entries.js
devtools/shared/specs/performance-entries.js
js/src/jit-test/tests/auto-regress/bug1334573.js
js/src/tests/test262-intl-extras.js
js/xpconnect/loader/ISO8601DateUtils.jsm
third_party/rust/rayon/src/slice.rs
tools/profiler/core/ProfileBufferEntry.cpp
tools/profiler/core/platform-linux-android.cpp
tools/profiler/core/platform-macos.cpp
tools/profiler/core/platform-win32.cpp
tools/profiler/core/platform.cpp
tools/profiler/core/platform.h
tools/profiler/tasktracer/GeckoTaskTracer.cpp
tools/profiler/tests/gtest/ThreadProfileTest.cpp
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1895,16 +1895,29 @@ var BookmarkingUI = {
       switch (button.id) {
         case "panelMenu_toggleBookmarksMenu":
           let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
           button.setAttribute("checked", !!placement && placement.area == CustomizableUI.AREA_NAVBAR);
           break;
         case "panelMenu_viewBookmarksSidebar":
           button.setAttribute("checked", SidebarUI.currentID == "viewBookmarksSidebar");
           break;
+        case "panelMenu_viewBookmarksToolbar":
+          let toolbar = document.getElementById("PersonalToolbar");
+          // This is an actual toolbarbutton[type=checkbox], and its checked
+          // attribute will get added/removed by the binding when clicked.
+          // Setting the attribute to 'false' breaks showing the toolbar,
+          // because the binding removes the attribute instead of setting it
+          // to 'true' when clicked.
+          if (toolbar.getAttribute("collapsed") != "true") {
+            button.setAttribute("checked", "true");
+          } else {
+            button.removeAttribute("checked");
+          }
+          break;
         default:
           update = false;
           break;
       }
       if (update) {
         updateToggleControlLabel(button);
       }
     }
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -158,18 +158,16 @@ var whitelist = [
   {file: "chrome://mozapps/skin/plugins/pluginBlocked.png"},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
   // Bug 1351078
   {file: "resource://gre/modules/Battery.jsm"},
-  // Bug 1351079
-  {file: "resource://gre/modules/ISO8601DateUtils.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
 ];
 
 whitelist = new Set(whitelist.filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -210,17 +210,17 @@ const CustomizableWidgets = [
       let document = event.target.ownerDocument;
       let window = document.defaultView;
       let viewType = panelview.id == this.recentlyClosedTabsPanel ? "Tabs" : "Windows";
 
       this._panelMenuView.clearAllContents(panelview);
 
       let utils = RecentlyClosedTabsAndWindowsMenuUtils;
       let method = `get${viewType}Fragment`;
-      let fragment = utils[method](window, "toolbarbutton");
+      let fragment = utils[method](window, "toolbarbutton", true);
       let elementCount = fragment.childElementCount;
       this._panelMenuView._setEmptyPopupStatus(panelview, !elementCount);
       if (!elementCount)
         return;
 
       let body = document.createElement("vbox");
       body.className = "panel-subview-body";
       body.appendChild(fragment);
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -457,18 +457,20 @@ var gSearchResultsPane = {
    * @param String query
    *    Word or words that are being searched for
    */
   createSearchTooltip(anchorNode, query) {
     if (anchorNode.tooltipNode) {
       return;
     }
     let searchTooltip = anchorNode.ownerDocument.createElement("span");
+    let searchTooltipText = anchorNode.ownerDocument.createElement("span");
     searchTooltip.setAttribute("class", "search-tooltip");
-    searchTooltip.textContent = query;
+    searchTooltipText.textContent = query;
+    searchTooltip.appendChild(searchTooltipText);
 
     // Set tooltipNode property to track corresponded tooltip node.
     anchorNode.tooltipNode = searchTooltip;
     anchorNode.parentElement.classList.add("search-tooltip-parent");
     anchorNode.parentElement.appendChild(searchTooltip);
 
     this.calculateTooltipPosition(anchorNode);
   },
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -170,17 +170,20 @@
         <label class="category-name" flex="1">&paneSync1.title;</label>
       </richlistitem>
     </richlistbox>
 
     <keyset>
       <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
     </keyset>
 
-    <html:a class="help-button" target="_blank" aria-label="&helpButton2.label;">&helpButton2.label;</html:a>
+    <html:a class="help-button" target="_blank" aria-label="&helpButton2.label;">
+      <html:img class="help-icon" src="chrome://global/skin/icons/help.svg"/>
+      <html:span class="help-label">&helpButton2.label;</html:span>
+    </html:a>
 
     <vbox class="main-content" flex="1" align="start">
       <vbox class="pane-container">
         <hbox class="search-container" pack="end">
           <textbox type="search" id="searchInput" style="width: &searchField.width;" hidden="true" clickSelectsAll="true"/>
         </hbox>
         <prefpane id="mainPrefPane">
 #include searchResults.xul
--- a/browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js
+++ b/browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js
@@ -16,22 +16,22 @@ add_task(async function setup() {
   await pushPrefs(["dom.ipc.processCount", 1],
                   ["toolkit.cosmeticAnimations.enabled", false],
                   ["browser.sessionstore.restore_on_demand", false]);
 });
 
 add_task(async function test_revive_bg_tabs_on_demand() {
   let newTab1 = BrowserTestUtils.addTab(gBrowser, PAGE_1);
   let browser1 = newTab1.linkedBrowser;
+  await BrowserTestUtils.browserLoaded(browser1);
+
   gBrowser.selectedTab = newTab1;
 
   let newTab2 = BrowserTestUtils.addTab(gBrowser, PAGE_2);
   let browser2 = newTab2.linkedBrowser;
-
-  await BrowserTestUtils.browserLoaded(browser1);
   await BrowserTestUtils.browserLoaded(browser2);
 
   await TabStateFlusher.flush(browser2);
 
   // Now crash the selected tab
   let windowReady = BrowserTestUtils.waitForEvent(window, "SSWindowStateReady");
   await BrowserTestUtils.crashBrowser(browser1);
 
--- a/browser/extensions/screenshots/webextension/background/startBackground.js
+++ b/browser/extensions/screenshots/webextension/background/startBackground.js
@@ -130,17 +130,19 @@ this.startBackground = (function() {
     // Set up this side of the Photon page action port.  The other side is in
     // bootstrap.js.  Ideally, in the future, WebExtension page actions and
     // Photon page actions would be one in the same, but they aren't right now.
     photonPageActionPort = browser.runtime.connect({ name: "photonPageActionPort" });
     photonPageActionPort.onMessage.addListener((message) => {
       switch (message.type) {
       case "click":
         loadIfNecessary().then(() => {
-          main.onClicked(message.tab);
+          return browser.tabs.get(message.tab.id);
+        }).then((tab) => {
+          main.onClicked(tab);
         }).catch((error) => {
           console.error("Error loading Screenshots:", error);
         });
         break;
       default:
         console.error("Unrecognized message:", message);
         break;
       }
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -946,34 +946,38 @@ toolbaritem[cui-areatype="menu-panel"][s
     (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
     need adjusting (see bug 1248506) */
   padding-bottom: 30px;
   padding-top: 15px;
 }
 
 .PanelUI-remotetabs-prefs-button {
   -moz-appearance: none;
-  background-color: #0096dd;
+  background-color: #0060df;
   /* !important for the color as an OSX specific rule when a lightweight theme
      is used for buttons in the toolbox overrides. See bug 1238531 for details */
   color: white !important;
+  font-size: 13px;
   border-radius: 2px;
   /* If you change the margin or padding below, the min-height of the synced tabs
      panel (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
      etc) may need adjusting (see bug 1248506) */
   margin-top: 10px;
   margin-bottom: 10px;
   padding: 8px;
   text-shadow: none;
   min-width: 200px;
 }
 
-.PanelUI-remotetabs-prefs-button:hover,
+.PanelUI-remotetabs-prefs-button:hover {
+  background-color: #003eaa;
+}
+
 .PanelUI-remotetabs-prefs-button:hover:active {
-  background-color: #018acb;
+  background-color: #002275;
 }
 
 .remotetabs-promo-link {
   margin: 0;
 }
 
 .PanelUI-remotetabs-notabsforclient-label {
   color: GrayText;
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -606,26 +606,24 @@ button > hbox > label {
 }
 
 .help-button {
   display: flex;
   align-items: center;
   position: fixed;
   left: 0;
   bottom: 36px;
-  background-image: url("chrome://global/skin/icons/help.svg");
+  background-image: none;
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   line-height: 22px;
   height: unset;
   min-height: 36px;
   width: 168px;
-  background-position: left 10px center;
-  background-size: 16px;
-  padding-inline-start: 38px;
+  padding-inline-start: 18px;
   margin-inline-start: 34px;
 }
 
 .help-button:-moz-locale-dir(rtl) {
   background-position: right 10px top 10px;
   left: auto;
   right: 0;
 }
@@ -636,16 +634,38 @@ button > hbox > label {
 
 .help-button:link,
 .help-button:visited {
   color: var(--in-content-category-text);
   text-decoration: none;
   border-radius: 2px;
 }
 
+.help-icon {
+  width: 16px;
+  height: 16px;
+}
+
+.help-label {
+  margin: 0 14px;
+}
+
+@media (max-width: 830px) {
+  .help-button {
+    width: 36px;
+    padding: 0;
+    justify-content: center;
+    margin-inline-start: 40px;
+  }
+
+  .help-label {
+    display: none;
+  }
+}
+
 .face-sad {
   list-style-image: url("chrome://browser/skin/preferences/in-content/face-sad.svg");
   width: 20px;
   height: 20px;
   margin-inline-end: 8px;
   margin-top: 5px;
   margin-bottom: 5px;
 }
@@ -671,22 +691,22 @@ button > hbox > label {
   margin: 20px 0 30px 0;
 }
 
 #searchInput .textbox-search-icons:not([selectedIndex="1"]) {
   display: none;
 }
 
 .search-tooltip {
+  max-width: 150px;
   font-size: 1.25rem;
   position: absolute;
   padding: 0 10px;
   background-color: #ffe900;
   border: 1px solid #d7b600;
-  -moz-user-select: none;
   bottom: 36px;
 }
 
 .search-tooltip:hover,
 .search-tooltip:hover::before {
   opacity: .1;
 }
 
@@ -707,16 +727,23 @@ button > hbox > label {
   top: 100%;
   offset-inline-start: calc(50% - 6px);
 }
 
 .search-tooltip-parent {
   position: relative;
 }
 
+.search-tooltip > span {
+  -moz-user-select: none;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
 .visually-hidden {
   visibility: hidden;
 }
 
 menulist {
   height: 30px;
   margin-top: 4px;
   margin-bottom: 4px;
--- a/devtools/client/dom/content/components/main-toolbar.js
+++ b/devtools/client/dom/content/components/main-toolbar.js
@@ -6,17 +6,17 @@
 "use strict";
 
 // React
 const React = require("devtools/client/shared/vendor/react");
 const { l10n } = require("../utils");
 
 // Reps
 const { createFactories } = require("devtools/client/shared/react-utils");
-const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/toolbar"));
+const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/Toolbar"));
 
 // DOM Panel
 const SearchBox = React.createFactory(require("devtools/client/shared/components/SearchBox"));
 
 // Actions
 const { fetchProperties } = require("../actions/grips");
 const { setVisibilityFilter } = require("../actions/filter");
 
rename from devtools/client/jsonview/components/headers.js
rename to devtools/client/jsonview/components/Headers.js
rename from devtools/client/jsonview/components/headers-panel.js
rename to devtools/client/jsonview/components/HeadersPanel.js
--- a/devtools/client/jsonview/components/headers-panel.js
+++ b/devtools/client/jsonview/components/HeadersPanel.js
@@ -6,18 +6,18 @@
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
   const { createFactories } = require("devtools/client/shared/react-utils");
 
-  const { Headers } = createFactories(require("./headers"));
-  const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
+  const { Headers } = createFactories(require("./Headers"));
+  const { Toolbar, ToolbarButton } = createFactories(require("./reps/Toolbar"));
 
   const { div } = dom;
 
   /**
    * This template represents the 'Headers' panel
    * s responsible for rendering its content.
    */
   let HeadersPanel = createClass({
rename from devtools/client/jsonview/components/json-panel.js
rename to devtools/client/jsonview/components/JsonPanel.js
--- a/devtools/client/jsonview/components/json-panel.js
+++ b/devtools/client/jsonview/components/JsonPanel.js
@@ -10,18 +10,18 @@ define(function (require, exports, modul
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
   const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
   const TreeView = createFactory(TreeViewClass);
 
   const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
   const { createFactories } = require("devtools/client/shared/react-utils");
   const { Rep } = REPS;
 
-  const { SearchBox } = createFactories(require("./search-box"));
-  const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
+  const { SearchBox } = createFactories(require("./SearchBox"));
+  const { Toolbar, ToolbarButton } = createFactories(require("./reps/Toolbar"));
 
   const { div } = dom;
   const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
   const AUTO_EXPAND_MAX_LEVEL = 7;
 
   function isObject(value) {
     return Object(value) === value;
   }
rename from devtools/client/jsonview/components/main-tabbed-area.js
rename to devtools/client/jsonview/components/MainTabbedArea.js
--- a/devtools/client/jsonview/components/main-tabbed-area.js
+++ b/devtools/client/jsonview/components/MainTabbedArea.js
@@ -5,19 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
   const { createFactories } = require("devtools/client/shared/react-utils");
-  const { JsonPanel } = createFactories(require("./json-panel"));
-  const { TextPanel } = createFactories(require("./text-panel"));
-  const { HeadersPanel } = createFactories(require("./headers-panel"));
+  const { JsonPanel } = createFactories(require("./JsonPanel"));
+  const { TextPanel } = createFactories(require("./TextPanel"));
+  const { HeadersPanel } = createFactories(require("./HeadersPanel"));
   const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/Tabs"));
 
   /**
    * This object represents the root application template
    * responsible for rendering the basic tab layout.
    */
   let MainTabbedArea = createClass({
     displayName: "MainTabbedArea",
rename from devtools/client/jsonview/components/search-box.js
rename to devtools/client/jsonview/components/SearchBox.js
rename from devtools/client/jsonview/components/text-panel.js
rename to devtools/client/jsonview/components/TextPanel.js
--- a/devtools/client/jsonview/components/text-panel.js
+++ b/devtools/client/jsonview/components/TextPanel.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
   const { createFactories } = require("devtools/client/shared/react-utils");
-  const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
+  const { Toolbar, ToolbarButton } = createFactories(require("./reps/Toolbar"));
   const { div, pre } = dom;
 
   /**
    * This template represents the 'Raw Data' panel displaying
    * JSON as a text received from the server.
    */
   let TextPanel = createClass({
     displayName: "TextPanel",
--- a/devtools/client/jsonview/components/moz.build
+++ b/devtools/client/jsonview/components/moz.build
@@ -4,15 +4,15 @@
 # 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 += [
     'reps'
 ]
 
 DevToolsModules(
-    'headers-panel.js',
-    'headers.js',
-    'json-panel.js',
-    'main-tabbed-area.js',
-    'search-box.js',
-    'text-panel.js'
+    'Headers.js',
+    'HeadersPanel.js',
+    'JsonPanel.js',
+    'MainTabbedArea.js',
+    'SearchBox.js',
+    'TextPanel.js'
 )
rename from devtools/client/jsonview/components/reps/toolbar.js
rename to devtools/client/jsonview/components/reps/Toolbar.js
--- a/devtools/client/jsonview/components/reps/moz.build
+++ b/devtools/client/jsonview/components/reps/moz.build
@@ -1,9 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'toolbar.js',
+    'Toolbar.js',
 )
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.js
@@ -4,17 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { render } = require("devtools/client/shared/vendor/react-dom");
   const { createFactories } = require("devtools/client/shared/react-utils");
-  const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
+  const { MainTabbedArea } = createFactories(require("./components/MainTabbedArea"));
 
   const json = document.getElementById("json");
 
   let prettyURL;
 
   // Application state object.
   let input = {
     jsonText: json.textContent,
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -26,48 +26,69 @@ const { tunnelToInnerBrowser } = require
  * @param containerURL
  *        URL to a page that holds an inner browser.
  * @param getInnerBrowser
  *        Function that returns a Promise to the inner browser within the
  *        container page.  It is called with the outer browser that loaded the
  *        container page.
  */
 function swapToInnerBrowser({ tab, containerURL, getInnerBrowser }) {
-  let gBrowser = tab.ownerDocument.defaultView.gBrowser;
+  let browserWindow = tab.ownerGlobal;
+  let gBrowser = browserWindow.gBrowser;
   let innerBrowser;
   let tunnel;
 
   // Dispatch a custom event each time the _viewport content_ is swapped from one browser
   // to another.  DevTools server code uses this to follow the content if there is an
   // active DevTools connection.  While browser.xml does dispatch it's own SwapDocShells
   // event, this one is easier for DevTools to follow because it's only emitted once per
   // transition, instead of twice like SwapDocShells.
   let dispatchDevToolsBrowserSwap = (from, to) => {
-    let CustomEvent = tab.ownerDocument.defaultView.CustomEvent;
+    let CustomEvent = browserWindow.CustomEvent;
     let event = new CustomEvent("DevTools:BrowserSwap", {
       detail: to,
       bubbles: true,
     });
     from.dispatchEvent(event);
   };
 
+  // A version of `gBrowser.addTab` that absorbs the `TabOpen` event.
+  // The swap process uses a temporary tab, and there's no real need for others to hear
+  // about it.  This hides the temporary tab from things like WebExtensions.
+  let addTabSilently = (uri, options) => {
+    browserWindow.addEventListener("TabOpen", event => {
+      event.stopImmediatePropagation();
+    }, { capture: true, once: true });
+    return gBrowser.addTab(uri, options);
+  };
+
+  // A version of `gBrowser.swapBrowsersAndCloseOther` that absorbs the `TabClose` event.
+  // The swap process uses a temporary tab, and there's no real need for others to hear
+  // about it.  This hides the temporary tab from things like WebExtensions.
+  let swapBrowsersAndCloseOtherSilently = (ourTab, otherTab) => {
+    browserWindow.addEventListener("TabClose", event => {
+      event.stopImmediatePropagation();
+    }, { capture: true, once: true });
+    gBrowser.swapBrowsersAndCloseOther(ourTab, otherTab);
+  };
+
   return {
 
     start: Task.async(function* () {
       tab.isResponsiveDesignMode = true;
 
       // Hide the browser content temporarily while things move around to avoid displaying
       // strange intermediate states.
       tab.linkedBrowser.style.visibility = "hidden";
 
       // Freeze navigation temporarily to avoid "blinking" in the location bar.
       freezeNavigationState(tab);
 
       // 1. Create a temporary, hidden tab to load the tool UI.
-      let containerTab = gBrowser.addTab("about:blank", {
+      let containerTab = addTabSilently("about:blank", {
         skipAnimation: true,
         forceNotRemote: true,
       });
       gBrowser.hideTab(containerTab);
       let containerBrowser = containerTab.linkedBrowser;
       // Even though we load the `containerURL` with `LOAD_FLAGS_BYPASS_HISTORY` below,
       // `SessionHistory.jsm` has a fallback path for tabs with no history which
       // fabricates a history entry by reading the current URL, and this can cause the
@@ -123,17 +144,17 @@ function swapToInnerBrowser({ tab, conta
       // 5. Force the original browser tab to be non-remote since the tool UI
       //    must be loaded in the parent process, and we're about to swap the
       //    tool UI into this tab.
       gBrowser.updateBrowserRemoteness(tab.linkedBrowser, false);
 
       // 6. Swap the tool UI (with viewport showing the content) into the
       //    original browser tab and close the temporary tab used to load the
       //    tool via `swapBrowsersAndCloseOther`.
-      gBrowser.swapBrowsersAndCloseOther(tab, containerTab);
+      swapBrowsersAndCloseOtherSilently(tab, containerTab);
 
       // 7. Start a tunnel from the tool tab's browser to the viewport browser
       //    so that some browser UI functions, like navigation, are connected to
       //    the content in the viewport, instead of the tool page.
       tunnel = tunnelToInnerBrowser(tab.linkedBrowser, innerBrowser);
       yield tunnel.start();
 
       // Swapping browsers disconnects the find bar UI from the browser.
@@ -161,17 +182,17 @@ function swapToInnerBrowser({ tab, conta
       // strange intermediate states.
       tab.linkedBrowser.style.visibility = "hidden";
 
       // 1. Stop the tunnel between outer and inner browsers.
       tunnel.stop();
       tunnel = null;
 
       // 2. Create a temporary, hidden tab to hold the content.
-      let contentTab = gBrowser.addTab("about:blank", {
+      let contentTab = addTabSilently("about:blank", {
         skipAnimation: true,
       });
       gBrowser.hideTab(contentTab);
       let contentBrowser = contentTab.linkedBrowser;
 
       // 3. Mark the content tab browser's docshell as active so the frame
       //    is created eagerly and will be ready to swap.
       contentBrowser.docShellIsActive = true;
@@ -194,30 +215,31 @@ function swapToInnerBrowser({ tab, conta
       gBrowser.updateBrowserRemoteness(tab.linkedBrowser, true, {
         remoteType: contentBrowser.remoteType,
       });
 
       // 6. Swap the content into the original browser tab and close the
       //    temporary tab used to hold the content via
       //    `swapBrowsersAndCloseOther`.
       dispatchDevToolsBrowserSwap(contentBrowser, tab.linkedBrowser);
-      gBrowser.swapBrowsersAndCloseOther(tab, contentTab);
+      swapBrowsersAndCloseOtherSilently(tab, contentTab);
 
       // Swapping browsers disconnects the find bar UI from the browser.
       // If the find bar has been initialized, reconnect it.
       if (gBrowser.isFindBarInitialized(tab)) {
         let findBar = gBrowser.getFindBar(tab);
         findBar.browser = tab.linkedBrowser;
         if (!findBar.hidden) {
           // Force the find bar to activate again, restoring the search string.
           findBar.onFindCommand();
         }
       }
 
       gBrowser = null;
+      browserWindow = null;
 
       // The focus manager seems to get a little dizzy after all this swapping.  If a
       // content element had been focused inside the viewport before stopping, it will
       // have lost focus.  Activate the frame to restore expected focus.
       tab.linkedBrowser.frameLoader.activateRemoteFrame();
 
       delete tab.isResponsiveDesignMode;
 
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -35,16 +35,37 @@ const SWAPPED_BROWSER_STATE = [
   "_fullZoom",
   "_textZoom",
   "_isSyntheticDocument",
   "_innerWindowID",
   "_manifestURI",
 ];
 
 /**
+ * Various parts of the Firefox code base expect to access properties on the browser
+ * window in response to events (by reaching for the window via the event's target).
+ *
+ * When RDM is enabled, these bits of code instead reach the RDM tool's window instead of
+ * the browser window, which won't have the properties they are looking for. At the
+ * moment, we address this by exposing them from the browser window on RDM's window as
+ * needed.
+ */
+const PROPERTIES_FROM_BROWSER_WINDOW = [
+  // This is used by PermissionUI.jsm for permission doorhangers.
+  "PopupNotifications",
+  // These are used by ContentClick.jsm when opening links in ways other than just
+  // navigating the viewport, such as a new tab by pressing Cmd-Click.
+  "whereToOpenLink",
+  "openLinkIn",
+  // This is used by various event handlers, typically to call `getTabForBrowser` to map
+  // a browser back to a tab.
+  "gBrowser",
+];
+
+/**
  * This module takes an "outer" <xul:browser> from a browser tab as described by
  * Firefox's tabbrowser.xml and wires it up to an "inner" <iframe mozbrowser>
  * browser element containing arbitrary page content of interest.
  *
  * The inner <iframe mozbrowser> element is _just_ the page content.  It is not
  * enough to to replace <xul:browser> on its own.  <xul:browser> comes along
  * with lots of associated functionality via XBL bindings defined for such
  * elements in browser.xml and remote-browser.xml, and the Firefox UI depends on
@@ -194,38 +215,28 @@ function tunnelToInnerBrowser(outer, inn
       gBrowser._tabForBrowser.set(inner, tab);
 
       // All of the browser state from content was swapped onto the inner browser.  Pull
       // this state up to the outer browser.
       for (let property of SWAPPED_BROWSER_STATE) {
         outer[property] = inner[property];
       }
 
-      // Expose `PopupNotifications` on the content's owner global.
-      // This is used by PermissionUI.jsm for permission doorhangers.
-      // Note: This pollutes the responsive.html tool UI's global.
-      Object.defineProperty(inner.ownerGlobal, "PopupNotifications", {
-        get() {
-          return outer.ownerGlobal.PopupNotifications;
-        },
-        configurable: true,
-        enumerable: true,
-      });
-
-      // Expose `whereToOpenLink` on the content's owner global.
-      // This is used by ContentClick.jsm when opening links in ways other than just
-      // navigating the viewport.
-      // Note: This pollutes the responsive.html tool UI's global.
-      Object.defineProperty(inner.ownerGlobal, "whereToOpenLink", {
-        get() {
-          return outer.ownerGlobal.whereToOpenLink;
-        },
-        configurable: true,
-        enumerable: true,
-      });
+      // Expose various properties from the browser window on the RDM tool's global.  This
+      // aids various bits of code that expect to find a browser window, such as event
+      // handlers that reach for the window via the event's target.
+      for (let property of PROPERTIES_FROM_BROWSER_WINDOW) {
+        Object.defineProperty(inner.ownerGlobal, property, {
+          get() {
+            return outer.ownerGlobal[property];
+          },
+          configurable: true,
+          enumerable: true,
+        });
+      }
 
       // Add mozbrowser event handlers
       inner.addEventListener("mozbrowseropenwindow", this);
     }),
 
     handleEvent(event) {
       if (event.type != "mozbrowseropenwindow") {
         return;
@@ -272,18 +283,19 @@ function tunnelToInnerBrowser(outer, inn
       outer.destroy();
       outer.style.MozBinding = "";
 
       // Reset @remote since this is now back to a regular, non-remote browser
       outer.setAttribute("remote", "false");
       outer.removeAttribute("remoteType");
 
       // Delete browser window properties exposed on content's owner global
-      delete inner.ownerGlobal.PopupNotifications;
-      delete inner.ownerGlobal.whereToOpenLink;
+      for (let property of PROPERTIES_FROM_BROWSER_WINDOW) {
+        delete inner.ownerGlobal[property];
+      }
 
       // Remove mozbrowser event handlers
       inner.removeEventListener("mozbrowseropenwindow", this);
 
       mmTunnel.destroy();
       mmTunnel = null;
 
       // Reset overridden XBL properties and methods.  Deleting the override
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -12,16 +12,17 @@ support-files =
   touch.html
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/framework/test/shared-redux-head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
+[browser_cmd_click.js]
 [browser_device_change.js]
 [browser_device_custom.js]
 [browser_device_modal_error.js]
 [browser_device_modal_exit.js]
 [browser_device_modal_submit.js]
 [browser_device_width.js]
 [browser_dpr_change.js]
 [browser_exit_button.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_cmd_click.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Ensure Cmd/Ctrl-clicking link opens a new tab
+
+const TAB_URL = "http://example.com/";
+const TEST_URL =
+  `data:text/html,<a href="${TAB_URL}">Click me</a>`
+  .replace(/ /g, "%20");
+
+addRDMTask(TEST_URL, function* ({ ui }) {
+  let store = ui.toolWindow.store;
+
+  // Wait until the viewport has been added
+  yield waitUntilState(store, state => state.viewports.length == 1);
+
+  // Cmd-click the link and wait for a new tab
+  yield waitForFrameLoad(ui, TEST_URL);
+  let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, TAB_URL);
+  BrowserTestUtils.synthesizeMouseAtCenter("a", {
+    ctrlKey: true,
+    metaKey: true,
+  }, ui.getViewportBrowser());
+  let newTab = yield newTabPromise;
+  ok(newTab, "New tab opened from link");
+  yield removeTab(newTab);
+});
--- a/devtools/client/responsive.html/test/browser/browser_target_blank.js
+++ b/devtools/client/responsive.html/test/browser/browser_target_blank.js
@@ -13,14 +13,16 @@ const TEST_URL =
 addRDMTask(TEST_URL, function* ({ ui }) {
   let store = ui.toolWindow.store;
 
   // Wait until the viewport has been added
   yield waitUntilState(store, state => state.viewports.length == 1);
 
   // Click the target="_blank" link and wait for a new tab
   yield waitForFrameLoad(ui, TEST_URL);
-  let newTab = BrowserTestUtils.waitForNewTab(gBrowser, TAB_URL);
+  let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, TAB_URL);
   spawnViewportTask(ui, {}, function* () {
     content.document.querySelector("a").click(); // eslint-disable-line
   });
-  ok(yield newTab, "New tab opened from link");
+  let newTab = yield newTabPromise;
+  ok(newTab, "New tab opened from link");
+  yield removeTab(newTab);
 });
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -167,17 +167,17 @@ skip-if = e10s # Test intermittently fai
 [browser_theme.js]
 [browser_tableWidget_basic.js]
 [browser_tableWidget_keyboard_interaction.js]
 [browser_tableWidget_mouse_interaction.js]
 [browser_telemetry_button_eyedropper.js]
 [browser_telemetry_button_paintflashing.js]
 skip-if = e10s # Bug 937167 - e10s paintflashing
 [browser_telemetry_button_responsive.js]
-skip-if = !e10s # RDM only works for remote tabs
+skip-if = !e10s || os == "win" # RDM only works for remote tabs, Win: bug 1404197
 [browser_telemetry_button_scratchpad.js]
 [browser_telemetry_sidebar.js]
 [browser_telemetry_toolbox.js]
 [browser_telemetry_toolboxtabs_canvasdebugger.js]
 [browser_telemetry_toolboxtabs_inspector.js]
 [browser_telemetry_toolboxtabs_jsdebugger.js]
 [browser_telemetry_toolboxtabs_jsprofiler.js]
 [browser_telemetry_toolboxtabs_netmonitor.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_object_inspector.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_object_inspector.js
@@ -23,57 +23,73 @@ add_task(async function () {
       }, []);
     info("messages : " + JSON.stringify(messages));
   });
 
   await ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
     content.wrappedJSObject.console.log(
       "oi-test",
       [1, 2, {a: "a", b: "b"}],
-      {c: "c", d: [3, 4]}
+      {c: "c", d: [3, 4], length: 987}
     );
   });
 
   let node = await waitFor(() => findMessage(hud, "oi-test"));
   const objectInspectors = [...node.querySelectorAll(".tree")];
   is(objectInspectors.length, 2, "There is the expected number of object inspectors");
 
-  const [arrayOi] = objectInspectors;
+  const [arrayOi, objectOi] = objectInspectors;
 
   info("Expanding the array object inspector");
 
   let onArrayOiMutation = waitForNodeMutation(arrayOi, {
     childList: true
   });
 
   arrayOi.querySelector(".arrow").click();
   await onArrayOiMutation;
 
   ok(arrayOi.querySelector(".arrow").classList.contains("expanded"),
     "The arrow of the root node of the tree is expanded after clicking on it");
 
   let arrayOiNodes = arrayOi.querySelectorAll(".node");
-  // There are 6 nodes: the root, 1, 2, {a: "a", b: "b"}, length and the proto.
+
+  // The object inspector now looks like:
+  // ▼ […]
+  // |  0: 1
+  // |  1: 2
+  // |  ▶︎ 2: {a: "a", b: "b"}
+  // |  length: 3
+  // |  ▶︎ __proto__
   is(arrayOiNodes.length, 6, "There is the expected number of nodes in the tree");
 
   info("Expanding a leaf of the array object inspector");
   let arrayOiNestedObject = arrayOiNodes[3];
   onArrayOiMutation = waitForNodeMutation(arrayOi, {
     childList: true
   });
 
   arrayOiNestedObject.querySelector(".arrow").click();
   await onArrayOiMutation;
 
   ok(arrayOiNestedObject.querySelector(".arrow").classList.contains("expanded"),
     "The arrow of the root node of the tree is expanded after clicking on it");
 
   arrayOiNodes = arrayOi.querySelectorAll(".node");
-  // There are now 9 nodes, the 6 original ones, and 3 new from the expanded object:
-  // a, b and the proto.
+
+  // The object inspector now looks like:
+  // ▼ […]
+  // |  0: 1
+  // |  1: 2
+  // |  ▼ 2: {…}
+  // |  |  a: "a"
+  // |  |  b: "b"
+  // |  |  ▶︎ __proto__
+  // |  length: 3
+  // |  ▶︎ __proto__
   is(arrayOiNodes.length, 9, "There is the expected number of nodes in the tree");
 
   info("Collapsing the root");
   onArrayOiMutation = waitForNodeMutation(arrayOi, {
     childList: true
   });
   arrayOi.querySelector(".arrow").click();
 
@@ -93,9 +109,28 @@ add_task(async function () {
     "The arrow of the root node of the tree is expanded again after clicking on it");
 
   arrayOiNodes = arrayOi.querySelectorAll(".node");
   arrayOiNestedObject = arrayOiNodes[3];
   ok(arrayOiNestedObject.querySelector(".arrow").classList.contains("expanded"),
     "The object tree is still expanded");
 
   is(arrayOiNodes.length, 9, "There is the expected number of nodes in the tree");
+
+  let onObjectOiMutation = waitForNodeMutation(objectOi, {
+    childList: true
+  });
+
+  objectOi.querySelector(".arrow").click();
+  await onObjectOiMutation;
+
+  ok(objectOi.querySelector(".arrow").classList.contains("expanded"),
+    "The arrow of the root node of the tree is expanded after clicking on it");
+
+  let objectOiNodes = objectOi.querySelectorAll(".node");
+  // The object inspector now looks like:
+  // ▼ {…}
+  // |  c: "c"
+  // |  ▶︎ d: [3, 4]
+  // |  length: 987
+  // |  ▶︎ __proto__
+  is(objectOiNodes.length, 5, "There is the expected number of nodes in the tree");
 });
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -54,17 +54,18 @@ const {Cc, Ci, Cu} = require("chrome");
 const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
 
-const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
+const {walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
+const {nodeSpec, nodeListSpec} = require("devtools/shared/specs/node");
 
 loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "AsyncUtils", "devtools/shared/async-utils");
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
 loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "getCssPath", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "getXPath", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "colorUtils", "devtools/shared/css/color", true);
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -37,17 +37,16 @@ DevToolsModules(
     'heap-snapshot-file.js',
     'highlighters.css',
     'highlighters.js',
     'inspector.js',
     'layout.js',
     'memory.js',
     'monitor.js',
     'object.js',
-    'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'pretty-print-worker.js',
     'process.js',
     'promises.js',
     'reflow.js',
     'root.js',
deleted file mode 100644
--- a/devtools/server/actors/performance-entries.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-/**
- * The performanceEntries actor emits events corresponding to performance
- * entries. It receives `performanceentry` events containing the performance
- * entry details and emits an event containing the name, type, origin, and
- * epoch of the performance entry.
- */
-
-const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
-const performanceSpec = require("devtools/shared/specs/performance-entries");
-
-var PerformanceEntriesActor = ActorClassWithSpec(performanceSpec, {
-  listenerAdded: false,
-
-  initialize: function (conn, tabActor) {
-    Actor.prototype.initialize.call(this, conn);
-    this.window = tabActor.window;
-  },
-
-  /**
-   * Start tracking the user timings.
-   */
-  start: function () {
-    if (!this.listenerAdded) {
-      this.onPerformanceEntry = this.onPerformanceEntry.bind(this);
-      this.window.addEventListener("performanceentry", this.onPerformanceEntry, true);
-      this.listenerAdded = true;
-    }
-  },
-
-  /**
-   * Stop tracking the user timings.
-   */
-  stop: function () {
-    if (this.listenerAdded) {
-      this.window.removeEventListener("performanceentry", this.onPerformanceEntry, true);
-      this.listenerAdded = false;
-    }
-  },
-
-  destroy: function () {
-    this.stop();
-    Actor.prototype.destroy.call(this);
-  },
-
-  onPerformanceEntry: function (e) {
-    let emitDetail = {
-      type: e.entryType,
-      name: e.name,
-      origin: e.origin,
-      epoch: e.epoch
-    };
-    this.emit("entry", emitDetail);
-  }
-});
-
-exports.PerformanceEntriesActor = PerformanceEntriesActor;
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -559,21 +559,16 @@ var DebuggerServer = {
       constructor: "AnimationsActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/promises", {
       prefix: "promises",
       constructor: "PromisesActor",
       type: { tab: true }
     });
-    this.registerModule("devtools/server/actors/performance-entries", {
-      prefix: "performanceEntries",
-      constructor: "PerformanceEntriesActor",
-      type: { tab: true }
-    });
     this.registerModule("devtools/server/actors/emulation", {
       prefix: "emulation",
       constructor: "EmulationActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/webextension-inspected-window", {
       prefix: "webExtensionInspectedWindow",
       constructor: "WebExtensionInspectedWindowActor",
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -1,489 +1,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/. */
 "use strict";
 
-const { SimpleStringFront } = require("devtools/shared/fronts/string");
 const {
   Front,
   FrontClassWithSpec,
   custom,
   preEvent,
   types
 } = require("devtools/shared/protocol.js");
 const {
   inspectorSpec,
-  nodeSpec,
-  nodeListSpec,
   walkerSpec
 } = require("devtools/shared/specs/inspector");
-const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const { Task } = require("devtools/shared/task");
 loader.lazyRequireGetter(this, "nodeConstants",
   "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "CommandUtils",
   "devtools/client/shared/developer-toolbar", true);
 
-const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
-
-/**
- * Convenience API for building a list of attribute modifications
- * for the `modifyAttributes` request.
- */
-class AttributeModificationList {
-  constructor(node) {
-    this.node = node;
-    this.modifications = [];
-  }
-
-  apply() {
-    let ret = this.node.modifyAttributes(this.modifications);
-    return ret;
-  }
-
-  destroy() {
-    this.node = null;
-    this.modification = null;
-  }
-
-  setAttributeNS(ns, name, value) {
-    this.modifications.push({
-      attributeNamespace: ns,
-      attributeName: name,
-      newValue: value
-    });
-  }
-
-  setAttribute(name, value) {
-    this.setAttributeNS(undefined, name, value);
-  }
-
-  removeAttributeNS(ns, name) {
-    this.setAttributeNS(ns, name, undefined);
-  }
-
-  removeAttribute(name) {
-    this.setAttributeNS(undefined, name, undefined);
-  }
-}
-
-/**
- * Client side of the node actor.
- *
- * Node fronts are strored in a tree that mirrors the DOM tree on the
- * server, but with a few key differences:
- *  - Not all children will be necessary loaded for each node.
- *  - The order of children isn't guaranteed to be the same as the DOM.
- * Children are stored in a doubly-linked list, to make addition/removal
- * and traversal quick.
- *
- * Due to the order/incompleteness of the child list, it is safe to use
- * the parent node from clients, but the `children` request should be used
- * to traverse children.
- */
-const NodeFront = FrontClassWithSpec(nodeSpec, {
-  initialize: function (conn, form, detail, ctx) {
-    // The parent node
-    this._parent = null;
-    // The first child of this node.
-    this._child = null;
-    // The next sibling of this node.
-    this._next = null;
-    // The previous sibling of this node.
-    this._prev = null;
-    Front.prototype.initialize.call(this, conn, form, detail, ctx);
-  },
-
-  /**
-   * Destroy a node front.  The node must have been removed from the
-   * ownership tree before this is called, unless the whole walker front
-   * is being destroyed.
-   */
-  destroy: function () {
-    Front.prototype.destroy.call(this);
-  },
-
-  // Update the object given a form representation off the wire.
-  form: function (form, detail, ctx) {
-    if (detail === "actorid") {
-      this.actorID = form;
-      return;
-    }
-
-    // backward-compatibility: shortValue indicates we are connected to old server
-    if (form.shortValue) {
-      // If the value is not complete, set nodeValue to null, it will be fetched
-      // when calling getNodeValue()
-      form.nodeValue = form.incompleteValue ? null : form.shortValue;
-    }
-
-    // Shallow copy of the form.  We could just store a reference, but
-    // eventually we'll want to update some of the data.
-    this._form = Object.assign({}, form);
-    this._form.attrs = this._form.attrs ? this._form.attrs.slice() : [];
-
-    if (form.parent) {
-      // Get the owner actor for this actor (the walker), and find the
-      // parent node of this actor from it, creating a standin node if
-      // necessary.
-      let parentNodeFront = ctx.marshallPool().ensureParentFront(form.parent);
-      this.reparent(parentNodeFront);
-    }
-
-    if (form.inlineTextChild) {
-      this.inlineTextChild =
-        types.getType("domnode").read(form.inlineTextChild, ctx);
-    } else {
-      this.inlineTextChild = undefined;
-    }
-  },
-
-  /**
-   * Returns the parent NodeFront for this NodeFront.
-   */
-  parentNode: function () {
-    return this._parent;
-  },
-
-  /**
-   * Process a mutation entry as returned from the walker's `getMutations`
-   * request.  Only tries to handle changes of the node's contents
-   * themselves (character data and attribute changes), the walker itself
-   * will keep the ownership tree up to date.
-   */
-  updateMutation: function (change) {
-    if (change.type === "attributes") {
-      // We'll need to lazily reparse the attributes after this change.
-      this._attrMap = undefined;
-
-      // Update any already-existing attributes.
-      let found = false;
-      for (let i = 0; i < this.attributes.length; i++) {
-        let attr = this.attributes[i];
-        if (attr.name == change.attributeName &&
-            attr.namespace == change.attributeNamespace) {
-          if (change.newValue !== null) {
-            attr.value = change.newValue;
-          } else {
-            this.attributes.splice(i, 1);
-          }
-          found = true;
-          break;
-        }
-      }
-      // This is a new attribute. The null check is because of Bug 1192270,
-      // in the case of a newly added then removed attribute
-      if (!found && change.newValue !== null) {
-        this.attributes.push({
-          name: change.attributeName,
-          namespace: change.attributeNamespace,
-          value: change.newValue
-        });
-      }
-    } else if (change.type === "characterData") {
-      this._form.nodeValue = change.newValue;
-    } else if (change.type === "pseudoClassLock") {
-      this._form.pseudoClassLocks = change.pseudoClassLocks;
-    } else if (change.type === "events") {
-      this._form.hasEventListeners = change.hasEventListeners;
-    }
-  },
-
-  // Some accessors to make NodeFront feel more like an nsIDOMNode
-
-  get id() {
-    return this.getAttribute("id");
-  },
-
-  get nodeType() {
-    return this._form.nodeType;
-  },
-  get namespaceURI() {
-    return this._form.namespaceURI;
-  },
-  get nodeName() {
-    return this._form.nodeName;
-  },
-  get displayName() {
-    let {displayName, nodeName} = this._form;
-
-    // Keep `nodeName.toLowerCase()` for backward compatibility
-    return displayName || nodeName.toLowerCase();
-  },
-  get doctypeString() {
-    return "<!DOCTYPE " + this._form.name +
-     (this._form.publicId ? " PUBLIC \"" + this._form.publicId + "\"" : "") +
-     (this._form.systemId ? " \"" + this._form.systemId + "\"" : "") +
-     ">";
-  },
-
-  get baseURI() {
-    return this._form.baseURI;
-  },
-
-  get className() {
-    return this.getAttribute("class") || "";
-  },
-
-  get hasChildren() {
-    return this._form.numChildren > 0;
-  },
-  get numChildren() {
-    return this._form.numChildren;
-  },
-  get hasEventListeners() {
-    return this._form.hasEventListeners;
-  },
-
-  get isBeforePseudoElement() {
-    return this._form.isBeforePseudoElement;
-  },
-  get isAfterPseudoElement() {
-    return this._form.isAfterPseudoElement;
-  },
-  get isPseudoElement() {
-    return this.isBeforePseudoElement || this.isAfterPseudoElement;
-  },
-  get isAnonymous() {
-    return this._form.isAnonymous;
-  },
-  get isInHTMLDocument() {
-    return this._form.isInHTMLDocument;
-  },
-  get tagName() {
-    return this.nodeType === nodeConstants.ELEMENT_NODE ? this.nodeName : null;
-  },
-
-  get isDocumentElement() {
-    return !!this._form.isDocumentElement;
-  },
-
-  // doctype properties
-  get name() {
-    return this._form.name;
-  },
-  get publicId() {
-    return this._form.publicId;
-  },
-  get systemId() {
-    return this._form.systemId;
-  },
-
-  getAttribute: function (name) {
-    let attr = this._getAttribute(name);
-    return attr ? attr.value : null;
-  },
-  hasAttribute: function (name) {
-    this._cacheAttributes();
-    return (name in this._attrMap);
-  },
-
-  get hidden() {
-    let cls = this.getAttribute("class");
-    return cls && cls.indexOf(HIDDEN_CLASS) > -1;
-  },
-
-  get attributes() {
-    return this._form.attrs;
-  },
-
-  get pseudoClassLocks() {
-    return this._form.pseudoClassLocks || [];
-  },
-  hasPseudoClassLock: function (pseudo) {
-    return this.pseudoClassLocks.some(locked => locked === pseudo);
-  },
-
-  get isDisplayed() {
-    // The NodeActor's form contains the isDisplayed information as a boolean
-    // starting from FF32. Before that, the property is missing
-    return "isDisplayed" in this._form ? this._form.isDisplayed : true;
-  },
-
-  get isTreeDisplayed() {
-    let parent = this;
-    while (parent) {
-      if (!parent.isDisplayed) {
-        return false;
-      }
-      parent = parent.parentNode();
-    }
-    return true;
-  },
-
-  getNodeValue: custom(function () {
-    // backward-compatibility: if nodevalue is null and shortValue is defined, the actual
-    // value of the node needs to be fetched on the server.
-    if (this._form.nodeValue === null && this._form.shortValue) {
-      return this._getNodeValue();
-    }
-
-    let str = this._form.nodeValue || "";
-    return promise.resolve(new SimpleStringFront(str));
-  }, {
-    impl: "_getNodeValue"
-  }),
-
-  // Accessors for custom form properties.
-
-  getFormProperty: function (name) {
-    return this._form.props ? this._form.props[name] : null;
-  },
-
-  hasFormProperty: function (name) {
-    return this._form.props ? (name in this._form.props) : null;
-  },
-
-  get formProperties() {
-    return this._form.props;
-  },
-
-  /**
-   * Return a new AttributeModificationList for this node.
-   */
-  startModifyingAttributes: function () {
-    return new AttributeModificationList(this);
-  },
-
-  _cacheAttributes: function () {
-    if (typeof this._attrMap != "undefined") {
-      return;
-    }
-    this._attrMap = {};
-    for (let attr of this.attributes) {
-      this._attrMap[attr.name] = attr;
-    }
-  },
-
-  _getAttribute: function (name) {
-    this._cacheAttributes();
-    return this._attrMap[name] || undefined;
-  },
-
-  /**
-   * Set this node's parent.  Note that the children saved in
-   * this tree are unordered and incomplete, so shouldn't be used
-   * instead of a `children` request.
-   */
-  reparent: function (parent) {
-    if (this._parent === parent) {
-      return;
-    }
-
-    if (this._parent && this._parent._child === this) {
-      this._parent._child = this._next;
-    }
-    if (this._prev) {
-      this._prev._next = this._next;
-    }
-    if (this._next) {
-      this._next._prev = this._prev;
-    }
-    this._next = null;
-    this._prev = null;
-    this._parent = parent;
-    if (!parent) {
-      // Subtree is disconnected, we're done
-      return;
-    }
-    this._next = parent._child;
-    if (this._next) {
-      this._next._prev = this;
-    }
-    parent._child = this;
-  },
-
-  /**
-   * Return all the known children of this node.
-   */
-  treeChildren: function () {
-    let ret = [];
-    for (let child = this._child; child != null; child = child._next) {
-      ret.push(child);
-    }
-    return ret;
-  },
-
-  /**
-   * Do we use a local target?
-   * Useful to know if a rawNode is available or not.
-   *
-   * This will, one day, be removed. External code should
-   * not need to know if the target is remote or not.
-   */
-  isLocalToBeDeprecated: function () {
-    return !!this.conn._transport._serverConnection;
-  },
-
-  /**
-   * Get an nsIDOMNode for the given node front.  This only works locally,
-   * and is only intended as a stopgap during the transition to the remote
-   * protocol.  If you depend on this you're likely to break soon.
-   */
-  rawNode: function (rawNode) {
-    if (!this.isLocalToBeDeprecated()) {
-      console.warn("Tried to use rawNode on a remote connection.");
-      return null;
-    }
-    const { DebuggerServer } = require("devtools/server/main");
-    let actor = DebuggerServer.searchAllConnectionsForActor(this.actorID);
-    if (!actor) {
-      // Can happen if we try to get the raw node for an already-expired
-      // actor.
-      return null;
-    }
-    return actor.rawNode;
-  }
-});
-
-exports.NodeFront = NodeFront;
-
-/**
- * Client side of a node list as returned by querySelectorAll()
- */
-const NodeListFront = FrontClassWithSpec(nodeListSpec, {
-  initialize: function (client, form) {
-    Front.prototype.initialize.call(this, client, form);
-  },
-
-  destroy: function () {
-    Front.prototype.destroy.call(this);
-  },
-
-  marshallPool: function () {
-    return this.parent();
-  },
-
-  // Update the object given a form representation off the wire.
-  form: function (json) {
-    this.length = json.length;
-  },
-
-  item: custom(function (index) {
-    return this._item(index).then(response => {
-      return response.node;
-    });
-  }, {
-    impl: "_item"
-  }),
-
-  items: custom(function (start, end) {
-    return this._items(start, end).then(response => {
-      return response.nodes;
-    });
-  }, {
-    impl: "_items"
-  })
-});
-
-exports.NodeListFront = NodeListFront;
-
 /**
  * Client side of the DOM walker.
  */
 const WalkerFront = FrontClassWithSpec(walkerSpec, {
   // Set to true if cleanup should be requested after every mutation list.
   autoCleanup: true,
 
   /**
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -17,17 +17,17 @@ DevToolsModules(
     'emulation.js',
     'eventlooplag.js',
     'framerate.js',
     'gcli.js',
     'highlighters.js',
     'inspector.js',
     'layout.js',
     'memory.js',
-    'performance-entries.js',
+    'node.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'reflow.js',
     'storage.js',
     'string.js',
     'styles.js',
copy from devtools/shared/fronts/inspector.js
copy to devtools/shared/fronts/node.js
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/node.js
@@ -1,38 +1,74 @@
 /* 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 { SimpleStringFront } = require("devtools/shared/fronts/string");
 const {
   Front,
   FrontClassWithSpec,
   custom,
-  preEvent,
   types
 } = require("devtools/shared/protocol.js");
+
 const {
-  inspectorSpec,
   nodeSpec,
   nodeListSpec,
-  walkerSpec
-} = require("devtools/shared/specs/inspector");
+} = require("devtools/shared/specs/node");
+
 const promise = require("promise");
-const defer = require("devtools/shared/defer");
-const { Task } = require("devtools/shared/task");
+const { SimpleStringFront } = require("devtools/shared/fronts/string");
+
 loader.lazyRequireGetter(this, "nodeConstants",
   "devtools/shared/dom-node-constants");
-loader.lazyRequireGetter(this, "CommandUtils",
-  "devtools/client/shared/developer-toolbar", true);
 
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 
 /**
+ * Client side of a node list as returned by querySelectorAll()
+ */
+const NodeListFront = FrontClassWithSpec(nodeListSpec, {
+  initialize: function (client, form) {
+    Front.prototype.initialize.call(this, client, form);
+  },
+
+  destroy: function () {
+    Front.prototype.destroy.call(this);
+  },
+
+  marshallPool: function () {
+    return this.parent();
+  },
+
+  // Update the object given a form representation off the wire.
+  form: function (json) {
+    this.length = json.length;
+  },
+
+  item: custom(function (index) {
+    return this._item(index).then(response => {
+      return response.node;
+    });
+  }, {
+    impl: "_item"
+  }),
+
+  items: custom(function (start, end) {
+    return this._items(start, end).then(response => {
+      return response.nodes;
+    });
+  }, {
+    impl: "_items"
+  })
+});
+
+exports.NodeListFront = NodeListFront;
+
+/**
  * Convenience API for building a list of attribute modifications
  * for the `modifyAttributes` request.
  */
 class AttributeModificationList {
   constructor(node) {
     this.node = node;
     this.modifications = [];
   }
@@ -433,576 +469,8 @@ const NodeFront = FrontClassWithSpec(nod
       // actor.
       return null;
     }
     return actor.rawNode;
   }
 });
 
 exports.NodeFront = NodeFront;
-
-/**
- * Client side of a node list as returned by querySelectorAll()
- */
-const NodeListFront = FrontClassWithSpec(nodeListSpec, {
-  initialize: function (client, form) {
-    Front.prototype.initialize.call(this, client, form);
-  },
-
-  destroy: function () {
-    Front.prototype.destroy.call(this);
-  },
-
-  marshallPool: function () {
-    return this.parent();
-  },
-
-  // Update the object given a form representation off the wire.
-  form: function (json) {
-    this.length = json.length;
-  },
-
-  item: custom(function (index) {
-    return this._item(index).then(response => {
-      return response.node;
-    });
-  }, {
-    impl: "_item"
-  }),
-
-  items: custom(function (start, end) {
-    return this._items(start, end).then(response => {
-      return response.nodes;
-    });
-  }, {
-    impl: "_items"
-  })
-});
-
-exports.NodeListFront = NodeListFront;
-
-/**
- * Client side of the DOM walker.
- */
-const WalkerFront = FrontClassWithSpec(walkerSpec, {
-  // Set to true if cleanup should be requested after every mutation list.
-  autoCleanup: true,
-
-  /**
-   * This is kept for backward-compatibility reasons with older remote target.
-   * Targets previous to bug 916443
-   */
-  pick: custom(function () {
-    return this._pick().then(response => {
-      return response.node;
-    });
-  }, {impl: "_pick"}),
-
-  initialize: function (client, form) {
-    this._createRootNodePromise();
-    Front.prototype.initialize.call(this, client, form);
-    this._orphaned = new Set();
-    this._retainedOrphans = new Set();
-  },
-
-  destroy: function () {
-    Front.prototype.destroy.call(this);
-  },
-
-  // Update the object given a form representation off the wire.
-  form: function (json) {
-    this.actorID = json.actor;
-    this.rootNode = types.getType("domnode").read(json.root, this);
-    this._rootNodeDeferred.resolve(this.rootNode);
-    // FF42+ the actor starts exposing traits
-    this.traits = json.traits || {};
-  },
-
-  /**
-   * Clients can use walker.rootNode to get the current root node of the
-   * walker, but during a reload the root node might be null.  This
-   * method returns a promise that will resolve to the root node when it is
-   * set.
-   */
-  getRootNode: function () {
-    return this._rootNodeDeferred.promise;
-  },
-
-  /**
-   * Create the root node promise, triggering the "new-root" notification
-   * on resolution.
-   */
-  _createRootNodePromise: function () {
-    this._rootNodeDeferred = defer();
-    this._rootNodeDeferred.promise.then(() => {
-      this.emit("new-root");
-    });
-  },
-
-  /**
-   * When reading an actor form off the wire, we want to hook it up to its
-   * parent front.  The protocol guarantees that the parent will be seen
-   * by the client in either a previous or the current request.
-   * So if we've already seen this parent return it, otherwise create
-   * a bare-bones stand-in node.  The stand-in node will be updated
-   * with a real form by the end of the deserialization.
-   */
-  ensureParentFront: function (id) {
-    let front = this.get(id);
-    if (front) {
-      return front;
-    }
-
-    return types.getType("domnode").read({ actor: id }, this, "standin");
-  },
-
-  /**
-   * See the documentation for WalkerActor.prototype.retainNode for
-   * information on retained nodes.
-   *
-   * From the client's perspective, `retainNode` can fail if the node in
-   * question is removed from the ownership tree before the `retainNode`
-   * request reaches the server.  This can only happen if the client has
-   * asked the server to release nodes but hasn't gotten a response
-   * yet: Either a `releaseNode` request or a `getMutations` with `cleanup`
-   * set is outstanding.
-   *
-   * If either of those requests is outstanding AND releases the retained
-   * node, this request will fail with noSuchActor, but the ownership tree
-   * will stay in a consistent state.
-   *
-   * Because the protocol guarantees that requests will be processed and
-   * responses received in the order they were sent, we get the right
-   * semantics by setting our local retained flag on the node only AFTER
-   * a SUCCESSFUL retainNode call.
-   */
-  retainNode: custom(function (node) {
-    return this._retainNode(node).then(() => {
-      node.retained = true;
-    });
-  }, {
-    impl: "_retainNode",
-  }),
-
-  unretainNode: custom(function (node) {
-    return this._unretainNode(node).then(() => {
-      node.retained = false;
-      if (this._retainedOrphans.has(node)) {
-        this._retainedOrphans.delete(node);
-        this._releaseFront(node);
-      }
-    });
-  }, {
-    impl: "_unretainNode"
-  }),
-
-  releaseNode: custom(function (node, options = {}) {
-    // NodeFront.destroy will destroy children in the ownership tree too,
-    // mimicking what the server will do here.
-    let actorID = node.actorID;
-    this._releaseFront(node, !!options.force);
-    return this._releaseNode({ actorID: actorID });
-  }, {
-    impl: "_releaseNode"
-  }),
-
-  findInspectingNode: custom(function () {
-    return this._findInspectingNode().then(response => {
-      return response.node;
-    });
-  }, {
-    impl: "_findInspectingNode"
-  }),
-
-  querySelector: custom(function (queryNode, selector) {
-    return this._querySelector(queryNode, selector).then(response => {
-      return response.node;
-    });
-  }, {
-    impl: "_querySelector"
-  }),
-
-  getNodeActorFromObjectActor: custom(function (objectActorID) {
-    return this._getNodeActorFromObjectActor(objectActorID).then(response => {
-      return response ? response.node : null;
-    });
-  }, {
-    impl: "_getNodeActorFromObjectActor"
-  }),
-
-  getNodeActorFromWindowID: custom(function (windowID) {
-    return this._getNodeActorFromWindowID(windowID).then(response => {
-      return response ? response.node : null;
-    });
-  }, {
-    impl: "_getNodeActorFromWindowID"
-  }),
-
-  getStyleSheetOwnerNode: custom(function (styleSheetActorID) {
-    return this._getStyleSheetOwnerNode(styleSheetActorID).then(response => {
-      return response ? response.node : null;
-    });
-  }, {
-    impl: "_getStyleSheetOwnerNode"
-  }),
-
-  getNodeFromActor: custom(function (actorID, path) {
-    return this._getNodeFromActor(actorID, path).then(response => {
-      return response ? response.node : null;
-    });
-  }, {
-    impl: "_getNodeFromActor"
-  }),
-
-  /*
-   * Incrementally search the document for a given string.
-   * For modern servers, results will be searched with using the WalkerActor
-   * `search` function (includes tag names, attributes, and text contents).
-   * Only 1 result is sent back, and calling the method again with the same
-   * query will send the next result. When there are no more results to be sent
-   * back, null is sent.
-   * @param {String} query
-   * @param {Object} options
-   *    - "reverse": search backwards
-   *    - "selectorOnly": treat input as a selector string (don't search text
-   *                      tags, attributes, etc)
-   */
-  search: custom(Task.async(function* (query, options = { }) {
-    let nodeList;
-    let searchType;
-    let searchData = this.searchData = this.searchData || { };
-    let selectorOnly = !!options.selectorOnly;
-
-    // Backwards compat.  Use selector only search if the new
-    // search functionality isn't implemented, or if the caller (tests)
-    // want it.
-    if (selectorOnly || !this.traits.textSearch) {
-      searchType = "selector";
-      if (this.traits.multiFrameQuerySelectorAll) {
-        nodeList = yield this.multiFrameQuerySelectorAll(query);
-      } else {
-        nodeList = yield this.querySelectorAll(this.rootNode, query);
-      }
-    } else {
-      searchType = "search";
-      let result = yield this._search(query, options);
-      nodeList = result.list;
-    }
-
-    // If this is a new search, start at the beginning.
-    if (searchData.query !== query ||
-        searchData.selectorOnly !== selectorOnly) {
-      searchData.selectorOnly = selectorOnly;
-      searchData.query = query;
-      searchData.index = -1;
-    }
-
-    if (!nodeList.length) {
-      return null;
-    }
-
-    // Move search result cursor and cycle if necessary.
-    searchData.index = options.reverse ? searchData.index - 1 :
-                                         searchData.index + 1;
-    if (searchData.index >= nodeList.length) {
-      searchData.index = 0;
-    }
-    if (searchData.index < 0) {
-      searchData.index = nodeList.length - 1;
-    }
-
-    // Send back the single node, along with any relevant search data
-    let node = yield nodeList.item(searchData.index);
-    return {
-      type: searchType,
-      node: node,
-      resultsLength: nodeList.length,
-      resultsIndex: searchData.index,
-    };
-  }), {
-    impl: "_search"
-  }),
-
-  _releaseFront: function (node, force) {
-    if (node.retained && !force) {
-      node.reparent(null);
-      this._retainedOrphans.add(node);
-      return;
-    }
-
-    if (node.retained) {
-      // Forcing a removal.
-      this._retainedOrphans.delete(node);
-    }
-
-    // Release any children
-    for (let child of node.treeChildren()) {
-      this._releaseFront(child, force);
-    }
-
-    // All children will have been removed from the node by this point.
-    node.reparent(null);
-    node.destroy();
-  },
-
-  /**
-   * Get any unprocessed mutation records and process them.
-   */
-  getMutations: custom(function (options = {}) {
-    return this._getMutations(options).then(mutations => {
-      let emitMutations = [];
-      for (let change of mutations) {
-        // The target is only an actorID, get the associated front.
-        let targetID;
-        let targetFront;
-
-        if (change.type === "newRoot") {
-          // We may receive a new root without receiving any documentUnload
-          // beforehand. Like when opening tools in middle of a document load.
-          if (this.rootNode) {
-            this._createRootNodePromise();
-          }
-          this.rootNode = types.getType("domnode").read(change.target, this);
-          this._rootNodeDeferred.resolve(this.rootNode);
-          targetID = this.rootNode.actorID;
-          targetFront = this.rootNode;
-        } else {
-          targetID = change.target;
-          targetFront = this.get(targetID);
-        }
-
-        if (!targetFront) {
-          console.warn("Got a mutation for an unexpected actor: " + targetID +
-            ", please file a bug on bugzilla.mozilla.org!");
-          console.trace();
-          continue;
-        }
-
-        let emittedMutation = Object.assign(change, { target: targetFront });
-
-        if (change.type === "childList" ||
-            change.type === "nativeAnonymousChildList") {
-          // Update the ownership tree according to the mutation record.
-          let addedFronts = [];
-          let removedFronts = [];
-          for (let removed of change.removed) {
-            let removedFront = this.get(removed);
-            if (!removedFront) {
-              console.error("Got a removal of an actor we didn't know about: " +
-                removed);
-              continue;
-            }
-            // Remove from the ownership tree
-            removedFront.reparent(null);
-
-            // This node is orphaned unless we get it in the 'added' list
-            // eventually.
-            this._orphaned.add(removedFront);
-            removedFronts.push(removedFront);
-          }
-          for (let added of change.added) {
-            let addedFront = this.get(added);
-            if (!addedFront) {
-              console.error("Got an addition of an actor we didn't know " +
-                "about: " + added);
-              continue;
-            }
-            addedFront.reparent(targetFront);
-
-            // The actor is reconnected to the ownership tree, unorphan
-            // it.
-            this._orphaned.delete(addedFront);
-            addedFronts.push(addedFront);
-          }
-
-          // Before passing to users, replace the added and removed actor
-          // ids with front in the mutation record.
-          emittedMutation.added = addedFronts;
-          emittedMutation.removed = removedFronts;
-
-          // If this is coming from a DOM mutation, the actor's numChildren
-          // was passed in. Otherwise, it is simulated from a frame load or
-          // unload, so don't change the front's form.
-          if ("numChildren" in change) {
-            targetFront._form.numChildren = change.numChildren;
-          }
-        } else if (change.type === "frameLoad") {
-          // Nothing we need to do here, except verify that we don't have any
-          // document children, because we should have gotten a documentUnload
-          // first.
-          for (let child of targetFront.treeChildren()) {
-            if (child.nodeType === nodeConstants.DOCUMENT_NODE) {
-              console.warn("Got an unexpected frameLoad in the inspector, " +
-                "please file a bug on bugzilla.mozilla.org!");
-              console.trace();
-            }
-          }
-        } else if (change.type === "documentUnload") {
-          if (targetFront === this.rootNode) {
-            this._createRootNodePromise();
-          }
-
-          // We try to give fronts instead of actorIDs, but these fronts need
-          // to be destroyed now.
-          emittedMutation.target = targetFront.actorID;
-          emittedMutation.targetParent = targetFront.parentNode();
-
-          // Release the document node and all of its children, even retained.
-          this._releaseFront(targetFront, true);
-        } else if (change.type === "unretained") {
-          // Retained orphans were force-released without the intervention of
-          // client (probably a navigated frame).
-          for (let released of change.nodes) {
-            let releasedFront = this.get(released);
-            this._retainedOrphans.delete(released);
-            this._releaseFront(releasedFront, true);
-          }
-        } else {
-          targetFront.updateMutation(change);
-        }
-
-        // Update the inlineTextChild property of the target for a selected list of
-        // mutation types.
-        if (change.type === "inlineTextChild" ||
-            change.type === "childList" ||
-            change.type === "nativeAnonymousChildList") {
-          if (change.inlineTextChild) {
-            targetFront.inlineTextChild =
-              types.getType("domnode").read(change.inlineTextChild, this);
-          } else {
-            targetFront.inlineTextChild = undefined;
-          }
-        }
-
-        emitMutations.push(emittedMutation);
-      }
-
-      if (options.cleanup) {
-        for (let node of this._orphaned) {
-          // This will move retained nodes to this._retainedOrphans.
-          this._releaseFront(node);
-        }
-        this._orphaned = new Set();
-      }
-
-      this.emit("mutations", emitMutations);
-    });
-  }, {
-    impl: "_getMutations"
-  }),
-
-  /**
-   * Handle the `new-mutations` notification by fetching the
-   * available mutation records.
-   */
-  onMutations: preEvent("new-mutations", function () {
-    // Fetch and process the mutations.
-    this.getMutations({cleanup: this.autoCleanup}).catch(() => {});
-  }),
-
-  isLocal: function () {
-    return !!this.conn._transport._serverConnection;
-  },
-
-  // XXX hack during transition to remote inspector: get a proper NodeFront
-  // for a given local node.  Only works locally.
-  frontForRawNode: function (rawNode) {
-    if (!this.isLocal()) {
-      console.warn("Tried to use frontForRawNode on a remote connection.");
-      return null;
-    }
-    const { DebuggerServer } = require("devtools/server/main");
-    let walkerActor = DebuggerServer.searchAllConnectionsForActor(this.actorID);
-    if (!walkerActor) {
-      throw Error("Could not find client side for actor " + this.actorID);
-    }
-    let nodeActor = walkerActor._ref(rawNode);
-
-    // Pass the node through a read/write pair to create the client side actor.
-    let nodeType = types.getType("domnode");
-    let returnNode = nodeType.read(
-      nodeType.write(nodeActor, walkerActor), this);
-    let top = returnNode;
-    let extras = walkerActor.parents(nodeActor, {sameTypeRootTreeItem: true});
-    for (let extraActor of extras) {
-      top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
-    }
-
-    if (top !== this.rootNode) {
-      // Imported an already-orphaned node.
-      this._orphaned.add(top);
-      walkerActor._orphaned
-        .add(DebuggerServer.searchAllConnectionsForActor(top.actorID));
-    }
-    return returnNode;
-  },
-
-  removeNode: custom(Task.async(function* (node) {
-    let previousSibling = yield this.previousSibling(node);
-    let nextSibling = yield this._removeNode(node);
-    return {
-      previousSibling: previousSibling,
-      nextSibling: nextSibling,
-    };
-  }), {
-    impl: "_removeNode"
-  }),
-});
-
-exports.WalkerFront = WalkerFront;
-
-/**
- * Client side of the inspector actor, which is used to create
- * inspector-related actors, including the walker.
- */
-var InspectorFront = FrontClassWithSpec(inspectorSpec, {
-  initialize: function (client, tabForm) {
-    Front.prototype.initialize.call(this, client);
-    this.actorID = tabForm.inspectorActor;
-
-    // XXX: This is the first actor type in its hierarchy to use the protocol
-    // library, so we're going to self-own on the client side for now.
-    this.manage(this);
-  },
-
-  destroy: function () {
-    delete this.walker;
-    Front.prototype.destroy.call(this);
-  },
-
-  getWalker: custom(function (options = {}) {
-    return this._getWalker(options).then(walker => {
-      this.walker = walker;
-      return walker;
-    });
-  }, {
-    impl: "_getWalker"
-  }),
-
-  getPageStyle: custom(function () {
-    return this._getPageStyle().then(pageStyle => {
-      // We need a walker to understand node references from the
-      // node style.
-      if (this.walker) {
-        return pageStyle;
-      }
-      return this.getWalker().then(() => {
-        return pageStyle;
-      });
-    });
-  }, {
-    impl: "_getPageStyle"
-  }),
-
-  pickColorFromPage: custom(Task.async(function* (toolbox, options) {
-    if (toolbox) {
-      // If the eyedropper was already started using the gcli command, hide it so we don't
-      // end up with 2 instances of the eyedropper on the page.
-      CommandUtils.executeOnTarget(toolbox.target, "eyedropper --hide");
-    }
-
-    yield this._pickColorFromPage(options);
-  }), {
-    impl: "_pickColorFromPage"
-  })
-});
-
-exports.InspectorFront = InspectorFront;
deleted file mode 100644
--- a/devtools/shared/fronts/performance-entries.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
-const performanceSpec = require("devtools/shared/specs/performance-entries");
-
-var PerformanceEntriesFront = FrontClassWithSpec(performanceSpec, {
-  initialize: function (client, form) {
-    Front.prototype.initialize.call(this, client);
-    this.actorID = form.performanceEntriesActor;
-    this.manage(this);
-  },
-});
-
-exports.PerformanceEntriesFront = PerformanceEntriesFront;
--- a/devtools/shared/specs/accessibility.js
+++ b/devtools/shared/specs/accessibility.js
@@ -1,18 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const protocol = require("devtools/shared/protocol");
 const { Arg, generateActorSpec, RetVal, types } = protocol;
-// eslint-disable-next-line no-unused-vars
-const { nodeSpec } = require("devtools/shared/specs/inspector");
 
 types.addActorType("accessible");
 
 const accessibleSpec = generateActorSpec({
   typeName: "accessible",
 
   events: {
     "actions-change": {
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -3,61 +3,231 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Registry indexing all specs and front modules
 //
 // All spec and front modules should be listed here
 // in order to be referenced by any other spec or front module.
-//
-// TODO: For now we only register dynamically loaded specs and fronts here.
-// See Bug 1399589 for supporting all specs and front modules.
 
-// Declare in which spec module and front module a set of types are defined
-const Types = [
+// Declare in which spec module and front module a set of types are defined.
+// This array should be sorted by `spec` attribute.
+const Types = exports.__TypesForTests = [
+  {
+    types: ["accessible", "accessiblewalker", "accessibility"],
+    spec: "devtools/shared/specs/accessibility",
+    front: "devtools/shared/fronts/accessibility",
+  },
+  {
+    types: ["actorActor", "actorRegistry"],
+    spec: "devtools/shared/specs/actor-registry",
+    front: "devtools/shared/fronts/actor-registry",
+  },
+  {
+    types: ["addons"],
+    spec: "devtools/shared/specs/addons",
+    front: "devtools/shared/fronts/addons",
+  },
+  {
+    types: ["animationplayer", "animations"],
+    spec: "devtools/shared/specs/animation",
+    front: "devtools/shared/fronts/animation",
+  },
+  /* breakpoint has old fashion client and no front */
+  {
+    types: ["breakpoint"],
+    spec: "devtools/shared/specs/breakpoint",
+    front: null,
+  },
+  {
+    types: ["function-call", "call-watcher"],
+    spec: "devtools/shared/specs/call-watcher",
+    front: "devtools/shared/fronts/call-watcher",
+  },
+  {
+    types: ["frame-snapshot", "canvas"],
+    spec: "devtools/shared/specs/canvas",
+    front: "devtools/shared/fronts/canvas",
+  },
+  {
+    types: ["cssProperties"],
+    spec: "devtools/shared/specs/css-properties",
+    front: "devtools/shared/fronts/css-properties",
+  },
   {
-    types: ["pagestyle", "domstylerule"],
-    spec: "devtools/shared/specs/styles",
-    front: "devtools/shared/fronts/styles",
+    types: ["cssUsage"],
+    spec: "devtools/shared/specs/csscoverage",
+    front: "devtools/shared/fronts/csscoverage",
+  },
+  {
+    types: ["device"],
+    spec: "devtools/shared/specs/device",
+    front: "devtools/shared/fronts/device",
+  },
+  {
+    types: ["emulation"],
+    spec: "devtools/shared/specs/emulation",
+    front: "devtools/shared/fronts/emulation",
+  },
+  /* environment has old fashion client and no front */
+  {
+    types: ["environment"],
+    spec: "devtools/shared/specs/environment",
+    front: null,
+  },
+  {
+    types: ["eventLoopLag"],
+    spec: "devtools/shared/specs/eventlooplag",
+    front: "devtools/shared/fronts/eventlooplag",
+  },
+  /* frame has old fashion client and no front */
+  {
+    types: ["frame"],
+    spec: "devtools/shared/specs/frame",
+    front: null,
+  },
+  {
+    types: ["framerate"],
+    spec: "devtools/shared/specs/framerate",
+    front: "devtools/shared/fronts/framerate",
+  },
+  {
+    types: ["gcli"],
+    spec: "devtools/shared/specs/gcli",
+    front: "devtools/shared/fronts/gcli",
+  },
+  /* heap snapshot has old fashion client and no front */
+  {
+    types: ["heapSnapshotFile"],
+    spec: "devtools/shared/specs/heap-snapshot-file",
+    front: null,
   },
   {
     types: ["highlighter", "customhighlighter"],
     spec: "devtools/shared/specs/highlighters",
     front: "devtools/shared/fronts/highlighters",
   },
   {
+    types: ["domnodelist", "domwalker", "inspector"],
+    spec: "devtools/shared/specs/inspector",
+    front: "devtools/shared/fronts/inspector",
+  },
+  {
     types: ["grid", "layout"],
     spec: "devtools/shared/specs/layout",
     front: "devtools/shared/fronts/layout",
   },
   {
+    types: ["memory"],
+    spec: "devtools/shared/specs/memory",
+    front: "devtools/shared/fronts/memory",
+  },
+  /* imageData isn't an actor but just a DictType */
+  {
+    types: ["imageData", "disconnectedNode", "disconnectedNodeArray"],
+    spec: "devtools/shared/specs/node",
+    front: null,
+  },
+  {
+    types: ["domnode", "domnodelist"],
+    spec: "devtools/shared/specs/node",
+    front: "devtools/shared/fronts/node",
+  },
+  {
+    types: ["performance"],
+    spec: "devtools/shared/specs/performance",
+    front: "devtools/shared/fronts/performance",
+  },
+  {
+    types: ["performance-recording"],
+    spec: "devtools/shared/specs/performance-recording",
+    front: "devtools/shared/fronts/performance-recording",
+  },
+  {
+    types: ["preference"],
+    spec: "devtools/shared/specs/preference",
+    front: "devtools/shared/fronts/preference",
+  },
+  {
+    types: ["promises"],
+    spec: "devtools/shared/specs/promises",
+    front: "devtools/shared/fronts/promises",
+  },
+  {
+    types: ["reflow"],
+    spec: "devtools/shared/specs/reflow",
+    front: "devtools/shared/fronts/reflow",
+  },
+  /* Script and source have old fashion client and no front */
+  {
+    types: ["context"],
+    spec: "devtools/shared/specs/script",
+    front: null,
+  },
+  {
+    types: ["source"],
+    spec: "devtools/shared/specs/source",
+    front: null,
+  },
+  {
+    types: ["cookies", "localStorage", "sessionStorage", "Cache", "indexedDB", "storage"],
+    spec: "devtools/shared/specs/storage",
+    front: "devtools/shared/fronts/storage",
+  },
+  /* longstring is special, it has a wrapper type. See its spec module */
+  {
     types: ["longstring"],
     spec: "devtools/shared/specs/string",
+    front: null
+  },
+  {
+    types: ["longstractor"],
+    spec: "devtools/shared/specs/string",
     front: "devtools/shared/fronts/string",
   },
   {
+    types: ["pagestyle", "domstylerule"],
+    spec: "devtools/shared/specs/styles",
+    front: "devtools/shared/fronts/styles",
+  },
+  {
     types: ["originalsource", "mediarule", "stylesheet", "stylesheets"],
     spec: "devtools/shared/specs/stylesheets",
     front: "devtools/shared/fronts/stylesheets",
   },
   {
-    types: ["imageData", "domnode"],
-    spec: "devtools/shared/specs/node",
-    front: "devtools/shared/fronts/inspector",
+    types: ["timeline"],
+    spec: "devtools/shared/specs/timeline",
+    front: "devtools/shared/fronts/timeline",
+  },
+  {
+    types: ["audionode", "webaudio"],
+    spec: "devtools/shared/specs/webaudio",
+    front: "devtools/shared/fronts/webaudio",
   },
   {
-    types: ["domwalker"],
-    spec: "devtools/shared/specs/inspector",
-    front: "devtools/shared/fronts/inspector",
+    types: ["webExtensionInspectedWindow"],
+    spec: "devtools/shared/specs/webextension-inspected-window",
+    front: "devtools/shared/fronts/webextension-inspected-window",
   },
   {
-    types: ["performance-recording"],
-    spec: "devtools/shared/specs/performance-recording",
-    front: "devtools/shared/fronts/performancec-recording",
+    types: ["webExtensionAddon"],
+    spec: "devtools/shared/specs/webextension-parent",
+    front: null,
+  },
+  {
+    types: ["gl-shader", "gl-program", "webgl"],
+    spec: "devtools/shared/specs/webgl",
+    front: "devtools/shared/fronts/webgl",
+  },
+  {
+    types: ["worker", "pushSubscription", "serviceWorkerRegistration", "serviceWorker"],
+    spec: "devtools/shared/specs/worker",
+    front: null,
   },
 ];
 
 const lazySpecs = new Map();
 const lazyFronts = new Map();
 
 // Convert the human readable `Types` list into efficient maps
 Types.forEach(item => {
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -5,73 +5,26 @@
 
 const {
   Arg,
   Option,
   RetVal,
   generateActorSpec,
   types
 } = require("devtools/shared/protocol");
-const { nodeSpec } = require("devtools/shared/specs/node");
-
-exports.nodeSpec = nodeSpec;
-
-/**
- * Returned from any call that might return a node that isn't connected to root
- * by nodes the child has seen, such as querySelector.
- */
-types.addDictType("disconnectedNode", {
-  // The actual node to return
-  node: "domnode",
-
-  // Nodes that are needed to connect the node to a node the client has already
-  // seen
-  newParents: "array:domnode"
-});
-
-types.addDictType("disconnectedNodeArray", {
-  // The actual node list to return
-  nodes: "array:domnode",
-
-  // Nodes that are needed to connect those nodes to the root.
-  newParents: "array:domnode"
-});
 
 types.addDictType("dommutation", {});
 
 types.addDictType("searchresult", {
   list: "domnodelist",
   // Right now there is isn't anything required for metadata,
   // but it's json so it can be extended with extra data.
   metadata: "array:json"
 });
 
-const nodeListSpec = generateActorSpec({
-  typeName: "domnodelist",
-
-  methods: {
-    item: {
-      request: { item: Arg(0) },
-      response: RetVal("disconnectedNode")
-    },
-    items: {
-      request: {
-        start: Arg(0, "nullable:number"),
-        end: Arg(1, "nullable:number")
-      },
-      response: RetVal("disconnectedNodeArray")
-    },
-    release: {
-      release: true
-    }
-  }
-});
-
-exports.nodeListSpec = nodeListSpec;
-
 // Some common request/response templates for the dom walker
 
 var nodeArrayMethod = {
   request: {
     node: Arg(0, "domnode"),
     maxNodes: Option(1),
     center: Option(1, "domnode"),
     start: Option(1, "domnode"),
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -23,17 +23,16 @@ DevToolsModules(
     'gcli.js',
     'heap-snapshot-file.js',
     'highlighters.js',
     'index.js',
     'inspector.js',
     'layout.js',
     'memory.js',
     'node.js',
-    'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'reflow.js',
     'script.js',
     'source.js',
     'storage.js',
--- a/devtools/shared/specs/node.js
+++ b/devtools/shared/specs/node.js
@@ -12,16 +12,60 @@ const {
 
 types.addDictType("imageData", {
   // The image data
   data: "nullable:longstring",
   // The original image dimensions
   size: "json"
 });
 
+/**
+ * Returned from any call that might return a node that isn't connected to root
+ * by nodes the child has seen, such as querySelector.
+ */
+types.addDictType("disconnectedNode", {
+  // The actual node to return
+  node: "domnode",
+
+  // Nodes that are needed to connect the node to a node the client has already
+  // seen
+  newParents: "array:domnode"
+});
+
+types.addDictType("disconnectedNodeArray", {
+  // The actual node list to return
+  nodes: "array:domnode",
+
+  // Nodes that are needed to connect those nodes to the root.
+  newParents: "array:domnode"
+});
+
+const nodeListSpec = generateActorSpec({
+  typeName: "domnodelist",
+
+  methods: {
+    item: {
+      request: { item: Arg(0) },
+      response: RetVal("disconnectedNode")
+    },
+    items: {
+      request: {
+        start: Arg(0, "nullable:number"),
+        end: Arg(1, "nullable:number")
+      },
+      response: RetVal("disconnectedNodeArray")
+    },
+    release: {
+      release: true
+    }
+  }
+});
+
+exports.nodeListSpec = nodeListSpec;
+
 const nodeSpec = generateActorSpec({
   typeName: "domnode",
 
   methods: {
     getNodeValue: {
       request: {},
       response: {
         value: RetVal("longstring")
deleted file mode 100644
--- a/devtools/shared/specs/performance-entries.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const { Arg, generateActorSpec } = require("devtools/shared/protocol");
-
-const performanceEntriesSpec = generateActorSpec({
-  typeName: "performanceEntries",
-
-  events: {
-    "entry": {
-      type: "entry",
-      // object containing performance entry name, type, origin, and epoch.
-      detail: Arg(0, "json")
-    }
-  },
-
-  methods: {
-    start: {},
-    stop: {}
-  }
-});
-
-exports.performanceEntriesSpec = performanceEntriesSpec;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/unit/test_protocol_index.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+const { lazyLoadFront } = require("devtools/shared/specs/index");
+const Types = require("devtools/shared/specs/index").__TypesForTests;
+const { getType } = require("devtools/shared/protocol").types;
+
+function run_test() {
+  test_index_is_alphabetically_sorted();
+  test_specs();
+  test_fronts();
+}
+
+// Check alphabetic order of specs defined in devtools/shared/specs/index.js,
+// in order to ease its maintenance and readability.
+function test_index_is_alphabetically_sorted() {
+  let lastSpec = "";
+  for (let type of Types) {
+    let spec = type.spec;
+    if (lastSpec && spec < lastSpec) {
+      ok(false, `Spec definition for "${spec}" should be before "${lastSpec}"`);
+    }
+    lastSpec = spec;
+  }
+  ok(true, "Specs index is alphabetically sorted");
+}
+
+function test_specs() {
+  for (let type of Types) {
+    for (let typeName of type.types) {
+      ok(getType(typeName), `${typeName} spec is defined`);
+    }
+  }
+  ok(true, "Specs are all accessible");
+}
+
+function test_fronts() {
+  for (let item of Types) {
+    if (!item.front) {
+      continue;
+    }
+    for (let typeName of item.types) {
+      lazyLoadFront(typeName);
+      let type = getType(typeName);
+      ok(type, `Front for ${typeName} has a spec`);
+      ok(type.frontClass, `${typeName} has a front correctly defined`);
+    }
+  }
+  ok(true, "Front are all accessible");
+}
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -36,8 +36,9 @@ run-if = nightly_build
 [test_prettifyCSS.js]
 [test_require_lazy.js]
 [test_require_raw.js]
 [test_require.js]
 [test_stack.js]
 [test_defer.js]
 [test_executeSoon.js]
 [test_wasm-source-map.js]
+[test_protocol_index.js]
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3491,31 +3491,41 @@ Element::Closest(const nsAString& aSelec
     }
   }
   return nullptr;
 }
 
 bool
 Element::Matches(const nsAString& aSelector, ErrorResult& aError)
 {
-  nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aError);
-  if (!selectorList) {
-    // Either we failed (and aError already has the exception), or this
-    // is a pseudo-element-only selector that matches nothing.
-    return false;
-  }
-
-  TreeMatchContext matchingContext(false,
-                                   nsRuleWalker::eRelevantLinkUnvisited,
-                                   OwnerDoc(),
-                                   TreeMatchContext::eNeverMatchVisited);
-  matchingContext.SetHasSpecifiedScope();
-  matchingContext.AddScopeElement(this);
-  return nsCSSRuleProcessor::SelectorListMatches(this, matchingContext,
-                                                 selectorList);
+  return WithSelectorList<bool>(
+    aSelector,
+    aError,
+    [&](const RawServoSelectorList* aList) {
+      if (!aList) {
+        return false;
+      }
+      return Servo_SelectorList_Matches(this, aList);
+    },
+    [&](nsCSSSelectorList* aList) {
+      if (!aList) {
+        // Either we failed (and aError already has the exception), or this
+        // is a pseudo-element-only selector that matches nothing.
+        return false;
+      }
+      TreeMatchContext matchingContext(false,
+                                       nsRuleWalker::eRelevantLinkUnvisited,
+                                       OwnerDoc(),
+                                       TreeMatchContext::eNeverMatchVisited);
+      matchingContext.SetHasSpecifiedScope();
+      matchingContext.AddScopeElement(this);
+      return nsCSSRuleProcessor::SelectorListMatches(this, matchingContext,
+                                                     aList);
+    }
+  );
 }
 
 static const nsAttrValue::EnumTable kCORSAttributeTable[] = {
   // Order matters here
   // See ParseCORSValue
   { "anonymous",       CORS_ANONYMOUS       },
   { "use-credentials", CORS_USE_CREDENTIALS },
   { nullptr,           0 }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -285,16 +285,17 @@ nsString* nsContentUtils::sModifierSepar
 bool nsContentUtils::sInitialized = false;
 bool nsContentUtils::sIsFullScreenApiEnabled = false;
 bool nsContentUtils::sIsUnprefixedFullscreenApiEnabled = false;
 bool nsContentUtils::sTrustedFullScreenOnly = true;
 bool nsContentUtils::sIsCutCopyAllowed = true;
 bool nsContentUtils::sIsFrameTimingPrefEnabled = false;
 bool nsContentUtils::sIsPerformanceTimingEnabled = false;
 bool nsContentUtils::sIsResourceTimingEnabled = false;
+bool nsContentUtils::sIsPerformanceNavigationTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsFormAutofillAutocompleteEnabled = false;
 bool nsContentUtils::sIsWebComponentsEnabled = false;
 bool nsContentUtils::sIsCustomElementsEnabled = false;
 bool nsContentUtils::sDevToolsEnabled = false;
 bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 bool nsContentUtils::sUseActivityCursor = false;
 bool nsContentUtils::sAnimationsAPICoreEnabled = false;
@@ -686,16 +687,19 @@ nsContentUtils::Init()
                                "dom.allow_cut_copy", true);
 
   Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled,
                                "dom.enable_performance", true);
 
   Preferences::AddBoolVarCache(&sIsResourceTimingEnabled,
                                "dom.enable_resource_timing", true);
 
+  Preferences::AddBoolVarCache(&sIsPerformanceNavigationTimingEnabled,
+                               "dom.enable_performance_navigation_timing", true);
+
   Preferences::AddBoolVarCache(&sIsUserTimingLoggingEnabled,
                                "dom.performance.enable_user_timing_logging", false);
 
   Preferences::AddBoolVarCache(&sIsFrameTimingPrefEnabled,
                                "dom.enable_frame_timing", false);
 
   Preferences::AddBoolVarCache(&sIsFormAutofillAutocompleteEnabled,
                                "dom.forms.autocomplete.formautofill", false);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2269,16 +2269,24 @@ public:
    * Returns true if the performance timing APIs are enabled.
    */
   static bool IsResourceTimingEnabled()
   {
     return sIsResourceTimingEnabled;
   }
 
   /*
+   * Returns true if the performance timing APIs are enabled.
+   */
+  static bool IsPerformanceNavigationTimingEnabled()
+  {
+    return sIsPerformanceNavigationTimingEnabled;
+  }
+
+  /*
    * Returns true if notification should be sent for peformance timing events.
    */
   static bool SendPerformanceTimingNotifications()
   {
     return sSendPerformanceTimingNotifications;
   }
 
   /*
@@ -3335,16 +3343,17 @@ private:
   static bool sAllowXULXBL_for_file;
   static bool sIsFullScreenApiEnabled;
   static bool sIsUnprefixedFullscreenApiEnabled;
   static bool sTrustedFullScreenOnly;
   static bool sIsCutCopyAllowed;
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
+  static bool sIsPerformanceNavigationTimingEnabled;
   static bool sIsUserTimingLoggingEnabled;
   static bool sIsFrameTimingPrefEnabled;
   static bool sIsFormAutofillAutocompleteEnabled;
   static bool sIsWebComponentsEnabled;
   static bool sIsCustomElementsEnabled;
   static bool sDevToolsEnabled;
   static bool sSendPerformanceTimingNotifications;
   static bool sUseActivityCursor;
--- a/dom/base/nsDOMNavigationTiming.cpp
+++ b/dom/base/nsDOMNavigationTiming.cpp
@@ -32,268 +32,259 @@ nsDOMNavigationTiming::~nsDOMNavigationT
 {
 }
 
 void
 nsDOMNavigationTiming::Clear()
 {
   mNavigationType = TYPE_RESERVED;
   mNavigationStartHighRes = 0;
-  mBeforeUnloadStart = 0;
-  mUnloadStart = 0;
-  mUnloadEnd = 0;
-  mLoadEventStart = 0;
-  mLoadEventEnd = 0;
-  mDOMLoading = 0;
-  mDOMInteractive = 0;
-  mDOMContentLoadedEventStart = 0;
-  mDOMContentLoadedEventEnd = 0;
-  mDOMComplete = 0;
 
-  mLoadEventStartSet = false;
-  mLoadEventEndSet = false;
-  mDOMLoadingSet = false;
-  mDOMInteractiveSet = false;
-  mDOMContentLoadedEventStartSet = false;
-  mDOMContentLoadedEventEndSet = false;
-  mDOMCompleteSet = false;
+  mBeforeUnloadStart = TimeStamp();
+  mUnloadStart = TimeStamp();
+  mUnloadEnd = TimeStamp();
+  mLoadEventStart = TimeStamp();
+  mLoadEventEnd = TimeStamp();
+  mDOMLoading = TimeStamp();
+  mDOMInteractive = TimeStamp();
+  mDOMContentLoadedEventStart = TimeStamp();
+  mDOMContentLoadedEventEnd = TimeStamp();
+  mDOMComplete = TimeStamp();
+
   mDocShellHasBeenActiveSinceNavigationStart = false;
 }
 
 DOMTimeMilliSec
 nsDOMNavigationTiming::TimeStampToDOM(TimeStamp aStamp) const
 {
   if (aStamp.IsNull()) {
     return 0;
   }
 
-  TimeDuration duration = aStamp - mNavigationStartTimeStamp;
+  TimeDuration duration = aStamp - mNavigationStart;
   return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds());
 }
 
-DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart()
-{
-  return TimeStampToDOM(TimeStamp::Now());
-}
-
 void
 nsDOMNavigationTiming::NotifyNavigationStart(DocShellState aDocShellState)
 {
   mNavigationStartHighRes = (double)PR_Now() / PR_USEC_PER_MSEC;
-  mNavigationStartTimeStamp = TimeStamp::Now();
+  mNavigationStart = TimeStamp::Now();
   mDocShellHasBeenActiveSinceNavigationStart = (aDocShellState == DocShellState::eActive);
   profiler_add_marker("Navigation::Start");
 }
 
 void
 nsDOMNavigationTiming::NotifyFetchStart(nsIURI* aURI, Type aNavigationType)
 {
   mNavigationType = aNavigationType;
   // At the unload event time we don't really know the loading uri.
   // Need it for later check for unload timing access.
   mLoadedURI = aURI;
 }
 
 void
 nsDOMNavigationTiming::NotifyBeforeUnload()
 {
-  mBeforeUnloadStart = DurationFromStart();
+  mBeforeUnloadStart = TimeStamp::Now();
 }
 
 void
 nsDOMNavigationTiming::NotifyUnloadAccepted(nsIURI* aOldURI)
 {
   mUnloadStart = mBeforeUnloadStart;
   mUnloadedURI = aOldURI;
 }
 
 void
 nsDOMNavigationTiming::NotifyUnloadEventStart()
 {
-  mUnloadStart = DurationFromStart();
+  mUnloadStart = TimeStamp::Now();
   profiler_tracing("Navigation", "Unload", TRACING_INTERVAL_START);
 }
 
 void
 nsDOMNavigationTiming::NotifyUnloadEventEnd()
 {
-  mUnloadEnd = DurationFromStart();
+  mUnloadEnd = TimeStamp::Now();
   profiler_tracing("Navigation", "Unload", TRACING_INTERVAL_END);
 }
 
 void
 nsDOMNavigationTiming::NotifyLoadEventStart()
 {
-  if (!mLoadEventStartSet) {
-    mLoadEventStart = DurationFromStart();
-    mLoadEventStartSet = true;
+  if (!mLoadEventStart.IsNull()) {
+    return;
+  }
+  mLoadEventStart = TimeStamp::Now();
 
-    profiler_tracing("Navigation", "Load", TRACING_INTERVAL_START);
+  profiler_tracing("Navigation", "Load", TRACING_INTERVAL_START);
 
-    if (IsTopLevelContentDocumentInContentProcess()) {
-      Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_START_MS,
-                                     mNavigationStartTimeStamp);
-    }
+  if (IsTopLevelContentDocumentInContentProcess()) {
+    Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_START_MS,
+                                   mNavigationStart);
   }
 }
 
 void
 nsDOMNavigationTiming::NotifyLoadEventEnd()
 {
-  if (!mLoadEventEndSet) {
-    mLoadEventEnd = DurationFromStart();
-    mLoadEventEndSet = true;
+  if (!mLoadEventEnd.IsNull()) {
+    return;
+  }
+  mLoadEventEnd = TimeStamp::Now();
 
-    profiler_tracing("Navigation", "Load", TRACING_INTERVAL_END);
+  profiler_tracing("Navigation", "Load", TRACING_INTERVAL_END);
 
-    if (IsTopLevelContentDocumentInContentProcess()) {
-      Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_END_MS,
-                                     mNavigationStartTimeStamp);
-    }
+  if (IsTopLevelContentDocumentInContentProcess()) {
+    Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_END_MS,
+                                   mNavigationStart);
   }
 }
 
 void
 nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI, TimeStamp aValue)
 {
-  if (!mDOMLoadingSet) {
-    mLoadedURI = aURI;
-    mDOMLoading = TimeStampToDOM(aValue);
-    mDOMLoadingSet = true;
+  if (!mDOMLoading.IsNull()) {
+    return;
   }
+  mLoadedURI = aURI;
+  mDOMLoading = aValue;
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMLoading(nsIURI* aURI)
 {
-  if (!mDOMLoadingSet) {
-    mLoadedURI = aURI;
-    mDOMLoading = DurationFromStart();
-    mDOMLoadingSet = true;
+  if (!mDOMLoading.IsNull()) {
+    return;
+  }
+  mLoadedURI = aURI;
+  mDOMLoading = TimeStamp::Now();
 
-    profiler_add_marker("Navigation::DOMLoading");
-  }
+  profiler_add_marker("Navigation::DOMLoading");
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMInteractive(nsIURI* aURI)
 {
-  if (!mDOMInteractiveSet) {
-    mLoadedURI = aURI;
-    mDOMInteractive = DurationFromStart();
-    mDOMInteractiveSet = true;
+  if (!mDOMInteractive.IsNull()) {
+    return;
+  }
+  mLoadedURI = aURI;
+  mDOMInteractive = TimeStamp::Now();
 
-    profiler_add_marker("Navigation::DOMInteractive");
-  }
+  profiler_add_marker("Navigation::DOMInteractive");
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMComplete(nsIURI* aURI)
 {
-  if (!mDOMCompleteSet) {
-    mLoadedURI = aURI;
-    mDOMComplete = DurationFromStart();
-    mDOMCompleteSet = true;
+  if (!mDOMComplete.IsNull()) {
+    return;
+  }
+  mLoadedURI = aURI;
+  mDOMComplete = TimeStamp::Now();
 
-    profiler_add_marker("Navigation::DOMComplete");
-  }
+  profiler_add_marker("Navigation::DOMComplete");
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMContentLoadedStart(nsIURI* aURI)
 {
-  if (!mDOMContentLoadedEventStartSet) {
-    mLoadedURI = aURI;
-    mDOMContentLoadedEventStart = DurationFromStart();
-    mDOMContentLoadedEventStartSet = true;
+  if (!mDOMContentLoadedEventStart.IsNull()) {
+    return;
+  }
 
-    profiler_tracing("Navigation", "DOMContentLoaded", TRACING_INTERVAL_START);
+  mLoadedURI = aURI;
+  mDOMContentLoadedEventStart = TimeStamp::Now();
 
-    if (IsTopLevelContentDocumentInContentProcess()) {
-      Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_CONTENT_LOADED_START_MS,
-                                     mNavigationStartTimeStamp);
-    }
+  profiler_tracing("Navigation", "DOMContentLoaded", TRACING_INTERVAL_START);
+
+  if (IsTopLevelContentDocumentInContentProcess()) {
+    Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_CONTENT_LOADED_START_MS,
+                                   mNavigationStart);
   }
 }
 
 void
 nsDOMNavigationTiming::NotifyDOMContentLoadedEnd(nsIURI* aURI)
 {
-  if (!mDOMContentLoadedEventEndSet) {
-    mLoadedURI = aURI;
-    mDOMContentLoadedEventEnd = DurationFromStart();
-    mDOMContentLoadedEventEndSet = true;
+  if (!mDOMContentLoadedEventEnd.IsNull()) {
+    return;
+  }
 
-    profiler_tracing("Navigation", "DOMContentLoaded", TRACING_INTERVAL_END);
+  mLoadedURI = aURI;
+  mDOMContentLoadedEventEnd = TimeStamp::Now();
 
-    if (IsTopLevelContentDocumentInContentProcess()) {
-      Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_CONTENT_LOADED_END_MS,
-                                     mNavigationStartTimeStamp);
-    }
+  profiler_tracing("Navigation", "DOMContentLoaded", TRACING_INTERVAL_END);
+
+  if (IsTopLevelContentDocumentInContentProcess()) {
+    Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_DOM_CONTENT_LOADED_END_MS,
+                                   mNavigationStart);
   }
 }
 
 void
 nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mNavigationStartTimeStamp.IsNull());
+  MOZ_ASSERT(!mNavigationStart.IsNull());
 
-  if (!mNonBlankPaintTimeStamp.IsNull()) {
+  if (!mNonBlankPaint.IsNull()) {
     return;
   }
 
-  mNonBlankPaintTimeStamp = TimeStamp::Now();
-  TimeDuration elapsed = mNonBlankPaintTimeStamp - mNavigationStartTimeStamp;
+  mNonBlankPaint = TimeStamp::Now();
+  TimeDuration elapsed = mNonBlankPaint - mNavigationStart;
 
   if (profiler_is_active()) {
     nsAutoCString spec;
     if (mLoadedURI) {
       mLoadedURI->GetSpec(spec);
     }
     nsPrintfCString marker("Non-blank paint after %dms for URL %s, %s",
                            int(elapsed.ToMilliseconds()), spec.get(),
                            mDocShellHasBeenActiveSinceNavigationStart ? "foreground tab" : "this tab was inactive some of the time between navigation start and first non-blank paint");
     profiler_add_marker(marker.get());
   }
 
   if (mDocShellHasBeenActiveSinceNavigationStart) {
     Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_MS,
-                                   mNavigationStartTimeStamp,
-                                   mNonBlankPaintTimeStamp);
+                                   mNavigationStart,
+                                   mNonBlankPaint);
   }
 }
 
 void
 nsDOMNavigationTiming::NotifyDocShellStateChanged(DocShellState aDocShellState)
 {
   mDocShellHasBeenActiveSinceNavigationStart &=
     (aDocShellState == DocShellState::eActive);
 }
 
-DOMTimeMilliSec
-nsDOMNavigationTiming::GetUnloadEventStart()
+mozilla::TimeStamp
+nsDOMNavigationTiming::GetUnloadEventStartTimeStamp() const
 {
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false);
   if (NS_SUCCEEDED(rv)) {
     return mUnloadStart;
   }
-  return 0;
+  return mozilla::TimeStamp();
 }
 
-DOMTimeMilliSec
-nsDOMNavigationTiming::GetUnloadEventEnd()
+mozilla::TimeStamp
+nsDOMNavigationTiming::GetUnloadEventEndTimeStamp() const
 {
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false);
   if (NS_SUCCEEDED(rv)) {
     return mUnloadEnd;
   }
-  return 0;
+  return mozilla::TimeStamp();
 }
 
 bool
 nsDOMNavigationTiming::IsTopLevelContentDocumentInContentProcess() const
 {
   if (!mDocShell) {
     return false;
   }
--- a/dom/base/nsDOMNavigationTiming.h
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -44,52 +44,101 @@ public:
 
   DOMTimeMilliSec GetNavigationStart() const
   {
     return static_cast<int64_t>(GetNavigationStartHighRes());
   }
 
   mozilla::TimeStamp GetNavigationStartTimeStamp() const
   {
-    return mNavigationStartTimeStamp;
+    return mNavigationStart;
+  }
+
+  DOMTimeMilliSec GetUnloadEventStart()
+  {
+    return TimeStampToDOM(GetUnloadEventStartTimeStamp());
   }
 
-  DOMTimeMilliSec GetUnloadEventStart();
-  DOMTimeMilliSec GetUnloadEventEnd();
+  DOMTimeMilliSec GetUnloadEventEnd()
+  {
+    return TimeStampToDOM(GetUnloadEventEndTimeStamp());
+  }
+
   DOMTimeMilliSec GetDomLoading() const
   {
-    return mDOMLoading;
+    return TimeStampToDOM(mDOMLoading);
   }
   DOMTimeMilliSec GetDomInteractive() const
   {
-    return mDOMInteractive;
+    return TimeStampToDOM(mDOMInteractive);
   }
   DOMTimeMilliSec GetDomContentLoadedEventStart() const
   {
-    return mDOMContentLoadedEventStart;
+    return TimeStampToDOM(mDOMContentLoadedEventStart);
   }
   DOMTimeMilliSec GetDomContentLoadedEventEnd() const
   {
-    return mDOMContentLoadedEventEnd;
+    return TimeStampToDOM(mDOMContentLoadedEventEnd);
   }
   DOMTimeMilliSec GetDomComplete() const
   {
-    return mDOMComplete;
+    return TimeStampToDOM(mDOMComplete);
   }
   DOMTimeMilliSec GetLoadEventStart() const
   {
-    return mLoadEventStart;
+    return TimeStampToDOM(mLoadEventStart);
   }
   DOMTimeMilliSec GetLoadEventEnd() const
   {
-    return mLoadEventEnd;
+    return TimeStampToDOM(mLoadEventEnd);
   }
   DOMTimeMilliSec GetTimeToNonBlankPaint() const
   {
-    return TimeStampToDOM(mNonBlankPaintTimeStamp);
+    return TimeStampToDOM(mNonBlankPaint);
+  }
+
+  DOMHighResTimeStamp GetUnloadEventStartHighRes()
+  {
+    mozilla::TimeStamp stamp = GetUnloadEventStartTimeStamp();
+    if (stamp.IsNull()) {
+      return 0;
+    }
+    return TimeStampToDOMHighRes(stamp);
+  }
+  DOMHighResTimeStamp GetUnloadEventEndHighRes()
+  {
+    mozilla::TimeStamp stamp = GetUnloadEventEndTimeStamp();
+    if (stamp.IsNull()) {
+      return 0;
+    }
+    return TimeStampToDOMHighRes(stamp);
+  }
+  DOMHighResTimeStamp GetDomInteractiveHighRes() const
+  {
+    return TimeStampToDOMHighRes(mDOMInteractive);
+  }
+  DOMHighResTimeStamp GetDomContentLoadedEventStartHighRes() const
+  {
+    return TimeStampToDOMHighRes(mDOMContentLoadedEventStart);
+  }
+  DOMHighResTimeStamp GetDomContentLoadedEventEndHighRes() const
+  {
+    return TimeStampToDOMHighRes(mDOMContentLoadedEventEnd);
+  }
+  DOMHighResTimeStamp GetDomCompleteHighRes() const
+  {
+    return TimeStampToDOMHighRes(mDOMComplete);
+  }
+  DOMHighResTimeStamp GetLoadEventStartHighRes() const
+  {
+    return TimeStampToDOMHighRes(mLoadEventStart);
+  }
+  DOMHighResTimeStamp GetLoadEventEndHighRes() const
+  {
+    return TimeStampToDOMHighRes(mLoadEventEnd);
   }
 
   enum class DocShellState : uint8_t {
     eActive,
     eInactive
   };
 
   void NotifyNavigationStart(DocShellState aDocShellState);
@@ -111,57 +160,53 @@ public:
 
   void NotifyNonBlankPaintForRootContentDocument();
   void NotifyDocShellStateChanged(DocShellState aDocShellState);
 
   DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;
 
   inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) const
   {
-    mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp;
+    MOZ_ASSERT(!aStamp.IsNull(), "The timestamp should not be null");
+    if (aStamp.IsNull()) {
+      return 0;
+    }
+    mozilla::TimeDuration duration = aStamp - mNavigationStart;
     return duration.ToMilliseconds();
   }
 
 private:
   nsDOMNavigationTiming(const nsDOMNavigationTiming &) = delete;
   ~nsDOMNavigationTiming();
 
   void Clear();
 
+  mozilla::TimeStamp GetUnloadEventStartTimeStamp() const;
+  mozilla::TimeStamp GetUnloadEventEndTimeStamp() const;
+
   bool IsTopLevelContentDocumentInContentProcess() const;
 
   mozilla::WeakPtr<nsDocShell> mDocShell;
 
   nsCOMPtr<nsIURI> mUnloadedURI;
   nsCOMPtr<nsIURI> mLoadedURI;
 
   Type mNavigationType;
   DOMHighResTimeStamp mNavigationStartHighRes;
-  mozilla::TimeStamp mNavigationStartTimeStamp;
-  mozilla::TimeStamp mNonBlankPaintTimeStamp;
-  DOMTimeMilliSec DurationFromStart();
-
-  DOMTimeMilliSec mBeforeUnloadStart;
-  DOMTimeMilliSec mUnloadStart;
-  DOMTimeMilliSec mUnloadEnd;
-  DOMTimeMilliSec mLoadEventStart;
-  DOMTimeMilliSec mLoadEventEnd;
+  mozilla::TimeStamp mNavigationStart;
+  mozilla::TimeStamp mNonBlankPaint;
 
-  DOMTimeMilliSec mDOMLoading;
-  DOMTimeMilliSec mDOMInteractive;
-  DOMTimeMilliSec mDOMContentLoadedEventStart;
-  DOMTimeMilliSec mDOMContentLoadedEventEnd;
-  DOMTimeMilliSec mDOMComplete;
+  mozilla::TimeStamp mBeforeUnloadStart;
+  mozilla::TimeStamp mUnloadStart;
+  mozilla::TimeStamp mUnloadEnd;
+  mozilla::TimeStamp mLoadEventStart;
+  mozilla::TimeStamp mLoadEventEnd;
 
-  // Booleans to keep track of what things we've already been notified
-  // about.  We don't update those once we've been notified about them
-  // once.
-  bool mLoadEventStartSet : 1;
-  bool mLoadEventEndSet : 1;
-  bool mDOMLoadingSet : 1;
-  bool mDOMInteractiveSet : 1;
-  bool mDOMContentLoadedEventStartSet : 1;
-  bool mDOMContentLoadedEventEndSet : 1;
-  bool mDOMCompleteSet : 1;
+  mozilla::TimeStamp mDOMLoading;
+  mozilla::TimeStamp mDOMInteractive;
+  mozilla::TimeStamp mDOMContentLoadedEventStart;
+  mozilla::TimeStamp mDOMContentLoadedEventEnd;
+  mozilla::TimeStamp mDOMComplete;
+
   bool mDocShellHasBeenActiveSinceNavigationStart : 1;
 };
 
 #endif /* nsDOMNavigationTiming_h___ */
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1401,23 +1401,49 @@ nsIDocument::SelectorCache::SelectorCach
       1000, "nsIDocument::SelectorCache", aEventTarget)
 { }
 
 nsIDocument::SelectorCache::~SelectorCache()
 {
   AgeAllGenerations();
 }
 
+void
+nsIDocument::SelectorCache::SelectorList::Reset()
+{
+  if (mIsServo) {
+    if (mServo) {
+      Servo_SelectorList_Drop(mServo);
+      mServo = nullptr;
+    }
+  } else {
+    if (mGecko) {
+      delete mGecko;
+      mGecko = nullptr;
+    }
+  }
+}
+
 // CacheList takes ownership of aSelectorList.
 void nsIDocument::SelectorCache::CacheList(const nsAString& aSelector,
-                                           nsCSSSelectorList* aSelectorList)
+                                           mozilla::UniquePtr<nsCSSSelectorList>&& aSelectorList)
 {
   MOZ_ASSERT(NS_IsMainThread());
   SelectorCacheKey* key = new SelectorCacheKey(aSelector);
-  mTable.Put(key->mKey, aSelectorList);
+  mTable.Put(key->mKey, SelectorList(Move(aSelectorList)));
+  AddObject(key);
+}
+
+void nsIDocument::SelectorCache::CacheList(
+  const nsAString& aSelector,
+  UniquePtr<RawServoSelectorList>&& aSelectorList)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  SelectorCacheKey* key = new SelectorCacheKey(aSelector);
+  mTable.Put(key->mKey, SelectorList(Move(aSelectorList)));
   AddObject(key);
 }
 
 void nsIDocument::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aSelector);
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -32,16 +32,17 @@
 #include "nsContentListDeclarations.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/dom/DispatcherTrait.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/SegmentedVector.h"
+#include "mozilla/ServoBindingTypes.h"
 #include "mozilla/StyleBackendType.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include <bitset>                        // for member
 
 #ifdef MOZILLA_INTERNAL_API
 #include "mozilla/dom/DocumentBinding.h"
@@ -1143,37 +1144,116 @@ private:
 
   class SelectorCacheKeyDeleter;
 
 public:
   class SelectorCache final
     : public nsExpirationTracker<SelectorCacheKey, 4>
   {
     public:
+      class SelectorList
+      {
+      public:
+        SelectorList()
+          : mIsServo(false)
+          , mGecko(nullptr)
+        {}
+
+        SelectorList(SelectorList&& aOther)
+        {
+          *this = mozilla::Move(aOther);
+        }
+
+        SelectorList& operator=(SelectorList&& aOther)
+        {
+          Reset();
+          mIsServo = aOther.mIsServo;
+          if (mIsServo) {
+            mServo = aOther.mServo;
+            aOther.mServo = nullptr;
+          } else {
+            mGecko = aOther.mGecko;
+            aOther.mGecko = nullptr;
+          }
+          return *this;
+        }
+
+        SelectorList(const SelectorList& aOther) = delete;
+
+        explicit SelectorList(mozilla::UniquePtr<RawServoSelectorList>&& aList)
+          : mIsServo(true)
+          , mServo(aList.release())
+        {}
+
+        explicit SelectorList(mozilla::UniquePtr<nsCSSSelectorList>&& aList)
+          : mIsServo(false)
+          , mGecko(aList.release())
+        {}
+
+        ~SelectorList() {
+          Reset();
+        }
+
+        bool IsServo() const { return mIsServo; }
+        bool IsGecko() const { return !IsServo(); }
+
+        explicit operator bool() const
+        {
+          return IsServo() ? !!AsServo() : !!AsGecko();
+        }
+
+        nsCSSSelectorList* AsGecko() const
+        {
+          MOZ_ASSERT(IsGecko());
+          return mGecko;
+        }
+
+        RawServoSelectorList* AsServo() const
+        {
+          MOZ_ASSERT(IsServo());
+          return mServo;
+        }
+
+      private:
+        void Reset();
+
+        bool mIsServo;
+
+        union {
+          nsCSSSelectorList* mGecko;
+          RawServoSelectorList* mServo;
+        };
+      };
+
       explicit SelectorCache(nsIEventTarget* aEventTarget);
 
       // CacheList takes ownership of aSelectorList.
-      void CacheList(const nsAString& aSelector, nsCSSSelectorList* aSelectorList);
+      void CacheList(const nsAString& aSelector,
+                     mozilla::UniquePtr<nsCSSSelectorList>&& aSelectorList);
+      void CacheList(const nsAString& aSelector,
+                     mozilla::UniquePtr<RawServoSelectorList>&& aSelectorList);
 
       virtual void NotifyExpired(SelectorCacheKey* aSelector) override;
 
       // We do not call MarkUsed because it would just slow down lookups and
       // because we're OK expiring things after a few seconds even if they're
       // being used.  Returns whether we actually had an entry for aSelector.
-      // If we have an entry and *aList is null, that indicates that aSelector
+      //
+      // If we have an entry and the selector list returned has a null
+      // nsCSSSelectorList*/RawServoSelectorList*, that indicates that aSelector
       // has already been parsed and is not a syntactically valid selector.
-      bool GetList(const nsAString& aSelector, nsCSSSelectorList** aList)
+      SelectorList* GetList(const nsAString& aSelector)
       {
-        return mTable.Get(aSelector, aList);
+        return mTable.GetValue(aSelector);
       }
 
       ~SelectorCache();
 
     private:
-      nsClassHashtable<nsStringHashKey, nsCSSSelectorList> mTable;
+      nsDataHashtable<nsStringHashKey, SelectorList> mTable;
   };
 
   SelectorCache& GetSelectorCache() {
     if (!mSelectorCache) {
       mSelectorCache =
         new SelectorCache(EventTargetFor(mozilla::TaskCategory::Other));
     }
     return *mSelectorCache;
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2671,37 +2671,84 @@ nsINode::Length() const
     MOZ_ASSERT(IsNodeOfType(eCONTENT));
     return static_cast<const nsIContent*>(this)->TextLength();
 
   default:
     return GetChildCount();
   }
 }
 
+const RawServoSelectorList*
+nsINode::ParseServoSelectorList(
+  const nsAString& aSelectorString,
+  ErrorResult& aRv)
+{
+  nsIDocument* doc = OwnerDoc();
+  MOZ_ASSERT(doc->IsStyledByServo());
+
+  nsIDocument::SelectorCache& cache = doc->GetSelectorCache();
+  nsIDocument::SelectorCache::SelectorList* list =
+    cache.GetList(aSelectorString);
+  if (list) {
+    if (!*list) {
+      // Invalid selector.
+      aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
+        NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
+        NS_LITERAL_CSTRING("' is not a valid selector")
+      );
+    }
+
+    // FIXME(emilio): Make this private and use `WithSelectorList` everywhere,
+    // then assert.
+    if (list->IsServo()) {
+      return list->AsServo();
+    }
+  }
+
+  NS_ConvertUTF16toUTF8 selectorString(aSelectorString);
+
+  auto* selectorList = Servo_SelectorList_Parse(&selectorString);
+  if (!selectorList) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
+      NS_LITERAL_CSTRING("'") + selectorString +
+      NS_LITERAL_CSTRING("' is not a valid selector")
+    );
+  }
+
+  cache.CacheList(aSelectorString, UniquePtr<RawServoSelectorList>(selectorList));
+  return selectorList;
+}
+
 nsCSSSelectorList*
 nsINode::ParseSelectorList(const nsAString& aSelectorString,
                            ErrorResult& aRv)
 {
   nsIDocument* doc = OwnerDoc();
   nsIDocument::SelectorCache& cache = doc->GetSelectorCache();
-  nsCSSSelectorList* selectorList = nullptr;
-  bool haveCachedList = cache.GetList(aSelectorString, &selectorList);
-  if (haveCachedList) {
-    if (!selectorList) {
+  nsIDocument::SelectorCache::SelectorList* list =
+    cache.GetList(aSelectorString);
+  if (list) {
+    if (!*list) {
       // Invalid selector.
       aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
         NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
         NS_LITERAL_CSTRING("' is not a valid selector")
       );
     }
-    return selectorList;
+
+    // FIXME(emilio): Make this private and use `WithSelectorList` everywhere,
+    // then assert.
+    if (list->IsGecko()) {
+      return list->AsGecko();
+    }
   }
 
   nsCSSParser parser(doc->CSSLoader());
 
+  nsCSSSelectorList* selectorList = nullptr;
   aRv = parser.ParseSelectorString(aSelectorString,
                                    doc->GetDocumentURI(),
                                    0, // XXXbz get the line number!
                                    &selectorList);
   if (aRv.Failed()) {
     // We hit this for syntax errors, which are quite common, so don't
     // use NS_ENSURE_SUCCESS.  (For example, jQuery has an extended set
     // of selectors, but it sees if we can parse them first.)
@@ -2709,17 +2756,17 @@ nsINode::ParseSelectorList(const nsAStri
                "Unexpected error, so cached version won't return it");
 
     // Change the error message to match above.
     aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
       NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
       NS_LITERAL_CSTRING("' is not a valid selector")
     );
 
-    cache.CacheList(aSelectorString, nullptr);
+    cache.CacheList(aSelectorString, UniquePtr<nsCSSSelectorList>());
     return nullptr;
   }
 
   // Filter out pseudo-element selectors from selectorList
   nsCSSSelectorList** slot = &selectorList;
   do {
     nsCSSSelectorList* cur = *slot;
     if (cur->mSelectors->IsPseudoElement()) {
@@ -2729,17 +2776,17 @@ nsINode::ParseSelectorList(const nsAStri
     } else {
       slot = &cur->mNext;
     }
   } while (*slot);
 
   if (selectorList) {
     NS_ASSERTION(selectorList->mSelectors,
                  "How can we not have any selectors?");
-    cache.CacheList(aSelectorString, selectorList);
+    cache.CacheList(aSelectorString, UniquePtr<nsCSSSelectorList>(selectorList));
   } else {
     // This is the "only pseudo-element selectors" case, which is
     // not common, so just don't worry about caching it.  That way a
     // null cached value can always indicate an invalid selector.
   }
 
   return selectorList;
 }
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -50,16 +50,17 @@ class nsINode;
 class nsINodeList;
 class nsIPresShell;
 class nsIPrincipal;
 class nsIURI;
 class nsNodeSupportsWeakRefTearoff;
 class nsNodeWeakReference;
 class nsDOMMutationObserver;
 class nsRange;
+struct RawServoSelectorList;
 
 namespace mozilla {
 class EventListenerManager;
 class TextEditor;
 namespace dom {
 /**
  * @return true if aChar is what the WHATWG defines as a 'ascii whitespace'.
  * https://infra.spec.whatwg.org/#ascii-whitespace
@@ -2061,20 +2062,56 @@ protected:
 
   /**
    * Parse the given selector string into an nsCSSSelectorList.
    *
    * A null return value with a non-failing aRv means the string only
    * contained pseudo-element selectors.
    *
    * A failing aRv means the string was not a valid selector.
+   *
+   * Note that the selector list returned here is owned by the owner doc's
+   * selector cache.
    */
   nsCSSSelectorList* ParseSelectorList(const nsAString& aSelectorString,
                                        mozilla::ErrorResult& aRv);
 
+  /**
+   * Parse the given selector string into a servo SelectorList.
+   *
+   * Never returns null if aRv is not failing.
+   *
+   * Note that the selector list returned here is owned by the owner doc's
+   * selector cache.
+   */
+  const RawServoSelectorList* ParseServoSelectorList(
+    const nsAString& aSelectorString,
+    mozilla::ErrorResult& aRv);
+
+  /**
+   * Parse the given selector string into a SelectorList.
+   *
+   * A null return value with a non-failing aRv means the string only
+   * contained pseudo-element selectors.
+   *
+   * A failing aRv means the string was not a valid selector.
+   */
+  template<typename Ret, typename ServoFunctor, typename GeckoFunctor>
+  Ret WithSelectorList(
+    const nsAString& aSelectorString,
+    mozilla::ErrorResult& aRv,
+    const ServoFunctor& aServoFunctor,
+    const GeckoFunctor& aGeckoFunctor)
+  {
+    if (IsStyledByServo()) {
+      return aServoFunctor(ParseServoSelectorList(aSelectorString, aRv));
+    }
+    return aGeckoFunctor(ParseSelectorList(aSelectorString, aRv));
+  }
+
 public:
   /* Event stuff that documents and elements share.  This needs to be
      NS_IMETHOD because some subclasses implement DOM methods with
      this exact name and signature and then the calling convention
      needs to match.
 
      Note that we include DOCUMENT_ONLY_EVENT events here so that we
      can forward all the document stuff to this implementation.
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1040,17 +1040,17 @@ ContentEventHandler::GenerateFlatFontRan
         MOZ_ASSERT(baseOffset == 0);
         FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset);
         nsIFrame* frame = content->GetPrimaryFrame();
         if (frame) {
           const nsFont& font = frame->GetParent()->StyleFont()->mFont;
           const FontFamilyList& fontList = font.fontlist;
           const FontFamilyName& fontName = fontList.IsEmpty() ?
             FontFamilyName(fontList.GetDefaultFontType()) :
-            fontList.GetFontlist()[0];
+            fontList.GetFontlist()->mNames[0];
           fontName.AppendToString(fontRange->mFontName, false);
           fontRange->mFontSize =
             frame->PresContext()->AppUnitsToDevPixels(font.size);
         }
       }
       baseOffset += GetBRLength(aLineBreakType);
     }
   }
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -1328,21 +1328,26 @@ IMEStateManager::SetIMEState(const IMESt
       mozilla::dom::Element* formElement = nullptr;
       nsCOMPtr<nsIForm> form;
       if (control) {
         formElement = control->GetFormElement();
         // is this a form and does it have a default submit element?
         if ((form = do_QueryInterface(formElement)) &&
             form->GetDefaultSubmitElement()) {
           willSubmit = true;
-        // is this an html form and does it only have a single text input element?
-        } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form) &&
-                   !static_cast<dom::HTMLFormElement*>(formElement)->
-                     ImplicitSubmissionIsDisabled()) {
-          willSubmit = true;
+        // is this an html form...
+        } else if (formElement && formElement->IsHTMLElement(nsGkAtoms::form)) {
+          dom::HTMLFormElement* htmlForm =
+            static_cast<dom::HTMLFormElement*>(formElement);
+          // ... and does it only have a single text input element ?
+          if (!htmlForm->ImplicitSubmissionIsDisabled() ||
+              // ... or is this the last non-disabled element?
+              htmlForm->IsLastActiveElement(control)) {
+            willSubmit = true;
+          }
         }
       }
       context.mActionHint.Assign(
         willSubmit ? (control->ControlType() == NS_FORM_INPUT_SEARCH ?
                        NS_LITERAL_STRING("search") : NS_LITERAL_STRING("go")) :
                      (formElement ?
                        NS_LITERAL_STRING("next") : EmptyString()));
     }
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -383,17 +383,17 @@ class WindowsGamepadService
   static void DevicesChangeCallback(nsITimer *aTimer, void* aService);
 
  private:
   void ScanForDevices();
   // Look for connected raw input devices.
   void ScanForRawInputDevices();
   // Look for connected XInput devices.
   bool ScanForXInputDevices();
-  bool HaveXInputGamepad(int userIndex);
+  bool HaveXInputGamepad(unsigned int userIndex);
 
   bool mIsXInputMonitoring;
   void PollXInput();
   void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);
 
   // Get information about a raw input gamepad.
   bool GetRawGamepad(HANDLE handle);
   void Cleanup();
@@ -457,17 +457,17 @@ void
 WindowsGamepadService::DevicesChangeCallback(nsITimer *aTimer, void* aService)
 {
   MOZ_ASSERT(aService);
   WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
   self->DevicesChanged(false);
 }
 
 bool
-WindowsGamepadService::HaveXInputGamepad(int userIndex)
+WindowsGamepadService::HaveXInputGamepad(unsigned int userIndex)
 {
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (mGamepads[i].type == kXInputGamepad
         && mGamepads[i].userIndex == userIndex) {
       mGamepads[i].present = true;
       return true;
     }
   }
@@ -481,17 +481,17 @@ WindowsGamepadService::ScanForXInputDevi
 
   bool found = false;
   RefPtr<GamepadPlatformService> service =
     GamepadPlatformService::GetParentService();
   if (!service) {
     return found;
   }
 
-  for (int i = 0; i < XUSER_MAX_COUNT; i++) {
+  for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++) {
     XINPUT_STATE state = {};
     if (mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
       continue;
     }
     found = true;
     // See if this device is already present in our list.
     if (HaveXInputGamepad(i)) {
       continue;
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -1823,24 +1823,37 @@ HTMLFormElement::IsDefaultSubmitElement(
 
 bool
 HTMLFormElement::ImplicitSubmissionIsDisabled() const
 {
   // Input text controls are always in the elements list.
   uint32_t numDisablingControlsFound = 0;
   uint32_t length = mControls->mElements.Length();
   for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
-    if (mControls->mElements[i]->IsSingleLineTextControl(false) ||
-        mControls->mElements[i]->ControlType() == NS_FORM_INPUT_NUMBER) {
+    if (mControls->mElements[i]->IsSingleLineTextOrNumberControl(false)) {
       numDisablingControlsFound++;
     }
   }
   return numDisablingControlsFound != 1;
 }
 
+bool
+HTMLFormElement::IsLastActiveElement(const nsIFormControl* aControl) const
+{
+  NS_PRECONDITION(aControl, "Unexpected call");
+
+  for (auto* element : Reversed(mControls->mElements)) {
+    if (element->IsSingleLineTextOrNumberControl(false) &&
+        !element->IsDisabled()) {
+      return element == aControl;
+    }
+  }
+  return false;
+}
+
 NS_IMETHODIMP
 HTMLFormElement::GetEncoding(nsAString& aEncoding)
 {
   return GetEnctype(aEncoding);
 }
 
 NS_IMETHODIMP
 HTMLFormElement::SetEncoding(const nsAString& aEncoding)
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -216,16 +216,22 @@ public:
     * Returns true if implicit submission of this form is disabled. For more
     * on implicit submission see:
     *
     * http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#implicit-submission
     */
   bool ImplicitSubmissionIsDisabled() const;
 
   /**
+  * Check whether a given nsIFormControl is the last single line input control
+  * that is not disabled. aControl is expected to not be null.
+  */
+  bool IsLastActiveElement(const nsIFormControl* aControl) const;
+
+  /**
    * Check whether a given nsIFormControl is the default submit
    * element.  This is different from just comparing to
    * GetDefaultSubmitElement() in certain situations inside an update
    * when GetDefaultSubmitElement() might not be up to date.  aControl
    * is expected to not be null.
    */
   bool IsDefaultSubmitElement(const nsIFormControl* aControl) const;
 
--- a/dom/html/nsIFormControl.h
+++ b/dom/html/nsIFormControl.h
@@ -197,16 +197,23 @@ public:
   /**
    * Returns whether this is a single line text control.
    * @param  aExcludePassword  to have NS_FORM_INPUT_PASSWORD returning false.
    * @return whether this is a single line text control.
    */
   inline bool IsSingleLineTextControl(bool aExcludePassword) const;
 
   /**
+  * Returns true if this is a single line text control or a number control.
+  * @param  aExcludePassword  to have NS_FORM_INPUT_PASSWORD returning false.
+  * @return true if this is a single line text control or a number control.
+  */
+  inline bool IsSingleLineTextOrNumberControl(bool aExcludePassword) const;
+
+  /**
    * Returns whether this is a submittable form control.
    * @return whether this is a submittable form control.
    */
   inline bool IsSubmittableControl() const;
 
   /**
    * Returns whether this form control can have draggable children.
    * @return whether this form control can have draggable children.
@@ -260,16 +267,23 @@ nsIFormControl::IsTextOrNumberControl(bo
 }
 
 bool
 nsIFormControl::IsSingleLineTextControl(bool aExcludePassword) const
 {
   return IsSingleLineTextControl(aExcludePassword, ControlType());
 }
 
+bool
+nsIFormControl::IsSingleLineTextOrNumberControl(bool aExcludePassword) const
+{
+  return IsSingleLineTextControl(aExcludePassword) ||
+         ControlType() == NS_FORM_INPUT_NUMBER;
+}
+
 /*static*/
 bool
 nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType)
 {
   return aType == NS_FORM_INPUT_TEXT ||
          aType == NS_FORM_INPUT_EMAIL ||
          aType == NS_FORM_INPUT_SEARCH ||
          aType == NS_FORM_INPUT_TEL ||
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1173,16 +1173,28 @@ class RTCPeerConnection {
   mozAddRIDExtension(receiver, extensionId) {
     this._impl.addRIDExtension(receiver.track, extensionId);
   }
 
   mozAddRIDFilter(receiver, rid) {
     this._impl.addRIDFilter(receiver.track, rid);
   }
 
+  mozSetPacketCallback(callback) {
+    this._onPacket = callback;
+  }
+
+  mozEnablePacketDump(level, type, sending) {
+    this._impl.enablePacketDump(level, type, sending);
+  }
+
+  mozDisablePacketDump(level, type, sending) {
+    this._impl.disablePacketDump(level, type, sending);
+  }
+
   get localDescription() {
     this._checkClosed();
     let sdp = this._impl.localDescription;
     if (sdp.length == 0) {
       return null;
     }
     return new this._win.RTCSessionDescription({ type: this._localType, sdp });
   }
@@ -1611,16 +1623,23 @@ class PeerConnectionObserver {
   }
 
   onDTMFToneChange(trackId, tone) {
     var pc = this._dompc;
     var sender = pc._senders.find(({track}) => track.id == trackId);
     sender.dtmf.dispatchEvent(new pc._win.RTCDTMFToneChangeEvent("tonechange",
                                                                  { tone }));
   }
+
+  onPacket(level, type, sending, packet) {
+    var pc = this._dompc;
+    if (pc._onPacket) {
+      pc._onPacket(level, type, sending, packet);
+    }
+  }
 }
 setupPrototype(PeerConnectionObserver, {
   classID: PC_OBS_CID,
   contractID: PC_OBS_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer])
 });
 
--- a/dom/media/fake-cdm/cdm-test-output-protection.h
+++ b/dom/media/fake-cdm/cdm-test-output-protection.h
@@ -37,17 +37,17 @@ static BOOL CALLBACK EnumDisplayMonitors
 
   ULONG numVideoOutputs = 0;
   IOPMVideoOutput** opmVideoOutputArray = nullptr;
   HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(hMonitor,
                                                    OPM_VOS_OPM_SEMANTICS,
                                                    &numVideoOutputs,
                                                    &opmVideoOutputArray);
   if (S_OK != hr) {
-    if (0x8007001f != hr && 0x80070032 != hr && 0xc02625e5 != hr) {
+    if ((HRESULT)0x8007001f != hr && (HRESULT)0x80070032 != hr && (HRESULT)0xc02625e5 != hr) {
       char msg[100];
       sprintf(msg, "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08x", hr);
       failureMsgs->push_back(msg);
     }
     return true;
   }
 
   DISPLAY_DEVICEA dd;
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -853,17 +853,17 @@ skip-if = android_version == '17' # andr
 tags=msg
 [test_mediarecorder_record_audionode.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_canvas_captureStream.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_changing_video_resolution.html]
-skip-if = toolkit == 'android' # android(bug 1232305)
+skip-if = toolkit == 'android' || webrender # android(bug 1232305), webrender(bug 1405686)
 tags=msg
 [test_mediarecorder_record_upsize_resolution.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_downsize_resolution.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_gum_video_timeslice.html]
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -91,16 +91,18 @@ skip-if = toolkit == 'android' # no scre
 [test_getUserMedia_trackCloneCleanup.html]
 [test_getUserMedia_trackEnded.html]
 [test_getUserMedia_peerIdentity.html]
 [test_peerConnection_addIceCandidate.html]
 [test_peerConnection_addtrack_removetrack_events.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudio.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
+[test_peerConnection_checkPacketDumpHook.html]
+skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 [test_peerConnection_basicAudioNATRelayTLS.html]
 skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' # websockets don't work on android (bug 1266217)
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_checkPacketDumpHook.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1377299",
+    title: "Check that packet dump hooks generate callbacks"
+  });
+
+  function waitForPacket(pc, checkFunction) {
+    return new Promise(resolve => {
+      function onPacket(level, type, sending, packet) {
+        if (checkFunction(level, type, sending, packet)) {
+          SpecialPowers.wrap(pc).mozSetPacketCallback(() => {});
+          resolve();
+        }
+      }
+
+      SpecialPowers.wrap(pc).mozSetPacketCallback(onPacket);
+    }
+    );
+  }
+
+  async function waitForSendPacket(pc, type, level) {
+    await SpecialPowers.wrap(pc).mozEnablePacketDump(level, type, true);
+    await timeout(
+      waitForPacket(pc, (obsLevel, obsType, sending) => {
+        is(obsLevel, level, "Level for packet is " + level);
+        is(obsType, type, "Type for packet is " + type);
+        ok(sending, "This is a send packet");
+        return true;
+      }),
+      10000, "Timeout waiting for " + type + " send packet on level " + level);
+    await SpecialPowers.wrap(pc).mozDisablePacketDump(level, type, true);
+  }
+
+  async function waitForRecvPacket(pc, type, level) {
+    await SpecialPowers.wrap(pc).mozEnablePacketDump(level, type, false);
+    await timeout(
+      waitForPacket(pc, (obsLevel, obsType, sending) => {
+        is(obsLevel, level, "Level for packet is " + level);
+        is(obsType, type, "Type for packet is " + type);
+        ok(!sending, "This is a recv packet");
+        return true;
+      }),
+      10000, "Timeout waiting for " + type + " recv packet on level " + level);
+    await SpecialPowers.wrap(pc).mozDisablePacketDump(level, type, false);
+  }
+
+  var test;
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    test.setMediaConstraints([{audio: true, video: true}],
+                             [{audio: true, video: true}]);
+    // pc.js uses video elements by default, we want to test audio elements here
+    test.pcLocal.audioElementsOnly = true;
+
+    test.chain.insertBefore('PC_LOCAL_WAIT_FOR_MEDIA_FLOW',[
+        async function PC_LOCAL_CHECK_PACKET_DUMP_HOOKS() {
+          await waitForRecvPacket(test.pcLocal._pc, "rtp", 0);
+          await waitForRecvPacket(test.pcLocal._pc, "rtcp", 0);
+          await waitForRecvPacket(test.pcLocal._pc, "srtp", 0);
+          await waitForRecvPacket(test.pcLocal._pc, "srtcp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "rtp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "rtcp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "srtp", 0);
+          await waitForSendPacket(test.pcLocal._pc, "srtcp", 0);
+
+          await waitForRecvPacket(test.pcLocal._pc, "rtp", 1);
+          await waitForRecvPacket(test.pcLocal._pc, "rtcp", 1);
+          await waitForRecvPacket(test.pcLocal._pc, "srtp", 1);
+          await waitForRecvPacket(test.pcLocal._pc, "srtcp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "rtp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "rtcp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "srtp", 1);
+          await waitForSendPacket(test.pcLocal._pc, "srtcp", 1);
+        },
+        async function PC_REMOTE_CHECK_PACKET_DUMP_HOOKS() {
+          await waitForRecvPacket(test.pcRemote._pc, "rtp", 0);
+          await waitForRecvPacket(test.pcRemote._pc, "rtcp", 0);
+          await waitForRecvPacket(test.pcRemote._pc, "srtp", 0);
+          await waitForRecvPacket(test.pcRemote._pc, "srtcp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "rtp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "rtcp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "srtp", 0);
+          await waitForSendPacket(test.pcRemote._pc, "srtcp", 0);
+
+          await waitForRecvPacket(test.pcRemote._pc, "rtp", 1);
+          await waitForRecvPacket(test.pcRemote._pc, "rtcp", 1);
+          await waitForRecvPacket(test.pcRemote._pc, "srtp", 1);
+          await waitForRecvPacket(test.pcRemote._pc, "srtcp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "rtp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "rtcp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "srtp", 1);
+          await waitForSendPacket(test.pcRemote._pc, "srtcp", 1);
+        }
+    ]);
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/performance/PerformanceMainThread.cpp
+++ b/dom/performance/PerformanceMainThread.cpp
@@ -307,19 +307,21 @@ DOMHighResTimeStamp
 PerformanceMainThread::CreationTime() const
 {
   return GetDOMTiming()->GetNavigationStart();
 }
 
 void
 PerformanceMainThread::EnsureDocEntry()
 {
-  if (!mDocEntry) {
+  if (!mDocEntry && nsContentUtils::IsPerformanceNavigationTimingEnabled()) {
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
-    mDocEntry = new PerformanceNavigationTiming(Timing(), this,
+    RefPtr<PerformanceTiming> timing =
+      new PerformanceTiming(this, mChannel, nullptr, 0);
+    mDocEntry = new PerformanceNavigationTiming(timing, this,
                                                 httpChannel);
   }
 }
 
 
 void
 PerformanceMainThread::GetEntries(nsTArray<RefPtr<PerformanceEntry>>& aRetval)
 {
--- a/dom/performance/PerformanceNavigationTiming.cpp
+++ b/dom/performance/PerformanceNavigationTiming.cpp
@@ -17,65 +17,65 @@ NS_IMPL_RELEASE_INHERITED(PerformanceNav
 
 JSObject*
 PerformanceNavigationTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PerformanceNavigationTimingBinding::Wrap(aCx, this, aGivenProto);
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::UnloadEventStart()
+PerformanceNavigationTiming::UnloadEventStart() const
 {
-  return mTiming->GetDOMTiming()->GetUnloadEventStart();
+  return mTiming->GetDOMTiming()->GetUnloadEventStartHighRes();
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::UnloadEventEnd()
+PerformanceNavigationTiming::UnloadEventEnd() const
 {
-  return mTiming->GetDOMTiming()->GetUnloadEventEnd();
+  return mTiming->GetDOMTiming()->GetUnloadEventEndHighRes();
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::DomInteractive()
+PerformanceNavigationTiming::DomInteractive() const
 {
-  return mTiming->GetDOMTiming()->GetDomInteractive();
+  return mTiming->GetDOMTiming()->GetDomInteractiveHighRes();
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::DomContentLoadedEventStart()
+PerformanceNavigationTiming::DomContentLoadedEventStart() const
 {
-  return mTiming->GetDOMTiming()->GetDomContentLoadedEventStart();
+  return mTiming->GetDOMTiming()->GetDomContentLoadedEventStartHighRes();
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::DomContentLoadedEventEnd()
+PerformanceNavigationTiming::DomContentLoadedEventEnd() const
 {
-  return mTiming->GetDOMTiming()->GetDomContentLoadedEventEnd();
+  return mTiming->GetDOMTiming()->GetDomContentLoadedEventEndHighRes();
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::DomComplete()
+PerformanceNavigationTiming::DomComplete() const
 {
-  return mTiming->GetDOMTiming()->GetDomComplete();
+  return mTiming->GetDOMTiming()->GetDomCompleteHighRes();
 }
 
 DOMHighResTimeStamp
-PerformanceNavigationTiming::LoadEventStart()
+PerformanceNavigationTiming::LoadEventStart() const
 {
-  return mTiming->GetDOMTiming()->GetLoadEventStart();
+  return mTiming->GetDOMTiming()->GetLoadEventStartHighRes();
 }
 
 DOMHighResTimeStamp
 PerformanceNavigationTiming::LoadEventEnd() const
 {
-  return mTiming->GetDOMTiming()->GetLoadEventEnd();
+  return mTiming->GetDOMTiming()->GetLoadEventEndHighRes();
 }
 
 NavigationType
-PerformanceNavigationTiming::Type()
+PerformanceNavigationTiming::Type() const
 {
   switch(mTiming->GetDOMTiming()->GetType()) {
     case nsDOMNavigationTiming::TYPE_NAVIGATE:
       return NavigationType::Navigate;
       break;
     case nsDOMNavigationTiming::TYPE_RELOAD:
       return NavigationType::Reload;
       break;
@@ -85,12 +85,12 @@ PerformanceNavigationTiming::Type()
     default:
       // The type is TYPE_RESERVED or some other value that was later added.
       // We fallback to the default of Navigate.
       return NavigationType::Navigate;
   }
 }
 
 uint16_t
-PerformanceNavigationTiming::RedirectCount()
+PerformanceNavigationTiming::RedirectCount() const
 {
   return mTiming->GetRedirectCount();
 }
--- a/dom/performance/PerformanceNavigationTiming.h
+++ b/dom/performance/PerformanceNavigationTiming.h
@@ -19,16 +19,20 @@ namespace dom {
 
 // https://www.w3.org/TR/navigation-timing-2/#sec-PerformanceNavigationTiming
 class PerformanceNavigationTiming final
   : public PerformanceResourceTiming
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
+  // Note that aPerformanceTiming must be initalized with zeroTime = 0
+  // so that timestamps are relative to startTime, as opposed to the
+  // performance.timing object for which timestamps are absolute and has a
+  // zeroTime initialized to navigationStart
   explicit PerformanceNavigationTiming(PerformanceTiming* aPerformanceTiming,
                                        Performance* aPerformance,
                                        nsIHttpChannel* aChannel)
     : PerformanceResourceTiming(aPerformanceTiming, aPerformance,
                                 NS_LITERAL_STRING("document"), aChannel) {
       SetEntryType(NS_LITERAL_STRING("navigation"));
       SetInitiatorType(NS_LITERAL_STRING("navigation"));
     }
@@ -40,27 +44,27 @@ public:
 
   DOMHighResTimeStamp StartTime() const override
   {
     return 0;
   }
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  DOMHighResTimeStamp UnloadEventStart();
-  DOMHighResTimeStamp UnloadEventEnd();
+  DOMHighResTimeStamp UnloadEventStart() const;
+  DOMHighResTimeStamp UnloadEventEnd() const;
 
-  DOMHighResTimeStamp DomInteractive();
-  DOMHighResTimeStamp DomContentLoadedEventStart();
-  DOMHighResTimeStamp DomContentLoadedEventEnd();
-  DOMHighResTimeStamp DomComplete();
-  DOMHighResTimeStamp LoadEventStart();
+  DOMHighResTimeStamp DomInteractive() const;
+  DOMHighResTimeStamp DomContentLoadedEventStart() const;
+  DOMHighResTimeStamp DomContentLoadedEventEnd() const;
+  DOMHighResTimeStamp DomComplete() const;
+  DOMHighResTimeStamp LoadEventStart() const;
   DOMHighResTimeStamp LoadEventEnd() const;
-  NavigationType Type();
-  uint16_t RedirectCount();
+  NavigationType Type() const;
+  uint16_t RedirectCount() const;
 
 private:
   ~PerformanceNavigationTiming() {}
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -2,21 +2,29 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "mozilla/dom/U2F.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "nsContentUtils.h"
+#include "nsIEffectiveTLDService.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsURLParsers.h"
 #include "U2FManager.h"
 
+// Forward decl because of nsHTMLDocument.h's complex dependency on /layout/style
+class nsHTMLDocument {
+public:
+  bool IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString,
+                                            const nsACString& aOrigHost);
+};
+
 namespace mozilla {
 namespace dom {
 
 static mozilla::LazyLogModule gU2FLog("u2fmanager");
 
 NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
 NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
 
@@ -85,17 +93,18 @@ RegisteredKeysToScopedCredentialList(con
 
     WebAuthnScopedCredentialDescriptor c;
     c.id() = keyHandle;
     aList.AppendElement(c);
   }
 }
 
 static ErrorCode
-EvaluateAppID(const nsString& aOrigin, /* in/out */ nsString& aAppId)
+EvaluateAppID(nsPIDOMWindowInner* aParent, const nsString& aOrigin,
+              /* in/out */ nsString& aAppId)
 {
   // Facet is the specification's way of referring to the web origin.
   nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
   nsCOMPtr<nsIURI> facetUri;
   if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
     return ErrorCode::BAD_REQUEST;
   }
 
@@ -119,30 +128,53 @@ EvaluateAppID(const nsString& aOrigin, /
   }
 
   // if the appId URL is not HTTPS, reject.
   bool appIdIsHttps = false;
   if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
     return ErrorCode::BAD_REQUEST;
   }
 
-  // If the facetId and the appId hosts match, accept
-  nsAutoCString facetHost;
-  if (NS_FAILED(facetUri->GetHost(facetHost))) {
+  // Run the HTML5 algorithm to relax the same-origin policy, copied from W3C
+  // Web Authentication. See Bug 1244959 comment #8 for context on why we are
+  // doing this instead of implementing the external-fetch FacetID logic.
+  nsCOMPtr<nsIDocument> document = aParent->GetDoc();
+  if (!document || !document->IsHTMLDocument()) {
+    return ErrorCode::BAD_REQUEST;
+  }
+  nsHTMLDocument* html = document->AsHTMLDocument();
+  if (NS_WARN_IF(!html)) {
+    return ErrorCode::BAD_REQUEST;
+  }
+
+  // Use the base domain as the facet for evaluation. This lets this algorithm
+  // relax the whole eTLD+1.
+  nsCOMPtr<nsIEffectiveTLDService> tldService =
+    do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+  if (!tldService) {
+    return ErrorCode::BAD_REQUEST;
+  }
+
+  nsAutoCString lowestFacetHost;
+  if (NS_FAILED(tldService->GetBaseDomain(facetUri, 0, lowestFacetHost))) {
     return ErrorCode::BAD_REQUEST;
   }
   nsAutoCString appIdHost;
-  if (NS_FAILED(appIdUri->GetHost(appIdHost))) {
+  if (NS_FAILED(appIdUri->GetAsciiHost(appIdHost))) {
     return ErrorCode::BAD_REQUEST;
   }
-  if (facetHost.Equals(appIdHost)) {
+
+  MOZ_LOG(gU2FLog, LogLevel::Debug,
+          ("AppId %s Facet %s", appIdHost.get(), lowestFacetHost.get()));
+
+  if (html->IsRegistrableDomainSuffixOfOrEqualTo(NS_ConvertUTF8toUTF16(lowestFacetHost),
+                                                 appIdHost)) {
     return ErrorCode::OK;
   }
 
-  // TODO(Bug 1244959) Implement the remaining algorithm.
   return ErrorCode::BAD_REQUEST;
 }
 
 template<typename T, typename C>
 static void
 ExecuteCallback(T& aResp, Maybe<nsMainThreadPtrHandle<C>>& aCb)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -228,17 +260,17 @@ U2F::Register(const nsAString& aAppId,
                         new nsMainThreadPtrHolder<U2FRegisterCallback>(
                             "U2F::Register::callback", &aCallback)));
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
   // Evaluate the AppID
   nsString adjustedAppId;
   adjustedAppId.Assign(aAppId);
-  ErrorCode appIdResult = EvaluateAppID(mOrigin, adjustedAppId);
+  ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, adjustedAppId);
   if (appIdResult != ErrorCode::OK) {
     RegisterResponse response;
     response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
     ExecuteCallback(response, mRegisterCallback);
     return;
   }
 
   // Produce the AppParam from the current AppID
@@ -325,17 +357,17 @@ U2F::Sign(const nsAString& aAppId,
                     new nsMainThreadPtrHolder<U2FSignCallback>(
                         "U2F::Sign::callback", &aCallback)));
 
   uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
 
   // Evaluate the AppID
   nsString adjustedAppId;
   adjustedAppId.Assign(aAppId);
-  ErrorCode appIdResult = EvaluateAppID(mOrigin, adjustedAppId);
+  ErrorCode appIdResult = EvaluateAppID(mParent, mOrigin, adjustedAppId);
   if (appIdResult != ErrorCode::OK) {
     SignResponse response;
     response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
     ExecuteCallback(response, mSignCallback);
     return;
   }
 
   // Produce the AppParam from the current AppID
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -9,17 +9,16 @@
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/U2FBinding.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/MozPromise.h"
-#include "nsPIDOMWindow.h"
 #include "nsProxyRelease.h"
 #include "nsWrapperCache.h"
 #include "U2FAuthenticator.h"
 
 class nsISerialEventTarget;
 
 namespace mozilla {
 namespace dom {
--- a/dom/u2f/tests/frame_appid_facet.html
+++ b/dom/u2f/tests/frame_appid_facet.html
@@ -49,25 +49,33 @@ async function doTests() {
       local_is(res.errorCode, 0, "HTTPS origin for example.com should work");
   });
 
   // Test: Sub-domain
   await promiseU2FRegister("https://test2.example.com/appId", [{
       version: version,
       challenge: bytesToBase64UrlSafe(challenge),
     }], [], function(res){
-      local_is(res.errorCode, 2, "HTTPS origin for test2.example.com shouldn't work");
+      local_is(res.errorCode, 0, "HTTPS origin for test2.example.com should work");
   });
 
   // Test: Sub-sub-domain
   await promiseU2FRegister("https://sub.test2.example.com/appId", [{
       version: version,
       challenge: bytesToBase64UrlSafe(challenge),
     }], [], function(res){
-      local_is(res.errorCode, 2, "HTTPS origin for sub.test2.example.com shouldn't work");
+      local_is(res.errorCode, 0, "HTTPS origin for sub.test2.example.com should work");
+  });
+
+  // Test: TLD
+  await promiseU2FRegister("https://com/crazyAppID", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 2, "HTTPS origin of the TLD should not work");
   });
 
   // Test: Dynamic origin
   await promiseU2FRegister(window.location.origin + "/otherAppId", [{
       version: version,
       challenge: bytesToBase64UrlSafe(challenge),
     }], [], function(res){
       local_is(res.errorCode, 0, "Direct window origin should work");
--- a/dom/u2f/tests/frame_appid_facet_subdomain.html
+++ b/dom/u2f/tests/frame_appid_facet_subdomain.html
@@ -31,27 +31,54 @@ async function doTests() {
     local_isnot(res.errorCode, 0, "AppID should not work when using a different scheme");
   });
 
   // eTLD+1 subdomain check
   await promiseU2FRegister("https://example.com/appId", [{
     version: version,
     challenge: bytesToBase64UrlSafe(challenge),
   }], [], function(res){
-    local_isnot(res.errorCode, 0, "AppID should not work from another subdomain in this registered domain");
+    // Changed in Bug 1244959 to behave like W3C Web Authentication
+    local_is(res.errorCode, 0, "AppID should work from another subdomain in this registered domain");
+  });
+
+  // sub-subdomain check
+  await promiseU2FRegister("https://sub.test1.example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    // Changed in Bug 1244959 to behave like W3C Web Authentication
+    local_is(res.errorCode, 0, "AppID should work from a sub-subdomain");
+  });
+
+  // sub-subdomain check
+  await promiseU2FRegister("https://test2.example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    // Changed in Bug 1244959 to behave like W3C Web Authentication
+    local_is(res.errorCode, 0, "AppID should work from another subdomain of the eTLD+1");
   });
 
   // other domain check
   await promiseU2FRegister("https://mochi.test:8888/appId", [{
     version: version,
     challenge: bytesToBase64UrlSafe(challenge),
   }], [], function(res){
     local_isnot(res.errorCode, 0, "AppID should not work from other domains");
   });
 
+  // TLD check
+  await promiseU2FRegister("https://com:8888/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "AppID should not work from the eTLD itself");
+  });
+
   local_finished();
 };
 
 doTests();
 
 </script>
 </body>
 </html>
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -52,17 +52,17 @@ function base64ToBytes(b64encoded) {
 function bytesToBase64UrlSafe(buf) {
   return bytesToBase64(buf)
                  .replace(/\+/g, "-")
                  .replace(/\//g, "_")
                  .replace(/=/g, "");
 }
 
 function base64ToBytesUrlSafe(str) {
-  if (str.length % 4 == 1) {
+  if (!str || str.length % 4 == 1) {
     throw "Improper b64 string";
   }
 
   var b64 = str.replace(/\-/g, "+").replace(/\_/g, "/");
   while (b64.length % 4 != 0) {
     b64 += "=";
   }
   return base64ToBytes(b64);
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -61,16 +61,24 @@ interface PeerConnectionImpl  {
   void closeStreams();
 
   sequence<MediaStream> getLocalStreams();
   sequence<MediaStream> getRemoteStreams();
 
   void addRIDExtension(MediaStreamTrack recvTrack, unsigned short extensionId);
   void addRIDFilter(MediaStreamTrack recvTrack, DOMString rid);
 
+  void enablePacketDump(unsigned long level,
+                        mozPacketDumpType type,
+                        boolean sending);
+
+  void disablePacketDump(unsigned long level,
+                         mozPacketDumpType type,
+                         boolean sending);
+
   /* As the ICE candidates roll in this one should be called each time
    * in order to keep the candidate list up-to-date for the next SDP-related
    * call PeerConnectionImpl does not parse ICE candidates, just sticks them
    * into the SDP.
    */
   [Throws]
   void addIceCandidate(DOMString candidate, DOMString mid, unsigned short level);
 
--- a/dom/webidl/PeerConnectionObserver.webidl
+++ b/dom/webidl/PeerConnectionObserver.webidl
@@ -42,9 +42,13 @@ interface PeerConnectionObserver
   /* Changes to MediaStreamTracks */
   void onAddStream(MediaStream stream);
   void onRemoveStream(MediaStream stream);
   void onAddTrack(MediaStreamTrack track, sequence<MediaStream> streams);
   void onRemoveTrack(MediaStreamTrack track);
 
   /* DTMF callback */
   void onDTMFToneChange(DOMString trackId, DOMString tone);
+
+  /* Packet dump callback */
+  void onPacket(unsigned long level, mozPacketDumpType type, boolean sending,
+                ArrayBuffer packet);
 };
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -31,16 +31,28 @@ enum RTCIceConnectionState {
     "checking",
     "connected",
     "completed",
     "failed",
     "disconnected",
     "closed"
 };
 
+enum mozPacketDumpType {
+  "rtp", // dump unencrypted rtp as the MediaPipeline sees it
+  "srtp", // dump encrypted rtp as the MediaPipeline sees it
+  "rtcp", // dump unencrypted rtcp as the MediaPipeline sees it
+  "srtcp" // dump encrypted rtcp as the MediaPipeline sees it
+};
+
+callback mozPacketCallback = void (unsigned long level,
+                                   mozPacketDumpType type,
+                                   boolean sending,
+                                   ArrayBuffer packet);
+
 dictionary RTCDataChannelInit {
   boolean        ordered = true;
   unsigned short maxPacketLifeTime;
   unsigned short maxRetransmits;
   DOMString      protocol = "";
   boolean        negotiated = false;
   unsigned short id;
 
@@ -118,16 +130,26 @@ interface RTCPeerConnection : EventTarge
 
   sequence<RTCRtpSender> getSenders();
   sequence<RTCRtpReceiver> getReceivers();
 
   [ChromeOnly]
   void mozAddRIDExtension(RTCRtpReceiver receiver, unsigned short extensionId);
   [ChromeOnly]
   void mozAddRIDFilter(RTCRtpReceiver receiver, DOMString rid);
+  [ChromeOnly]
+  void mozSetPacketCallback(mozPacketCallback callback);
+  [ChromeOnly]
+  void mozEnablePacketDump(unsigned long level,
+                           mozPacketDumpType type,
+                           boolean sending);
+  [ChromeOnly]
+  void mozDisablePacketDump(unsigned long level,
+                            mozPacketDumpType type,
+                            boolean sending);
 
   void close ();
   attribute EventHandler onnegotiationneeded;
   attribute EventHandler onicecandidate;
   attribute EventHandler onsignalingstatechange;
   attribute EventHandler onaddstream; // obsolete
   attribute EventHandler onaddtrack;  // obsolete
   attribute EventHandler ontrack;     // replaces onaddtrack and onaddstream.
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1619,17 +1619,28 @@ HTMLEditRules::WillInsertBreak(Selection
     ReturnInListItem(aSelection, *listItem, node, offset);
     *aHandled = true;
     return NS_OK;
   } else if (HTMLEditUtils::IsHeader(*blockParent)) {
     // Headers: close (or split) header
     ReturnInHeader(aSelection, *blockParent, node, offset);
     *aHandled = true;
     return NS_OK;
-  } else if (blockParent->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div)) {
+  }
+  // XXX Ideally, we should take same behavior with both <p> container and
+  //     <div> container.  However, we are still using <br> as default
+  //     paragraph separator (non-standard) and we've split only <p> container
+  //     long time.  Therefore, some web apps may depend on this behavior like
+  //     Gmail.  So, let's use traditional odd behavior only when the default
+  //     paragraph separator is <br>.  Otherwise, take consistent behavior
+  //     between <p> container and <div> container.
+  else if ((separator == ParagraphSeparator::br &&
+            blockParent->IsHTMLElement(nsGkAtoms::p)) ||
+           (separator != ParagraphSeparator::br &&
+            blockParent->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) {
     // Paragraphs: special rules to look for <br>s
     nsresult rv =
       ReturnInParagraph(&aSelection, GetAsDOMNode(blockParent),
                         GetAsDOMNode(node), offset, aCancel, aHandled);
     NS_ENSURE_SUCCESS(rv, rv);
     // Fall through, we may not have handled it in ReturnInParagraph()
   }
 
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -247,16 +247,17 @@ skip-if = toolkit == 'android' # bug 131
 [test_bug1330796.html]
 [test_bug1332876.html]
 [test_bug1352799.html]
 [test_bug1355792.html]
 [test_bug1358025.html]
 [test_bug1361008.html]
 [test_bug1368544.html]
 [test_bug1385905.html]
+[test_bug1390562.html]
 [test_bug1394758.html]
 [test_bug1399722.html]
 
 [test_CF_HTML_clipboard.html]
 subsuite = clipboard
 [test_composition_event_created_in_chrome.html]
 [test_contenteditable_focus.html]
 [test_documentCharacterSet.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1390562.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1390562
+-->
+<html>
+<head>
+  <title>Test for Bug 1390562</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=1390562">Mozilla Bug 1390562</a>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<div id="editor" contenteditable></div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("editor");
+
+  editor.focus();
+
+  // Make the HTML editor's default break is <br>
+  document.execCommand("defaultParagraphSeparator", false, "br");
+
+  editor.innerHTML = "<div>abc<br><br></div>def";
+
+  // Collapse selection at the end of the first text node.
+  window.getSelection().collapse(editor.firstChild.firstChild, 3);
+
+  // Then, typing Enter should insert <br> for <div> container.
+  // This is necessary for backward compatibility.  When we change default
+  // value of "defaultParagraphSeparator" to "div" or "p", it may be possible
+  // to remove this hack.
+  synthesizeKey("KEY_Enter", { code: "Enter" });
+
+  is(editor.innerHTML,
+     "<div>abc<br><br><br></div>def",
+     "Enter key press at end of a text node followed by a visible <br> shouldn't split <div> container when defaultParagraphSeparator is 'br'");
+
+  // Check also the case of <p> as container.
+  editor.innerHTML = "<p>abc<br><br></p>def";
+
+  // Collapse selection at the end of the first text node.
+  window.getSelection().collapse(editor.firstChild.firstChild, 3);
+
+  // Then, typing Enter should splitting <p> container and remove the visible
+  // <br> element next to the caret position.
+  // This is not consistent with <div> container, but this is better behavior
+  // and keep using this behavior.
+  synthesizeKey("KEY_Enter", { code: "Enter" });
+
+  is(editor.innerHTML,
+     "<p>abc</p><p><br></p>def",
+     "Enter key press at end of a text node followed by a visible <br> should split <p> container and remove the visible <br> when defaultParagraphSeparator is 'br'");
+
+  SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -231,16 +231,17 @@ EXPORTS.mozilla.layers += [
     'UpdateImageHelper.h',
     'wr/AsyncImagePipelineManager.h',
     'wr/IpcResourceUpdateQueue.h',
     'wr/ScrollingLayersHelper.h',
     'wr/StackingContextHelper.h',
     'wr/WebRenderBridgeChild.h',
     'wr/WebRenderBridgeParent.h',
     'wr/WebRenderCanvasRenderer.h',
+    'wr/WebRenderCommandBuilder.h',
     'wr/WebRenderImageHost.h',
     'wr/WebRenderLayerManager.h',
     'wr/WebRenderLayersLogging.h',
     'wr/WebRenderMessageUtils.h',
     'wr/WebRenderScrollData.h',
     'wr/WebRenderScrollDataWrapper.h',
     'wr/WebRenderTextureHost.h',
     'wr/WebRenderUserData.h',
@@ -461,16 +462,17 @@ UNIFIED_SOURCES += [
     'TextureWrapperImage.cpp',
     'wr/AsyncImagePipelineManager.cpp',
     'wr/IpcResourceUpdateQueue.cpp',
     'wr/ScrollingLayersHelper.cpp',
     'wr/StackingContextHelper.cpp',
     'wr/WebRenderBridgeChild.cpp',
     'wr/WebRenderBridgeParent.cpp',
     'wr/WebRenderCanvasRenderer.cpp',
+    'wr/WebRenderCommandBuilder.cpp',
     'wr/WebRenderImageHost.cpp',
     'wr/WebRenderLayerManager.cpp',
     'wr/WebRenderLayersLogging.cpp',
     'wr/WebRenderScrollData.cpp',
     'wr/WebRenderUserData.cpp',
     # XXX here are some unified build error.
     #'wr/WebRenderTextureHost.cpp'
 ]
--- a/gfx/layers/wr/ScrollingLayersHelper.cpp
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -2,27 +2,26 @@
  * 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 "mozilla/layers/ScrollingLayersHelper.h"
 
 #include "FrameMetrics.h"
 #include "mozilla/layers/StackingContextHelper.h"
-#include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
 namespace layers {
 
 ScrollingLayersHelper::ScrollingLayersHelper(nsDisplayItem* aItem,
                                              wr::DisplayListBuilder& aBuilder,
                                              const StackingContextHelper& aStackingContext,
-                                             WebRenderLayerManager::ClipIdMap& aCache,
+                                             WebRenderCommandBuilder::ClipIdMap& aCache,
                                              bool aApzEnabled)
   : mBuilder(&aBuilder)
   , mPushedClipAndScroll(false)
 {
   int32_t auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
 
   if (!aApzEnabled) {
     // If APZ is not enabled, we can ignore all the stuff with ASRs; we just
@@ -71,17 +70,17 @@ ScrollingLayersHelper::ScrollingLayersHe
 
 void
 ScrollingLayersHelper::DefineAndPushScrollLayers(nsDisplayItem* aItem,
                                                  const ActiveScrolledRoot* aAsr,
                                                  const DisplayItemClipChain* aChain,
                                                  wr::DisplayListBuilder& aBuilder,
                                                  int32_t aAppUnitsPerDevPixel,
                                                  const StackingContextHelper& aStackingContext,
-                                                 WebRenderLayerManager::ClipIdMap& aCache)
+                                                 WebRenderCommandBuilder::ClipIdMap& aCache)
 {
   if (!aAsr) {
     return;
   }
   FrameMetrics::ViewID scrollId = nsLayoutUtils::ViewIDForASR(aAsr);
   if (aBuilder.TopmostScrollId() == scrollId) {
     // it's already been pushed, so we don't need to recurse any further.
     return;
@@ -125,17 +124,17 @@ ScrollingLayersHelper::DefineAndPushScro
   }
 }
 
 void
 ScrollingLayersHelper::DefineAndPushChain(const DisplayItemClipChain* aChain,
                                           wr::DisplayListBuilder& aBuilder,
                                           const StackingContextHelper& aStackingContext,
                                           int32_t aAppUnitsPerDevPixel,
-                                          WebRenderLayerManager::ClipIdMap& aCache)
+                                          WebRenderCommandBuilder::ClipIdMap& aCache)
 {
   if (!aChain) {
     return;
   }
   auto it = aCache.find(aChain);
   Maybe<wr::WrClipId> clipId = (it != aCache.end() ? Some(it->second) : Nothing());
   if (clipId && clipId == aBuilder.TopmostClipId()) {
     // it was already in the cache and pushed on the WR clip stack, so we don't
--- a/gfx/layers/wr/ScrollingLayersHelper.h
+++ b/gfx/layers/wr/ScrollingLayersHelper.h
@@ -2,17 +2,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 GFX_SCROLLINGLAYERSHELPER_H
 #define GFX_SCROLLINGLAYERSHELPER_H
 
 #include "mozilla/Attributes.h"
-#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/WebRenderCommandBuilder.h"
 
 namespace mozilla {
 
 struct DisplayItemClipChain;
 
 namespace wr {
 class DisplayListBuilder;
 }
@@ -23,33 +23,33 @@ struct FrameMetrics;
 class StackingContextHelper;
 
 class MOZ_RAII ScrollingLayersHelper
 {
 public:
   ScrollingLayersHelper(nsDisplayItem* aItem,
                         wr::DisplayListBuilder& aBuilder,
                         const StackingContextHelper& aStackingContext,
-                        WebRenderLayerManager::ClipIdMap& aCache,
+                        WebRenderCommandBuilder::ClipIdMap& aCache,
                         bool aApzEnabled);
   ~ScrollingLayersHelper();
 
 private:
   void DefineAndPushScrollLayers(nsDisplayItem* aItem,
                                  const ActiveScrolledRoot* aAsr,
                                  const DisplayItemClipChain* aChain,
                                  wr::DisplayListBuilder& aBuilder,
                                  int32_t aAppUnitsPerDevPixel,
                                  const StackingContextHelper& aStackingContext,
-                                 WebRenderLayerManager::ClipIdMap& aCache);
+                                 WebRenderCommandBuilder::ClipIdMap& aCache);
   void DefineAndPushChain(const DisplayItemClipChain* aChain,
                           wr::DisplayListBuilder& aBuilder,
                           const StackingContextHelper& aStackingContext,
                           int32_t aAppUnitsPerDevPixel,
-                          WebRenderLayerManager::ClipIdMap& aCache);
+                          WebRenderCommandBuilder::ClipIdMap& aCache);
   bool DefineAndPushScrollLayer(const FrameMetrics& aMetrics,
                                 const StackingContextHelper& aStackingContext);
 
   wr::DisplayListBuilder* mBuilder;
   bool mPushedClipAndScroll;
   std::vector<wr::ScrollOrClipId> mPushedClips;
 };
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -0,0 +1,605 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebRenderCommandBuilder.h"
+
+#include "BasicLayers.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layers/ImageClient.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/ScrollingLayersHelper.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/UpdateImageHelper.h"
+#include "nsDisplayListInvalidation.h"
+#include "WebRenderCanvasRenderer.h"
+#include "LayerTreeInvalidation.h"
+
+namespace mozilla {
+namespace layers {
+
+void WebRenderCommandBuilder::Destroy()
+{
+  mLastCanvasDatas.Clear();
+  RemoveUnusedAndResetWebRenderUserData();
+}
+
+void
+WebRenderCommandBuilder::BuildWebRenderCommands(wr::DisplayListBuilder& aBuilder,
+                                                wr::IpcResourceUpdateQueue& aResourceUpdates,
+                                                nsDisplayList* aDisplayList,
+                                                nsDisplayListBuilder* aDisplayListBuilder,
+                                                WebRenderScrollData& aScrollData,
+                                                wr::LayoutSize& aContentSize)
+{
+  { // scoping for StackingContextHelper RAII
+
+    StackingContextHelper sc;
+    mParentCommands.Clear();
+    aScrollData = WebRenderScrollData();
+    MOZ_ASSERT(mLayerScrollData.empty());
+    mLastCanvasDatas.Clear();
+    mLastAsr = nullptr;
+
+    CreateWebRenderCommandsFromDisplayList(aDisplayList, aDisplayListBuilder, sc,
+                                           aBuilder, aResourceUpdates);
+
+    aBuilder.Finalize(aContentSize, mBuiltDisplayList);
+
+    // Make a "root" layer data that has everything else as descendants
+    mLayerScrollData.emplace_back();
+    mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1);
+    if (aDisplayListBuilder->IsBuildingLayerEventRegions()) {
+      nsIPresShell* shell = aDisplayListBuilder->RootReferenceFrame()->PresContext()->PresShell();
+      if (nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(shell)) {
+        mLayerScrollData.back().SetEventRegionsOverride(EventRegionsOverride::ForceDispatchToContent);
+      }
+    }
+    auto callback = [&aScrollData](FrameMetrics::ViewID aScrollId) -> bool {
+      return aScrollData.HasMetadataFor(aScrollId);
+    };
+    if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata(
+          aDisplayListBuilder, nullptr, ContainerLayerParameters(), callback)) {
+      mLayerScrollData.back().AppendScrollMetadata(aScrollData, rootMetadata.ref());
+    }
+    // Append the WebRenderLayerScrollData items into WebRenderScrollData
+    // in reverse order, from topmost to bottommost. This is in keeping with
+    // the semantics of WebRenderScrollData.
+    for (auto i = mLayerScrollData.crbegin(); i != mLayerScrollData.crend(); i++) {
+      aScrollData.AddLayerData(*i);
+    }
+    mLayerScrollData.clear();
+    mClipIdCache.clear();
+
+    // Remove the user data those are not displayed on the screen and
+    // also reset the data to unused for next transaction.
+    RemoveUnusedAndResetWebRenderUserData();
+  }
+
+  aBuilder.PushBuiltDisplayList(mBuiltDisplayList);
+  mManager->WrBridge()->AddWebRenderParentCommands(mParentCommands);
+}
+
+void
+WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(nsDisplayList* aDisplayList,
+                                                                nsDisplayListBuilder* aDisplayListBuilder,
+                                                                const StackingContextHelper& aSc,
+                                                                wr::DisplayListBuilder& aBuilder,
+                                                                wr::IpcResourceUpdateQueue& aResources)
+{
+  bool apzEnabled = mManager->AsyncPanZoomEnabled();
+  EventRegions eventRegions;
+
+  for (nsDisplayItem* i = aDisplayList->GetBottom(); i; i = i->GetAbove()) {
+    nsDisplayItem* item = i;
+    DisplayItemType itemType = item->GetType();
+
+    // If the item is a event regions item, but is empty (has no regions in it)
+    // then we should just throw it out
+    if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
+      nsDisplayLayerEventRegions* eventRegions =
+        static_cast<nsDisplayLayerEventRegions*>(item);
+      if (eventRegions->IsEmpty()) {
+        continue;
+      }
+    }
+
+    // Peek ahead to the next item and try merging with it or swapping with it
+    // if necessary.
+    AutoTArray<nsDisplayItem*, 1> mergedItems;
+    mergedItems.AppendElement(item);
+    for (nsDisplayItem* peek = item->GetAbove(); peek; peek = peek->GetAbove()) {
+      if (!item->CanMerge(peek)) {
+        break;
+      }
+
+      mergedItems.AppendElement(peek);
+
+      // Move the iterator forward since we will merge this item.
+      i = peek;
+    }
+
+    if (mergedItems.Length() > 1) {
+      item = aDisplayListBuilder->MergeItems(mergedItems);
+      MOZ_ASSERT(item && itemType == item->GetType());
+    }
+
+    nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
+    if (item->ShouldFlattenAway(aDisplayListBuilder)) {
+      MOZ_ASSERT(childItems);
+      CreateWebRenderCommandsFromDisplayList(childItems, aDisplayListBuilder, aSc,
+                                             aBuilder, aResources);
+      continue;
+    }
+
+    bool forceNewLayerData = false;
+    size_t layerCountBeforeRecursing = mLayerScrollData.size();
+    if (apzEnabled) {
+      // For some types of display items we want to force a new
+      // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant
+      // data that is in the display item.
+      forceNewLayerData = item->UpdateScrollData(nullptr, nullptr);
+
+      // Anytime the ASR changes we also want to force a new layer data because
+      // the stack of scroll metadata is going to be different for this
+      // display item than previously, so we can't squash the display items
+      // into the same "layer".
+      const ActiveScrolledRoot* asr = item->GetActiveScrolledRoot();
+      if (asr != mLastAsr) {
+        mLastAsr = asr;
+        forceNewLayerData = true;
+      }
+
+      // If we're creating a new layer data then flush whatever event regions
+      // we've collected onto the old layer.
+      if (forceNewLayerData && !eventRegions.IsEmpty()) {
+        // If eventRegions is non-empty then we must have a layer data already,
+        // because we (below) force one if we encounter an event regions item
+        // with an empty layer data list. Additionally, the most recently
+        // created layer data must have been created from an item whose ASR
+        // is the same as the ASR on the event region items that were collapsed
+        // into |eventRegions|. This is because any ASR change causes us to force
+        // a new layer data which flushes the eventRegions.
+        MOZ_ASSERT(!mLayerScrollData.empty());
+        mLayerScrollData.back().AddEventRegions(eventRegions);
+        eventRegions.SetEmpty();
+      }
+
+      // Collapse event region data into |eventRegions|, which will either be
+      // empty, or filled with stuff from previous display items with the same
+      // ASR.
+      if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
+        nsDisplayLayerEventRegions* regionsItem =
+            static_cast<nsDisplayLayerEventRegions*>(item);
+        int32_t auPerDevPixel = item->Frame()->PresContext()->AppUnitsPerDevPixel();
+        EventRegions regions(
+            regionsItem->HitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
+            regionsItem->MaybeHitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
+            regionsItem->DispatchToContentHitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
+            regionsItem->NoActionRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
+            regionsItem->HorizontalPanRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
+            regionsItem->VerticalPanRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel));
+
+        eventRegions.OrWith(regions);
+        if (mLayerScrollData.empty()) {
+          // If we don't have a layer data yet then create one because we will
+          // need it to store this event region information.
+          forceNewLayerData = true;
+        }
+      }
+
+      // If we're going to create a new layer data for this item, stash the
+      // ASR so that if we recurse into a sublist they will know where to stop
+      // walking up their ASR chain when building scroll metadata.
+      if (forceNewLayerData) {
+        mAsrStack.push_back(asr);
+      }
+    }
+
+    { // scope the ScrollingLayersHelper
+      ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache, apzEnabled);
+
+      // Note: this call to CreateWebRenderCommands can recurse back into
+      // this function if the |item| is a wrapper for a sublist.
+      if (!item->CreateWebRenderCommands(aBuilder, aResources, aSc, mManager,
+                                         aDisplayListBuilder)) {
+        PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder);
+      }
+    }
+
+    if (apzEnabled && forceNewLayerData) {
+      // Pop the thing we pushed before the recursion, so the topmost item on
+      // the stack is enclosing display item's ASR (or the stack is empty)
+      mAsrStack.pop_back();
+      const ActiveScrolledRoot* stopAtAsr =
+          mAsrStack.empty() ? nullptr : mAsrStack.back();
+
+      int32_t descendants = mLayerScrollData.size() - layerCountBeforeRecursing;
+
+      mLayerScrollData.emplace_back();
+      mLayerScrollData.back().Initialize(mManager->GetScrollData(), item, descendants, stopAtAsr);
+    }
+  }
+
+  // If we have any event region info left over we need to flush it before we
+  // return. Again, at this point the layer data list must be non-empty, and
+  // the most recently created layer data will have been created by an item
+  // with matching ASRs.
+  if (!eventRegions.IsEmpty()) {
+    MOZ_ASSERT(apzEnabled);
+    MOZ_ASSERT(!mLayerScrollData.empty());
+    mLayerScrollData.back().AddEventRegions(eventRegions);
+  }
+}
+
+Maybe<wr::ImageKey>
+WebRenderCommandBuilder::CreateImageKey(nsDisplayItem* aItem,
+                                        ImageContainer* aContainer,
+                                        mozilla::wr::DisplayListBuilder& aBuilder,
+                                        mozilla::wr::IpcResourceUpdateQueue& aResources,
+                                        const StackingContextHelper& aSc,
+                                        gfx::IntSize& aSize)
+{
+  RefPtr<WebRenderImageData> imageData = CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
+  MOZ_ASSERT(imageData);
+
+  if (aContainer->IsAsync()) {
+    bool snap;
+    nsRect bounds = aItem->GetBounds(nullptr, &snap);
+    int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+    LayerRect rect = ViewAs<LayerPixel>(
+      LayoutDeviceRect::FromAppUnits(bounds, appUnitsPerDevPixel),
+      PixelCastJustification::WebRenderHasUnitResolution);
+    LayerRect scBounds(0, 0, rect.width, rect.Height());
+    gfx::MaybeIntSize scaleToSize;
+    if (!aContainer->GetScaleHint().IsEmpty()) {
+      scaleToSize = Some(aContainer->GetScaleHint());
+    }
+    // TODO!
+    // We appear to be using the image bridge for a lot (most/all?) of
+    // layers-free image handling and that breaks frame consistency.
+    imageData->CreateAsyncImageWebRenderCommands(aBuilder,
+                                                 aContainer,
+                                                 aSc,
+                                                 rect,
+                                                 scBounds,
+                                                 gfx::Matrix4x4(),
+                                                 scaleToSize,
+                                                 wr::ImageRendering::Auto,
+                                                 wr::MixBlendMode::Normal,
+                                                 !aItem->BackfaceIsHidden());
+    return Nothing();
+  }
+
+  AutoLockImage autoLock(aContainer);
+  if (!autoLock.HasImage()) {
+    return Nothing();
+  }
+  mozilla::layers::Image* image = autoLock.GetImage();
+  aSize = image->GetSize();
+
+  return imageData->UpdateImageKey(aContainer, aResources);
+}
+
+bool
+WebRenderCommandBuilder::PushImage(nsDisplayItem* aItem,
+                                   ImageContainer* aContainer,
+                                   mozilla::wr::DisplayListBuilder& aBuilder,
+                                   mozilla::wr::IpcResourceUpdateQueue& aResources,
+                                   const StackingContextHelper& aSc,
+                                   const LayerRect& aRect)
+{
+  gfx::IntSize size;
+  Maybe<wr::ImageKey> key = CreateImageKey(aItem, aContainer,
+                                           aBuilder, aResources,
+                                           aSc, size);
+  if (aContainer->IsAsync()) {
+    // Async ImageContainer does not create ImageKey, instead it uses Pipeline.
+    MOZ_ASSERT(key.isNothing());
+    return true;
+  }
+  if (!key) {
+    return false;
+  }
+
+  auto r = aSc.ToRelativeLayoutRect(aRect);
+  gfx::SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
+  aBuilder.PushImage(r, r, !aItem->BackfaceIsHidden(), wr::ToImageRendering(sampleFilter), key.value());
+
+  return true;
+}
+
+static void
+PaintItemByDrawTarget(nsDisplayItem* aItem,
+                      gfx::DrawTarget* aDT,
+                      const LayerRect& aImageRect,
+                      const LayerPoint& aOffset,
+                      nsDisplayListBuilder* aDisplayListBuilder,
+                      RefPtr<BasicLayerManager>& aManager,
+                      WebRenderLayerManager* aWrManager,
+                      const gfx::Size& aScale)
+{
+  MOZ_ASSERT(aDT);
+
+  aDT->ClearRect(aImageRect.ToUnknownRect());
+  RefPtr<gfxContext> context = gfxContext::CreateOrNull(aDT);
+  MOZ_ASSERT(context);
+
+  context->SetMatrix(context->CurrentMatrix().PreScale(aScale.width, aScale.height).PreTranslate(-aOffset.x, -aOffset.y));
+
+  switch (aItem->GetType()) {
+  case DisplayItemType::TYPE_MASK:
+    static_cast<nsDisplayMask*>(aItem)->PaintMask(aDisplayListBuilder, context);
+    break;
+  case DisplayItemType::TYPE_FILTER:
+    {
+      if (aManager == nullptr) {
+        aManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
+      }
+
+      FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
+      layerBuilder->Init(aDisplayListBuilder, aManager);
+      layerBuilder->DidBeginRetainedLayerTransaction(aManager);
+
+      aManager->BeginTransactionWithTarget(context);
+
+      ContainerLayerParameters param;
+      RefPtr<Layer> layer =
+        static_cast<nsDisplayFilter*>(aItem)->BuildLayer(aDisplayListBuilder,
+                                                         aManager, param);
+
+      if (layer) {
+        UniquePtr<LayerProperties> props;
+        props = Move(LayerProperties::CloneFrom(aManager->GetRoot()));
+
+        aManager->SetRoot(layer);
+        layerBuilder->WillEndTransaction();
+
+        static_cast<nsDisplayFilter*>(aItem)->PaintAsLayer(aDisplayListBuilder,
+                                                           context, aManager);
+      }
+
+      if (aManager->InTransaction()) {
+        aManager->AbortTransaction();
+      }
+      aManager->SetTarget(nullptr);
+      break;
+    }
+  default:
+    aItem->Paint(aDisplayListBuilder, context);
+    break;
+  }
+
+  if (gfxPrefs::WebRenderHighlightPaintedLayers()) {
+    aDT->SetTransform(gfx::Matrix());
+    aDT->FillRect(gfx::Rect(0, 0, aImageRect.Width(), aImageRect.Height()), gfx::ColorPattern(gfx::Color(1.0, 0.0, 0.0, 0.5)));
+  }
+  if (aItem->Frame()->PresContext()->GetPaintFlashing()) {
+    aDT->SetTransform(gfx::Matrix());
+    float r = float(rand()) / RAND_MAX;
+    float g = float(rand()) / RAND_MAX;
+    float b = float(rand()) / RAND_MAX;
+    aDT->FillRect(gfx::Rect(0, 0, aImageRect.Width(), aImageRect.Height()), gfx::ColorPattern(gfx::Color(r, g, b, 0.5)));
+  }
+}
+
+already_AddRefed<WebRenderFallbackData>
+WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem,
+                                              wr::DisplayListBuilder& aBuilder,
+                                              wr::IpcResourceUpdateQueue& aResources,
+                                              const StackingContextHelper& aSc,
+                                              nsDisplayListBuilder* aDisplayListBuilder,
+                                              LayerRect& aImageRect)
+{
+  RefPtr<WebRenderFallbackData> fallbackData = CreateOrRecycleWebRenderUserData<WebRenderFallbackData>(aItem);
+
+  bool snap;
+  nsRect itemBounds = aItem->GetBounds(aDisplayListBuilder, &snap);
+  nsRect clippedBounds = itemBounds;
+
+  const DisplayItemClip& clip = aItem->GetClip();
+  // Blob images will only draw the visible area of the blob so we don't need to clip
+  // them here and can just rely on the webrender clipping.
+  if (clip.HasClip() && !gfxPrefs::WebRenderBlobImages()) {
+    clippedBounds = itemBounds.Intersect(clip.GetClipRect());
+  }
+
+  // nsDisplayItem::Paint() may refer the variables that come from ComputeVisibility().
+  // So we should call ComputeVisibility() before painting. e.g.: nsDisplayBoxShadowInner
+  // uses mVisibleRegion in Paint() and mVisibleRegion is computed in
+  // nsDisplayBoxShadowInner::ComputeVisibility().
+  nsRegion visibleRegion(clippedBounds);
+  aItem->ComputeVisibility(aDisplayListBuilder, &visibleRegion);
+
+  const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+  LayerRect bounds = ViewAs<LayerPixel>(
+      LayoutDeviceRect::FromAppUnits(clippedBounds, appUnitsPerDevPixel),
+      PixelCastJustification::WebRenderHasUnitResolution);
+
+  gfx::Size scale = aSc.GetInheritedScale();
+  LayerIntSize paintSize = RoundedToInt(LayerSize(bounds.width * scale.width, bounds.height * scale.height));
+  if (paintSize.width == 0 || paintSize.height == 0) {
+    return nullptr;
+  }
+
+  bool needPaint = true;
+  LayerIntPoint offset = RoundedToInt(bounds.TopLeft());
+  aImageRect = LayerRect(offset, LayerSize(RoundedToInt(bounds.Size())));
+  LayerRect paintRect = LayerRect(LayerPoint(0, 0), LayerSize(paintSize));
+  nsAutoPtr<nsDisplayItemGeometry> geometry = fallbackData->GetGeometry();
+
+  // nsDisplayFilter is rendered via BasicLayerManager which means the invalidate
+  // region is unknown until we traverse the displaylist contained by it.
+  if (geometry && !fallbackData->IsInvalid() &&
+      aItem->GetType() != DisplayItemType::TYPE_FILTER) {
+    nsRect invalid;
+    nsRegion invalidRegion;
+
+    if (aItem->IsInvalid(invalid)) {
+      invalidRegion.OrWith(clippedBounds);
+    } else {
+      nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
+      geometry->MoveBy(shift);
+      aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, &invalidRegion);
+
+      nsRect lastBounds = fallbackData->GetBounds();
+      lastBounds.MoveBy(shift);
+
+      if (!lastBounds.IsEqualInterior(clippedBounds)) {
+        invalidRegion.OrWith(lastBounds);
+        invalidRegion.OrWith(clippedBounds);
+      }
+    }
+    needPaint = !invalidRegion.IsEmpty();
+  }
+
+  if (needPaint) {
+    gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ?
+                                                      gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8;
+    if (gfxPrefs::WebRenderBlobImages()) {
+      bool snapped;
+      bool isOpaque = aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(clippedBounds);
+
+      RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
+      RefPtr<gfx::DrawTarget> dummyDt =
+        gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
+      RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, paintSize.ToUnknownSize());
+      PaintItemByDrawTarget(aItem, dt, paintRect, offset, aDisplayListBuilder,
+                            fallbackData->mBasicLayerManager, mManager, scale);
+      recorder->Finish();
+
+      Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
+      wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
+      wr::ImageDescriptor descriptor(paintSize.ToUnknownSize(), 0, dt->GetFormat(), isOpaque);
+      aResources.AddBlobImage(key, descriptor, bytes);
+      fallbackData->SetKey(key);
+    } else {
+      fallbackData->CreateImageClientIfNeeded();
+      RefPtr<ImageClient> imageClient = fallbackData->GetImageClient();
+      RefPtr<ImageContainer> imageContainer = LayerManager::CreateImageContainer();
+
+      {
+        UpdateImageHelper helper(imageContainer, imageClient, paintSize.ToUnknownSize(), format);
+        {
+          RefPtr<gfx::DrawTarget> dt = helper.GetDrawTarget();
+          if (!dt) {
+            return nullptr;
+          }
+          PaintItemByDrawTarget(aItem, dt, paintRect, offset,
+                                aDisplayListBuilder,
+                                fallbackData->mBasicLayerManager, mManager, scale);
+        }
+        if (!helper.UpdateImage()) {
+          return nullptr;
+        }
+      }
+
+      // Force update the key in fallback data since we repaint the image in this path.
+      // If not force update, fallbackData may reuse the original key because it
+      // doesn't know UpdateImageHelper already updated the image container.
+      if (!fallbackData->UpdateImageKey(imageContainer, aResources, true)) {
+        return nullptr;
+      }
+    }
+
+    geometry = aItem->AllocateGeometry(aDisplayListBuilder);
+    fallbackData->SetInvalid(false);
+  }
+
+  // Update current bounds to fallback data
+  fallbackData->SetGeometry(Move(geometry));
+  fallbackData->SetBounds(clippedBounds);
+
+  MOZ_ASSERT(fallbackData->GetKey());
+
+  return fallbackData.forget();
+}
+
+Maybe<wr::WrImageMask>
+WebRenderCommandBuilder::BuildWrMaskImage(nsDisplayItem* aItem,
+                                          wr::DisplayListBuilder& aBuilder,
+                                          wr::IpcResourceUpdateQueue& aResources,
+                                          const StackingContextHelper& aSc,
+                                          nsDisplayListBuilder* aDisplayListBuilder,
+                                          const LayerRect& aBounds)
+{
+  LayerRect imageRect;
+  RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(aItem, aBuilder, aResources,
+                                                                    aSc, aDisplayListBuilder,
+                                                                    imageRect);
+  if (!fallbackData) {
+    return Nothing();
+  }
+
+  wr::WrImageMask imageMask;
+  imageMask.image = fallbackData->GetKey().value();
+  imageMask.rect = aSc.ToRelativeLayoutRect(aBounds);
+  imageMask.repeat = false;
+  return Some(imageMask);
+}
+
+bool
+WebRenderCommandBuilder::PushItemAsImage(nsDisplayItem* aItem,
+                                         wr::DisplayListBuilder& aBuilder,
+                                         wr::IpcResourceUpdateQueue& aResources,
+                                         const StackingContextHelper& aSc,
+                                         nsDisplayListBuilder* aDisplayListBuilder)
+{
+  LayerRect imageRect;
+  RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(aItem, aBuilder, aResources,
+                                                                    aSc, aDisplayListBuilder,
+                                                                    imageRect);
+  if (!fallbackData) {
+    return false;
+  }
+
+  wr::LayoutRect dest = aSc.ToRelativeLayoutRect(imageRect);
+  gfx::SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
+  aBuilder.PushImage(dest,
+                     dest,
+                     !aItem->BackfaceIsHidden(),
+                     wr::ToImageRendering(sampleFilter),
+                     fallbackData->GetKey().value());
+  return true;
+}
+
+void
+WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData()
+{
+  for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) {
+    WebRenderUserData* data = iter.Get()->GetKey();
+    if (!data->IsUsed()) {
+      nsIFrame* frame = data->GetFrame();
+
+      MOZ_ASSERT(frame->HasProperty(nsIFrame::WebRenderUserDataProperty()));
+
+      nsIFrame::WebRenderUserDataTable* userDataTable =
+        frame->GetProperty(nsIFrame::WebRenderUserDataProperty());
+
+      MOZ_ASSERT(userDataTable->Count());
+
+      userDataTable->Remove(data->GetDisplayItemKey());
+
+      if (!userDataTable->Count()) {
+        frame->RemoveProperty(nsIFrame::WebRenderUserDataProperty());
+      }
+
+      if (data->GetType() == WebRenderUserData::UserDataType::eCanvas) {
+        mLastCanvasDatas.RemoveEntry(data->AsCanvasData());
+      }
+
+      iter.Remove();
+      continue;
+    }
+
+    data->SetUsed(false);
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/wr/WebRenderCommandBuilder.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_WEBRENDERCOMMANDBUILDER_H
+#define GFX_WEBRENDERCOMMANDBUILDER_H
+
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/WebRenderMessages.h"
+#include "mozilla/layers/WebRenderScrollData.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+namespace layers {
+
+class CanvasLayer;
+class ImageClient;
+class ImageContainer;
+class WebRenderBridgeChild;
+class WebRenderCanvasData;
+class WebRenderCanvasRendererAsync;
+class WebRenderImageData;
+class WebRenderFallbackData;
+class WebRenderParentCommand;
+class WebRenderUserData;
+
+class WebRenderCommandBuilder {
+  typedef nsTHashtable<nsRefPtrHashKey<WebRenderUserData>> WebRenderUserDataRefTable;
+  typedef nsTHashtable<nsRefPtrHashKey<WebRenderCanvasData>> CanvasDataSet;
+
+public:
+  explicit WebRenderCommandBuilder(WebRenderLayerManager* aManager)
+  : mManager(aManager)
+  , mLastAsr(nullptr)
+  {}
+
+  void Destroy();
+
+  void BuildWebRenderCommands(wr::DisplayListBuilder& aBuilder,
+                              wr::IpcResourceUpdateQueue& aResourceUpdates,
+                              nsDisplayList* aDisplayList,
+                              nsDisplayListBuilder* aDisplayListBuilder,
+                              WebRenderScrollData& aScrollData,
+                              wr::LayoutSize& aContentSize);
+
+  Maybe<wr::ImageKey> CreateImageKey(nsDisplayItem* aItem,
+                                     ImageContainer* aContainer,
+                                     mozilla::wr::DisplayListBuilder& aBuilder,
+                                     mozilla::wr::IpcResourceUpdateQueue& aResources,
+                                     const StackingContextHelper& aSc,
+                                     gfx::IntSize& aSize);
+
+  WebRenderUserDataRefTable* GetWebRenderUserDataTable() { return &mWebRenderUserDatas; }
+
+  bool PushImage(nsDisplayItem* aItem,
+                 ImageContainer* aContainer,
+                 mozilla::wr::DisplayListBuilder& aBuilder,
+                 mozilla::wr::IpcResourceUpdateQueue& aResources,
+                 const StackingContextHelper& aSc,
+                 const LayerRect& aRect);
+
+  Maybe<wr::WrImageMask> BuildWrMaskImage(nsDisplayItem* aItem,
+                                          wr::DisplayListBuilder& aBuilder,
+                                          wr::IpcResourceUpdateQueue& aResources,
+                                          const StackingContextHelper& aSc,
+                                          nsDisplayListBuilder* aDisplayListBuilder,
+                                          const LayerRect& aBounds);
+
+  bool PushItemAsImage(nsDisplayItem* aItem,
+                       wr::DisplayListBuilder& aBuilder,
+                       wr::IpcResourceUpdateQueue& aResources,
+                       const StackingContextHelper& aSc,
+                       nsDisplayListBuilder* aDisplayListBuilder);
+
+  void CreateWebRenderCommandsFromDisplayList(nsDisplayList* aDisplayList,
+                                              nsDisplayListBuilder* aDisplayListBuilder,
+                                              const StackingContextHelper& aSc,
+                                              wr::DisplayListBuilder& aBuilder,
+                                              wr::IpcResourceUpdateQueue& aResources);
+
+  already_AddRefed<WebRenderFallbackData> GenerateFallbackData(nsDisplayItem* aItem,
+                                                               wr::DisplayListBuilder& aBuilder,
+                                                               wr::IpcResourceUpdateQueue& aResources,
+                                                               const StackingContextHelper& aSc,
+                                                               nsDisplayListBuilder* aDisplayListBuilder,
+                                                               LayerRect& aImageRect);
+
+  void RemoveUnusedAndResetWebRenderUserData();
+
+  // Those are data that we kept between transactions. We used to cache some
+  // data in the layer. But in layers free mode, we don't have layer which
+  // means we need some other place to cached the data between transaction.
+  // We store the data in frame's property.
+  template<class T> already_AddRefed<T>
+  CreateOrRecycleWebRenderUserData(nsDisplayItem* aItem,
+                                   bool* aOutIsRecycled = nullptr)
+  {
+    MOZ_ASSERT(aItem);
+    nsIFrame* frame = aItem->Frame();
+    if (aOutIsRecycled) {
+      *aOutIsRecycled = true;
+    }
+
+    nsIFrame::WebRenderUserDataTable* userDataTable =
+      frame->GetProperty(nsIFrame::WebRenderUserDataProperty());
+
+    if (!userDataTable) {
+      userDataTable = new nsIFrame::WebRenderUserDataTable();
+      frame->AddProperty(nsIFrame::WebRenderUserDataProperty(), userDataTable);
+    }
+
+    RefPtr<WebRenderUserData>& data = userDataTable->GetOrInsert(aItem->GetPerFrameKey());
+    if (!data || (data->GetType() != T::Type()) || !data->IsDataValid(mManager)) {
+      // To recreate a new user data, we should remove the data from the table first.
+      if (data) {
+        data->RemoveFromTable();
+      }
+      data = new T(mManager, aItem);
+      mWebRenderUserDatas.PutEntry(data);
+      if (aOutIsRecycled) {
+        *aOutIsRecycled = false;
+      }
+    }
+
+    MOZ_ASSERT(data);
+    MOZ_ASSERT(data->GetType() == T::Type());
+
+    // Mark the data as being used. We will remove unused user data in the end of EndTransaction.
+    data->SetUsed(true);
+
+    if (T::Type() == WebRenderUserData::UserDataType::eCanvas) {
+      mLastCanvasDatas.PutEntry(data->AsCanvasData());
+    }
+    RefPtr<T> res = static_cast<T*>(data.get());
+    return res.forget();
+  }
+
+public:
+  // Note: two DisplayItemClipChain* A and B might actually be "equal" (as per
+  // DisplayItemClipChain::Equal(A, B)) even though they are not the same pointer
+  // (A != B). In this hopefully-rare case, they will get separate entries
+  // in this map when in fact we could collapse them. However, to collapse
+  // them involves writing a custom hash function for the pointer type such that
+  // A and B hash to the same things whenever DisplayItemClipChain::Equal(A, B)
+  // is true, and that will incur a performance penalty for all the hashmap
+  // operations, so is probably not worth it. With the current code we might
+  // end up creating multiple clips in WR that are effectively identical but
+  // have separate clip ids. Hopefully this won't happen very often.
+  typedef std::unordered_map<const DisplayItemClipChain*, wr::WrClipId> ClipIdMap;
+
+private:
+  WebRenderLayerManager* mManager;
+  ClipIdMap mClipIdCache;
+
+  // These fields are used to save a copy of the display list for
+  // empty transactions in layers-free mode.
+  wr::BuiltDisplayList mBuiltDisplayList;
+  nsTArray<WebRenderParentCommand> mParentCommands;
+
+  // We use this as a temporary data structure while building the mScrollData
+  // inside a layers-free transaction.
+  std::vector<WebRenderLayerScrollData> mLayerScrollData;
+  // We use this as a temporary data structure to track the current display
+  // item's ASR as we recurse in CreateWebRenderCommandsFromDisplayList. We
+  // need this so that WebRenderLayerScrollData items that deeper in the
+  // tree don't duplicate scroll metadata that their ancestors already have.
+  std::vector<const ActiveScrolledRoot*> mAsrStack;
+  const ActiveScrolledRoot* mLastAsr;
+
+  WebRenderUserDataRefTable mWebRenderUserDatas;
+
+  // Store of WebRenderCanvasData objects for use in empty transactions
+  CanvasDataSet mLastCanvasDatas;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_WEBRENDERCOMMANDBUILDER_H */
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -24,21 +24,21 @@ namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
 WebRenderLayerManager::WebRenderLayerManager(nsIWidget* aWidget)
   : mWidget(aWidget)
   , mLatestTransactionId(0)
-  , mLastAsr(nullptr)
   , mNeedsComposite(false)
   , mIsFirstPaint(false)
   , mTarget(nullptr)
   , mPaintSequenceNumber(0)
+  , mWebRenderCommandBuilder(this)
 {
   MOZ_COUNT_CTOR(WebRenderLayerManager);
 }
 
 KnowsCompositor*
 WebRenderLayerManager::AsKnowsCompositor()
 {
   return mWrChild;
@@ -101,18 +101,17 @@ WebRenderLayerManager::DoDestroy(bool aI
   }
 
   // Clear this before calling RemoveUnusedAndResetWebRenderUserData(),
   // otherwise that function might destroy some WebRenderAnimationData instances
   // which will put stuff back into mDiscardedCompositorAnimationsIds. If
   // mActiveCompositorAnimationIds is empty that won't happen.
   mActiveCompositorAnimationIds.clear();
 
-  mLastCanvasDatas.Clear();
-  RemoveUnusedAndResetWebRenderUserData();
+  mWebRenderCommandBuilder.Destroy();
 
   if (mTransactionIdAllocator) {
     // Make sure to notify the refresh driver just in case it's waiting on a
     // pending transaction. Do this at the top of the event loop so we don't
     // cause a paint to occur during compositor shutdown.
     RefPtr<TransactionIdAllocator> allocator = mTransactionIdAllocator;
     uint64_t id = mLatestTransactionId;
 
@@ -207,499 +206,16 @@ PopulateScrollData(WebRenderScrollData& 
   for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) {
     descendants += PopulateScrollData(aTarget, child);
   }
   aTarget.GetLayerDataMutable(index)->Initialize(aTarget, aLayer, descendants);
   return descendants + 1;
 }
 
 void
-WebRenderLayerManager::CreateWebRenderCommandsFromDisplayList(nsDisplayList* aDisplayList,
-                                                              nsDisplayListBuilder* aDisplayListBuilder,
-                                                              const StackingContextHelper& aSc,
-                                                              wr::DisplayListBuilder& aBuilder,
-                                                              wr::IpcResourceUpdateQueue& aResources)
-{
-  bool apzEnabled = AsyncPanZoomEnabled();
-  EventRegions eventRegions;
-
-  for (nsDisplayItem* i = aDisplayList->GetBottom(); i; i = i->GetAbove()) {
-    nsDisplayItem* item = i;
-    DisplayItemType itemType = item->GetType();
-
-    // If the item is a event regions item, but is empty (has no regions in it)
-    // then we should just throw it out
-    if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
-      nsDisplayLayerEventRegions* eventRegions =
-        static_cast<nsDisplayLayerEventRegions*>(item);
-      if (eventRegions->IsEmpty()) {
-        continue;
-      }
-    }
-
-    // Peek ahead to the next item and try merging with it or swapping with it
-    // if necessary.
-    AutoTArray<nsDisplayItem*, 1> mergedItems;
-    mergedItems.AppendElement(item);
-    for (nsDisplayItem* peek = item->GetAbove(); peek; peek = peek->GetAbove()) {
-      if (!item->CanMerge(peek)) {
-        break;
-      }
-
-      mergedItems.AppendElement(peek);
-
-      // Move the iterator forward since we will merge this item.
-      i = peek;
-    }
-
-    if (mergedItems.Length() > 1) {
-      item = aDisplayListBuilder->MergeItems(mergedItems);
-      MOZ_ASSERT(item && itemType == item->GetType());
-    }
-
-    nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
-    if (item->ShouldFlattenAway(aDisplayListBuilder)) {
-      MOZ_ASSERT(childItems);
-      CreateWebRenderCommandsFromDisplayList(childItems, aDisplayListBuilder, aSc,
-                                             aBuilder, aResources);
-      continue;
-    }
-
-    bool forceNewLayerData = false;
-    size_t layerCountBeforeRecursing = mLayerScrollData.size();
-    if (apzEnabled) {
-      // For some types of display items we want to force a new
-      // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant
-      // data that is in the display item.
-      forceNewLayerData = item->UpdateScrollData(nullptr, nullptr);
-
-      // Anytime the ASR changes we also want to force a new layer data because
-      // the stack of scroll metadata is going to be different for this
-      // display item than previously, so we can't squash the display items
-      // into the same "layer".
-      const ActiveScrolledRoot* asr = item->GetActiveScrolledRoot();
-      if (asr != mLastAsr) {
-        mLastAsr = asr;
-        forceNewLayerData = true;
-      }
-
-      // If we're creating a new layer data then flush whatever event regions
-      // we've collected onto the old layer.
-      if (forceNewLayerData && !eventRegions.IsEmpty()) {
-        // If eventRegions is non-empty then we must have a layer data already,
-        // because we (below) force one if we encounter an event regions item
-        // with an empty layer data list. Additionally, the most recently
-        // created layer data must have been created from an item whose ASR
-        // is the same as the ASR on the event region items that were collapsed
-        // into |eventRegions|. This is because any ASR change causes us to force
-        // a new layer data which flushes the eventRegions.
-        MOZ_ASSERT(!mLayerScrollData.empty());
-        mLayerScrollData.back().AddEventRegions(eventRegions);
-        eventRegions.SetEmpty();
-      }
-
-      // Collapse event region data into |eventRegions|, which will either be
-      // empty, or filled with stuff from previous display items with the same
-      // ASR.
-      if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
-        nsDisplayLayerEventRegions* regionsItem =
-            static_cast<nsDisplayLayerEventRegions*>(item);
-        int32_t auPerDevPixel = item->Frame()->PresContext()->AppUnitsPerDevPixel();
-        EventRegions regions(
-            regionsItem->HitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
-            regionsItem->MaybeHitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
-            regionsItem->DispatchToContentHitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
-            regionsItem->NoActionRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
-            regionsItem->HorizontalPanRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel),
-            regionsItem->VerticalPanRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel));
-
-        eventRegions.OrWith(regions);
-        if (mLayerScrollData.empty()) {
-          // If we don't have a layer data yet then create one because we will
-          // need it to store this event region information.
-          forceNewLayerData = true;
-        }
-      }
-
-      // If we're going to create a new layer data for this item, stash the
-      // ASR so that if we recurse into a sublist they will know where to stop
-      // walking up their ASR chain when building scroll metadata.
-      if (forceNewLayerData) {
-        mAsrStack.push_back(asr);
-      }
-    }
-
-    { // scope the ScrollingLayersHelper
-      ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache, AsyncPanZoomEnabled());
-
-      // Note: this call to CreateWebRenderCommands can recurse back into
-      // this function if the |item| is a wrapper for a sublist.
-      if (!item->CreateWebRenderCommands(aBuilder, aResources, aSc, this,
-                                         aDisplayListBuilder)) {
-        PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder);
-      }
-    }
-
-    if (apzEnabled && forceNewLayerData) {
-      // Pop the thing we pushed before the recursion, so the topmost item on
-      // the stack is enclosing display item's ASR (or the stack is empty)
-      mAsrStack.pop_back();
-      const ActiveScrolledRoot* stopAtAsr =
-          mAsrStack.empty() ? nullptr : mAsrStack.back();
-
-      int32_t descendants = mLayerScrollData.size() - layerCountBeforeRecursing;
-
-      mLayerScrollData.emplace_back();
-      mLayerScrollData.back().Initialize(mScrollData, item, descendants, stopAtAsr);
-    }
-  }
-
-  // If we have any event region info left over we need to flush it before we
-  // return. Again, at this point the layer data list must be non-empty, and
-  // the most recently created layer data will have been created by an item
-  // with matching ASRs.
-  if (!eventRegions.IsEmpty()) {
-    MOZ_ASSERT(apzEnabled);
-    MOZ_ASSERT(!mLayerScrollData.empty());
-    mLayerScrollData.back().AddEventRegions(eventRegions);
-  }
-}
-
-Maybe<wr::ImageKey>
-WebRenderLayerManager::CreateImageKey(nsDisplayItem* aItem,
-                                      ImageContainer* aContainer,
-                                      mozilla::wr::DisplayListBuilder& aBuilder,
-                                      mozilla::wr::IpcResourceUpdateQueue& aResources,
-                                      const StackingContextHelper& aSc,
-                                      gfx::IntSize& aSize)
-{
-  RefPtr<WebRenderImageData> imageData = CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
-  MOZ_ASSERT(imageData);
-
-  if (aContainer->IsAsync()) {
-    bool snap;
-    nsRect bounds = aItem->GetBounds(nullptr, &snap);
-    int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
-    LayerRect rect = ViewAs<LayerPixel>(
-      LayoutDeviceRect::FromAppUnits(bounds, appUnitsPerDevPixel),
-      PixelCastJustification::WebRenderHasUnitResolution);
-    LayerRect scBounds(0, 0, rect.width, rect.Height());
-    MaybeIntSize scaleToSize;
-    if (!aContainer->GetScaleHint().IsEmpty()) {
-      scaleToSize = Some(aContainer->GetScaleHint());
-    }
-    // TODO!
-    // We appear to be using the image bridge for a lot (most/all?) of
-    // layers-free image handling and that breaks frame consistency.
-    imageData->CreateAsyncImageWebRenderCommands(aBuilder,
-                                                 aContainer,
-                                                 aSc,
-                                                 rect,
-                                                 scBounds,
-                                                 gfx::Matrix4x4(),
-                                                 scaleToSize,
-                                                 wr::ImageRendering::Auto,
-                                                 wr::MixBlendMode::Normal,
-                                                 !aItem->BackfaceIsHidden());
-    return Nothing();
-  }
-
-  AutoLockImage autoLock(aContainer);
-  if (!autoLock.HasImage()) {
-    return Nothing();
-  }
-  mozilla::layers::Image* image = autoLock.GetImage();
-  aSize = image->GetSize();
-
-  return imageData->UpdateImageKey(aContainer, aResources);
-}
-
-bool
-WebRenderLayerManager::PushImage(nsDisplayItem* aItem,
-                                 ImageContainer* aContainer,
-                                 mozilla::wr::DisplayListBuilder& aBuilder,
-                                 mozilla::wr::IpcResourceUpdateQueue& aResources,
-                                 const StackingContextHelper& aSc,
-                                 const LayerRect& aRect)
-{
-  gfx::IntSize size;
-  Maybe<wr::ImageKey> key = CreateImageKey(aItem, aContainer,
-                                          aBuilder, aResources,
-                                          aSc, size);
-  if (aContainer->IsAsync()) {
-    // Async ImageContainer does not create ImageKey, instead it uses Pipeline.
-    MOZ_ASSERT(key.isNothing());
-    return true;
-  }
-  if (!key) {
-    return false;
-  }
-
-  auto r = aSc.ToRelativeLayoutRect(aRect);
-  SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
-  aBuilder.PushImage(r, r, !aItem->BackfaceIsHidden(), wr::ToImageRendering(sampleFilter), key.value());
-
-  return true;
-}
-
-static void
-PaintItemByDrawTarget(nsDisplayItem* aItem,
-                      DrawTarget* aDT,
-                      const LayerRect& aImageRect,
-                      const LayerPoint& aOffset,
-                      nsDisplayListBuilder* aDisplayListBuilder,
-                      RefPtr<BasicLayerManager>& aManager,
-                      WebRenderLayerManager* aWrManager,
-                      const gfx::Size& aScale)
-{
-  MOZ_ASSERT(aDT);
-
-  aDT->ClearRect(aImageRect.ToUnknownRect());
-  RefPtr<gfxContext> context = gfxContext::CreateOrNull(aDT);
-  MOZ_ASSERT(context);
-
-  context->SetMatrix(context->CurrentMatrix().PreScale(aScale.width, aScale.height).PreTranslate(-aOffset.x, -aOffset.y));
-
-  switch (aItem->GetType()) {
-  case DisplayItemType::TYPE_MASK:
-    static_cast<nsDisplayMask*>(aItem)->PaintMask(aDisplayListBuilder, context);
-    break;
-  case DisplayItemType::TYPE_FILTER:
-    {
-      if (aManager == nullptr) {
-        aManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
-      }
-
-      FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
-      layerBuilder->Init(aDisplayListBuilder, aManager);
-      layerBuilder->DidBeginRetainedLayerTransaction(aManager);
-
-      aManager->BeginTransactionWithTarget(context);
-
-      ContainerLayerParameters param;
-      RefPtr<Layer> layer =
-        static_cast<nsDisplayFilter*>(aItem)->BuildLayer(aDisplayListBuilder,
-                                                         aManager, param);
-
-      if (layer) {
-        UniquePtr<LayerProperties> props;
-        props = Move(LayerProperties::CloneFrom(aManager->GetRoot()));
-
-        aManager->SetRoot(layer);
-        layerBuilder->WillEndTransaction();
-
-        static_cast<nsDisplayFilter*>(aItem)->PaintAsLayer(aDisplayListBuilder,
-                                                           context, aManager);
-      }
-
-      if (aManager->InTransaction()) {
-        aManager->AbortTransaction();
-      }
-      aManager->SetTarget(nullptr);
-      break;
-    }
-  default:
-    aItem->Paint(aDisplayListBuilder, context);
-    break;
-  }
-
-  if (gfxPrefs::WebRenderHighlightPaintedLayers()) {
-    aDT->SetTransform(Matrix());
-    aDT->FillRect(Rect(0, 0, aImageRect.Width(), aImageRect.Height()), ColorPattern(Color(1.0, 0.0, 0.0, 0.5)));
-  }
-  if (aItem->Frame()->PresContext()->GetPaintFlashing()) {
-    aDT->SetTransform(Matrix());
-    float r = float(rand()) / RAND_MAX;
-    float g = float(rand()) / RAND_MAX;
-    float b = float(rand()) / RAND_MAX;
-    aDT->FillRect(Rect(0, 0, aImageRect.Width(), aImageRect.Height()), ColorPattern(Color(r, g, b, 0.5)));
-  }
-}
-
-already_AddRefed<WebRenderFallbackData>
-WebRenderLayerManager::GenerateFallbackData(nsDisplayItem* aItem,
-                                            wr::DisplayListBuilder& aBuilder,
-                                            wr::IpcResourceUpdateQueue& aResources,
-                                            const StackingContextHelper& aSc,
-                                            nsDisplayListBuilder* aDisplayListBuilder,
-                                            LayerRect& aImageRect)
-{
-  RefPtr<WebRenderFallbackData> fallbackData = CreateOrRecycleWebRenderUserData<WebRenderFallbackData>(aItem);
-
-  bool snap;
-  nsRect itemBounds = aItem->GetBounds(aDisplayListBuilder, &snap);
-  nsRect clippedBounds = itemBounds;
-
-  const DisplayItemClip& clip = aItem->GetClip();
-  // Blob images will only draw the visible area of the blob so we don't need to clip
-  // them here and can just rely on the webrender clipping.
-  if (clip.HasClip() && !gfxPrefs::WebRenderBlobImages()) {
-    clippedBounds = itemBounds.Intersect(clip.GetClipRect());
-  }
-
-  // nsDisplayItem::Paint() may refer the variables that come from ComputeVisibility().
-  // So we should call ComputeVisibility() before painting. e.g.: nsDisplayBoxShadowInner
-  // uses mVisibleRegion in Paint() and mVisibleRegion is computed in
-  // nsDisplayBoxShadowInner::ComputeVisibility().
-  nsRegion visibleRegion(clippedBounds);
-  aItem->ComputeVisibility(aDisplayListBuilder, &visibleRegion);
-
-  const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
-  LayerRect bounds = ViewAs<LayerPixel>(
-      LayoutDeviceRect::FromAppUnits(clippedBounds, appUnitsPerDevPixel),
-      PixelCastJustification::WebRenderHasUnitResolution);
-
-  gfx::Size scale = aSc.GetInheritedScale();
-  LayerIntSize paintSize = RoundedToInt(LayerSize(bounds.width * scale.width, bounds.height * scale.height));
-  if (paintSize.width == 0 || paintSize.height == 0) {
-    return nullptr;
-  }
-
-  bool needPaint = true;
-  LayerIntPoint offset = RoundedToInt(bounds.TopLeft());
-  aImageRect = LayerRect(offset, LayerSize(RoundedToInt(bounds.Size())));
-  LayerRect paintRect = LayerRect(LayerPoint(0, 0), LayerSize(paintSize));
-  nsAutoPtr<nsDisplayItemGeometry> geometry = fallbackData->GetGeometry();
-
-  // nsDisplayFilter is rendered via BasicLayerManager which means the invalidate
-  // region is unknown until we traverse the displaylist contained by it.
-  if (geometry && !fallbackData->IsInvalid() &&
-      aItem->GetType() != DisplayItemType::TYPE_FILTER) {
-    nsRect invalid;
-    nsRegion invalidRegion;
-
-    if (aItem->IsInvalid(invalid)) {
-      invalidRegion.OrWith(clippedBounds);
-    } else {
-      nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft();
-      geometry->MoveBy(shift);
-      aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, &invalidRegion);
-
-      nsRect lastBounds = fallbackData->GetBounds();
-      lastBounds.MoveBy(shift);
-
-      if (!lastBounds.IsEqualInterior(clippedBounds)) {
-        invalidRegion.OrWith(lastBounds);
-        invalidRegion.OrWith(clippedBounds);
-      }
-    }
-    needPaint = !invalidRegion.IsEmpty();
-  }
-
-  if (needPaint) {
-    gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ?
-                                                      gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8;
-    if (gfxPrefs::WebRenderBlobImages()) {
-      bool snapped;
-      bool isOpaque = aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(clippedBounds);
-
-      RefPtr<gfx::DrawEventRecorderMemory> recorder = MakeAndAddRef<gfx::DrawEventRecorderMemory>();
-      RefPtr<gfx::DrawTarget> dummyDt =
-        gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
-      RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, paintSize.ToUnknownSize());
-      PaintItemByDrawTarget(aItem, dt, paintRect, offset, aDisplayListBuilder,
-                            fallbackData->mBasicLayerManager, this, scale);
-      recorder->Finish();
-
-      Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
-      wr::ImageKey key = WrBridge()->GetNextImageKey();
-      wr::ImageDescriptor descriptor(paintSize.ToUnknownSize(), 0, dt->GetFormat(), isOpaque);
-      aResources.AddBlobImage(key, descriptor, bytes);
-      fallbackData->SetKey(key);
-    } else {
-      fallbackData->CreateImageClientIfNeeded();
-      RefPtr<ImageClient> imageClient = fallbackData->GetImageClient();
-      RefPtr<ImageContainer> imageContainer = LayerManager::CreateImageContainer();
-
-      {
-        UpdateImageHelper helper(imageContainer, imageClient, paintSize.ToUnknownSize(), format);
-        {
-          RefPtr<gfx::DrawTarget> dt = helper.GetDrawTarget();
-          if (!dt) {
-            return nullptr;
-          }
-          PaintItemByDrawTarget(aItem, dt, paintRect, offset,
-                                aDisplayListBuilder,
-                                fallbackData->mBasicLayerManager, this, scale);
-        }
-        if (!helper.UpdateImage()) {
-          return nullptr;
-        }
-      }
-
-      // Force update the key in fallback data since we repaint the image in this path.
-      // If not force update, fallbackData may reuse the original key because it
-      // doesn't know UpdateImageHelper already updated the image container.
-      if (!fallbackData->UpdateImageKey(imageContainer, aResources, true)) {
-        return nullptr;
-      }
-    }
-
-    geometry = aItem->AllocateGeometry(aDisplayListBuilder);
-    fallbackData->SetInvalid(false);
-  }
-
-  // Update current bounds to fallback data
-  fallbackData->SetGeometry(Move(geometry));
-  fallbackData->SetBounds(clippedBounds);
-
-  MOZ_ASSERT(fallbackData->GetKey());
-
-  return fallbackData.forget();
-}
-
-Maybe<wr::WrImageMask>
-WebRenderLayerManager::BuildWrMaskImage(nsDisplayItem* aItem,
-                                        wr::DisplayListBuilder& aBuilder,
-                                        wr::IpcResourceUpdateQueue& aResources,
-                                        const StackingContextHelper& aSc,
-                                        nsDisplayListBuilder* aDisplayListBuilder,
-                                        const LayerRect& aBounds)
-{
-  LayerRect imageRect;
-  RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(aItem, aBuilder, aResources,
-                                                                    aSc, aDisplayListBuilder,
-                                                                    imageRect);
-  if (!fallbackData) {
-    return Nothing();
-  }
-
-  wr::WrImageMask imageMask;
-  imageMask.image = fallbackData->GetKey().value();
-  imageMask.rect = aSc.ToRelativeLayoutRect(aBounds);
-  imageMask.repeat = false;
-  return Some(imageMask);
-}
-
-bool
-WebRenderLayerManager::PushItemAsImage(nsDisplayItem* aItem,
-                                       wr::DisplayListBuilder& aBuilder,
-                                       wr::IpcResourceUpdateQueue& aResources,
-                                       const StackingContextHelper& aSc,
-                                       nsDisplayListBuilder* aDisplayListBuilder)
-{
-  LayerRect imageRect;
-  RefPtr<WebRenderFallbackData> fallbackData = GenerateFallbackData(aItem, aBuilder, aResources,
-                                                                    aSc, aDisplayListBuilder,
-                                                                    imageRect);
-  if (!fallbackData) {
-    return false;
-  }
-
-  wr::LayoutRect dest = aSc.ToRelativeLayoutRect(imageRect);
-  SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame());
-  aBuilder.PushImage(dest,
-                     dest,
-                     !aItem->BackfaceIsHidden(),
-                     wr::ToImageRendering(sampleFilter),
-                     fallbackData->GetKey().value());
-  return true;
-}
-
-void
 WebRenderLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback,
                                       void* aCallbackData,
                                       EndTransactionFlags aFlags)
 {
   // This should never get called, all callers should use
   // EndTransactionWithoutLayer instead.
   MOZ_ASSERT(false);
 }
@@ -726,62 +242,22 @@ WebRenderLayerManager::EndTransactionWit
     return;
   }
   DiscardCompositorAnimations();
 
   wr::LayoutSize contentSize { (float)size.width, (float)size.height };
   wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), contentSize);
   wr::IpcResourceUpdateQueue resourceUpdates(WrBridge()->GetShmemAllocator());
 
-  { // scoping for StackingContextHelper RAII
-
-    StackingContextHelper sc;
-    mParentCommands.Clear();
-    mScrollData = WebRenderScrollData();
-    MOZ_ASSERT(mLayerScrollData.empty());
-    mLastCanvasDatas.Clear();
-    mLastAsr = nullptr;
-
-    CreateWebRenderCommandsFromDisplayList(aDisplayList, aDisplayListBuilder, sc, builder, resourceUpdates);
-
-    builder.Finalize(contentSize, mBuiltDisplayList);
-
-    // Make a "root" layer data that has everything else as descendants
-    mLayerScrollData.emplace_back();
-    mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1);
-    if (aDisplayListBuilder->IsBuildingLayerEventRegions()) {
-      nsIPresShell* shell = aDisplayListBuilder->RootReferenceFrame()->PresContext()->PresShell();
-      if (nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(shell)) {
-        mLayerScrollData.back().SetEventRegionsOverride(EventRegionsOverride::ForceDispatchToContent);
-      }
-    }
-    RefPtr<WebRenderLayerManager> self(this);
-    auto callback = [self](FrameMetrics::ViewID aScrollId) -> bool {
-      return self->mScrollData.HasMetadataFor(aScrollId);
-    };
-    if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata(
-          aDisplayListBuilder, nullptr, ContainerLayerParameters(), callback)) {
-      mLayerScrollData.back().AppendScrollMetadata(mScrollData, rootMetadata.ref());
-    }
-    // Append the WebRenderLayerScrollData items into WebRenderScrollData
-    // in reverse order, from topmost to bottommost. This is in keeping with
-    // the semantics of WebRenderScrollData.
-    for (auto i = mLayerScrollData.crbegin(); i != mLayerScrollData.crend(); i++) {
-      mScrollData.AddLayerData(*i);
-    }
-    mLayerScrollData.clear();
-    mClipIdCache.clear();
-
-    // Remove the user data those are not displayed on the screen and
-    // also reset the data to unused for next transaction.
-    RemoveUnusedAndResetWebRenderUserData();
-  }
-
-  builder.PushBuiltDisplayList(mBuiltDisplayList);
-  WrBridge()->AddWebRenderParentCommands(mParentCommands);
+  mWebRenderCommandBuilder.BuildWebRenderCommands(builder,
+                                                  resourceUpdates,
+                                                  aDisplayList,
+                                                  aDisplayListBuilder,
+                                                  mScrollData,
+                                                  contentSize);
 
   mWidget->AddWindowOverlayWebRenderCommands(WrBridge(), builder, resourceUpdates);
   WrBridge()->ClearReadLocks();
 
   // We can't finish this transaction so return. This usually
   // happens in an empty transaction where we can't repaint a painted layer.
   // In this case, leave the transaction open and let a full transaction happen.
   if (mTransactionIncomplete) {
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -11,16 +11,17 @@
 
 #include "gfxPrefs.h"
 #include "Layers.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/layers/FocusTarget.h"
 #include "mozilla/layers/StackingContextHelper.h"
 #include "mozilla/layers/TransactionIdAllocator.h"
+#include "mozilla/layers/WebRenderCommandBuilder.h"
 #include "mozilla/layers/WebRenderScrollData.h"
 #include "mozilla/layers/WebRenderUserData.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsDisplayList.h"
 
 class nsIWidget;
 
@@ -58,53 +59,16 @@ public:
   WebRenderLayerManager* AsWebRenderLayerManager() override { return this; }
   virtual CompositorBridgeChild* GetCompositorBridgeChild() override;
 
   virtual int32_t GetMaxTextureSize() const override;
 
   virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override;
   virtual bool BeginTransaction() override;
   virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override;
-  Maybe<wr::ImageKey> CreateImageKey(nsDisplayItem* aItem,
-                                     ImageContainer* aContainer,
-                                     mozilla::wr::DisplayListBuilder& aBuilder,
-                                     mozilla::wr::IpcResourceUpdateQueue& aResources,
-                                     const StackingContextHelper& aSc,
-                                     gfx::IntSize& aSize);
-  bool PushImage(nsDisplayItem* aItem,
-                 ImageContainer* aContainer,
-                 mozilla::wr::DisplayListBuilder& aBuilder,
-                 mozilla::wr::IpcResourceUpdateQueue& aResources,
-                 const StackingContextHelper& aSc,
-                 const LayerRect& aRect);
-
-  already_AddRefed<WebRenderFallbackData>
-  GenerateFallbackData(nsDisplayItem* aItem,
-                       wr::DisplayListBuilder& aBuilder,
-                       wr::IpcResourceUpdateQueue& aResourceUpdates,
-                       const StackingContextHelper& aSc,
-                       nsDisplayListBuilder* aDisplayListBuilder,
-                       LayerRect& aImageRect);
-
-  Maybe<wr::WrImageMask> BuildWrMaskImage(nsDisplayItem* aItem,
-                                          wr::DisplayListBuilder& aBuilder,
-                                          wr::IpcResourceUpdateQueue& aResources,
-                                          const StackingContextHelper& aSc,
-                                          nsDisplayListBuilder* aDisplayListBuilder,
-                                          const LayerRect& aBounds);
-  bool PushItemAsImage(nsDisplayItem* aItem,
-                       wr::DisplayListBuilder& aBuilder,
-                       wr::IpcResourceUpdateQueue& aResources,
-                       const StackingContextHelper& aSc,
-                       nsDisplayListBuilder* aDisplayListBuilder);
-  void CreateWebRenderCommandsFromDisplayList(nsDisplayList* aDisplayList,
-                                              nsDisplayListBuilder* aDisplayListBuilder,
-                                              const StackingContextHelper& aSc,
-                                              wr::DisplayListBuilder& aBuilder,
-                                              wr::IpcResourceUpdateQueue& aResources);
   void EndTransactionWithoutLayer(nsDisplayList* aDisplayList,
                                   nsDisplayListBuilder* aDisplayListBuilder);
   virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
                               void* aCallbackData,
                               EndTransactionFlags aFlags = END_DEFAULT) override;
 
   virtual LayersBackend GetBackendType() override { return LayersBackend::LAYERS_WR; }
   virtual void GetBackendName(nsAString& name) override { name.AssignLiteral("WebRender"); }
@@ -179,102 +143,32 @@ public:
                                   const std::string& aValue) {
     MOZ_ASSERT(gfxPrefs::APZTestLoggingEnabled(), "don't call me");
     mApzTestData.LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey, aValue);
   }
   // See equivalent function in ClientLayerManager
   const APZTestData& GetAPZTestData() const
   { return mApzTestData; }
 
-  // Those are data that we kept between transactions. We used to cache some
-  // data in the layer. But in layers free mode, we don't have layer which
-  // means we need some other place to cached the data between transaction.
-  // We store the data in frame's property.
-  template<class T> already_AddRefed<T>
-  CreateOrRecycleWebRenderUserData(nsDisplayItem* aItem, bool* aOutIsRecycled = nullptr)
-  {
-    MOZ_ASSERT(aItem);
-    nsIFrame* frame = aItem->Frame();
-    if (aOutIsRecycled) {
-      *aOutIsRecycled = true;
-    }
-
-    nsIFrame::WebRenderUserDataTable* userDataTable =
-      frame->GetProperty(nsIFrame::WebRenderUserDataProperty());
-
-    if (!userDataTable) {
-      userDataTable = new nsIFrame::WebRenderUserDataTable();
-      frame->AddProperty(nsIFrame::WebRenderUserDataProperty(), userDataTable);
-    }
-
-    RefPtr<WebRenderUserData>& data = userDataTable->GetOrInsert(aItem->GetPerFrameKey());
-    if (!data || (data->GetType() != T::Type()) || !data->IsDataValid(this)) {
-      // To recreate a new user data, we should remove the data from the table first.
-      if (data) {
-        data->RemoveFromTable();
-      }
-      data = new T(this, aItem, &mWebRenderUserDatas);
-      mWebRenderUserDatas.PutEntry(data);
-      if (aOutIsRecycled) {
-        *aOutIsRecycled = false;
-      }
-    }
-
-    MOZ_ASSERT(data);
-    MOZ_ASSERT(data->GetType() == T::Type());
-
-    // Mark the data as being used. We will remove unused user data in the end of EndTransaction.
-    data->SetUsed(true);
-
-    if (T::Type() == WebRenderUserData::UserDataType::eCanvas) {
-      mLastCanvasDatas.PutEntry(data->AsCanvasData());
-    }
-    RefPtr<T> res = static_cast<T*>(data.get());
-    return res.forget();
-  }
-
   bool SetPendingScrollUpdateForNextTransaction(FrameMetrics::ViewID aScrollId,
                                                 const ScrollUpdateInfo& aUpdateInfo) override;
 
+  WebRenderCommandBuilder& CommandBuilder() { return mWebRenderCommandBuilder; }
+  WebRenderUserDataRefTable* GetWebRenderUserDataTable() { return mWebRenderCommandBuilder.GetWebRenderUserDataTable(); }
+  WebRenderScrollData& GetScrollData() { return mScrollData; }
+
 private:
   /**
    * Take a snapshot of the parent context, and copy
    * it into mTarget.
    */
   void MakeSnapshotIfRequired(LayoutDeviceIntSize aSize);
 
   void ClearLayer(Layer* aLayer);
 
-  void RemoveUnusedAndResetWebRenderUserData()
-  {
-    for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) {
-      WebRenderUserData* data = iter.Get()->GetKey();
-      if (!data->IsUsed()) {
-        nsIFrame* frame = data->GetFrame();
-
-        MOZ_ASSERT(frame->HasProperty(nsIFrame::WebRenderUserDataProperty()));
-
-        nsIFrame::WebRenderUserDataTable* userDataTable =
-          frame->GetProperty(nsIFrame::WebRenderUserDataProperty());
-
-        MOZ_ASSERT(userDataTable->Count());
-
-        userDataTable->Remove(data->GetDisplayItemKey());
-
-        if (!userDataTable->Count()) {
-          frame->RemoveProperty(nsIFrame::WebRenderUserDataProperty());
-        }
-        iter.Remove();
-        continue;
-      }
-
-      data->SetUsed(false);
-    }
-  }
-
 private:
   nsIWidget* MOZ_NON_OWNING_REF mWidget;
   nsTArray<wr::ImageKey> mImageKeysToDelete;
   // TODO - This is needed because we have some code that creates image keys
   // and enqueues them for deletion right away which is bad not only because
   // of poor texture cache usage, but also because images end up deleted before
   // they are used. This should hopfully be temporary.
   nsTArray<wr::ImageKey> mImageKeysToDeleteLater;
@@ -288,72 +182,39 @@ private:
 
   RefPtr<WebRenderBridgeChild> mWrChild;
 
   RefPtr<TransactionIdAllocator> mTransactionIdAllocator;
   uint64_t mLatestTransactionId;
 
   nsTArray<DidCompositeObserver*> mDidCompositeObservers;
 
-  // These fields are used to save a copy of the display list for
-  // empty transactions in layers-free mode.
-  wr::BuiltDisplayList mBuiltDisplayList;
-  nsTArray<WebRenderParentCommand> mParentCommands;
-
   // This holds the scroll data that we need to send to the compositor for
   // APZ to do it's job
   WebRenderScrollData mScrollData;
-  // We use this as a temporary data structure while building the mScrollData
-  // inside a layers-free transaction.
-  std::vector<WebRenderLayerScrollData> mLayerScrollData;
-  // We use this as a temporary data structure to track the current display
-  // item's ASR as we recurse in CreateWebRenderCommandsFromDisplayList. We
-  // need this so that WebRenderLayerScrollData items that deeper in the
-  // tree don't duplicate scroll metadata that their ancestors already have.
-  std::vector<const ActiveScrolledRoot*> mAsrStack;
-  const ActiveScrolledRoot* mLastAsr;
-
-public:
-  // Note: two DisplayItemClipChain* A and B might actually be "equal" (as per
-  // DisplayItemClipChain::Equal(A, B)) even though they are not the same pointer
-  // (A != B). In this hopefully-rare case, they will get separate entries
-  // in this map when in fact we could collapse them. However, to collapse
-  // them involves writing a custom hash function for the pointer type such that
-  // A and B hash to the same things whenever DisplayItemClipChain::Equal(A, B)
-  // is true, and that will incur a performance penalty for all the hashmap
-  // operations, so is probably not worth it. With the current code we might
-  // end up creating multiple clips in WR that are effectively identical but
-  // have separate clip ids. Hopefully this won't happen very often.
-  typedef std::unordered_map<const DisplayItemClipChain*, wr::WrClipId> ClipIdMap;
-private:
-  ClipIdMap mClipIdCache;
 
   bool mTransactionIncomplete;
 
   bool mNeedsComposite;
   bool mIsFirstPaint;
   FocusTarget mFocusTarget;
 
- // When we're doing a transaction in order to draw to a non-default
- // target, the layers transaction is only performed in order to send
- // a PLayers:Update.  We save the original non-default target to
- // mTarget, and then perform the transaction. After the transaction ends,
- // we send a message to our remote side to capture the actual pixels
- // being drawn to the default target, and then copy those pixels
- // back to mTarget.
- RefPtr<gfxContext> mTarget;
+  // When we're doing a transaction in order to draw to a non-default
+  // target, the layers transaction is only performed in order to send
+  // a PLayers:Update.  We save the original non-default target to
+  // mTarget, and then perform the transaction. After the transaction ends,
+  // we send a message to our remote side to capture the actual pixels
+  // being drawn to the default target, and then copy those pixels
+  // back to mTarget.
+  RefPtr<gfxContext> mTarget;
 
   // See equivalent field in ClientLayerManager
   uint32_t mPaintSequenceNumber;
   // See equivalent field in ClientLayerManager
   APZTestData mApzTestData;
 
-  typedef nsTHashtable<nsRefPtrHashKey<WebRenderCanvasData>> CanvasDataSet;
-  // Store of WebRenderCanvasData objects for use in empty transactions
-  CanvasDataSet mLastCanvasDatas;
-
-  WebRenderUserDataRefTable mWebRenderUserDatas;
+  WebRenderCommandBuilder mWebRenderCommandBuilder;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* GFX_WEBRENDERLAYERMANAGER_H */
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -11,22 +11,21 @@
 #include "mozilla/layers/WebRenderMessages.h"
 #include "mozilla/layers/IpcResourceUpdateQueue.h"
 #include "nsDisplayListInvalidation.h"
 #include "WebRenderCanvasRenderer.h"
 
 namespace mozilla {
 namespace layers {
 
-WebRenderUserData::WebRenderUserData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                     WebRenderUserDataRefTable* aTable)
+WebRenderUserData::WebRenderUserData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
   : mWRManager(aWRManager)
   , mFrame(aItem->Frame())
   , mDisplayItemKey(aItem->GetPerFrameKey())
-  , mTable(aTable)
+  , mTable(aWRManager->GetWebRenderUserDataTable())
   , mUsed(false)
 {
 }
 
 WebRenderUserData::~WebRenderUserData()
 {
 }
 
@@ -43,19 +42,18 @@ WebRenderUserData::RemoveFromTable()
 }
 
 WebRenderBridgeChild*
 WebRenderUserData::WrBridge() const
 {
   return mWRManager->WrBridge();
 }
 
-WebRenderImageData::WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                       WebRenderUserDataRefTable* aTable)
-  : WebRenderUserData(aWRManager, aItem, aTable)
+WebRenderImageData::WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
+  : WebRenderUserData(aWRManager, aItem)
 {
 }
 
 WebRenderImageData::~WebRenderImageData()
 {
   if (mKey) {
     mWRManager->AddImageKeyForDiscard(mKey.value());
   }
@@ -185,19 +183,18 @@ WebRenderImageData::CreateImageClientIfN
 void
 WebRenderImageData::CreateExternalImageIfNeeded()
 {
   if (!mExternalImageId)  {
     mExternalImageId = Some(WrBridge()->AllocExternalImageIdForCompositable(mImageClient));
   }
 }
 
-WebRenderFallbackData::WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                             WebRenderUserDataRefTable* aTable)
-  : WebRenderImageData(aWRManager, aItem, aTable)
+WebRenderFallbackData::WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
+  : WebRenderImageData(aWRManager, aItem)
   , mInvalid(false)
 {
 }
 
 WebRenderFallbackData::~WebRenderFallbackData()
 {
 }
 
@@ -208,38 +205,36 @@ WebRenderFallbackData::GetGeometry()
 }
 
 void
 WebRenderFallbackData::SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry)
 {
   mGeometry = aGeometry;
 }
 
-WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                               WebRenderUserDataRefTable* aTable)
-  : WebRenderUserData(aWRManager, aItem, aTable)
+WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
+  : WebRenderUserData(aWRManager, aItem)
   , mAnimationInfo(aWRManager)
 {
 }
 
 WebRenderAnimationData::~WebRenderAnimationData()
 {
   // It may be the case that nsDisplayItem that created this WebRenderUserData
   // gets destroyed without getting a chance to discard the compositor animation
   // id, so we should do it as part of cleanup here.
   uint64_t animationId = mAnimationInfo.GetCompositorAnimationsId();
   // animationId might be 0 if mAnimationInfo never held any active animations.
   if (animationId) {
     mWRManager->AddCompositorAnimationsIdForDiscard(animationId);
   }
 }
 
-WebRenderCanvasData::WebRenderCanvasData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                         WebRenderUserDataRefTable* aTable)
-  : WebRenderUserData(aWRManager, aItem, aTable)
+WebRenderCanvasData::WebRenderCanvasData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
+  : WebRenderUserData(aWRManager, aItem)
 {
 }
 
 WebRenderCanvasData::~WebRenderCanvasData()
 {
 }
 
 WebRenderCanvasRendererAsync*
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -31,18 +31,17 @@ class WebRenderLayerManager;
 
 class WebRenderUserData
 {
 public:
   typedef nsTHashtable<nsRefPtrHashKey<WebRenderUserData> > WebRenderUserDataRefTable;
 
   NS_INLINE_DECL_REFCOUNTING(WebRenderUserData)
 
-  WebRenderUserData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                    WebRenderUserDataRefTable* aTable);
+  WebRenderUserData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
 
   virtual WebRenderImageData* AsImageData() { return nullptr; }
   virtual WebRenderFallbackData* AsFallbackData() { return nullptr; }
   virtual WebRenderCanvasData* AsCanvasData() { return nullptr; }
 
   enum class UserDataType {
     eImage,
     eFallback,
@@ -68,18 +67,17 @@ protected:
   uint32_t mDisplayItemKey;
   WebRenderUserDataRefTable* mTable;
   bool mUsed;
 };
 
 class WebRenderImageData : public WebRenderUserData
 {
 public:
-  explicit WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                              WebRenderUserDataRefTable* aTable);
+  explicit WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderImageData();
 
   virtual WebRenderImageData* AsImageData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eImage; }
   static UserDataType Type() { return UserDataType::eImage; }
   Maybe<wr::ImageKey> GetKey() { return mKey; }
   void SetKey(const wr::ImageKey& aKey) { mKey = Some(aKey); }
   already_AddRefed<ImageClient> GetImageClient();
@@ -109,18 +107,17 @@ protected:
   RefPtr<ImageClient> mImageClient;
   Maybe<wr::PipelineId> mPipelineId;
   RefPtr<ImageContainer> mContainer;
 };
 
 class WebRenderFallbackData : public WebRenderImageData
 {
 public:
-  explicit WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                 WebRenderUserDataRefTable* aTable);
+  explicit WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderFallbackData();
 
   virtual WebRenderFallbackData* AsFallbackData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eFallback; }
   static UserDataType Type() { return UserDataType::eFallback; }
   nsAutoPtr<nsDisplayItemGeometry> GetGeometry();
   void SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
   nsRect GetBounds() { return mBounds; }
@@ -133,33 +130,31 @@ protected:
   nsAutoPtr<nsDisplayItemGeometry> mGeometry;
   nsRect mBounds;
   bool mInvalid;
 };
 
 class WebRenderAnimationData : public WebRenderUserData
 {
 public:
-  explicit WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                                  WebRenderUserDataRefTable* aTable);
+  explicit WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderAnimationData();
 
   virtual UserDataType GetType() override { return UserDataType::eAnimation; }
   static UserDataType Type() { return UserDataType::eAnimation; }
   AnimationInfo& GetAnimationInfo() { return mAnimationInfo; }
 
 protected:
   AnimationInfo mAnimationInfo;
 };
 
 class WebRenderCanvasData : public WebRenderUserData
 {
 public:
-  explicit WebRenderCanvasData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem,
-                               WebRenderUserDataRefTable* aTable);
+  explicit WebRenderCanvasData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderCanvasData();
 
   virtual WebRenderCanvasData* AsCanvasData() override { return this; }
   virtual UserDataType GetType() override { return UserDataType::eCanvas; }
   static UserDataType Type() { return UserDataType::eCanvas; }
 
   WebRenderCanvasRendererAsync* GetCanvasRenderer();
 
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -4061,8 +4061,22 @@ gfxFont::TryGetMathTable()
                 mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize());
             }
             hb_face_destroy(face);
         }
     }
 
     return !!mMathTable;
 }
+
+/* static */ void
+SharedFontList::Initialize()
+{
+  sEmpty = new SharedFontList();
+}
+
+/* static */ void
+SharedFontList::Shutdown()
+{
+  sEmpty = nullptr;
+}
+
+StaticRefPtr<SharedFontList> SharedFontList::sEmpty;
--- a/gfx/thebes/gfxFontFamilyList.h
+++ b/gfx/thebes/gfxFontFamilyList.h
@@ -7,16 +7,18 @@
 #define GFX_FONT_FAMILY_LIST_H
 
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "nsTArray.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/StaticPtr.h"
 
 namespace mozilla {
 
 /**
  * type of font family name, either a name (e.g. Helvetica) or a
  * generic (e.g. serif, sans-serif), with the ability to distinguish
  * between unquoted and quoted names for serializaiton
  */ 
@@ -162,163 +164,251 @@ struct FontFamilyName final {
 };
 
 inline bool
 operator==(const FontFamilyName& a, const FontFamilyName& b) {
     return a.mType == b.mType && a.mName == b.mName;
 }
 
 /**
+ * A refcounted array of FontFamilyNames.  We use this to store the specified
+ * value (in Servo) and the computed value (in both Gecko and Servo) of the
+ * font-family property.
+ */
+class SharedFontList
+{
+public:
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedFontList);
+
+    SharedFontList()
+    {
+    }
+
+    explicit SharedFontList(FontFamilyType aGenericType)
+        : mNames { FontFamilyName(aGenericType) }
+    {
+    }
+
+    SharedFontList(const nsAString& aFamilyName, QuotedName aQuoted)
+        : mNames { FontFamilyName(aFamilyName, aQuoted) }
+    {
+    }
+
+    explicit SharedFontList(const FontFamilyName& aName)
+        : mNames { aName }
+    {
+    }
+
+    explicit SharedFontList(nsTArray<FontFamilyName>&& aNames)
+        : mNames(Move(aNames))
+    {
+    }
+
+    FontFamilyType FirstGeneric() const
+    {
+        for (const FontFamilyName& name : mNames) {
+            if (name.IsGeneric()) {
+                return name.mType;
+            }
+        }
+        return eFamily_none;
+    }
+
+    bool HasGeneric() const
+    {
+        return FirstGeneric() != eFamily_none;
+    }
+
+    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t n = 0;
+      n += aMallocSizeOf(this);
+      n += mNames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+      for (const FontFamilyName& name : mNames) {
+          n += name.SizeOfExcludingThis(aMallocSizeOf);
+      }
+      return n;
+    }
+
+    size_t SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const
+    {
+        size_t n = 0;
+        if (mRefCnt.get() == 1) {
+          n += SizeOfIncludingThis(aMallocSizeOf);
+        }
+        return n;
+    }
+
+    const nsTArray<FontFamilyName> mNames;
+
+    static void Initialize();
+    static void Shutdown();
+    static StaticRefPtr<SharedFontList> sEmpty;
+
+private:
+    ~SharedFontList() = default;
+};
+
+/**
  * font family list, array of font families and a default font type.
  * font family names are either named strings or generics. the default
  * font type is used to preserve the variable font fallback behavior
- */ 
-
+ */
 class FontFamilyList {
 public:
     FontFamilyList()
-        : mDefaultFontType(eFamily_none)
+        : mFontlist(WrapNotNull(SharedFontList::sEmpty.get()))
+        , mDefaultFontType(eFamily_none)
     {
     }
 
     explicit FontFamilyList(FontFamilyType aGenericType)
-        : mDefaultFontType(eFamily_none)
+        : mFontlist(WrapNotNull(new SharedFontList(aGenericType)))
+        , mDefaultFontType(eFamily_none)
     {
-        Append(FontFamilyName(aGenericType));
     }
 
     FontFamilyList(const nsAString& aFamilyName,
                    QuotedName aQuoted)
-        : mDefaultFontType(eFamily_none)
+        : mFontlist(WrapNotNull(new SharedFontList(aFamilyName, aQuoted)))
+        , mDefaultFontType(eFamily_none)
     {
-        Append(FontFamilyName(aFamilyName, aQuoted));
+    }
+
+    explicit FontFamilyList(const FontFamilyName& aName)
+        : mFontlist(WrapNotNull(new SharedFontList(aName)))
+        , mDefaultFontType(eFamily_none)
+    {
+    }
+
+    explicit FontFamilyList(nsTArray<FontFamilyName>&& aNames)
+        : mFontlist(WrapNotNull(new SharedFontList(Move(aNames))))
+    {
     }
 
     FontFamilyList(const FontFamilyList& aOther)
         : mFontlist(aOther.mFontlist)
         , mDefaultFontType(aOther.mDefaultFontType)
     {
     }
 
-    void Append(const FontFamilyName& aFamilyName) {
-        mFontlist.AppendElement(aFamilyName);
+    explicit FontFamilyList(NotNull<SharedFontList*> aFontList)
+        : mFontlist(aFontList)
+        , mDefaultFontType(eFamily_none)
+    {
     }
 
-    void Append(const nsTArray<nsString>& aFamilyNameList) {
-        uint32_t len = aFamilyNameList.Length();
-        for (uint32_t i = 0; i < len; i++) {
-            mFontlist.AppendElement(FontFamilyName(aFamilyNameList[i],
-                                                   eUnquotedName));
-        }
+    void SetFontlist(nsTArray<FontFamilyName>&& aNames)
+    {
+        mFontlist = WrapNotNull(new SharedFontList(Move(aNames)));
     }
 
-    void Clear() {
-        mFontlist.Clear();
+    void SetFontlist(NotNull<SharedFontList*> aFontlist)
+    {
+        mFontlist = aFontlist;
     }
 
     uint32_t Length() const {
-        return mFontlist.Length();
+        return mFontlist->mNames.Length();
     }
 
     bool IsEmpty() const {
-      return mFontlist.IsEmpty();
+        return mFontlist->mNames.IsEmpty();
     }
 
-    const nsTArray<FontFamilyName>& GetFontlist() const {
+    NotNull<SharedFontList*> GetFontlist() const {
         return mFontlist;
     }
 
     bool Equals(const FontFamilyList& aFontlist) const {
-        return mFontlist == aFontlist.mFontlist &&
+        return (mFontlist == aFontlist.mFontlist ||
+                mFontlist->mNames == aFontlist.mFontlist->mNames) &&
                mDefaultFontType == aFontlist.mDefaultFontType;
     }
 
     FontFamilyType FirstGeneric() const {
-        uint32_t len = mFontlist.Length();
-        for (uint32_t i = 0; i < len; i++) {
-            const FontFamilyName& name = mFontlist[i];
-            if (name.IsGeneric()) {
-                return name.mType;
-            }
-        }
-        return eFamily_none;
+        return mFontlist->FirstGeneric();
     }
 
-    bool HasGeneric() const {
-        return FirstGeneric() != eFamily_none;
+    bool HasGeneric() const
+    {
+        return mFontlist->HasGeneric();
     }
 
     bool HasDefaultGeneric() const {
-        uint32_t len = mFontlist.Length();
-        for (uint32_t i = 0; i < len; i++) {
-            const FontFamilyName& name = mFontlist[i];
+        for (const FontFamilyName& name : mFontlist->mNames) {
             if (name.mType == mDefaultFontType) {
                 return true;
             }
         }
         return false;
     }
 
     // Find the first generic (but ignoring cursive and fantasy, as they are
     // rarely configured in any useful way) in the list.
     // If found, move it to the start and return true; else return false.
     bool PrioritizeFirstGeneric() {
-        uint32_t len = mFontlist.Length();
+        uint32_t len = mFontlist->mNames.Length();
         for (uint32_t i = 0; i < len; i++) {
-            const FontFamilyName name = mFontlist[i];
+            const FontFamilyName name = mFontlist->mNames[i];
             if (name.IsGeneric()) {
                 if (name.mType == eFamily_cursive ||
                     name.mType == eFamily_fantasy) {
                     continue;
                 }
                 if (i > 0) {
-                    mFontlist.RemoveElementAt(i);
-                    mFontlist.InsertElementAt(0, name);
+                    nsTArray<FontFamilyName> names;
+                    names.AppendElements(mFontlist->mNames);
+                    names.RemoveElementAt(i);
+                    names.InsertElementAt(0, name);
+                    SetFontlist(Move(names));
                 }
                 return true;
             }
         }
         return false;
     }
 
     void PrependGeneric(FontFamilyType aType) {
-        mFontlist.InsertElementAt(0, FontFamilyName(aType));
+        nsTArray<FontFamilyName> names;
+        names.AppendElements(mFontlist->mNames);
+        names.InsertElementAt(0, FontFamilyName(aType));
+        SetFontlist(Move(names));
     }
 
     void ToString(nsAString& aFamilyList,
                   bool aQuotes = true,
                   bool aIncludeDefault = false) const {
+        const nsTArray<FontFamilyName>& names = mFontlist->mNames;
         aFamilyList.Truncate();
-        uint32_t len = mFontlist.Length();
+        uint32_t len = names.Length();
         for (uint32_t i = 0; i < len; i++) {
             if (i != 0) {
                 aFamilyList.Append(',');
             }
-            const FontFamilyName& name = mFontlist[i];
+            const FontFamilyName& name = names[i];
             name.AppendToString(aFamilyList, aQuotes);
         }
         if (aIncludeDefault && mDefaultFontType != eFamily_none) {
             if (!aFamilyList.IsEmpty()) {
                 aFamilyList.Append(',');
             }
             if (mDefaultFontType == eFamily_serif) {
                 aFamilyList.AppendLiteral("serif");
             } else {
                 aFamilyList.AppendLiteral("sans-serif");
             }
         }
     }
 
     // searches for a specific non-generic name, lowercase comparison
     bool Contains(const nsAString& aFamilyName) const {
-        uint32_t len = mFontlist.Length();
         nsAutoString fam(aFamilyName);
         ToLowerCase(fam);
-        for (uint32_t i = 0; i < len; i++) {
-            const FontFamilyName& name = mFontlist[i];
+        for (const FontFamilyName& name : mFontlist->mNames) {
             if (name.mType != eFamily_named &&
                 name.mType != eFamily_named_quoted) {
                 continue;
             }
             nsAutoString listname(name.mName);
             ToLowerCase(listname);
             if (listname.Equals(fam)) {
                 return true;
@@ -333,29 +423,26 @@ public:
                      aType == eFamily_sans_serif,
                      "default font type must be either serif or sans-serif");
         mDefaultFontType = aType;
     }
 
     // memory reporting
     size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
         size_t n = 0;
-        n += mFontlist.ShallowSizeOfExcludingThis(aMallocSizeOf);
-        for (size_t i = 0; i < mFontlist.Length(); i++) {
-            n += mFontlist[i].SizeOfExcludingThis(aMallocSizeOf);
-        }
+        n += mFontlist->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
         return n;
     }
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
         return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
     }
 
-private:
-    nsTArray<FontFamilyName>   mFontlist;
+protected:
+    NotNull<RefPtr<SharedFontList>> mFontlist;
     FontFamilyType             mDefaultFontType; // none, serif or sans-serif
 };
 
 inline bool
 operator==(const FontFamilyList& a, const FontFamilyList& b) {
     return a.Equals(b);
 }
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1830,17 +1830,17 @@ gfxFontGroup::~gfxFontGroup()
 void
 gfxFontGroup::BuildFontList()
 {
     // initialize fonts in the font family list
     AutoTArray<gfxFontFamily*,10> fonts;
     gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
 
     // lookup fonts in the fontlist
-    for (const FontFamilyName& name : mFamilyList.GetFontlist()) {
+    for (const FontFamilyName& name : mFamilyList.GetFontlist()->mNames) {
         if (name.IsNamed()) {
             AddPlatformFont(name.mName, fonts);
         } else {
             pfl->AddGenericFonts(name.mType, mStyle.language, fonts);
             if (mTextPerf) {
                 mTextPerf->current.genericLookups++;
             }
         }
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -1069,17 +1069,17 @@ gfxUserFontSet::LookupFamily(const nsASt
     ToLowerCase(key);
 
     return mFontFamilies.GetWeak(key);
 }
 
 bool
 gfxUserFontSet::ContainsUserFontSetFonts(const FontFamilyList& aFontList) const
 {
-    for (const FontFamilyName& name : aFontList.GetFontlist()) {
+    for (const FontFamilyName& name : aFontList.GetFontlist()->mNames) {
         if (name.mType != eFamily_named &&
             name.mType != eFamily_named_quoted) {
             continue;
         }
         if (LookupFamily(name.mName)) {
             return true;
         }
     }
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -35,36 +35,17 @@
 #ifndef OS_WIN
 #include <unistd.h>
 #endif
 
 #include "base/child_privileges.h"
 #include "base/command_line.h"
 #include "base/process.h"
 
-#if defined(OS_WIN)
-typedef PROCESSENTRY32 ProcessEntry;
-typedef IO_COUNTERS IoCounters;
-#elif defined(OS_POSIX)
-// TODO(port): we should not rely on a Win32 structure.
-struct ProcessEntry {
-  int pid;
-  int ppid;
-  char szExeFile[_POSIX_PATH_MAX + 1];
-};
-
-struct IoCounters {
-  unsigned long long ReadOperationCount;
-  unsigned long long WriteOperationCount;
-  unsigned long long OtherOperationCount;
-  unsigned long long ReadTransferCount;
-  unsigned long long WriteTransferCount;
-  unsigned long long OtherTransferCount;
-};
-
+#if defined(OS_POSIX)
 #include "base/file_descriptor_shuffle.h"
 #endif
 
 #if defined(OS_MACOSX)
 struct kinfo_proc;
 #endif
 
 namespace base {
--- a/ipc/chromium/src/base/process_util_linux.cc
+++ b/ipc/chromium/src/base/process_util_linux.cc
@@ -27,21 +27,16 @@
  * We fall back to an arbitrary UID. This is generally the UID for user
  * `nobody', albeit it is not always the case.
  */
 # define CHILD_UNPRIVILEGED_UID 65534
 # define CHILD_UNPRIVILEGED_GID 65534
 
 namespace {
 
-enum ParsingState {
-  KEY_NAME,
-  KEY_VALUE
-};
-
 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG");
 
 }  // namespace
 
 namespace base {
 
 class EnvironmentEnvp
 {
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -226,17 +226,17 @@ ScheduleDeferredMessageRun()
 static void
 DumpNeuteredMessage(HWND hwnd, UINT uMsg)
 {
 #ifdef DEBUG
   nsAutoCString log("Received \"nonqueued\" ");
   // classify messages
   if (uMsg < WM_USER) {
     int idx = 0;
-    while (mozilla::widget::gAllEvents[idx].mId != (long)uMsg &&
+    while (mozilla::widget::gAllEvents[idx].mId != uMsg &&
            mozilla::widget::gAllEvents[idx].mStr != nullptr) {
       idx++;
     }
     if (mozilla::widget::gAllEvents[idx].mStr) {
       log.AppendPrintf("ui message \"%s\"", mozilla::widget::gAllEvents[idx].mStr);
     } else {
       log.AppendPrintf("ui message (0x%X)", uMsg);
     }
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -3576,46 +3576,16 @@ CreatePluralRulesPrototype(JSContext* cx
 
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineDataProperty(cx, Intl, cx->names().PluralRules, ctorValue, 0))
         return nullptr;
 
     return proto;
 }
 
-/* static */ bool
-js::GlobalObject::addPluralRulesConstructor(JSContext* cx, HandleObject intl)
-{
-    Handle<GlobalObject*> global = cx->global();
-
-    {
-        const HeapSlot& slot = global->getReservedSlotRef(PLURAL_RULES_PROTO);
-        if (!slot.isUndefined()) {
-            MOZ_ASSERT(slot.isObject());
-            JS_ReportErrorASCII(cx,
-                                "the PluralRules constructor can't be added "
-                                "multiple times in the same global");
-            return false;
-        }
-    }
-
-    JSObject* pluralRulesProto = CreatePluralRulesPrototype(cx, intl, global);
-    if (!pluralRulesProto)
-        return false;
-
-    global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
-    return true;
-}
-
-bool
-js::AddPluralRulesConstructor(JSContext* cx, JS::Handle<JSObject*> intl)
-{
-    return GlobalObject::addPluralRulesConstructor(cx, intl);
-}
-
 bool
 js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     RootedValue result(cx);
     // We're going to use ULocale availableLocales as per ICU recommendation:
@@ -4377,16 +4347,19 @@ GlobalObject::initIntlObject(JSContext* 
     RootedObject dateTimeFormatProto(cx), dateTimeFormat(cx);
     dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, DateTimeFormatOptions::Standard);
     if (!dateTimeFormatProto)
         return false;
     RootedObject numberFormatProto(cx), numberFormat(cx);
     numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
     if (!numberFormatProto)
         return false;
+    RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
+    if (!pluralRulesProto)
+        return false;
 
     // The |Intl| object is fully set up now, so define the global property.
     RootedValue intlValue(cx, ObjectValue(*intl));
     if (!DefineDataProperty(cx, global, cx->names().Intl, intlValue, JSPROP_RESOLVING))
         return false;
 
     // Now that the |Intl| object is successfully added, we can OOM-safely fill
     // in all relevant reserved global slots.
@@ -4397,16 +4370,17 @@ GlobalObject::initIntlObject(JSContext* 
     // |String.prototype| we have |JSProto_*| that enables
     // |getPrototype(JSProto_*)|, but that has global-object-property-related
     // baggage we don't need or want, so we use one-off reserved slots.
     global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
     global->setReservedSlot(DATE_TIME_FORMAT, ObjectValue(*dateTimeFormat));
     global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
     global->setReservedSlot(NUMBER_FORMAT, ObjectValue(*numberFormat));
     global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+    global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
 
     // Also cache |Intl| to implement spec language that conditions behavior
     // based on values being equal to "the standard built-in |Intl| object".
     // Use |setConstructor| to correspond with |JSProto_Intl|.
     //
     // XXX We should possibly do a one-off reserved slot like above.
     global->setConstructor(JSProto_Intl, ObjectValue(*intl));
     return true;
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -233,16 +233,18 @@ function treatAsSafeArgument(entry, varN
         ["Gecko_nsStyleFont_PrefillDefaultForGeneric", "aFont", null],
         ["Gecko_nsStyleSVG_SetContextPropertiesLength", "aSvg", null],
         ["Gecko_ClearAlternateValues", "aFont", null],
         ["Gecko_AppendAlternateValues", "aFont", null],
         ["Gecko_CopyAlternateValuesFrom", "aDest", null],
         ["Gecko_CounterStyle_GetName", "aResult", null],
         ["Gecko_CounterStyle_GetSingleString", "aResult", null],
         ["Gecko_EnsureMozBorderColors", "aBorder", null],
+        ["Gecko_nsTArray_FontFamilyName_AppendNamed", "aNames", null],
+        ["Gecko_nsTArray_FontFamilyName_AppendGeneric", "aNames", null],
     ];
     for (var [entryMatch, varMatch, csuMatch] of whitelist) {
         assert(entryMatch || varMatch || csuMatch);
         if (entryMatch && !nameMatches(entry.name, entryMatch))
             continue;
         if (varMatch && !nameMatches(varName, varMatch))
             continue;
         if (csuMatch && (!csuName || !nameMatches(csuName, csuMatch)))
@@ -501,17 +503,16 @@ function ignoreContents(entry)
             /(nsTSubstring<T>|nsAC?String)::Assign/,
             /(nsTSubstring<T>|nsAC?String)::Append/,
             /(nsTSubstring<T>|nsAC?String)::Replace/,
             /(nsTSubstring<T>|nsAC?String)::Trim/,
             /(nsTSubstring<T>|nsAC?String)::Truncate/,
             /(nsTSubstring<T>|nsAC?String)::StripTaggedASCII/,
             /(nsTSubstring<T>|nsAC?String)::operator=/,
             /nsTAutoStringN<T, N>::nsTAutoStringN/,
-            /nsTFixedString<T>::nsTFixedString/,
 
             // Similar for some other data structures
             /nsCOMArray_base::SetCapacity/,
             /nsCOMArray_base::Clear/,
             /nsCOMArray_base::AppendElement/,
 
             // UniquePtr is similar.
             /mozilla::UniquePtr/,
deleted file mode 100644
--- a/js/src/jit-test/tests/auto-regress/bug1334573.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// |jit-test| error:Error
-
-if (this.Intl) {
-    addIntlExtras(Intl);
-    addIntlExtras(Intl);
-} else {
-    throw new Error();
-}
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -3057,22 +3057,16 @@ ToWindowProxyIfWindow(JSObject* obj)
 /**
  * If `obj` is a WindowProxy, get its associated Window (the compartment's
  * global), else return `obj`. This function is infallible and never returns
  * nullptr.
  */
 extern JS_FRIEND_API(JSObject*)
 ToWindowIfWindowProxy(JSObject* obj);
 
-// Create and add the Intl.PluralRules constructor function to the provided
-// object.  This function throws if called more than once per realm/global
-// object.
-extern bool
-AddPluralRulesConstructor(JSContext* cx, JS::Handle<JSObject*> intl);
-
 // Create and add the Intl.MozDateTimeFormat constructor function to the provided
 // object.
 //
 // This custom date/time formatter constructor gives users the ability
 // to specify a custom format pattern. This pattern is passed *directly*
 // to ICU with NO SYNTAX PARSING OR VALIDATION WHATSOEVER. ICU appears to
 // have a a modicum of testing of this, and it won't fall over completely
 // if passed bad input. But the current behavior is entirely under-specified
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -784,19 +784,16 @@ AddIntlExtras(JSContext* cx, unsigned ar
         JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
         JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
         JS_FS_END
     };
 
     if (!JS_DefineFunctions(cx, intl, funcs))
         return false;
 
-    if (!js::AddPluralRulesConstructor(cx, intl))
-        return false;
-
     if (!js::AddMozDateTimeFormatConstructor(cx, intl))
         return false;
 
     args.rval().setUndefined();
     return true;
 }
 #endif // ENABLE_INTL_API
 
@@ -6685,18 +6682,17 @@ static const JSFunctionSpecWithHelp shel
 
 #ifdef ENABLE_INTL_API
     JS_FN_HELP("addIntlExtras", AddIntlExtras, 1, 0,
 "addIntlExtras(obj)",
 "Adds various not-yet-standardized Intl functions as properties on the\n"
 "provided object (this should generally be Intl itself).  The added\n"
 "functions and their behavior are experimental: don't depend upon them\n"
 "unless you're willing to update your code if these experimental APIs change\n"
-"underneath you.  Calling this function more than once in a realm/global\n"
-"will throw."),
+"underneath you."),
 #endif // ENABLE_INTL_API
 
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
     JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
 "getSelfHostedValue()",
--- a/js/src/tests/Intl/PluralRules/call.js
+++ b/js/src/tests/Intl/PluralRules/call.js
@@ -1,11 +1,9 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
-
-addIntlExtras(Intl);
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
 
 function IsConstructor(o) {
   try {
     new (new Proxy(o, {construct: () => ({})}));
     return true;
   } catch (e) {
     return false;
   }
--- a/js/src/tests/Intl/PluralRules/construct-newtarget.js
+++ b/js/src/tests/Intl/PluralRules/construct-newtarget.js
@@ -1,11 +1,9 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
-
-addIntlExtras(Intl);
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
 
 // Test subclassing %Intl.PluralRules% works correctly.
 class MyPluralRules extends Intl.PluralRules {}
 
 var obj = new MyPluralRules();
 assertEq(obj instanceof MyPluralRules, true);
 assertEq(obj instanceof Intl.PluralRules, true);
 assertEq(Object.getPrototypeOf(obj), MyPluralRules.prototype);
--- a/js/src/tests/Intl/PluralRules/negativeZeroFractionDigits.js
+++ b/js/src/tests/Intl/PluralRules/negativeZeroFractionDigits.js
@@ -1,11 +1,9 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty('addIntlExtras'))
-
-addIntlExtras(Intl);
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
 
 const optionsList = [
     {minimumFractionDigits: -0, maximumFractionDigits: -0},
     {minimumFractionDigits: -0, maximumFractionDigits: +0},
     {minimumFractionDigits: +0, maximumFractionDigits: -0},
     {minimumFractionDigits: +0, maximumFractionDigits: +0},
 ];
 
--- a/js/src/tests/Intl/PluralRules/pluralrules.js
+++ b/js/src/tests/Intl/PluralRules/pluralrules.js
@@ -1,16 +1,14 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty('addIntlExtras'))
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
 
 // Tests the format function with a diverse set of locales and options.
 
 var pr;
 
-addIntlExtras(Intl);
-
 pr = new Intl.PluralRules("en-us");
 assertEq(pr.resolvedOptions().locale, "en-US");
 assertEq(pr.resolvedOptions().type, "cardinal");
 assertEq(pr.resolvedOptions().pluralCategories.length, 2);
 
 pr = new Intl.PluralRules("de", {type: 'cardinal'});
 assertEq(pr.resolvedOptions().pluralCategories.length, 2);
 
--- a/js/src/tests/Intl/PluralRules/resolvedOptions-overridden-species.js
+++ b/js/src/tests/Intl/PluralRules/resolvedOptions-overridden-species.js
@@ -1,14 +1,12 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.hasOwnProperty("addIntlExtras"))
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
 
 // Tests the PluralRules.resolvedOptions function for overriden Array[Symbol.species].
 
-addIntlExtras(Intl);
-
 var pl = new Intl.PluralRules("de");
 
 Object.defineProperty(Array, Symbol.species, {
     value: function() {
         return new Proxy(["?"], {
             get(t, pk, r) {
                 return Reflect.get(t, pk, r);
             },
--- a/js/src/tests/Intl/PluralRules/select.js
+++ b/js/src/tests/Intl/PluralRules/select.js
@@ -1,16 +1,14 @@
-// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras'))
+// |reftest| skip-if(!this.hasOwnProperty('Intl'))
 
 // Tests the format function with a diverse set of locales and options.
 
 var pr;
 
-addIntlExtras(Intl);
-
 pr = new Intl.PluralRules("en-us");
 assertEq(pr.select(0), "other");
 assertEq(pr.select(0.5), "other");
 assertEq(pr.select(1.2), "other");
 assertEq(pr.select(1.5), "other");
 assertEq(pr.select(1.7), "other");
 assertEq(pr.select(-1), "one");
 assertEq(pr.select(1), "one");
--- a/js/src/tests/Intl/PluralRules/supportedLocalesOf.js
+++ b/js/src/tests/Intl/PluralRules/supportedLocalesOf.js
@@ -1,9 +1,9 @@
-// |reftest| skip-if(!this.hasOwnProperty('Intl')||!this.hasOwnProperty('addIntlExtras')||xulRuntime.shell)
+// |reftest| skip-if(!this.hasOwnProperty('Intl')||xulRuntime.shell)
 // -- test in browser only that ICU has locale data for all Mozilla languages
 
 // This array contains the locales that ICU supports in
 // number formatting whose languages Mozilla localizes Firefox into.
 // Current as of ICU 50.1.2 and Firefox March 2013.
 var locales = [
     "af",
     "af-NA",
@@ -357,15 +357,13 @@ var locales = [
     "zh-Hans-MO",
     "zh-Hans-SG",
     "zh-Hant",
     "zh-Hant-HK",
     "zh-Hant-MO",
     "zh-Hant-TW",
 ];
 
-addIntlExtras(Intl);
-
 const result = Intl.PluralRules.supportedLocalesOf(locales);
 
 assertEqArray(locales, result);
 
 reportCompare(0, 0, 'ok');
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -1,19 +1,16 @@
 # Manifest entries for imported test suites whose individual test cases
 # we don't want to change.
 
 skip script ecma_6/String/normalize-generateddata-input.js # input data for other test
 
 # Skip intl402 tests when Intl isn't available.
 skip-if(!this.hasOwnProperty('Intl')) include test262/intl402/jstests.list
 
-# Skip Intl.PluralRules tests when the addIntlExtras helper isn't available.
-skip-if(!this.hasOwnProperty('addIntlExtras')) include test262/intl402/PluralRules/jstests.list
-
 # Skip built-ins/Simd tests when SIMD isn't available.
 skip-if(!this.hasOwnProperty('SIMD')) include test262/built-ins/Simd/jstests.list
 
 # Skip built-in/Atomics when Atomics isn't available
 skip-if(!this.hasOwnProperty('Atomics')) include test262/built-ins/Atomics/jstests.list
 
 # Skip built-in/SharedArrayBuffer
 skip-if(!this.hasOwnProperty('SharedArrayBuffer')) include test262/built-ins/SharedArrayBuffer/jstests.list
deleted file mode 100644
--- a/js/src/tests/test262-intl-extras.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-// Call the shell helper to add experimental features to the Intl object.
-if (typeof addIntlExtras === "function") {
-    let intlExtras = {};
-    addIntlExtras(intlExtras);
-
-    Object.defineProperty(Intl, "PluralRules", {
-        value: intlExtras.PluralRules,
-        writable: true, enumerable: false, configurable: true
-    });
-}
--- a/js/src/tests/test262-update.py
+++ b/js/src/tests/test262-update.py
@@ -316,19 +316,16 @@ def process_test262(test262Dir, test262O
     explicitIncludes = {}
     explicitIncludes["intl402"] = ["testBuiltInObject.js"]
     explicitIncludes[os.path.join("built-ins", "DataView")] = ["byteConversionValues.js"]
     explicitIncludes[os.path.join("built-ins", "Promise")] = ["promiseHelper.js"]
     explicitIncludes[os.path.join("built-ins", "TypedArray")] = ["byteConversionValues.js",
         "detachArrayBuffer.js", "nans.js"]
     explicitIncludes[os.path.join("built-ins", "TypedArrays")] = ["detachArrayBuffer.js"]
 
-    # Intl.PluralRules isn't yet enabled by default.
-    localIncludesMap[os.path.join("intl402", "PluralRules")] = ["test262-intl-extras.js"]
-
     # Process all test directories recursively.
     for (dirPath, dirNames, fileNames) in os.walk(testDir):
         relPath = os.path.relpath(dirPath, testDir)
         if relPath == ".":
             continue
 
         # Skip creating a "prs" directory if it already exists
         if relPath != "prs" or not os.path.exists(os.path.join(test262OutDir, relPath)):
--- a/js/src/tests/test262/intl402/PluralRules/shell.js
+++ b/js/src/tests/test262/intl402/PluralRules/shell.js
@@ -1,15 +0,0 @@
-// file: test262-intl-extras.js
-// 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/.
-
-// Call the shell helper to add experimental features to the Intl object.
-if (typeof addIntlExtras === "function") {
-    let intlExtras = {};
-    addIntlExtras(intlExtras);
-
-    Object.defineProperty(Intl, "PluralRules", {
-        value: intlExtras.PluralRules,
-        writable: true, enumerable: false, configurable: true
-    });
-}
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -277,17 +277,16 @@
     macro(ownKeys, ownKeys, "ownKeys") \
     macro(Object_valueOf, Object_valueOf, "Object_valueOf") \
     macro(package, package, "package") \
     macro(parseFloat, parseFloat, "parseFloat") \
     macro(parseInt, parseInt, "parseInt") \
     macro(pattern, pattern, "pattern") \
     macro(pending, pending, "pending") \
     macro(PluralRules, PluralRules, "PluralRules") \
-    macro(PluralRulesSelect, PluralRulesSelect, "Intl_PluralRules_Select") \
     macro(percentSign, percentSign, "percentSign") \
     macro(plusSign, plusSign, "plusSign") \
     macro(public, public_, "public") \
     macro(pull, pull, "pull") \
     macro(preventExtensions, preventExtensions, "preventExtensions") \
     macro(private, private_, "private") \
     macro(promise, promise, "promise") \
     macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -773,17 +773,16 @@ class GlobalObject : public NativeObject
     static bool initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in builtin/MapObject.cpp.
     static bool initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in Intl.cpp.
     static bool initIntlObject(JSContext* cx, Handle<GlobalObject*> global);
-    static bool addPluralRulesConstructor(JSContext* cx, HandleObject intl);
 
     // Implemented in builtin/ModuleObject.cpp
     static bool initModuleProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initImportEntryProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initExportEntryProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initRequestedModuleProto(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in builtin/TypedObject.cpp
deleted file mode 100644
--- a/js/xpconnect/loader/ISO8601DateUtils.jsm
+++ /dev/null
@@ -1,144 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const HOURS_TO_MINUTES = 60;
-const MINUTES_TO_SECONDS = 60;
-const SECONDS_TO_MILLISECONDS = 1000;
-const MINUTES_TO_MILLISECONDS = MINUTES_TO_SECONDS * SECONDS_TO_MILLISECONDS;
-const HOURS_TO_MILLISECONDS = HOURS_TO_MINUTES * MINUTES_TO_MILLISECONDS;
-
-this.EXPORTED_SYMBOLS = ["ISO8601DateUtils"];
-
-debug("*** loading ISO8601DateUtils\n");
-
-this.ISO8601DateUtils = {
-
-  /**
-  * XXX Thunderbird's W3C-DTF function
-  *
-  * Converts a W3C-DTF (subset of ISO 8601) date string to a Javascript
-  * date object. W3C-DTF is described in this note:
-  * http://www.w3.org/TR/NOTE-datetime IETF is obtained via the Date
-  * object's toUTCString() method.  The object's toString() method is
-  * insufficient because it spells out timezones on Win32
-  * (f.e. "Pacific Standard Time" instead of "PST"), which Mail doesn't
-  * grok.  For info, see
-  * http://lxr.mozilla.org/mozilla/source/js/src/jsdate.c#1526.
-  */
-  parse: function ISO8601_parse(aDateString) {
-    var dateString = aDateString;
-    if (!dateString.match('-')) {
-      // Workaround for server sending
-      // dates such as: 20030530T11:18:50-08:00
-      // instead of: 2003-05-30T11:18:50-08:00
-      var year = dateString.slice(0, 4);
-      var month = dateString.slice(4, 6);
-      var rest = dateString.slice(6, dateString.length);
-      dateString = year + "-" + month + "-" + rest;
-    }
-
-    var parts = dateString.match(/(\d{4})(-(\d{2,3}))?(-(\d{2}))?(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|([+-])(\d{2}):(\d{2}))?)?/);
-
-    // Here's an example of a W3C-DTF date string and what .match returns for it.
-    //
-    // date: 2003-05-30T11:18:50.345-08:00
-    // date.match returns array values:
-    //
-    //   0: 2003-05-30T11:18:50-08:00,
-    //   1: 2003,
-    //   2: -05,
-    //   3: 05,
-    //   4: -30,
-    //   5: 30,
-    //   6: T11:18:50-08:00,
-    //   7: 11,
-    //   8: 18,
-    //   9: :50,
-    //   10: 50,
-    //   11: .345,
-    //   12: 345,
-    //   13: -08:00,
-    //   14: -,
-    //   15: 08,
-    //   16: 00
-
-    // Create a Date object from the date parts.  Note that the Date
-    // object apparently can't deal with empty string parameters in lieu
-    // of numbers, so optional values (like hours, minutes, seconds, and
-    // milliseconds) must be forced to be numbers.
-    var date = new Date(parts[1], parts[3] - 1, parts[5], parts[7] || 0,
-      parts[8] || 0, parts[10] || 0, parts[12] || 0);
-
-    // We now have a value that the Date object thinks is in the local
-    // timezone but which actually represents the date/time in the
-    // remote timezone (f.e. the value was "10:00 EST", and we have
-    // converted it to "10:00 PST" instead of "07:00 PST").  We need to
-    // correct that.  To do so, we're going to add the offset between
-    // the remote timezone and UTC (to convert the value to UTC), then
-    // add the offset between UTC and the local timezone //(to convert
-    // the value to the local timezone).
-
-    // Ironically, W3C-DTF gives us the offset between UTC and the
-    // remote timezone rather than the other way around, while the
-    // getTimezoneOffset() method of a Date object gives us the offset
-    // between the local timezone and UTC rather than the other way
-    // around.  Both of these are the additive inverse (i.e. -x for x)
-    // of what we want, so we have to invert them to use them by
-    // multipying by -1 (f.e. if "the offset between UTC and the remote
-    // timezone" is -5 hours, then "the offset between the remote
-    // timezone and UTC" is -5*-1 = 5 hours).
-
-    // Note that if the timezone portion of the date/time string is
-    // absent (which violates W3C-DTF, although ISO 8601 allows it), we
-    // assume the value to be in UTC.
-
-    // The offset between the remote timezone and UTC in milliseconds.
-    var remoteToUTCOffset = 0;
-    if (parts[13] && parts[13] != "Z") {
-      var direction = (parts[14] == "+" ? 1 : -1);
-      if (parts[15])
-        remoteToUTCOffset += direction * parts[15] * HOURS_TO_MILLISECONDS;
-      if (parts[16])
-        remoteToUTCOffset += direction * parts[16] * MINUTES_TO_MILLISECONDS;
-    }
-    remoteToUTCOffset = remoteToUTCOffset * -1; // invert it
-
-    // The offset between UTC and the local timezone in milliseconds.
-    var UTCToLocalOffset = date.getTimezoneOffset() * MINUTES_TO_MILLISECONDS;
-    UTCToLocalOffset = UTCToLocalOffset * -1; // invert it
-    date.setTime(date.getTime() + remoteToUTCOffset + UTCToLocalOffset);
-
-    return date;
-  },
-
-  create: function ISO8601_create(aDate) {
-    function zeropad (s, l) {
-      s = s.toString(); // force it to a string
-      while (s.length < l) {
-        s = '0' + s;
-      }
-      return s;
-    }
-
-    var myDate;
-    // if d is a number, turn it into a date
-    if (typeof aDate == 'number') {
-      myDate = new Date()
-      myDate.setTime(aDate);
-    } else {
-      myDate = aDate;
-    }
-
-    // YYYY-MM-DDThh:mm:ssZ
-    var result = zeropad(myDate.getUTCFullYear (), 4) +
-                 zeropad(myDate.getUTCMonth () + 1, 2) +
-                 zeropad(myDate.getUTCDate (), 2) + 'T' +
-                 zeropad(myDate.getUTCHours (), 2) + ':' +
-                 zeropad(myDate.getUTCMinutes (), 2) + ':' +
-                 zeropad(myDate.getUTCSeconds (), 2) + 'Z';
-
-    return result;
-  }
-}
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -34,17 +34,16 @@ EXPORTS.mozilla.dom += [
 ]
 
 EXPORTS.mozilla.loader += [
     'AutoMemMap.h',
     'ScriptCacheActors.h',
 ]
 
 EXTRA_JS_MODULES += [
-    'ISO8601DateUtils.jsm',
     'XPCOMUtils.jsm',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../src',
     '../wrappers',
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -851,18 +851,18 @@ XPCWrappedNative::FlatJSObjectFinalized(
         cache->ClearWrapper(mFlatJSObject.unbarrieredGetPtr());
 
     mFlatJSObject = nullptr;
     mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID);
 
     MOZ_ASSERT(mIdentity, "bad pointer!");
 #ifdef XP_WIN
     // Try to detect free'd pointer
-    MOZ_ASSERT(*(int*)mIdentity.get() != 0xdddddddd, "bad pointer!");
-    MOZ_ASSERT(*(int*)mIdentity.get() != 0,          "bad pointer!");
+    MOZ_ASSERT(*(int*)mIdentity.get() != (int)0xdddddddd, "bad pointer!");
+    MOZ_ASSERT(*(int*)mIdentity.get() != (int)0,          "bad pointer!");
 #endif
 
     if (IsWrapperExpired()) {
         Destroy();
     }
 
     // Note that it's not safe to touch mNativeWrapper here since it's
     // likely that it has already been finalized.
--- a/js/xpconnect/tests/unit/test_isModuleLoaded.js
+++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js
@@ -1,24 +1,24 @@
 const Cu = Components.utils;
 
 function run_test() {
   // Existing module.
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
-  Cu.import("resource://gre/modules/ISO8601DateUtils.jsm");
-  do_check_true(Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"),
+  Cu.import("resource://gre/modules/NetUtil.jsm");
+  do_check_true(Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
                 "isModuleLoaded returned true after loading that module");
-  Cu.unload("resource://gre/modules/ISO8601DateUtils.jsm");
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"),
+  Cu.unload("resource://gre/modules/NetUtil.jsm");
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
                 "isModuleLoaded returned false after unloading that module");
 
   // Non-existing module
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/non-existing-module.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
   try {
-    Cu.import("resource://gre/modules/ISO8601DateUtils1.jsm");
+    Cu.import("resource://gre/modules/non-existing-module.jsm");
     do_check_true(false,
                   "Should have thrown while trying to load a non existing file");
   } catch (ex) {}
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/non-existing-module.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
 }
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -187,16 +187,17 @@ nsLayoutStatics::Initialize()
   rv = nsTextFragment::Init();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize nsTextFragment");
     return rv;
   }
 
   nsCellMap::Init();
 
+  mozilla::SharedFontList::Initialize();
   StaticPresData::Init();
   nsCSSRendering::Init();
 
   rv = nsHTMLDNSPrefetch::Initialize();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize HTML DNS prefetch");
     return rv;
   }
@@ -413,16 +414,17 @@ nsLayoutStatics::Shutdown()
 
   nsRegion::ShutdownStatic();
 
   mozilla::EventDispatcher::Shutdown();
 
   HTMLInputElement::DestroyUploadLastDir();
 
   nsLayoutUtils::Shutdown();
+  mozilla::SharedFontList::Shutdown();
 
   nsHyphenationManager::Shutdown();
   nsDOMMutationObserver::Shutdown();
 
   DateTimeFormat::Shutdown();
 
   ContentParent::ShutDown();
 
new file mode 100644
--- /dev/null
+++ b/layout/forms/crashtests/1388230-1.html
@@ -0,0 +1,3 @@
+<cite contenteditable='true'>
+  <input type='color'/>
+</cite>
new file mode 100644
--- /dev/null
+++ b/layout/forms/crashtests/1388230-2.html
@@ -0,0 +1,1 @@
+<input contenteditable='true' type='color'>
--- a/layout/forms/crashtests/crashtests.list
+++ b/layout/forms/crashtests/crashtests.list
@@ -62,8 +62,10 @@ load 959311.html
 load 960277-2.html
 load 997709-1.html
 load 1102791.html
 load 1140216.html
 load 1182414.html
 load 1212688.html
 load 1228670.xhtml
 load 1279354.html
+load 1388230-1.html
+load 1388230-2.html
--- a/layout/forms/nsColorControlFrame.cpp
+++ b/layout/forms/nsColorControlFrame.cpp
@@ -85,27 +85,41 @@ nsColorControlFrame::AppendAnonymousCont
     aElements.AppendElement(mColorContent);
   }
 }
 
 nsresult
 nsColorControlFrame::UpdateColor()
 {
   // Get the color from the "value" property of our content; it will return the
-  // default color (through the sanitization algorithm) if there is none.
+  // default color (through the sanitization algorithm) if the value is empty.
   nsAutoString color;
   HTMLInputElement* elt = HTMLInputElement::FromContent(mContent);
   elt->GetValue(color, CallerType::System);
-  MOZ_ASSERT(!color.IsEmpty(),
-             "Content node's GetValue() should return a valid color string "
-             "(the default color, in case no valid color is set)");
 
-  // Set the background-color style property of the swatch element to this color
+  if (color.IsEmpty()) {
+    // OK, there is one case the color string might be empty -- if our content
+    // is still being created, i.e. if it has mDoneCreating==false.  In that
+    // case, we simply do nothing, because we'll be called again with a complete
+    // content node before we ever reflow or paint. Specifically: we can expect
+    // that HTMLInputElement::DoneCreatingElement() will set mDoneCreating to
+    // true (which enables sanitization) and then it'll call SetValueInternal(),
+    // which produces a nonempty color (via sanitization), and then it'll call
+    // this function here, and we'll get the nonempty default color.
+    MOZ_ASSERT(HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
+               "Content node's GetValue() should return a valid color string "
+               "by the time we've been reflowed (the default color, in case "
+               "no valid color is set)");
+    return NS_OK;
+  }
+
+  // Set the background-color CSS property of the swatch element to this color.
   return mColorContent->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
-      NS_LITERAL_STRING("background-color:") + color, true);
+                                NS_LITERAL_STRING("background-color:") + color,
+                                /* aNotify */ true);
 }
 
 nsresult
 nsColorControlFrame::AttributeChanged(int32_t  aNameSpaceID,
                                       nsIAtom* aAttribute,
                                       int32_t  aModType)
 {
   NS_ASSERTION(mColorContent, "The color div must exist");
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -467,17 +467,17 @@ BulletRenderer::CreateWebRenderCommandsF
 
   RefPtr<layers::ImageContainer> container =
     mImage->GetImageContainer(aManager, flags);
   if (!container) {
     return;
   }
 
   gfx::IntSize size;
-  Maybe<wr::ImageKey> key = aManager->CreateImageKey(aItem, container, aBuilder, aResources, aSc, size);
+  Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(aItem, container, aBuilder, aResources, aSc, size);
   if (key.isNothing()) {
     return;
   }
 
   const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(mDest, appUnitsPerDevPixel);
   wr::LayoutRect dest = aSc.ToRelativeLayoutRect(destRect);
 
@@ -493,17 +493,17 @@ BulletRenderer::CreateWebRenderCommandsF
                                                wr::DisplayListBuilder& aBuilder,
                                                wr::IpcResourceUpdateQueue& aResources,
                                                const layers::StackingContextHelper& aSc,
                                                mozilla::layers::WebRenderLayerManager* aManager,
                                                nsDisplayListBuilder* aDisplayListBuilder)
 {
   MOZ_ASSERT(IsPathType());
 
-  if (!aManager->PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder)) {
+  if (!aManager->CommandBuilder().PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder)) {
     NS_WARNING("Fail to create WebRender commands for Bullet path.");
   }
 }
 
 void
 BulletRenderer::CreateWebRenderCommandsForText(nsDisplayItem* aItem,
                                                wr::DisplayListBuilder& aBuilder,
                                                wr::IpcResourceUpdateQueue& aResources,
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -132,17 +132,17 @@ public:
     HTMLCanvasElement* element = static_cast<HTMLCanvasElement*>(mFrame->GetContent());
     switch(element->GetCurrentContextType()) {
       case CanvasContextType::Canvas2D:
       case CanvasContextType::WebGL1:
       case CanvasContextType::WebGL2:
       {
         bool isRecycled;
         RefPtr<WebRenderCanvasData> canvasData =
-          aManager->CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(this, &isRecycled);
+          aManager->CommandBuilder().CreateOrRecycleWebRenderUserData<WebRenderCanvasData>(this, &isRecycled);
         WebRenderCanvasRendererAsync* data =
           static_cast<WebRenderCanvasRendererAsync*>(canvasData->GetCanvasRenderer());
 
         if (!isRecycled) {
           nsHTMLCanvasFrame* canvasFrame = static_cast<nsHTMLCanvasFrame*>(mFrame);
           if (!canvasFrame->InitializeCanvasRenderer(aDisplayListBuilder, data)) {
             return true;
           }
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1719,17 +1719,17 @@ nsDisplayImage::CreateWebRenderCommands(
     return false;
   }
 
 
   const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
   const LayoutDeviceRect destRect(
     LayoutDeviceIntRect::FromAppUnits(GetDestRect(), factor));
   const LayerRect dest = ViewAs<LayerPixel>(destRect, PixelCastJustification::WebRenderHasUnitResolution);
-  return aManager->PushImage(this, container, aBuilder, aResources, aSc, dest);
+  return aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources, aSc, dest);
 }
 
 DrawResult
 nsImageFrame::PaintImage(gfxContext& aRenderingContext, nsPoint aPt,
                          const nsRect& aDirtyRect, imgIContainer* aImage,
                          uint32_t aFlags)
 {
   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -1440,17 +1440,17 @@ nsPluginFrame::CreateWebRenderCommands(n
 
   RefPtr<LayerManager> lm = aDisplayListBuilder->GetWidgetLayerManager();
   if (!mDidCompositeObserver || !mDidCompositeObserver->IsValid(lm)) {
     mDidCompositeObserver = MakeUnique<PluginFrameDidCompositeObserver>(mInstanceOwner, lm);
   }
   lm->AddDidCompositeObserver(mDidCompositeObserver.get());
 
   LayerRect dest(r.x, r.y, size.width, size.height);
-  return aManager->PushImage(aItem, container, aBuilder, aResources, aSc, dest);
+  return aManager->CommandBuilder().PushImage(aItem, container, aBuilder, aResources, aSc, dest);
 }
 
 
 already_AddRefed<Layer>
 nsPluginFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
                           LayerManager* aManager,
                           nsDisplayItem* aItem,
                           const ContainerLayerParameters& aContainerParameters)
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -493,17 +493,17 @@ public:
     VideoInfo::Rotation rotationDeg = element->RotationDegrees();
     IntSize scaleHint(static_cast<int32_t>(destGFXRect.Width()),
                       static_cast<int32_t>(destGFXRect.Height()));
     // scaleHint is set regardless of rotation, so swap w/h if needed.
     SwapScaleWidthHeightForRotation(scaleHint, rotationDeg);
     container->SetScaleHint(scaleHint);
 
     LayerRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width, destGFXRect.height);
-    return aManager->PushImage(this, container, aBuilder, aResources, aSc, rect);
+    return aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources, aSc, rect);
   }
 
   // It would be great if we could override GetOpaqueRegion to return nonempty here,
   // but it's probably not safe to do so in general. Video frames are
   // updated asynchronously from decoder threads, and it's possible that
   // we might have an opaque video frame when GetOpaqueRegion is called, but
   // when we come to paint, the video frame is transparent or has gone
   // away completely (e.g. because of a decoder error). The problem would
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -53,17 +53,21 @@ using namespace mozilla::image;
 static const float kMaxScaleFactor = 20.0;
 static const float kLargeOpFactor = float(M_SQRT2);
 static const float kIntegralFactor = 2.0;
 
 static void
 NormalizeDefaultFont(nsFont& aFont, float aFontSizeInflation)
 {
   if (aFont.fontlist.GetDefaultFontType() != eFamily_none) {
-    aFont.fontlist.Append(FontFamilyName(aFont.fontlist.GetDefaultFontType()));
+    nsTArray<FontFamilyName> names;
+    names.AppendElements(aFont.fontlist.GetFontlist()->mNames);
+    names.AppendElement(FontFamilyName(aFont.fontlist.GetDefaultFontType()));
+
+    aFont.fontlist.SetFontlist(Move(names));
     aFont.fontlist.SetDefaultFontType(eFamily_none);
   }
   aFont.size = NSToCoordRound(aFont.size * aFontSizeInflation);
 }
 
 // -----------------------------------------------------------------------------
 static const nsGlyphCode kNullGlyph = {{{0, 0}}, 0};
 
@@ -969,17 +973,19 @@ nsMathMLChar::SetFontFamily(nsPresContex
                             const nsGlyphCode&      aGlyphCode,
                             const FontFamilyList&   aDefaultFamilyList,
                             nsFont&                 aFont,
                             RefPtr<gfxFontGroup>* aFontGroup)
 {
   FontFamilyList glyphCodeFont;
 
   if (aGlyphCode.font) {
-    glyphCodeFont.Append(aGlyphTable->FontNameFor(aGlyphCode));
+    nsTArray<FontFamilyName> names;
+    names.AppendElement(aGlyphTable->FontNameFor(aGlyphCode));
+    glyphCodeFont.SetFontlist(Move(names));
   }
 
   const FontFamilyList& familyList =
     aGlyphCode.font ? glyphCodeFont : aDefaultFamilyList;
 
   if (!*aFontGroup || !(aFont.fontlist == familyList)) {
     nsFont font = aFont;
     font.fontlist = familyList;
@@ -989,19 +995,18 @@ nsMathMLChar::SetFontFamily(nsPresContex
     params.explicitLanguage = styleFont->mExplicitLanguage;
     params.userFontSet = aPresContext->GetUserFontSet();
     params.textPerf = aPresContext->GetTextPerfMetrics();
     RefPtr<nsFontMetrics> fm =
       aPresContext->DeviceContext()->GetMetricsFor(font, params);
     // Set the font if it is an unicode table
     // or if the same family name has been found
     gfxFont *firstFont = fm->GetThebesFontGroup()->GetFirstValidFont();
-    FontFamilyList firstFontList;
-    firstFontList.Append(
-      FontFamilyName(firstFont->GetFontEntry()->FamilyName(), eUnquotedName));
+    FontFamilyList firstFontList(
+      firstFont->GetFontEntry()->FamilyName(), eUnquotedName);
     if (aGlyphTable == &gGlyphTableList->mUnicodeTable ||
         firstFontList == familyList) {
       aFont.fontlist = familyList;
       *aFontGroup = fm->GetThebesFontGroup();
     } else {
       return false; // We did not set the font
     }
   }
@@ -1413,18 +1418,17 @@ nsMathMLChar::StretchEnumContext::EnumCa
   }
 
   // Check font family if it is not a generic one
   // We test with the kNullGlyph
   nsStyleContext *sc = context->mChar->mStyleContext;
   nsFont font = sc->StyleFont()->mFont;
   NormalizeDefaultFont(font, context->mFontSizeInflation);
   RefPtr<gfxFontGroup> fontGroup;
-  FontFamilyList family;
-  family.Append(unquotedFamilyName);
+  FontFamilyList family(unquotedFamilyName);
   if (!aGeneric && !context->mChar->SetFontFamily(context->mPresContext,
                                                   nullptr, kNullGlyph, family,
                                                   font, &fontGroup))
      return true; // Could not set the family
 
   // Determine the glyph table to use for this font.
   nsAutoPtr<nsOpenTypeTable> openTypeTable;
   nsGlyphTable* glyphTable;
@@ -1464,40 +1468,46 @@ nsMathMLChar::StretchEnumContext::EnumCa
      (context->mTryParts && context->TryParts(glyphTable,
                                               &fontGroup,
                                               familyList)))
     return false; // no need to continue
 
   return true; // true means continue
 }
 
+static void
+AppendFallbacks(nsTArray<FontFamilyName>& aNames,
+                const nsTArray<nsString>& aFallbacks)
+{
+  for (const nsString& fallback : aFallbacks) {
+    aNames.AppendElement(FontFamilyName(fallback, eUnquotedName));
+  }
+}
+
 // insert math fallback families just before the first generic or at the end
 // when no generic present
 static void
 InsertMathFallbacks(FontFamilyList& aFamilyList,
                     nsTArray<nsString>& aFallbacks)
 {
-  FontFamilyList aMergedList;
+  nsTArray<FontFamilyName> mergedList;
 
   bool inserted = false;
-  const nsTArray<FontFamilyName>& fontlist = aFamilyList.GetFontlist();
-  uint32_t i, num = fontlist.Length();
-  for (i = 0; i < num; i++) {
-    const FontFamilyName& name = fontlist[i];
+  for (const FontFamilyName& name : aFamilyList.GetFontlist()->mNames) {
     if (!inserted && name.IsGeneric()) {
       inserted = true;
-      aMergedList.Append(aFallbacks);
+      AppendFallbacks(mergedList, aFallbacks);
     }
-    aMergedList.Append(name);
+    mergedList.AppendElement(name);
   }
 
   if (!inserted) {
-    aMergedList.Append(aFallbacks);
+    AppendFallbacks(mergedList, aFallbacks);
   }
-  aFamilyList = aMergedList;
+  aFamilyList.SetFontlist(Move(mergedList));
 }
 
 nsresult
 nsMathMLChar::StretchInternal(nsIFrame*                aForFrame,
                               DrawTarget*              aDrawTarget,
                               float                    aFontSizeInflation,
                               nsStretchDirection&      aStretchDirection,
                               const nsBoundingMetrics& aContainerSize,
@@ -1651,17 +1661,17 @@ nsMathMLChar::StretchInternal(nsIFrame* 
            NS_ConvertUTF16toUTF8(fontlistStr).get(), mData[0], mData[0]&0x00FF);
 #endif
     StretchEnumContext enumData(this, presContext, aDrawTarget,
                                 aFontSizeInflation,
                                 aStretchDirection, targetSize, aStretchHint,
                                 aDesiredStretchSize, font.fontlist, glyphFound);
     enumData.mTryParts = !largeopOnly;
 
-    const nsTArray<FontFamilyName>& fontlist = font.fontlist.GetFontlist();
+    const nsTArray<FontFamilyName>& fontlist = font.fontlist.GetFontlist()->mNames;
     uint32_t i, num = fontlist.Length();
     bool next = true;
     for (i = 0; i < num && next; i++) {
       const FontFamilyName& name = fontlist[i];
       next = StretchEnumContext::EnumCallback(name, name.IsGeneric(), &enumData);
     }
   }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -5084,17 +5084,18 @@ nsDisplayBorder::CreateBorderImageWebRen
 
       RefPtr<imgIContainer> img = mBorderImageRenderer->mImageRenderer.GetImage();
       RefPtr<layers::ImageContainer> container = img->GetImageContainer(aManager, flags);
       if (!container) {
         return;
       }
 
       gfx::IntSize size;
-      Maybe<wr::ImageKey> key = aManager->CreateImageKey(this, container, aBuilder, aResources, aSc, size);
+      Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(this, container, aBuilder,
+                                                                          aResources, aSc, size);
       if (key.isNothing()) {
         return;
       }
 
       aBuilder.PushBorderImage(dest,
                                clip,
                                !BackfaceIsHidden(),
                                wr::ToBorderWidths(widths[0], widths[1], widths[2], widths[3]),
@@ -5912,21 +5913,21 @@ nsDisplayWrapList::SetReferenceFrame(con
 
 bool
 nsDisplayWrapList::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                            mozilla::wr::IpcResourceUpdateQueue& aResources,
                                            const StackingContextHelper& aSc,
                                            mozilla::layers::WebRenderLayerManager* aManager,
                                            nsDisplayListBuilder* aDisplayListBuilder)
 {
-  aManager->CreateWebRenderCommandsFromDisplayList(GetChildren(),
-                                                   aDisplayListBuilder,
-                                                   aSc,
-                                                   aBuilder,
-                                                   aResources);
+  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(GetChildren(),
+                                                                    aDisplayListBuilder,
+                                                                    aSc,
+                                                                    aBuilder,
+                                                                    aResources);
   return true;
 }
 
 static nsresult
 WrapDisplayList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                 nsDisplayList* aList, nsDisplayWrapper* aWrapper) {
   if (!aList->GetTop())
     return NS_OK;
@@ -6247,17 +6248,17 @@ bool
 nsDisplayOpacity::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                           mozilla::wr::IpcResourceUpdateQueue& aResources,
                                           const StackingContextHelper& aSc,
                                           mozilla::layers::WebRenderLayerManager* aManager,
                                           nsDisplayListBuilder* aDisplayListBuilder)
 {
   float* opacityForSC = &mOpacity;
 
-  RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
+  RefPtr<WebRenderAnimationData> animationData = aManager->CommandBuilder().CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
   AnimationInfo& animationInfo = animationData->GetAnimationInfo();
   AddAnimationsForProperty(Frame(), aDisplayListBuilder,
                            this, eCSSProperty_opacity,
                            animationInfo, false);
   animationInfo.StartPendingAnimations(aManager->GetAnimationReadyTime());
 
   // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
   // are no active animations.
@@ -6285,21 +6286,21 @@ nsDisplayOpacity::CreateWebRenderCommand
                            &mList,
                            nullptr,
                            animationsId,
                            opacityForSC,
                            nullptr,
                            nullptr,
                            filters);
 
-  aManager->CreateWebRenderCommandsFromDisplayList(&mList,
-                                                   aDisplayListBuilder,
-                                                   sc,
-                                                   aBuilder,
-                                                   aResources);
+  aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(&mList,
+                                                                    aDisplayListBuilder,
+                                                                    sc,
+                                                                    aBuilder,
+                                                                    aResources);
   return true;
 }
 
 nsDisplayBlendMode::nsDisplayBlendMode(nsDisplayListBuilder* aBuilder,
                                              nsIFrame* aFrame, nsDisplayList* aList,
                                              uint8_t aBlendMode,
                                              const ActiveScrolledRoot* aActiveScrolledRoot,
                                              uint32_t aIndex)
@@ -6567,17 +6568,17 @@ nsDisplayOwnLayer::CreateWebRenderComman
       mThumbData.mDirection == ScrollDirection::NONE) {
     return nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, aSc,
                                                       aManager, aDisplayListBuilder);
   }
 
   // APZ is enabled and this is a scroll thumb, so we need to create and
   // set an animation id. That way APZ can move this scrollthumb around as
   // needed.
-  RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
+  RefPtr<WebRenderAnimationData> animationData = aManager->CommandBuilder().CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
   AnimationInfo& animationInfo = animationData->GetAnimationInfo();
   animationInfo.EnsureAnimationsId();
   mWrAnimationId = animationInfo.GetCompositorAnimationsId();
 
   StackingContextHelper sc(aSc, aBuilder, aDisplayListBuilder, this,
                            &mList, nullptr, mWrAnimationId, nullptr, nullptr);
 
   nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc,
@@ -8015,17 +8016,17 @@ nsDisplayTransform::CreateWebRenderComma
   gfx::Matrix4x4* transformForSC = &newTransformMatrix;
   if (newTransformMatrix.IsIdentity()) {
     // If the transform is an identity transform, strip it out so that WR
     // doesn't turn this stacking context into a reference frame, as it
     // affects positioning. Bug 1345577 tracks a better fix.
     transformForSC = nullptr;
   }
 
-  RefPtr<WebRenderAnimationData> animationData = aManager->CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
+  RefPtr<WebRenderAnimationData> animationData = aManager->CommandBuilder().CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(this);
 
   AnimationInfo& animationInfo = animationData->GetAnimationInfo();
   AddAnimationsForProperty(Frame(), aDisplayListBuilder,
                            this, eCSSProperty_transform,
                            animationInfo, false);
   animationInfo.StartPendingAnimations(aManager->GetAnimationReadyTime());
 
   // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
@@ -9163,18 +9164,19 @@ nsDisplayMask::CreateWebRenderCommands(m
                                        nsDisplayListBuilder* aDisplayListBuilder)
 {
   bool snap;
   float appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
   nsRect displayBound = GetBounds(aDisplayListBuilder, &snap);
   LayerRect bounds = ViewAs<LayerPixel>(LayoutDeviceRect::FromAppUnits(displayBound, appUnitsPerDevPixel),
                                         PixelCastJustification::WebRenderHasUnitResolution);
 
-  Maybe<wr::WrImageMask> mask = aManager->BuildWrMaskImage(this, aBuilder, aResources,
-                                                           aSc, aDisplayListBuilder, bounds);
+  Maybe<wr::WrImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage(this, aBuilder, aResources,
+                                                                            aSc, aDisplayListBuilder,
+                                                                            bounds);
   if (mask) {
     wr::WrClipId clipId = aBuilder.DefineClip(
         aSc.ToRelativeLayoutRect(bounds), nullptr, mask.ptr());
     // Don't record this clip push in aBuilder's internal clip stack, because
     // otherwise any nested ScrollingLayersHelper instances that are created
     // will get confused about which clips are pushed.
     aBuilder.PushClip(clipId, /*aMask*/ true);
   }
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -9,16 +9,17 @@
 #include "nsImageRenderer.h"
 
 #include "mozilla/webrender/WebRenderAPI.h"
 
 #include "gfxContext.h"
 #include "gfxDrawable.h"
 #include "ImageOps.h"
 #include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
 #include "nsContentUtils.h"
 #include "nsCSSRendering.h"
 #include "nsCSSRenderingGradients.h"
 #include "nsIFrame.h"
 #include "nsStyleStructInlines.h"
 #include "nsSVGDisplayableFrame.h"
 #include "SVGObserverUtils.h"
 #include "nsSVGIntegrationUtils.h"
@@ -629,17 +630,18 @@ nsImageRenderer::BuildWebRenderDisplayIt
       RefPtr<layers::ImageContainer> container =
         mImageContainer->GetImageContainer(aManager, containerFlags);
       if (!container) {
         NS_WARNING("Failed to get image container");
         return DrawResult::NOT_READY;
       }
 
       gfx::IntSize size;
-      Maybe<wr::ImageKey> key = aManager->CreateImageKey(aItem, container, aBuilder, aResources, aSc, size);
+      Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(aItem, container, aBuilder,
+                                                                          aResources, aSc, size);
 
       if (key.isNothing()) {
         return DrawResult::BAD_IMAGE;
       }
 
       const int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
       LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
           aDest, appUnitsPerDevPixel);
--- a/layout/reftests/ogg-video/reftest.list
+++ b/layout/reftests/ogg-video/reftest.list
@@ -1,10 +1,10 @@
 # NOTE: bug 1084564 covers "fails"/"skip" annotations for android below:
-fuzzy(255,5000) == 444-1.html 444-1-ref.html
+fuzzy(255,5000) random-if(webrender) == 444-1.html 444-1-ref.html
 fails-if(Android) HTTP(..) == aspect-ratio-1a.xhtml aspect-ratio-1-ref.html
 fails-if(Android) HTTP(..) == aspect-ratio-1b.xhtml aspect-ratio-1-ref.html
 fails-if(Android) skip-if(gtkWidget) HTTP(..) == aspect-ratio-2a.xhtml aspect-ratio-2-ref.html
 fails-if(Android) skip-if(gtkWidget) HTTP(..) == aspect-ratio-2b.xhtml aspect-ratio-2-ref.html
 HTTP(..) == aspect-ratio-3a.xhtml aspect-ratio-3-ref.xhtml
 HTTP(..) == aspect-ratio-3b.xhtml aspect-ratio-3-ref.xhtml
 fails-if(Android) random-if(layersGPUAccelerated) == encoded-aspect-ratio-1.html encoded-aspect-ratio-1-ref.html
 fails-if(Android) HTTP(..) == basic-1.xhtml basic-1-ref.html
--- a/layout/reftests/transform-3d/reftest.list
+++ b/layout/reftests/transform-3d/reftest.list
@@ -49,17 +49,17 @@ fuzzy-if(winWidget&&!layersGPUAccelerate
 == perspective-zindex-2.html green-rect.html
 fails-if(webrender) != sorting-1a.html sorting-1-ref.html
 # Parallel planes, different z depth
 fails-if(webrender) == sorting-2a.html sorting-2-ref.html
 # Parallel planes, same z depth (shouldn't be sorted!)
 == sorting-2b.html sorting-2-ref.html
 == sorting-3a.html green-rect.html
 # Different, but equivalent (for the given transform) transform origins
-fuzzy-if(webrender,0-1,0-1) == rotatex-transformorigin-1a.html rotatex-transformorigin-1-ref.html
+fuzzy-if(webrender,0-1,0-2) == rotatex-transformorigin-1a.html rotatex-transformorigin-1-ref.html
 fuzzy-if((gtkWidget&&layersOMTC)||(winWidget&&!layersGPUAccelerated),1,86) == overflow-hidden-1a.html overflow-hidden-1-ref.html
 fails-if(webrender) == transform-style-flat-1a.html transform-style-flat-1-ref.html
 == willchange-containing-block.html?willchange willchange-containing-block.html?ref
 != willchange-containing-block.html?willchange willchange-containing-block.html?noblock
 fuzzy-if(winWidget&&!layersGPUAccelerated,1,606) == scroll-perspective-1.html scroll-perspective-1-ref.html
 # Bugs
 fails-if(!layersGPUAccelerated) fails-if(webrender) == 1035611-1.html 1035611-1-ref.html # Bug 1072898 for !layersGPUAccelerated failures
 fails-if(webrender) != 1157984-1.html about:blank # Bug 1157984
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -194,17 +194,17 @@ FontFaceSet::RemoveDOMContentLoadedListe
     mDocument->RemoveSystemEventListener(NS_LITERAL_STRING("DOMContentLoaded"),
                                          this, false);
   }
 }
 
 void
 FontFaceSet::ParseFontShorthandForMatching(
                             const nsAString& aFont,
-                            RefPtr<FontFamilyListRefCnt>& aFamilyList,
+                            RefPtr<SharedFontList>& aFamilyList,
                             uint32_t& aWeight,
                             int32_t& aStretch,
                             uint8_t& aStyle,
                             ErrorResult& aRv)
 {
   // Parse aFont as a 'font' property value.
   RefPtr<Declaration> declaration = new Declaration;
   declaration->InitializeEmpty();
@@ -236,18 +236,17 @@ FontFaceSet::ParseFontShorthandForMatchi
 
   const nsCSSValue* family = data->ValueFor(eCSSProperty_font_family);
   if (family->GetUnit() != eCSSUnit_FontFamilyList) {
     // We got inherit, initial, unset, a system font, or a token stream.
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
-  aFamilyList =
-    static_cast<FontFamilyListRefCnt*>(family->GetFontFamilyListValue());
+  aFamilyList = family->GetFontFamilyListValue();
 
   int32_t weight = data->ValueFor(eCSSProperty_font_weight)->GetIntValue();
 
   // Resolve relative font weights against the initial of font-weight
   // (normal, which is equivalent to 400).
   if (weight == NS_STYLE_FONT_WEIGHT_BOLDER) {
     weight = NS_FONT_WEIGHT_BOLD;
   } else if (weight == NS_STYLE_FONT_WEIGHT_LIGHTER) {
@@ -277,17 +276,17 @@ HasAnyCharacterInUnicodeRange(gfxUserFon
 }
 
 void
 FontFaceSet::FindMatchingFontFaces(const nsAString& aFont,
                                    const nsAString& aText,
                                    nsTArray<FontFace*>& aFontFaces,
                                    ErrorResult& aRv)
 {
-  RefPtr<FontFamilyListRefCnt> familyList;
+  RefPtr<SharedFontList> familyList;
   uint32_t weight;
   int32_t stretch;
   uint8_t italicStyle;
   ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle,
                                 aRv);
   if (aRv.Failed()) {
     return;
   }
@@ -299,17 +298,17 @@ FontFaceSet::FindMatchingFontFaces(const
 
   nsTArray<FontFaceRecord>* arrays[2];
   arrays[0] = &mNonRuleFaces;
   arrays[1] = &mRuleFaces;
 
   // Set of FontFaces that we want to return.
   nsTHashtable<nsPtrHashKey<FontFace>> matchingFaces;
 
-  for (const FontFamilyName& fontFamilyName : familyList->GetFontlist()) {
+  for (const FontFamilyName& fontFamilyName : familyList->mNames) {
     RefPtr<gfxFontFamily> family =
       mUserFontSet->LookupFamily(fontFamilyName.mName);
 
     if (!family) {
       continue;
     }
 
     AutoTArray<gfxFontEntry*,4> entries;
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -17,19 +17,17 @@ struct gfxFontFaceSrc;
 class gfxFontSrcPrincipal;
 class gfxUserFontEntry;
 class nsFontFaceLoader;
 class nsIPrincipal;
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 class PostTraversalTask;
-namespace css {
-class FontFamilyListRefCnt;
-} // namespace css
+class SharedFontList;
 namespace dom {
 class FontFace;
 class Promise;
 } // namespace dom
 } // namespace mozilla
 
 namespace mozilla {
 namespace dom {
@@ -310,17 +308,17 @@ private:
    */
   bool HasLoadingFontFaces();
 
   // Helper function for HasLoadingFontFaces.
   void UpdateHasLoadingFontFaces();
 
   void ParseFontShorthandForMatching(
               const nsAString& aFont,
-              RefPtr<mozilla::css::FontFamilyListRefCnt>& aFamilyList,
+              RefPtr<SharedFontList>& aFamilyList,
               uint32_t& aWeight,
               int32_t& aStretch,
               uint8_t& aStyle,
               ErrorResult& aRv);
   void FindMatchingFontFaces(const nsAString& aFont,
                              const nsAString& aText,
                              nsTArray<FontFace*>& aFontFaces,
                              mozilla::ErrorResult& aRv);
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -124,16 +124,23 @@ SERVO_BINDING_FUNC(Servo_StyleSet_GetCou
 SERVO_BINDING_FUNC(Servo_StyleSet_BuildFontFeatureValueSet,
                    gfxFontFeatureValueSet*,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_ResolveForDeclarations,
                    ServoStyleContextStrong,
                    RawServoStyleSetBorrowed set,
                    ServoStyleContextBorrowedOrNull parent_style,
                    RawServoDeclarationBlockBorrowed declarations)
+SERVO_BINDING_FUNC(Servo_SelectorList_Drop, void,
+                   RawServoSelectorList* selector_list)
+SERVO_BINDING_FUNC(Servo_SelectorList_Parse,
+                   RawServoSelectorList*,
+                   const nsACString* selector_list)
+SERVO_BINDING_FUNC(Servo_SelectorList_Matches, bool,
+                   RawGeckoElementBorrowed, RawServoSelectorListBorrowed)
 SERVO_BINDING_FUNC(Servo_StyleSet_AddSizeOfExcludingThis, void,
                    mozilla::MallocSizeOf malloc_size_of,
                    mozilla::MallocSizeOf malloc_enclosing_size_of,
                    mozilla::ServoStyleSetSizes* sizes,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_UACache_AddSizeOf, void,
                    mozilla::MallocSizeOf malloc_size_of,
                    mozilla::MallocSizeOf malloc_enclosing_size_of,
--- a/layout/style/ServoBindingTypes.h
+++ b/layout/style/ServoBindingTypes.h
@@ -11,16 +11,17 @@
 #include "mozilla/ServoTypes.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/Types.h"
 #include "nsCSSPropertyID.h"
 #include "nsStyleAutoArray.h"
 #include "nsTArray.h"
 
 struct RawServoStyleSet;
+struct RawServoSelectorList;
 struct RawServoAnimationValueMap;
 struct RustString;
 
 #define SERVO_ARC_TYPE(name_, type_) struct type_;
 #include "mozilla/ServoArcTypeList.h"
 #undef SERVO_ARC_TYPE
 
 namespace mozilla {
@@ -140,16 +141,17 @@ DECL_OWNED_REF_TYPE_FOR(StyleChildrenIte
 DECL_OWNED_REF_TYPE_FOR(ServoElementSnapshot)
 DECL_OWNED_REF_TYPE_FOR(RawServoAnimationValueMap)
 
 // We don't use BorrowedMut because the nodes may alias
 // Servo itself doesn't directly read or mutate these;
 // it only asks Gecko to do so. In case we wish to in
 // the future, we should ensure that things being mutated
 // are protected from noalias violations by a cell type
+DECL_BORROWED_REF_TYPE_FOR(RawServoSelectorList)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoNode)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoNode)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoElement)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoElement)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoDocument)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoXBLBinding)
 DECL_NULLABLE_BORROWED_REF_TYPE_FOR(RawGeckoXBLBinding)
@@ -172,16 +174,18 @@ DECL_BORROWED_REF_TYPE_FOR(nsTimingFunct
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoFontFaceRuleList)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoAnimationPropertySegment)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoComputedTiming)
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoServoStyleRuleList)
 DECL_BORROWED_MUT_REF_TYPE_FOR(nsCSSPropertyIDSet)
 DECL_BORROWED_REF_TYPE_FOR(RawGeckoCSSPropertyIDList)
 DECL_BORROWED_REF_TYPE_FOR(nsXBLBinding)
 DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoStyleChildrenIterator)
+DECL_OWNED_REF_TYPE_FOR(RawServoSelectorList)
+DECL_BORROWED_REF_TYPE_FOR(RawServoSelectorList)
 
 #undef DECL_ARC_REF_TYPE_FOR
 #undef DECL_OWNED_REF_TYPE_FOR
 #undef DECL_NULLABLE_OWNED_REF_TYPE_FOR
 #undef DECL_BORROWED_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_REF_TYPE_FOR
 #undef DECL_BORROWED_MUT_REF_TYPE_FOR
 #undef DECL_NULLABLE_BORROWED_MUT_REF_TYPE_FOR
@@ -214,12 +218,13 @@ DECL_BORROWED_MUT_REF_TYPE_FOR(RawGeckoS
     void operator()(type_* aPtr) const                      \
     {                                                       \
       Servo_##name_##_Drop(aPtr);                           \
     }                                                       \
   };                                                        \
   }
 
 DEFINE_BOXED_TYPE(StyleSet, RawServoStyleSet);
+DEFINE_BOXED_TYPE(SelectorList, RawServoSelectorList);
 
 #undef DEFINE_BOXED_TYPE
 
 #endif // mozilla_ServoBindingTypes_h
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -85,46 +85,44 @@ using namespace mozilla::dom;
     RefPtr<type_> result;            \
     result.swap(mPtr);               \
     return result.forget();          \
   }
 #include "mozilla/ServoArcTypeList.h"
 SERVO_ARC_TYPE(StyleContext, ServoStyleContext)
 #undef SERVO_ARC_TYPE
 
-static Mutex* sServoFontMetricsLock = nullptr;
-static Mutex* sServoWidgetLock = nullptr;
-static RWLock* sServoLangFontPrefsLock = nullptr;
+static RWLock* sServoFFILock = nullptr;
 
 static
 const nsFont*
 ThreadSafeGetDefaultFontHelper(const nsPresContext* aPresContext,
                                nsIAtom* aLanguage, uint8_t aGenericId)
 {
   bool needsCache = false;
   const nsFont* retval;
 
   {
-    AutoReadLock guard(*sServoLangFontPrefsLock);
+    AutoReadLock guard(*sServoFFILock);
     retval = aPresContext->GetDefaultFont(aGenericId, aLanguage, &needsCache);
   }
   if (!needsCache) {
     return retval;
   }
   {
-    AutoWriteLock guard(*sServoLangFontPrefsLock);
+    AutoWriteLock guard(*sServoFFILock);
   retval = aPresContext->GetDefaultFont(aGenericId, aLanguage, nullptr);
   }
   return retval;
 }
 
 void
 AssertIsMainThreadOrServoLangFontPrefsCacheLocked()
 {
-  MOZ_ASSERT(NS_IsMainThread() || sServoLangFontPrefsLock->LockedForWritingByCurrentThread());
+  MOZ_ASSERT(NS_IsMainThread() || sServoFFILock->LockedForWritingByCurrentThread());
 }
 
 bool
 Gecko_IsInDocument(RawGeckoNodeBorrowed aNode)
 {
   return aNode->IsInComposedDoc();
 }
 
@@ -853,17 +851,17 @@ Gecko_IsDocumentBody(RawGeckoElementBorr
 
 nscolor
 Gecko_GetLookAndFeelSystemColor(int32_t aId,
                                 RawGeckoPresContextBorrowed aPresContext)
 {
   bool useStandinsForNativeColors = aPresContext && !aPresContext->IsChrome();
   nscolor result;
   LookAndFeel::ColorID colorId = static_cast<LookAndFeel::ColorID>(aId);
-  MutexAutoLock guard(*sServoWidgetLock);
+  AutoWriteLock guard(*sServoFFILock);
   LookAndFeel::GetColor(colorId, useStandinsForNativeColors, &result);
   return result;
 }
 
 bool
 Gecko_MatchStringArgPseudo(RawGeckoElementBorrowed aElement,
                            CSSPseudoClassType aType,
                            const char16_t* aIdent)
@@ -1251,38 +1249,61 @@ Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* 
 
 void
 Gecko_EnsureMozBorderColors(nsStyleBorder* aBorder)
 {
   aBorder->EnsureBorderColors();
 }
 
 void
-Gecko_FontFamilyList_Clear(FontFamilyList* aList) {
-  aList->Clear();
-}
-
-void
-Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted)
+Gecko_nsTArray_FontFamilyName_AppendNamed(nsTArray<FontFamilyName>* aNames,
+                                          nsIAtom* aName,
+                                          bool aQuoted)
 {
   FontFamilyName family;
   aName->ToString(family.mName);
   if (aQuoted) {
     family.mType = eFamily_named_quoted;
   }
 
-  aList->Append(family);
+  aNames->AppendElement(family);
 }
 
 void
-Gecko_FontFamilyList_AppendGeneric(FontFamilyList* aList, FontFamilyType aType)
+Gecko_nsTArray_FontFamilyName_AppendGeneric(nsTArray<FontFamilyName>* aNames,
+                                            FontFamilyType aType)
+{
+  aNames->AppendElement(FontFamilyName(aType));
+}
+
+SharedFontList*
+Gecko_SharedFontList_Create()
 {
-  aList->Append(FontFamilyName(aType));
+  RefPtr<SharedFontList> fontlist = new SharedFontList();
+  return fontlist.forget().take();
 }
 
+MOZ_DEFINE_MALLOC_SIZE_OF(GeckoSharedFontListMallocSizeOf)
+
+size_t
+Gecko_SharedFontList_SizeOfIncludingThisIfUnshared(SharedFontList* aFontlist)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return aFontlist->SizeOfIncludingThisIfUnshared(GeckoSharedFontListMallocSizeOf);
+}
+
+size_t
+Gecko_SharedFontList_SizeOfIncludingThis(SharedFontList* aFontlist)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return aFontlist->SizeOfIncludingThis(GeckoSharedFontListMallocSizeOf);
+}
+
+NS_IMPL_THREADSAFE_FFI_REFCOUNTING(mozilla::SharedFontList, SharedFontList);
+
 void
 Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src)
 {
   dst->fontlist = src->fontlist;
 }
 
 void
 Gecko_nsFont_InitSystem(nsFont* aDest, int32_t aFontId,
@@ -1297,17 +1318,17 @@ Gecko_nsFont_InitSystem(nsFont* aDest, i
   // itself, so this will do.
   nsFont* system = new (aDest) nsFont(*defaultVariableFont);
 
   MOZ_RELEASE_ASSERT(system);
 
   *aDest = *defaultVariableFont;
   LookAndFeel::FontID fontID = static_cast<LookAndFeel::FontID>(aFontId);
 
-  MutexAutoLock lock(*sServoFontMetricsLock);
+  AutoWriteLock guard(*sServoFFILock);
   nsRuleNode::ComputeSystemFont(aDest, fontID, aPresContext, defaultVariableFont);
 }
 
 void
 Gecko_nsFont_Destroy(nsFont* aDest)
 {
   aDest->~nsFont();
 }
@@ -2346,22 +2367,22 @@ Gecko_nsStyleFont_PrefillDefaultForGener
 void
 Gecko_nsStyleFont_FixupMinFontSize(nsStyleFont* aFont,
                                    RawGeckoPresContextBorrowed aPresContext)
 {
   nscoord minFontSize;
   bool needsCache = false;
 
   {
-    AutoReadLock guard(*sServoLangFontPrefsLock);
+    AutoReadLock guard(*sServoFFILock);
     minFontSize = aPresContext->MinFontSize(aFont->mLanguage, &needsCache);
   }
 
   if (needsCache) {
-    AutoWriteLock guard(*sServoLangFontPrefsLock);
+    AutoWriteLock guard(*sServoFFILock);
     minFontSize = aPresContext->MinFontSize(aFont->mLanguage, nullptr);
   }
 
   nsRuleNode::ApplyMinFontSize(aFont, aPresContext, minFontSize);
 }
 
 void
 FontSizePrefs::CopyFrom(const LangGroupFontPrefs& prefs)
@@ -2420,58 +2441,52 @@ void
 InitializeServo()
 {
   URLExtraData::InitDummy();
   Servo_Initialize(URLExtraData::Dummy());
 
   gUACacheReporter = new UACacheReporter();
   RegisterWeakMemoryReporter(gUACacheReporter);
 
-  sServoFontMetricsLock = new Mutex("Gecko_GetFontMetrics");
-  sServoWidgetLock = new Mutex("Servo::WidgetLock");
-  sServoLangFontPrefsLock = new RWLock("nsPresContext::GetDefaultFont");
+  sServoFFILock = new RWLock("Servo::FFILock");
 }
 
 void
 ShutdownServo()
 {
-  MOZ_ASSERT(sServoFontMetricsLock);
-  MOZ_ASSERT(sServoWidgetLock);
-  MOZ_ASSERT(sServoLangFontPrefsLock);
+  MOZ_ASSERT(sServoFFILock);
 
   UnregisterWeakMemoryReporter(gUACacheReporter);
   gUACacheReporter = nullptr;
 
-  delete sServoFontMetricsLock;
-  delete sServoWidgetLock;
-  delete sServoLangFontPrefsLock;
+  delete sServoFFILock;
   Servo_Shutdown();
 }
 
 namespace mozilla {
 
 void
 AssertIsMainThreadOrServoFontMetricsLocked()
 {
   if (!NS_IsMainThread()) {
-    MOZ_ASSERT(sServoFontMetricsLock);
-    sServoFontMetricsLock->AssertCurrentThreadOwns();
+    MOZ_ASSERT(sServoFFILock &&
+               sServoFFILock->LockedForWritingByCurrentThread());
   }
 }
 
 } // namespace mozilla
 
 GeckoFontMetrics
 Gecko_GetFontMetrics(RawGeckoPresContextBorrowed aPresContext,
                      bool aIsVertical,
                      const nsStyleFont* aFont,
                      nscoord aFontSize,
                      bool aUseUserFontSet)
 {
-  MutexAutoLock lock(*sServoFontMetricsLock);
+  AutoWriteLock guard(*sServoFFILock);
   GeckoFontMetrics ret;
 
   // Getting font metrics can require some main thread only work to be
   // done, such as work that needs to touch non-threadsafe refcounted
   // objects (like the DOM FontFace/FontFaceSet objects), network loads, etc.
   //
   // To handle this work, font code checks whether we are in a Servo traversal
   // and if so, appends PostTraversalTasks to the current ServoStyleSet
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -30,17 +30,19 @@
  */
 
 class nsIAtom;
 class nsIPrincipal;
 class nsIURI;
 struct nsFont;
 namespace mozilla {
   class FontFamilyList;
+  struct FontFamilyName;
   enum FontFamilyType : uint32_t;
+  class SharedFontList;
   enum class CSSPseudoElementType : uint8_t;
   struct Keyframe;
   enum Side;
   struct StyleTransition;
   namespace css {
     struct URLValue;
     struct ImageValue;
     class LoaderReusableStyleSheets;
@@ -51,18 +53,20 @@ namespace mozilla {
   enum class UpdateAnimationsTasks : uint8_t;
   struct LangGroupFontPrefs;
   class SeenPtrs;
   class ServoStyleContext;
   class ServoStyleSheet;
   class ServoElementSnapshotTable;
 }
 using mozilla::FontFamilyList;
+using mozilla::FontFamilyName;
 using mozilla::FontFamilyType;
 using mozilla::ServoElementSnapshot;
+using mozilla::SharedFontList;
 class nsCSSCounterStyleRule;
 class nsCSSFontFaceRule;
 struct nsMediaFeature;
 struct nsStyleList;
 struct nsStyleImage;
 struct nsStyleGradientStop;
 class nsStyleGradient;
 class nsStyleCoord;
@@ -279,20 +283,24 @@ void Gecko_ReleaseAtom(nsIAtom* aAtom);
 const uint16_t* Gecko_GetAtomAsUTF16(nsIAtom* aAtom, uint32_t* aLength);
 bool Gecko_AtomEqualsUTF8(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 bool Gecko_AtomEqualsUTF8IgnoreCase(nsIAtom* aAtom, const char* aString, uint32_t aLength);
 
 // Border style
 void Gecko_EnsureMozBorderColors(nsStyleBorder* aBorder);
 
 // Font style
-void Gecko_FontFamilyList_Clear(FontFamilyList* aList);
-void Gecko_FontFamilyList_AppendNamed(FontFamilyList* aList, nsIAtom* aName, bool aQuoted);
-void Gecko_FontFamilyList_AppendGeneric(FontFamilyList* list, FontFamilyType familyType);
 void Gecko_CopyFontFamilyFrom(nsFont* dst, const nsFont* src);
+void Gecko_nsTArray_FontFamilyName_AppendNamed(nsTArray<FontFamilyName>* aNames, nsIAtom* aName, bool aQuoted);
+void Gecko_nsTArray_FontFamilyName_AppendGeneric(nsTArray<FontFamilyName>* aNames, FontFamilyType aType);
+// Returns an already-AddRefed SharedFontList with an empty mNames array.
+SharedFontList* Gecko_SharedFontList_Create();
+size_t Gecko_SharedFontList_SizeOfIncludingThis(SharedFontList* fontlist);
+size_t Gecko_SharedFontList_SizeOfIncludingThisIfUnshared(SharedFontList* fontlist);
+NS_DECL_THREADSAFE_FFI_REFCOUNTING(mozilla::SharedFontList, SharedFontList);
 // will not run destructors on dst, give it uninitialized memory
 // font_id is LookAndFeel::FontID
 void Gecko_nsFont_InitSystem(nsFont* dst, int32_t font_id,
                              const nsStyleFont* font, RawGeckoPresContextBorrowed pres_context);
 void Gecko_nsFont_Destroy(nsFont* dst);
 
 // The gfxFontFeatureValueSet returned from this function has zero reference.
 gfxFontFeatureValueSet* Gecko_ConstructFontFeatureValueSet();
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -172,17 +172,16 @@ whitelist-types = [
     ".*ThreadSafe.*Holder",
     "AnonymousContent",
     "AudioContext",
     "CapturingContentInfo",
     "DefaultDelete",
     "DOMIntersectionObserverEntry",
     "Element",
     "FontFamilyList",
-    "FontFamilyListRefCnt",
     "FontFamilyName",
     "FontFamilyType",
     "FontSizePrefs",
     "FragmentOrURL",
     "FrameRequestCallback",
     "GeckoParserExtraData",
     "GeckoFontMetrics",
     "gfxAlternateValue",
@@ -416,24 +415,24 @@ structs-types = [
     "RawServoStyleRule",
     "RawGeckoPresContext",
     "RawGeckoPresContextOwned",
     "RawGeckoStyleAnimationList",
     "RawGeckoStyleChildrenIterator",
     "RawGeckoServoStyleRuleList",
     "RawGeckoURLExtraData",
     "RawGeckoXBLBinding",
+    "RawServoSelectorList",
     "RefPtr",
     "RustString",
     "CSSPseudoClassType",
     "CSSPseudoElementType",
     "ServoTraversalFlags",
     "ComputedTimingFunction_BeforeFlag",
     "CounterStylePtr",
-    "FontFamilyList",
     "FontFamilyType",
     "FontSizePrefs",
     "GeckoFontMetrics",
     "IterationCompositeOperation",
     "Keyframe",
     "PropertyValuePair",
     "SeenPtrs",
     "ServoBundledURI",
@@ -519,22 +518,25 @@ structs-types = [
     "EffectCompositor_CascadeLevel",
     "UpdateAnimationsTasks",
     "ParsingMode",
     "InheritTarget",
     "URLMatchingFunction",
     "StyleRuleInclusion",
     "nsStyleTransformMatrix::MatrixTransformOperator",
     "RawGeckoGfxMatrix4x4",
+    "FontFamilyName",
+    "mozilla::SharedFontList",
 ]
 array-types = [
     { cpp-type = "uintptr_t", rust-type = "usize" },
 ]
 servo-owned-types = [
     { name = "RawServoStyleSet", opaque = true },
+    { name = "RawServoSelectorList", opaque = false },
     { name = "ServoElementSnapshot", opaque = false },
     { name = "RawServoAnimationValueMap", opaque = true },
 ]
 servo-immutable-borrow-types = [
     "RawGeckoNode",
     "RawGeckoElement",
     "RawGeckoDocument",
     "RawServoDeclarationBlockStrong",
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -4034,25 +4034,25 @@ CSSParserImpl::ParseFontFeatureValuesRul
   if (!ParseFamily(fontlistValue) ||
       fontlistValue.GetUnit() != eCSSUnit_FontFamilyList)
   {
     REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily);
     return false;
   }
 
   // add family to rule
-  const FontFamilyList* fontlist = fontlistValue.GetFontFamilyListValue();
+  SharedFontList* fontlist = fontlistValue.GetFontFamilyListValue();
 
   // family list has generic ==> parse error
   if (fontlist->HasGeneric()) {
     REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList);
     return false;
   }
 
-  valuesRule->SetFamilyList(*fontlist);
+  valuesRule->SetFamilyList(fontlist);
 
   // open brace
   if (!ExpectSymbol('{', true)) {
     REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart);
     return false;
   }
 
   // list of sets of feature values, each set bound to a specific
@@ -11992,18 +11992,18 @@ CSSParserImpl::ParseFontDescriptorValue(
     // possibly with more restrictions on the values they can take.
   case eCSSFontDesc_Family: {
     nsCSSValue value;
     if (!ParseFamily(value) ||
         value.GetUnit() != eCSSUnit_FontFamilyList)
       return false;
 
     // name can only be a single, non-generic name
-    const FontFamilyList* f = value.GetFontFamilyListValue();
-    const nsTArray<FontFamilyName>& fontlist = f->GetFontlist();
+    const SharedFontList* f = value.GetFontFamilyListValue();
+    const nsTArray<FontFamilyName>& fontlist = f->mNames;
 
     if (fontlist.Length() != 1 || !fontlist[0].IsNamed()) {
       return false;
     }
 
     aValue.SetStringValue(fontlist[0].mName, eCSSUnit_String);
     return true;
   }
@@ -14766,49 +14766,48 @@ CSSParserImpl::ParseOneFamily(nsAString&
   } else {
     UngetToken();
     return false;
   }
 }
 
 
 static bool
-AppendGeneric(nsCSSKeyword aKeyword, FontFamilyList *aFamilyList)
+AppendGeneric(nsCSSKeyword aKeyword, nsTArray<FontFamilyName>& aFamilyList)
 {
   switch (aKeyword) {
     case eCSSKeyword_serif:
-      aFamilyList->Append(FontFamilyName(eFamily_serif));
+      aFamilyList.AppendElement(FontFamilyName(eFamily_serif));
       return true;
     case eCSSKeyword_sans_serif:
-      aFamilyList->Append(FontFamilyName(eFamily_sans_serif));
+      aFamilyList.AppendElement(FontFamilyName(eFamily_sans_serif));
       return true;
     case eCSSKeyword_monospace:
-      aFamilyList->Append(FontFamilyName(eFamily_monospace));
+      aFamilyList.AppendElement(FontFamilyName(eFamily_monospace));
       return true;
     case eCSSKeyword_cursive:
-      aFamilyList->Append(FontFamilyName(eFamily_cursive));
+      aFamilyList.AppendElement(FontFamilyName(eFamily_cursive));
       return true;
     case eCSSKeyword_fantasy:
-      aFamilyList->Append(FontFamilyName(eFamily_fantasy));
+      aFamilyList.AppendElement(FontFamilyName(eFamily_fantasy));
       return true;
     case eCSSKeyword__moz_fixed:
-      aFamilyList->Append(FontFamilyName(eFamily_moz_fixed));
+      aFamilyList.AppendElement(FontFamilyName(eFamily_moz_fixed));
       return true;
     default:
       break;
   }
 
   return false;
 }
 
 bool
 CSSParserImpl::ParseFamily(nsCSSValue& aValue)
 {
-  RefPtr<css::FontFamilyListRefCnt> familyList =
-    new css::FontFamilyListRefCnt();
+  nsTArray<FontFamilyName> families;
   nsAutoString family;
   bool single, quoted;
 
   // keywords only have meaning in the first position
   if (!ParseOneFamily(family, single, quoted))
     return false;
 
   // check for keywords, but only when keywords appear by themselves
@@ -14834,22 +14833,22 @@ CSSParserImpl::ParseFamily(nsCSSValue& a
         break;
       case eCSSKeyword__moz_use_system_font:
         if (!IsParsingCompoundProperty()) {
           aValue.SetSystemFontValue();
           return true;
         }
         break;
       default:
-        foundGeneric = AppendGeneric(keyword, familyList);
+        foundGeneric = AppendGeneric(keyword, families);
     }
   }
 
   if (!foundGeneric) {
-    familyList->Append(
+    families.AppendElement(
       FontFamilyName(family, (quoted ? eQuotedName : eUnquotedName)));
   }
 
   for (;;) {
     if (!ExpectSymbol(',', true))
       break;
 
     nsAutoString nextFamily;
@@ -14868,32 +14867,34 @@ CSSParserImpl::ParseFamily(nsCSSValue& a
         case eCSSKeyword__moz_use_system_font:
           return false;
         case eCSSKeyword_unset:
           if (nsLayoutUtils::UnsetValueEnabled()) {
             return false;
           }
           break;
         default:
-          foundGeneric = AppendGeneric(keyword, familyList);
+          foundGeneric = AppendGeneric(keyword, families);
           break;
       }
     }
 
     if (!foundGeneric) {
-      familyList->Append(
+      families.AppendElement(
         FontFamilyName(nextFamily, (quoted ? eQuotedName : eUnquotedName)));
     }
   }
 
-  if (familyList->IsEmpty()) {
-    return false;
-  }
-
-  aValue.SetFontFamilyListValue(familyList);
+  if (families.IsEmpty()) {
+    return false;
+  }
+
+  RefPtr<SharedFontList> familyList =
+    new SharedFontList(Move(families));
+  aValue.SetFontFamilyListValue(familyList.forget());
   return true;
 }
 
 // src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )*
 // uri-src: uri [ 'format(' string ( ',' string )* ')' ]
 // local-src: 'local(' ( string | ident ) ')'
 
 bool
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -1297,17 +1297,17 @@ FeatureValuesToString(
       aOutStr.Append(';');
     }
     aOutStr.AppendLiteral(" }\n");
   }
 }
 
 static void
 FontFeatureValuesRuleToString(
-  const mozilla::FontFamilyList& aFamilyList,
+  mozilla::SharedFontList* aFamilyList,
   const nsTArray<gfxFontFeatureValueSet::FeatureValues>& aFeatureValues,
   nsAString& aOutStr)
 {
   aOutStr.AssignLiteral("@font-feature-values ");
   nsAutoString familyListStr, valueTextStr;
   nsStyleUtil::AppendEscapedCSSFontFamilyList(aFamilyList, familyListStr);
   aOutStr.Append(familyListStr);
   aOutStr.AppendLiteral(" {\n");
@@ -1393,23 +1393,16 @@ struct MakeFamilyArray {
     return true;
   }
 
   nsTArray<nsString>& familyArray;
   bool hasGeneric;
 };
 
 void
-nsCSSFontFeatureValuesRule::SetFamilyList(
-  const mozilla::FontFamilyList& aFamilyList)
-{
-  mFamilyList = aFamilyList;
-}
-
-void
 nsCSSFontFeatureValuesRule::AddValueList(int32_t aVariantAlternate,
                      nsTArray<gfxFontFeatureValueSet::ValueList>& aValueList)
 {
   uint32_t i, len = mFeatureValues.Length();
   bool foundAlternate = false;
 
   // add to an existing list for a given property value
   for (i = 0; i < len; i++) {
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -185,33 +185,36 @@ public:
   already_AddRefed<mozilla::css::Rule> Clone() const final;
 
   // nsIDOMCSSFontFeatureValuesRule interface
   NS_DECL_NSIDOMCSSFONTFEATUREVALUESRULE
 
   // WebIDL interface
   void GetCssTextImpl(nsAString& aCssText) const final;
 
-  const mozilla::FontFamilyList& GetFamilyList() { return mFamilyList; }
-  void SetFamilyList(const mozilla::FontFamilyList& aFamilyList);
+  mozilla::SharedFontList* GetFamilyList() const { return mFamilyList; }
+  void SetFamilyList(mozilla::SharedFontList* aFamilyList)
+  {
+    mFamilyList = aFamilyList;
+  }
 
   void AddValueList(int32_t aVariantAlternate,
                     nsTArray<gfxFontFeatureValueSet::ValueList>& aValueList);
 
   const nsTArray<gfxFontFeatureValueSet::FeatureValues>& GetFeatureValues()
   {
     return mFeatureValues;
   }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
 
 protected:
   ~nsCSSFontFeatureValuesRule() {}
 
-  mozilla::FontFamilyList mFamilyList;
+  RefPtr<mozilla::SharedFontList> mFamilyList;
   nsTArray<gfxFontFeatureValueSet::FeatureValues> mFeatureValues;
 };
 
 class nsCSSKeyframeRule;
 
 class nsCSSKeyframeStyleDeclaration final : public nsDOMCSSDeclaration
 {
 public:
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -125,17 +125,17 @@ nsCSSValue::nsCSSValue(nsCSSValueTokenSt
 
 nsCSSValue::nsCSSValue(mozilla::css::GridTemplateAreasValue* aValue)
   : mUnit(eCSSUnit_GridTemplateAreas)
 {
   mValue.mGridTemplateAreas = aValue;
   mValue.mGridTemplateAreas->AddRef();
 }
 
-nsCSSValue::nsCSSValue(css::FontFamilyListRefCnt* aValue)
+nsCSSValue::nsCSSValue(SharedFontList* aValue)
   : mUnit(eCSSUnit_FontFamilyList)
 {
   mValue.mFontFamilyList = aValue;
   mValue.mFontFamilyList->AddRef();
 }
 
 nsCSSValue::nsCSSValue(const nsCSSValue& aCopy)
   : mUnit(aCopy.mUnit)
@@ -315,17 +315,18 @@ bool nsCSSValue::operator==(const nsCSSV
     else if (eCSSUnit_PairList == mUnit) {
       return nsCSSValuePairList::Equal(mValue.mPairList,
                                        aOther.mValue.mPairList);
     }
     else if (eCSSUnit_GridTemplateAreas == mUnit) {
       return *mValue.mGridTemplateAreas == *aOther.mValue.mGridTemplateAreas;
     }
     else if (eCSSUnit_FontFamilyList == mUnit) {
-      return *mValue.mFontFamilyList == *aOther.mValue.mFontFamilyList;
+      return mValue.mFontFamilyList->mNames ==
+             aOther.mValue.mFontFamilyList->mNames;
     }
     else if (eCSSUnit_AtomIdent == mUnit) {
       return mValue.mAtom == aOther.mValue.mAtom;
     }
     else {
       return mValue.mFloat == aOther.mValue.mFloat;
     }
   }
@@ -612,22 +613,21 @@ void nsCSSValue::SetTokenStreamValue(nsC
 void nsCSSValue::SetGridTemplateAreas(mozilla::css::GridTemplateAreasValue* aValue)
 {
   Reset();
   mUnit = eCSSUnit_GridTemplateAreas;
   mValue.mGridTemplateAreas = aValue;
   mValue.mGridTemplateAreas->AddRef();
 }
 
-void nsCSSValue::SetFontFamilyListValue(css::FontFamilyListRefCnt* aValue)
+void nsCSSValue::SetFontFamilyListValue(already_AddRefed<SharedFontList> aValue)
 {
   Reset();
   mUnit = eCSSUnit_FontFamilyList;
-  mValue.mFontFamilyList = aValue;
-  mValue.mFontFamilyList->AddRef();
+  mValue.mFontFamilyList = aValue.take();
 }
 
 void nsCSSValue::SetPairValue(const nsCSSValuePair* aValue)
 {
   // pairs should not be used for null/inherit/initial values
   MOZ_ASSERT(aValue &&
              aValue->mXValue.GetUnit() != eCSSUnit_Null &&
              aValue->mYValue.GetUnit() != eCSSUnit_Null &&
@@ -1984,17 +1984,17 @@ nsCSSValue::AppendToString(nsCSSProperty
     MOZ_ASSERT(!areas->mTemplates.IsEmpty(),
                "Unexpected empty array in GridTemplateAreasValue");
     nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[0], aResult);
     for (uint32_t i = 1; i < areas->mTemplates.Length(); i++) {
       aResult.Append(char16_t(' '));
       nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[i], aResult);
     }
   } else if (eCSSUnit_FontFamilyList == unit) {
-    nsStyleUtil::AppendEscapedCSSFontFamilyList(*mValue.mFontFamilyList,
+    nsStyleUtil::AppendEscapedCSSFontFamilyList(mValue.mFontFamilyList,
                                                 aResult);
   } else if (eCSSUnit_AtomIdent == unit) {
     nsDependentAtomString buffer(GetAtomValue());
     nsStyleUtil::AppendEscapedCSSIdent(buffer, aResult);
   }
 
   switch (unit) {
     case eCSSUnit_Null:         break;
@@ -2203,16 +2203,19 @@ nsCSSValue::SizeOfExcludingThis(mozilla:
       break;
 
     // GridTemplateAreas
     case eCSSUnit_GridTemplateAreas:
       n += mValue.mGridTemplateAreas->SizeOfIncludingThis(aMallocSizeOf);
       break;
 
     case eCSSUnit_FontFamilyList:
+      // The SharedFontList is a refcounted object, but is unique per
+      // declaration. We don't measure the references from computed
+      // values.
       n += mValue.mFontFamilyList->SizeOfIncludingThis(aMallocSizeOf);
       break;
 
     // Atom is always shared, and thus should not be counted.
     case eCSSUnit_AtomIdent:
       break;
 
     // Int: nothing extra to measure.
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -331,41 +331,16 @@ private:
   {
   }
 
   GridTemplateAreasValue(const GridTemplateAreasValue& aOther) = delete;
   GridTemplateAreasValue&
   operator=(const GridTemplateAreasValue& aOther) = delete;
 };
 
-class FontFamilyListRefCnt final : public FontFamilyList {
-public:
-    FontFamilyListRefCnt()
-        : FontFamilyList()
-    {}
-
-    explicit FontFamilyListRefCnt(FontFamilyType aGenericType)
-        : FontFamilyList(aGenericType)
-    {}
-
-    FontFamilyListRefCnt(const nsAString& aFamilyName,
-                         QuotedName aQuoted)
-        : FontFamilyList(aFamilyName, aQuoted)
-    {}
-
-    FontFamilyListRefCnt(const FontFamilyListRefCnt& aOther)
-        : FontFamilyList(aOther)
-    {}
-
-    NS_INLINE_DECL_REFCOUNTING(FontFamilyListRefCnt);
-
-private:
-    ~FontFamilyListRefCnt() {}
-};
-
 struct RGBAColorData
 {
   // 1.0 means 100% for all components, but the value may fall outside
   // the range of [0.0, 1.0], so it is necessary to clamp them when
   // converting to nscolor.
   float mR;
   float mG;
   float mB;
@@ -523,17 +498,17 @@ enum nsCSSUnit {
   eCSSUnit_ListDep      = 54,     // (nsCSSValueList*) same as List
                                   //   but does not own the list
   eCSSUnit_SharedList   = 55,     // (nsCSSValueSharedList*) same as list
                                   //   but reference counted and shared
   eCSSUnit_PairList     = 56,     // (nsCSSValuePairList*) list of value pairs
   eCSSUnit_PairListDep  = 57,     // (nsCSSValuePairList*) same as PairList
                                   //   but does not own the list
 
-  eCSSUnit_FontFamilyList = 58,   // (FontFamilyList*) value
+  eCSSUnit_FontFamilyList = 58,   // (SharedFontList*) value
 
   // Atom units
   eCSSUnit_AtomIdent    = 60,     // (nsIAtom*) for its string as an identifier
 
   eCSSUnit_Integer      = 70,     // (int) simple value
   eCSSUnit_Enumerated   = 71,     // (int) value has enumerated meaning
 
   eCSSUnit_EnumColor           = 80,   // (int) enumerated color (kColorKTable)
@@ -636,17 +611,17 @@ public:
   nsCSSValue(float aValue, nsCSSUnit aUnit);
   nsCSSValue(const nsString& aValue, nsCSSUnit aUnit);
   nsCSSValue(Array* aArray, nsCSSUnit aUnit);
   explicit nsCSSValue(mozilla::css::URLValue* aValue);
   explicit nsCSSValue(mozilla::css::ImageValue* aValue);
   explicit nsCSSValue(nsCSSValueGradient* aValue);
   explicit nsCSSValue(nsCSSValueTokenStream* aValue);
   explicit nsCSSValue(mozilla::css::GridTemplateAreasValue* aValue);
-  explicit nsCSSValue(mozilla::css::FontFamilyListRefCnt* aValue);
+  explicit nsCSSValue(mozilla::SharedFontList* aValue);
   nsCSSValue(const nsCSSValue& aCopy);
   nsCSSValue(nsCSSValue&& aOther)
     : mUnit(aOther.mUnit)
     , mValue(aOther.mValue)
   {
     aOther.mUnit = eCSSUnit_Null;
   }
   ~nsCSSValue() { Reset(); }
@@ -838,23 +813,23 @@ public:
   }
 
   nsCSSValueSharedList* GetSharedListValue() const
   {
     MOZ_ASSERT(mUnit == eCSSUnit_SharedList, "not a shared list value");
     return mValue.mSharedList;
   }
 
-  mozilla::FontFamilyList* GetFontFamilyListValue() const
+  mozilla::NotNull<mozilla::SharedFontList*> GetFontFamilyListValue() const
   {
     MOZ_ASSERT(mUnit == eCSSUnit_FontFamilyList,
                "not a font family list value");
     NS_ASSERTION(mValue.mFontFamilyList != nullptr,
                  "font family list value should never be null");
-    return mValue.mFontFamilyList;
+    return mozilla::WrapNotNull(mValue.mFontFamilyList);
   }
 
   // bodies of these are below
   inline nsCSSValuePair& GetPairValue();
   inline const nsCSSValuePair& GetPairValue() const;
 
   inline nsCSSRect& GetRectValue();
   inline const nsCSSRect& GetRectValue() const;
@@ -957,17 +932,17 @@ public:
   void SetComplexColorValue(
     already_AddRefed<mozilla::css::ComplexColorValue> aValue);
   void SetArrayValue(nsCSSValue::Array* aArray, nsCSSUnit aUnit);
   void SetURLValue(mozilla::css::URLValue* aURI);
   void SetImageValue(mozilla::css::ImageValue* aImage);
   void SetGradientValue(nsCSSValueGradient* aGradient);
   void SetTokenStreamValue(nsCSSValueTokenStream* aTokenStream);
   void SetGridTemplateAreas(mozilla::css::GridTemplateAreasValue* aValue);
-  void SetFontFamilyListValue(mozilla::css::FontFamilyListRefCnt* aFontListValue);
+  void SetFontFamilyListValue(already_AddRefed<mozilla::SharedFontList> aFontListValue);
   void SetPairValue(const nsCSSValuePair* aPair);
   void SetPairValue(const nsCSSValue& xValue, const nsCSSValue& yValue);
   void SetSharedListValue(nsCSSValueSharedList* aList);
   void SetDependentListValue(nsCSSValueList* aList);
   void SetDependentPairListValue(nsCSSValuePairList* aList);
   void SetTripletValue(const nsCSSValueTriplet* aTriplet);
   void SetTripletValue(const nsCSSValue& xValue, const nsCSSValue& yValue, const nsCSSValue& zValue);
   void SetAutoValue();
@@ -1059,17 +1034,17 @@ protected:
     nsCSSRect_heap* MOZ_OWNING_REF mRect;
     nsCSSValueTriplet_heap* MOZ_OWNING_REF mTriplet;
     nsCSSValueList_heap* MOZ_OWNING_REF mList;
     nsCSSValueList* mListDependent;
     nsCSSValueSharedList* MOZ_OWNING_REF mSharedList;
     nsCSSValuePairList_heap* MOZ_OWNING_REF mPairList;
     nsCSSValuePairList* mPairListDependent;
     nsCSSValueFloatColor* MOZ_OWNING_REF mFloatColor;
-    mozilla::css::FontFamilyListRefCnt* MOZ_OWNING_REF mFontFamilyList;
+    mozilla::SharedFontList* MOZ_OWNING_REF mFontFamilyList;
     mozilla::css::ComplexColorValue* MOZ_OWNING_REF mComplexColor;
   } mValue;
 };
 
 struct nsCSSValue::Array final {
 
   // return |Array| with reference count of zero
   static Array* Create(size_t aItemCount) {
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4371,26 +4371,23 @@ nsRuleNode::ComputeFontData(void* aStart
   // using the 'font' shorthand).
 
   // Figure out if we are a generic font
   uint8_t generic = kGenericFont_NONE;
   // XXXldb What if we would have had a string if we hadn't been doing
   // the optimization with a non-null aStartStruct?
   const nsCSSValue* familyValue = aRuleData->ValueForFontFamily();
   if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) {
-    const FontFamilyList* fontlist = familyValue->GetFontFamilyListValue();
-    FontFamilyList& fl = font->mFont.fontlist;
-    fl = *fontlist;
-
-    // extract the first generic in the fontlist, if exists
-    FontFamilyType fontType = fontlist->FirstGeneric();
+    NotNull<SharedFontList*> fontlist = familyValue->GetFontFamilyListValue();
+    font->mFont.fontlist = FontFamilyList(fontlist);
 
     // if only a single generic, set the generic type
-    if (fontlist->Length() == 1) {
-      switch (fontType) {
+    if (fontlist->mNames.Length() == 1) {
+      // extract the first generic in the fontlist, if exists
+      switch (font->mFont.fontlist.FirstGeneric()) {
         case eFamily_serif:
           generic = kGenericFont_serif;
           break;
         case eFamily_sans_serif:
           generic = kGenericFont_sans_serif;
           break;
         case eFamily_monospace:
           generic = kGenericFont_monospace;
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -2291,17 +2291,17 @@ nsStyleSet::BuildFontFeatureValueSet()
   AppendFontFeatureValuesRules(rules);
 
   if (rules.IsEmpty()) {
     return nullptr;
   }
 
   RefPtr<gfxFontFeatureValueSet> set = new gfxFontFeatureValueSet();
   for (nsCSSFontFeatureValuesRule* rule : rules) {
-    const nsTArray<FontFamilyName>& familyList = rule->GetFamilyList().GetFontlist();
+    const nsTArray<FontFamilyName>& familyList = rule->GetFamilyList()->mNames;
     const nsTArray<gfxFontFeatureValueSet::FeatureValues>&
       featureValues = rule->GetFeatureValues();
 
     for (const FontFamilyName& f : familyList) {
       set->AddFontFeatureValues(f.mName, featureValues);
     }
   }
   return set.forget();
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -201,49 +201,56 @@ AppendUnquotedFamilyName(const nsAString
      }
 
      ++p;
   }
 }
 
 /* static */ void
 nsStyleUtil::AppendEscapedCSSFontFamilyList(
+  const nsTArray<mozilla::FontFamilyName>& aNames,
+  nsAString& aResult)
+{
+  size_t i, len = aNames.Length();
+  for (i = 0; i < len; i++) {
+    if (i != 0) {
+      aResult.AppendLiteral(", ");
+    }
+    const FontFamilyName& name = aNames[i];
+    switch (name.mType) {
+      case eFamily_named:
+        AppendUnquotedFamilyName(name.mName, aResult);
+        break;
+      case eFamily_named_quoted:
+        AppendEscapedCSSString(name.mName, aResult);
+        break;
+      default:
+        name.AppendToString(aResult);
+    }
+  }
+}
+
+/* static */ void
+nsStyleUtil::AppendEscapedCSSFontFamilyList(
   const mozilla::FontFamilyList& aFamilyList,
   nsAString& aResult)
 {
   if (aFamilyList.IsEmpty()) {
     FontFamilyType defaultGeneric = aFamilyList.GetDefaultFontType();
     // If the font list is empty, then serialize the default generic.
     // See also: gfxFontGroup::BuildFontList()
     if (defaultGeneric != eFamily_none) {
       FontFamilyName(defaultGeneric).AppendToString(aResult);
     } else {
       NS_NOTREACHED("No fonts to serialize");
     }
     return;
   }
 
-  const nsTArray<FontFamilyName>& fontlist = aFamilyList.GetFontlist();
-  size_t i, len = fontlist.Length();
-  for (i = 0; i < len; i++) {
-    if (i != 0) {
-      aResult.AppendLiteral(", ");
-    }
-    const FontFamilyName& name = fontlist[i];
-    switch (name.mType) {
-      case eFamily_named:
-        AppendUnquotedFamilyName(name.mName, aResult);
-        break;
-      case eFamily_named_quoted:
-        AppendEscapedCSSString(name.mName, aResult);
-        break;
-      default:
-        name.AppendToString(aResult);
-    }
-  }
+  AppendEscapedCSSFontFamilyList(aFamilyList.GetFontlist().get(), aResult);
 }
 
 
 /* static */ void
 nsStyleUtil::AppendBitmaskCSSValue(nsCSSPropertyID aProperty,
                                    int32_t aMaskedValue,
                                    int32_t aFirstMask,
                                    int32_t aLastMask,
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -46,17 +46,29 @@ public:
   // identifier.  An exception is if aIdent contains U+0000, which
   // will be escaped as U+FFFD and then reparsed back to U+FFFD.
   static void AppendEscapedCSSIdent(const nsAString& aIdent,
                                     nsAString& aResult);
 
   static void
   AppendEscapedCSSFontFamilyList(const mozilla::FontFamilyList& aFamilyList,
                                  nsAString& aResult);
+  static void
+  AppendEscapedCSSFontFamilyList(mozilla::SharedFontList* aFontlist,
+                                 nsAString& aResult)
+  {
+    AppendEscapedCSSFontFamilyList(aFontlist->mNames, aResult);
+  }
 
+private:
+  static void
+  AppendEscapedCSSFontFamilyList(const nsTArray<mozilla::FontFamilyName>& aNames,
+                                 nsAString& aResult);
+
+public:
   // Append a bitmask-valued property's value(s) (space-separated) to aResult.
   static void AppendBitmaskCSSValue(nsCSSPropertyID aProperty,
                                     int32_t aMaskedValue,
                                     int32_t aFirstMask,
                                     int32_t aLastMask,
                                     nsAString& aResult);
 
   static void AppendAngleValue(const nsStyleCoord& aValue, nsAString& aResult);
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -444,19 +444,19 @@ nsImageBoxFrame::CreateWebRenderCommands
   RefPtr<layers::ImageContainer> container =
     imgCon->GetImageContainer(aManager, containerFlags);
   if (!container) {
     NS_WARNING("Failed to get image container");
     return DrawResult::NOT_READY;
   }
 
   gfx::IntSize size;
-  Maybe<wr::ImageKey> key = aManager->CreateImageKey(aItem, container,
-                                                     aBuilder, aResources,
-                                                     aSc, size);
+  Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(aItem, container,
+                                                                      aBuilder, aResources,
+                                                                      aSc, size);
   if (key.isNothing()) {
     return DrawResult::BAD_IMAGE;
   }
   const int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
   LayoutDeviceRect fillRect = LayoutDeviceRect::FromAppUnits(dest,