Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 04 Oct 2017 14:57:59 -0700
changeset 384543 c3b7759671deae73e40ebca01d7f23a326a4b8c2
parent 384542 ee1c41cf306df0043a8e68af042f133acf2ef94e (current diff)
parent 384489 1d9045285aa9a481f4d115153991178a8485fc53 (diff)
child 384601 dedd9a48da6982467b8b4b635eec59521168ec19
push id52690
push userkwierso@gmail.com
push dateWed, 04 Oct 2017 23:47:58 +0000
treeherderautoland@645f013cd54f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone58.0a1
first release with
nightly linux32
c3b7759671de / 58.0a1 / 20171004220309 / files
nightly linux64
c3b7759671de / 58.0a1 / 20171004220309 / files
nightly mac
c3b7759671de / 58.0a1 / 20171004220309 / files
nightly win32
c3b7759671de / 58.0a1 / 20171004220309 / files
nightly win64
c3b7759671de / 58.0a1 / 20171004220309 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to 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