Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Thu, 15 Nov 2018 00:37:56 +0200
changeset 502931 8affbbef5298262c208006fd22aa6ed650a30d81
parent 502930 95b2486e8c545fcd43cea96e84556d0267e94104 (current diff)
parent 502838 b0a40093b6b7a0784a6f38b318f597419d86fd8e (diff)
child 502932 6adb19445d389edb62ba8743041f42d0a3fe7711
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
devtools/client/inspector/changes/ChangesManager.js
dom/base/nsGlobalWindowInner.cpp
dom/base/nsPIDOMWindow.h
layout/painting/nsDisplayList.cpp
modules/libpref/init/StaticPrefList.h
testing/web-platform/meta/cors/redirect-userinfo.htm.ini
testing/web-platform/meta/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js.ini
testing/web-platform/meta/fetch/api/headers/headers-no-cors.window.js.ini
--- a/.gitignore
+++ b/.gitignore
@@ -8,16 +8,18 @@ TAGS
 tags
 compile_commands.json
 # Ignore ID generated by idutils and un-ignore id directory (for Indonesian locale)
 ID
 !id/
 .DS_Store*
 *.pdb
 *.egg-info
+# Filesystem temporaries
+.fuse_hidden*
 
 # Vim swap files.
 .*.sw[a-z]
 .sw[a-z]
 
 # Emacs directory variable files.
 **/.dir-locals.el
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -65,20 +65,16 @@
 }
 
 /* Prevent shrinking the page content to 0 height and width */
 .browserStack > browser {
   min-height: 25px;
   min-width: 25px;
 }
 
-toolbar[customizable="true"] {
-  -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar");
-}
-
 %ifdef MENUBAR_CAN_AUTOHIDE
 #toolbar-menubar[autohide="true"] {
   overflow: hidden;
 }
 
 #toolbar-menubar[autohide="true"][inactive="true"]:not([customizing="true"]) {
   min-height: 0 !important;
   height: 0 !important;
@@ -251,20 +247,20 @@ panelview[mainview] > .panel-header {
 }
 
 #navigator-toolbox[movingtab] > #nav-bar {
   margin-top: -15px;
   pointer-events: none;
 }
 
 /* Allow dropping a tab on buttons with associated drop actions. */
-#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #personal-bookmarks,
-#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #home-button,
-#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #downloads-button,
-#TabsToolbar[movingtab] + #nav-bar > #nav-bar-customization-target > #bookmarks-menu-button {
+#navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #personal-bookmarks,
+#navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #home-button,
+#navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #downloads-button,
+#navigator-toolbox[movingtab] > #nav-bar > #nav-bar-customization-target > #bookmarks-menu-button {
   pointer-events: auto;
 }
 
 toolbar[overflowable] > .customization-target {
   overflow: hidden;
 }
 
 toolbar:not([overflowing]) > .overflow-button,
--- a/browser/components/customizableui/content/toolbar.xml
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -2,26 +2,21 @@
 <!-- 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/. -->
 
 <bindings id="browserToolbarBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="toolbar">
-  </binding>
-
   <!-- The toolbar-drag binding is almost a verbatim copy of its toolkit counterpart,
        but it inherits from the customizableui's toolbar binding instead of toolkit's.
        This functionality will move into CustomizableUI proper as part of our move
        away from XBL. -->
-  <binding id="toolbar-drag"
-           extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
+  <binding id="toolbar-drag">
     <implementation>
       <field name="_dragBindingAlive">true</field>
       <constructor><![CDATA[
         if (!this._draggableStarted) {
           this._draggableStarted = true;
           try {
             let tmp = {};
             ChromeUtils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
--- a/browser/components/tests/browser/browser_urlbar_matchBuckets_migration60.js
+++ b/browser/components/tests/browser/browser_urlbar_matchBuckets_migration60.js
@@ -73,16 +73,20 @@ add_task(async function setDefaultPrefAn
 
 
 // Installs the study using the pref that the overwhelming majority of users
 // will see ("ratio": 97, "value": "suggestion:4,general:5") and migrates.  The
 // study should be stopped and the pref should remain cleared.
 add_task(async function installStudyAndMigrate() {
   await sanityCheckInitialState();
 
+  // Normandy can't unset the pref if it didn't already have a value, so give it
+  // a value that will be treated as empty by the migration.
+  Services.prefs.getDefaultBranch(PREF_NAME).setCharPref("", "");
+
   // Install the study.  It should set the pref.
   await PreferenceExperiments.start(newExperimentOpts());
   Assert.ok(await PreferenceExperiments.has(STUDY_NAME),
             "Study installed");
   Assert.equal(Services.prefs.getCharPref(PREF_NAME, ""),
                PREF_VALUE_SUGGESTIONS_FIRST,
                "Pref should be set by study");
 
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -656,47 +656,41 @@ notification[value="translation"] menuli
   }
 
   /* Add extra space to titlebar for dragging */
   :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar,
   :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar {
     padding-top: var(--space-above-tabbar);
   }
 
-  /**
-   * Titlebar items (window caption buttons, private browsing indicator,
-   * accessibility indicator, etc)
-   */
-  :root[sizemode="normal"][chromehidden~="menubar"] #TabsToolbar > .titlebar-item,
-  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .titlebar-item {
+  /* Center items (window caption buttons, private browsing indicator,
+   * accessibility indicator, etc) vertically. */
+  :root[sizemode="normal"] #TabsToolbar > .titlebar-item {
     margin-top: calc(-1 * var(--space-above-tabbar));
   }
 
   /* Make #TabsToolbar transparent as we style underlying #titlebar with
-   * -moz-window-titlebar (Gtk+ theme).
-   */
+   * -moz-window-titlebar (Gtk+ theme). */
   :root[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #TabsToolbar,
   :root[tabsintitlebar][sizemode="maximized"] #TabsToolbar,
   :root[tabsintitlebar] #toolbar-menubar {
     -moz-appearance: none;
   }
 
   /* The button box must appear on top of the navigator-toolbox in order for
    * click and hover mouse events to work properly for the button in the restored
    * window state. Otherwise, elements in the navigator-toolbox, like the menubar,
-   * can swallow those events.
-   */
+   * can swallow those events. */
   .titlebar-buttonbox {
     z-index: 1;
     -moz-box-align: center;
   }
 
   /* Render titlebar command buttons according to system config.
-   * Use full scale icons here as the Gtk+ does.
-   */
+   * Use full scale icons here as the Gtk+ does. */
   @media (-moz-gtk-csd-minimize-button) {
     .titlebar-min {
       -moz-appearance: -moz-window-button-minimize;
     }
   }
   @media (-moz-gtk-csd-minimize-button: 0) {
     .titlebar-min {
       display: none;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -949,31 +949,31 @@ notification[value="translation"] {
  * paint, so this hack is how we sidestep that performance bottleneck.
  */
 #main-window:-moz-any([customize-entering],[customize-exiting]) label {
   transform: perspective(0.01px);
 }
 
 /* End customization mode */
 
-/**
- * Titlebar items (window caption buttons, private browsing indicator,
- * accessibility indicator, etc)
- */
-:root[sizemode="normal"][chromehidden~="menubar"] .titlebar-item,
-:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .titlebar-item,
-:root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] > .titlebar-item {
+/* Prevent titlebar items (window caption buttons, private browsing indicator,
+ * accessibility indicator, etc) from overlapping the nav bar's shadow on the
+ * tab bar. */
+#TabsToolbar > .titlebar-item {
+  margin-bottom: @navbarTabsShadowSize@;
+}
+
+/* Center titlebar items vertically. */
+:root[sizemode="normal"] #TabsToolbar > .titlebar-item {
   margin-top: calc(-1 * var(--space-above-tabbar));
 }
 
 /* Compensate for 4px extra margin on top of the tabs toolbar on Windows 7. */
 @media (-moz-os-version: windows-win7) {
-  :root[sizemode="normal"][chromehidden~="menubar"] .titlebar-item,
-  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] + #TabsToolbar > .titlebar-item,
-  :root[sizemode="normal"] #toolbar-menubar[autohide="true"][inactive] > .titlebar-item {
+  :root[sizemode="normal"] #TabsToolbar > .titlebar-item {
     margin-top: calc(-1 * (var(--space-above-tabbar) + 4px));
   }
 }
 
 :root:not([privatebrowsingmode=temporary]) .accessibility-indicator,
 .private-browsing-indicator {
   margin-inline-end: 12px;
 }
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -67,16 +67,19 @@ const AboutDebugging = {
     this.actions.updateNetworkLocations(getNetworkLocations());
 
     addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
     addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
     await enableUSBRuntimes();
 
     adbAddon.on("update", this.onAdbAddonUpdated);
     this.onAdbAddonUpdated();
+
+    // Remove deprecated remote debugging extensions.
+    await adbAddon.uninstallUnsupportedExtensions();
   },
 
   onAdbAddonUpdated() {
     this.actions.updateAdbAddonStatus(adbAddon.status);
   },
 
   onNetworkLocationsUpdated() {
     this.actions.updateNetworkLocations(getNetworkLocations());
deleted file mode 100644
--- a/devtools/client/inspector/changes/ChangesManager.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {
-  trackChange,
-} = require("./actions/changes");
-
-class ChangesManager {
-  constructor(inspector) {
-    this.store = inspector.store;
-  }
-
-  track(change) {
-    this.store.dispatch(trackChange(change));
-  }
-
-  destroy() {
-    this.store = null;
-  }
-}
-
-module.exports = ChangesManager;
--- a/devtools/client/inspector/changes/moz.build
+++ b/devtools/client/inspector/changes/moz.build
@@ -7,13 +7,12 @@
 DIRS += [
     'actions',
     'components',
     'reducers',
     'utils',
 ]
 
 DevToolsModules(
-    'ChangesManager.js',
     'ChangesView.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -31,18 +31,16 @@ loader.lazyRequireGetter(this, "MarkupVi
 loader.lazyRequireGetter(this, "HighlightersOverlay", "devtools/client/inspector/shared/highlighters-overlay");
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
 loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
 loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
-loader.lazyRequireGetter(this, "ChangesManager",
-"devtools/client/inspector/changes/ChangesManager");
 
 loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
 
 const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 loader.lazyGetter(this, "TOOLBOX_L10N", function() {
   return new LocalizationHelper("devtools/client/locales/toolbox.properties");
@@ -118,19 +116,16 @@ function Inspector(toolbox) {
   this._markupBox = this.panelDoc.getElementById("markup-box");
 
   // Map [panel id => panel instance]
   // Stores all the instances of sidebar panels like rule view, computed view, ...
   this._panels = new Map();
 
   this.reflowTracker = new ReflowTracker(this._target);
   this.styleChangeTracker = new InspectorStyleChangeTracker(this);
-  if (Services.prefs.getBoolPref(TRACK_CHANGES_PREF)) {
-    this.changesManager = new ChangesManager(this);
-  }
 
   // Store the URL of the target page prior to navigation in order to ensure
   // telemetry counts in the Grid Inspector are not double counted on reload.
   this.previousURL = this.target.url;
 
   this.nodeMenuTriggerInfo = null;
 
   this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this);
@@ -933,21 +928,26 @@ Inspector.prototype = {
       {
         id: "animationinspector",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle"),
       },
       {
         id: "fontinspector",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
       },
-      {
+    ];
+
+    if (Services.prefs.getBoolPref(TRACK_CHANGES_PREF)) {
+      // Insert Changes as third tab, right after Computed.
+      // TODO: move this inline to `sidebarPanels` above when addressing Bug 1491887.
+      sidebarPanels.splice(2, 0, {
         id: "changesview",
         title: INSPECTOR_L10N.getStr("inspector.sidebar.changesViewTitle"),
-      },
-    ];
+      });
+    }
 
     for (const { id, title } of sidebarPanels) {
       // The Computed panel is not a React-based panel. We pick its element container from
       // the DOM and wrap it in a React component (InspectorTabPanel) so it behaves like
       // other panels when using the Inspector's tool sidebar.
       if (id === "computedview") {
         this.sidebar.queueExistingTab(id, title, defaultTab === id);
       } else {
@@ -1433,20 +1433,16 @@ Inspector.prototype = {
     const markupDestroyer = this._destroyMarkup();
 
     this.teardownToolbar();
 
     this.breadcrumbs.destroy();
     this.reflowTracker.destroy();
     this.styleChangeTracker.destroy();
 
-    if (this.changesManager) {
-      this.changesManager.destroy();
-    }
-
     this._is3PaneModeChromeEnabled = null;
     this._is3PaneModeEnabled = null;
     this._markupBox = null;
     this._markupFrame = null;
     this._notificationBox = null;
     this._target = null;
     this._toolbox = null;
     this.breadcrumbs = null;
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -89,16 +89,19 @@ var UI = {
 
     // Auto install the ADB Addon Helper. Only once.
     // If the user decides to uninstall any of this addon, we won't install it again.
     const autoinstallADBExtension = Services.prefs.getBoolPref("devtools.webide.autoinstallADBExtension");
     if (autoinstallADBExtension) {
       adbAddon.install("webide");
     }
 
+    // Remove deprecated remote debugging extensions.
+    adbAddon.uninstallUnsupportedExtensions();
+
     Services.prefs.setBoolPref("devtools.webide.autoinstallADBExtension", false);
 
     this.setupDeck();
 
     this.contentViewer = window.docShell.contentViewer;
     this.contentViewer.fullZoom = Services.prefs.getCharPref("devtools.webide.zoom");
 
     gDevToolsBrowser.isWebIDEInitialized.resolve();
--- a/devtools/shared/adb/adb-addon.js
+++ b/devtools/shared/adb/adb-addon.js
@@ -7,17 +7,19 @@
 const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const PREF_ADB_EXTENSION_URL = "devtools.remote.adb.extensionURL";
 const PREF_ADB_EXTENSION_ID = "devtools.remote.adb.extensionID";
 
 // Extension ID for adb helper extension that might be installed on Firefox 63 or older.
-const OLD_ADB_ADDON_ID = "adbhelper@mozilla.org";
+const ADB_HELPER_ADDON_ID = "adbhelper@mozilla.org";
+// Extension ID for Valence extension that is no longer supported.
+const VALENCE_ADDON_ID = "fxdevtools-adapters@mozilla.org";
 
 // Possible values for ADBAddon::state. WebIDE relies on the exact values for localization
 // and styles, so they should not be updated until WebIDE is removed.
 const ADB_ADDON_STATES = {
   DOWNLOADING: "downloading",
   INSTALLED: "installed",
   INSTALLING: "installing",
   PREPARING: "preparing",
@@ -36,19 +38,16 @@ exports.ADB_ADDON_STATES = ADB_ADDON_STA
  * AdbAddon::state can take any of the values from ADB_ADDON_STATES.
  */
 class ADBAddon extends EventEmitter {
   constructor() {
     super();
 
     this._status = ADB_ADDON_STATES.UNKNOWN;
 
-    // Uninstall old version of the extension that might be installed on this profile.
-    this.uninstallOldExtension();
-
     const addonsListener = {};
     addonsListener.onEnabled =
     addonsListener.onDisabled =
     addonsListener.onInstalled =
     addonsListener.onUninstalled = () => this.updateInstallStatus();
     AddonManager.addAddonListener(addonsListener);
 
     this.updateInstallStatus();
@@ -133,20 +132,32 @@ class ADBAddon extends EventEmitter {
     }
   }
 
   async uninstall() {
     const addon = await this._getAddon();
     addon.uninstall();
   }
 
-  async uninstallOldExtension() {
-    const oldAddon = await AddonManager.getAddonByID(OLD_ADB_ADDON_ID);
-    if (oldAddon) {
-      oldAddon.uninstall();
+  /**
+   * Cleanup old remote debugging extensions from profiles that might still have them
+   * installed. Should be called from remote debugging entry points.
+   */
+  async uninstallUnsupportedExtensions() {
+    const [adbHelperAddon, valenceAddon] = await Promise.all([
+      AddonManager.getAddonByID(ADB_HELPER_ADDON_ID),
+      AddonManager.getAddonByID(VALENCE_ADDON_ID),
+    ]);
+
+    if (adbHelperAddon) {
+      adbHelperAddon.uninstall();
+    }
+
+    if (valenceAddon) {
+      valenceAddon.uninstall();
     }
   }
 
   installFailureHandler(install, message) {
     this.status = ADB_ADDON_STATES.UNINSTALLED;
     this.emit("failure", message);
   }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7380,31 +7380,88 @@ bool
 nsContentUtils::IsForbiddenResponseHeader(const nsACString& aHeader)
 {
   return (aHeader.LowerCaseEqualsASCII("set-cookie") ||
           aHeader.LowerCaseEqualsASCII("set-cookie2"));
 }
 
 // static
 bool
+nsContentUtils::IsCorsUnsafeRequestHeaderValue(const nsACString& aHeaderValue)
+{
+  const char* cur = aHeaderValue.BeginReading();
+  const char* end = aHeaderValue.EndReading();
+
+  while (cur != end) {
+    // Implementation of https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte
+    // Is less than a space but not a horizontal tab
+    if ((*cur < ' ' && *cur != '\t') ||
+      *cur == '"' || *cur ==  '(' || *cur ==  ')' || *cur ==  ':' ||
+      *cur ==  '<' || *cur ==  '>' || *cur ==  '?' || *cur ==  '@' ||
+      *cur ==  '[' || *cur ==  '\\' || *cur ==  ']' || *cur ==  '{' ||
+      *cur ==  '}' || *cur ==  0x7F) { // 0x75 is DEL
+      return true;
+    }
+    cur++;
+  }
+  return false;
+}
+
+// static
+bool
+nsContentUtils::IsAllowedNonCorsAccept(const nsACString& aHeaderValue)
+{
+  if (IsCorsUnsafeRequestHeaderValue(aHeaderValue)) {
+    return false;
+  }
+  return true;
+}
+
+// static
+bool
 nsContentUtils::IsAllowedNonCorsContentType(const nsACString& aHeaderValue)
 {
   nsAutoCString contentType;
   nsAutoCString unused;
 
+  if (IsCorsUnsafeRequestHeaderValue(aHeaderValue)) {
+    return false;
+  }
+
   nsresult rv = NS_ParseRequestContentType(aHeaderValue, contentType, unused);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   return contentType.LowerCaseEqualsLiteral("text/plain") ||
          contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") ||
          contentType.LowerCaseEqualsLiteral("multipart/form-data");
 }
 
+// static
+bool
+nsContentUtils::IsAllowedNonCorsLanguage(const nsACString& aHeaderValue)
+{
+  const char* cur = aHeaderValue.BeginReading();
+  const char* end = aHeaderValue.EndReading();
+
+  while (cur != end) {
+    if ((*cur >= '0' && *cur <= '9') ||
+        (*cur >= 'A' && *cur <= 'Z') ||
+        (*cur >= 'a' && *cur <= 'z') ||
+        *cur == ' ' || *cur == '*' || *cur == ',' ||
+        *cur == '-' || *cur == '.' || *cur == ';' || *cur == '=') {
+      cur++;
+      continue;
+    }
+    return false;
+  }
+  return true;
+}
+
 bool
 nsContentUtils::DoNotTrackEnabled()
 {
   return nsContentUtils::sDoNotTrackEnabled;
 }
 
 mozilla::LogModule*
 nsContentUtils::DOMDumpLog()
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2765,22 +2765,39 @@ public:
 
   /**
    * Returns whether a given header is forbidden for a system XHR
    * request.
    */
   static bool IsForbiddenSystemRequestHeader(const nsACString& aHeader);
 
   /**
+   * Returns whether a given header has characters that aren't permitted
+   */
+  static bool IsCorsUnsafeRequestHeaderValue(const nsACString& aHeaderValue);
+
+  /**
+   * Returns whether a given Accept header value is allowed
+   * for a non-CORS XHR or fetch request.
+   */
+  static bool IsAllowedNonCorsAccept(const nsACString& aHeaderValue);
+
+  /**
    * Returns whether a given Content-Type header value is allowed
    * for a non-CORS XHR or fetch request.
    */
   static bool IsAllowedNonCorsContentType(const nsACString& aHeaderValue);
 
   /**
+   * Returns whether a given Content-Language or accept-language header value is allowed
+   * for a non-CORS XHR or fetch request.
+   */
+  static bool IsAllowedNonCorsLanguage(const nsACString& aHeaderValue);
+
+  /**
    * Returns whether a given header is forbidden for an XHR or fetch
    * response.
    */
   static bool IsForbiddenResponseHeader(const nsACString& aHeader);
 
   /**
    * Returns the inner window ID for the window associated with a request.
    */
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1103,16 +1103,18 @@ nsGlobalWindowInner::~nsGlobalWindowInne
                   url.get());
   }
 #endif
 
   MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug, ("DOMWINDOW %p destroyed", this));
 
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                         mMutationBits ? 1 : 0);
+  Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_TEXT_EVENT_LISTENERS,
+                        mMayHaveTextEventListenerInDefaultGroup ? 1 : 0);
 
   // An inner window is destroyed, pull it out of the outer window's
   // list if inner windows.
 
   PR_REMOVE_LINK(this);
 
   // If our outer window's inner window is this window, null out the
   // outer window's reference to this window that's being deleted.
@@ -1725,19 +1727,23 @@ nsGlobalWindowInner::InnerSetNewDocument
   ClearDocumentDependentSlots(aCx);
 
 #ifdef DEBUG
   mLastOpenedURI = aDocument->GetDocumentURI();
 #endif
 
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                         mMutationBits ? 1 : 0);
+  Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_TEXT_EVENT_LISTENERS,
+                        mMayHaveTextEventListenerInDefaultGroup ? 1 : 0);
 
   // Clear our mutation bitfield.
   mMutationBits = 0;
+
+  mMayHaveTextEventListenerInDefaultGroup = false;
 }
 
 nsresult
 nsGlobalWindowInner::EnsureClientSource()
 {
   MOZ_DIAGNOSTIC_ASSERT(mDoc);
 
   bool newClientSource = false;
@@ -8071,16 +8077,17 @@ NextWindowID();
 
 nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter *aOuterWindow)
 : mMutationBits(0), mActivePeerConnections(0), mIsDocumentLoaded(false),
   mIsHandlingResizeEvent(false),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveSelectionChangeEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
+  mMayHaveTextEventListenerInDefaultGroup(false),
   mAudioCaptured(false),
   mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
   mMarkedCCGeneration(0),
   mHasTriedToCacheTopInnerWindow(false),
   mNumOfIndexedDBDatabases(0),
   mNumOfOpenWebSockets(0),
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -254,16 +254,26 @@ public:
    * Call this to indicate that some node (this window, its document,
    * or content in that document) has a Pointerenter/leave event listener.
    */
   void SetHasPointerEnterLeaveEventListeners()
   {
     mMayHavePointerEnterLeaveEventListener = true;
   }
 
+  /**
+   * Call this to indiate that some node (this window, its document,
+   * or content in that document) has a text event listener in the default
+   * group.
+   */
+  void SetHasTextEventListenerInDefaultGroup()
+  {
+    mMayHaveTextEventListenerInDefaultGroup = true;
+  }
+
   // Sets the event for window.event. Does NOT take ownership, so
   // the caller is responsible for clearing the event before the
   // event gets deallocated. Pass nullptr to set window.event to
   // undefined. Returns the previous value.
   mozilla::dom::Event* SetEvent(mozilla::dom::Event* aEvent)
   {
     mozilla::dom::Event* old = mEvent;
     mEvent = aEvent;
@@ -672,16 +682,19 @@ protected:
 
   bool mIsDocumentLoaded;
   bool mIsHandlingResizeEvent;
   bool mMayHavePaintEventListener;
   bool mMayHaveTouchEventListener;
   bool mMayHaveSelectionChangeEventListener;
   bool mMayHaveMouseEnterLeaveEventListener;
   bool mMayHavePointerEnterLeaveEventListener;
+  // Only for telemetry probe so that you can remove this after the
+  // telemetry stops working.
+  bool mMayHaveTextEventListenerInDefaultGroup;
 
   bool mAudioCaptured;
 
   // Our inner window's outer window.
   nsCOMPtr<nsPIDOMWindowOuter> mOuterWindow;
 
   // The element within the document that is currently focused when this
   // window is active.
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -419,16 +419,24 @@ EventListenerManager::AddEventListenerIn
     }
   } else if (aTypeAtom == nsGkAtoms::onfinish) {
     if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) {
       nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
       if (doc) {
         doc->SetDocumentAndPageUseCounter(eUseCounter_custom_onfinish);
       }
     }
+  } else if (aTypeAtom == nsGkAtoms::ontext) {
+    // Ignore event listeners in the system group since editor needs to
+    // listen "text" events in the system group.
+    if (!aFlags.mInSystemGroup) {
+      if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) {
+        window->SetHasTextEventListenerInDefaultGroup();
+      }
+    }
   }
 
   if (IsApzAwareListener(listener)) {
     ProcessApzAwareEventListenerAdd();
   }
 
   if (mTarget) {
     mTarget->EventListenerAdded(aTypeAtom);
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -205,22 +205,28 @@ InternalHeaders::SetGuard(HeadersGuardEn
 InternalHeaders::~InternalHeaders()
 {
 }
 
 // static
 bool
 InternalHeaders::IsSimpleHeader(const nsACString& aName, const nsACString& aValue)
 {
+  if (aValue.Length() > 128) {
+    return false;
+  }
   // Note, we must allow a null content-type value here to support
   // get("content-type"), but the IsInvalidValue() check will prevent null
   // from being set or appended.
-  return aName.EqualsLiteral("accept") ||
-         aName.EqualsLiteral("accept-language") ||
-         aName.EqualsLiteral("content-language") ||
+  return (aName.EqualsLiteral("accept") &&
+          nsContentUtils::IsAllowedNonCorsAccept(aValue)) ||
+         (aName.EqualsLiteral("accept-language") &&
+          nsContentUtils::IsAllowedNonCorsLanguage(aValue))||
+         (aName.EqualsLiteral("content-language") &&
+          nsContentUtils::IsAllowedNonCorsLanguage(aValue))||
          (aName.EqualsLiteral("content-type") &&
           nsContentUtils::IsAllowedNonCorsContentType(aValue));
 }
 
 // static
 bool
 InternalHeaders::IsRevalidationHeader(const nsACString& aName)
 {
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -15,16 +15,17 @@
 #include "HandlerServiceChild.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
+#include "mozilla/RemoteDecoderManagerChild.h"
 #include "mozilla/Unused.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientOpenWindowOpActors.h"
 #include "mozilla/dom/ChildProcessMessageManager.h"
@@ -1505,16 +1506,24 @@ ContentChild::RecvReinitRenderingForDevi
   for (const auto& tabChild : tabs) {
     if (tabChild->GetLayersId().IsValid()) {
       tabChild->ReinitRenderingForDeviceReset();
     }
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentChild::RecvInitRemoteDecoder(
+                  Endpoint<PRemoteDecoderManagerChild>&& aRemoteManager)
+{
+  RemoteDecoderManagerChild::InitForContent(std::move(aRemoteManager));
+  return IPC_OK();
+}
+
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 extern "C" {
 CGError
 CGSSetDenyWindowServerConnections(bool);
 void CGSShutdownServerConnections();
 };
 
 static bool
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -206,16 +206,20 @@ public:
     Endpoint<PVRManagerChild>&& aVRBridge,
     Endpoint<PVideoDecoderManagerChild>&& aVideoManager,
     nsTArray<uint32_t>&& namespaces) override;
 
   virtual mozilla::ipc::IPCResult RecvAudioDefaultDeviceChange() override;
 
   mozilla::ipc::IPCResult RecvReinitRenderingForDeviceReset() override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvInitRemoteDecoder(
+    Endpoint<PRemoteDecoderManagerChild>&& aRemoteManager) override;
+
   virtual mozilla::ipc::IPCResult RecvSetProcessSandbox(const MaybeFileDesc& aBroker) override;
 
   virtual PBrowserChild* AllocPBrowserChild(const TabId& aTabId,
                                             const TabId& aSameTabGroupAs,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
                                             const ContentParentId& aCpID,
                                             const bool& aIsForBrowser) override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -90,16 +90,17 @@
 #include "mozilla/Move.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/net/CookieServiceParent.h"
 #include "mozilla/net/PCookieServiceParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
+#include "mozilla/RDDProcessManager.h"
 #include "mozilla/recordreplay/ParentIPC.h"
 #include "mozilla/Scheduler.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/ScriptPreloader.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
@@ -2650,16 +2651,33 @@ ContentParent::InitInternal(ProcessPrior
   Unused << SendInitRendering(std::move(compositor),
                               std::move(imageBridge),
                               std::move(vrBridge),
                               std::move(videoManager),
                               namespaces);
 
   gpm->AddListener(this);
 
+  if (StaticPrefs::MediaRddProcessEnabled()) {
+    RDDProcessManager* rdd = RDDProcessManager::Get();
+
+    Endpoint<PRemoteDecoderManagerChild> remoteManager;
+    bool rddOpened = rdd->CreateContentBridge(OtherPid(),
+                                              &remoteManager);
+    MOZ_ASSERT(rddOpened);
+
+    if (rddOpened) {
+      // not using std::move here (like in SendInitRendering above) because
+      // clang-tidy says:
+      // Warning: Passing result of std::move() as a const reference
+      // argument; no move will actually happen
+      Unused << SendInitRemoteDecoder(remoteManager);
+    }
+  }
+
   nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance();
   if (sheetService) {
     // This looks like a lot of work, but in a normal browser session we just
     // send two loads.
     //
     // The URIs of the Gecko and Servo sheets should be the same, so it
     // shouldn't matter which we look at.
 
@@ -3098,16 +3116,18 @@ NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
 ContentParent::Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const char16_t* aData)
 {
   if (mSubprocess && (!strcmp(aTopic, "profile-before-change") ||
                       !strcmp(aTopic, "xpcom-shutdown"))) {
+    mShuttingDown = true;
+
     // Make sure that our process will get scheduled.
     ProcessPriorityManager::SetProcessPriority(this,
                                                PROCESS_PRIORITY_FOREGROUND);
 
     // Okay to call ShutDownProcess multiple times.
     ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
     MarkAsDead();
 
@@ -3400,36 +3420,24 @@ ContentParent::ForceKillTimerCallback(ns
   if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
     return;
   }
 
   auto self = static_cast<ContentParent*>(aClosure);
   self->KillHard("ShutDownKill");
 }
 
-// WARNING: aReason appears in telemetry, so any new value passed in requires
-// data review.
 void
-ContentParent::KillHard(const char* aReason)
-{
-  AUTO_PROFILER_LABEL("ContentParent::KillHard", OTHER);
-
-  // On Windows, calling KillHard multiple times causes problems - the
-  // process handle becomes invalid on the first call, causing a second call
-  // to crash our process - more details in bug 890840.
-  if (mCalledKillHard) {
-    return;
-  }
-  mCalledKillHard = true;
-  mForceKillTimer = nullptr;
-
+ContentParent::GeneratePairedMinidump(const char* aReason)
+{
   // We're about to kill the child process associated with this content.
   // Something has gone wrong to get us here, so we generate a minidump
-  // of the parent and child for submission to the crash server.
-  if (mCrashReporter) {
+  // of the parent and child for submission to the crash server unless we're
+  // already shutting down.
+  if (mCrashReporter && !mShuttingDown) {
     // GeneratePairedMinidump creates two minidumps for us - the main
     // one is for the content process we're about to kill, and the other
     // one is for the main browser process. That second one is the extra
     // minidump tagging along, so we have to tell the crash reporter that
     // it exists and is being appended.
     nsAutoCString additionalDumps("browser");
     mCrashReporter->AddAnnotation(
       CrashReporter::Annotation::additional_minidumps, additionalDumps);
@@ -3442,16 +3450,35 @@ ContentParent::KillHard(const char* aRea
                                                 nullptr,
                                                 NS_LITERAL_CSTRING("browser")))
     {
       mCreatedPairedMinidumps = mCrashReporter->FinalizeCrashReport();
     }
 
     Telemetry::Accumulate(Telemetry::SUBPROCESS_KILL_HARD, reason, 1);
   }
+}
+
+// WARNING: aReason appears in telemetry, so any new value passed in requires
+// data review.
+void
+ContentParent::KillHard(const char* aReason)
+{
+  AUTO_PROFILER_LABEL("ContentParent::KillHard", OTHER);
+
+  // On Windows, calling KillHard multiple times causes problems - the
+  // process handle becomes invalid on the first call, causing a second call
+  // to crash our process - more details in bug 890840.
+  if (mCalledKillHard) {
+    return;
+  }
+  mCalledKillHard = true;
+  mForceKillTimer = nullptr;
+
+  GeneratePairedMinidump(aReason);
 
   ProcessHandle otherProcessHandle;
   if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle)) {
     NS_ERROR("Failed to open child process when attempting kill.");
     return;
   }
 
   if (!KillProcess(otherProcessHandle, base::PROCESS_END_KILLED_BY_USER,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -787,16 +787,19 @@ private:
 
   // Launch the subprocess and associated initialization.
   // Returns false if the process fails to start.
   bool LaunchSubprocess(hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
 
   // Common initialization after sub process launch.
   void InitInternal(ProcessPriority aPriority);
 
+  // Generate a minidump for the child process and one for the main process
+  void GeneratePairedMinidump(const char* aReason);
+
   virtual ~ContentParent();
 
   void Init();
 
   // Some information could be sent to content very early, it
   // should be send from this function. This function should only be
   // called after the process has been transformed to browser.
   void ForwardKnownInfo();
@@ -1291,18 +1294,16 @@ private:
   int32_t mGeolocationWatchID;
 
   // This contains the id for the JS plugin (@see nsFakePluginTag) if this is the
   // ContentParent for a process containing iframes for that JS plugin.
   // If this is not a ContentParent for a JS plugin then it contains the value
   // nsFakePluginTag::NOT_JSPLUGIN.
   int32_t mJSPluginID;
 
-  nsCString mKillHardAnnotation;
-
   // After we initiate shutdown, we also start a timer to ensure
   // that even content processes that are 100% blocked (say from
   // SIGSTOP), are still killed eventually.  This task enforces that
   // timer.
   nsCOMPtr<nsITimer> mForceKillTimer;
   // How many tabs we're waiting to finish their destruction
   // sequence.  Precisely, how many TabParents have called
   // NotifyTabDestroying() but not called NotifyTabDestroyed().
@@ -1310,16 +1311,17 @@ private:
   // True only while this process is in "good health" and may be used for
   // new remote tabs.
   bool mIsAvailable;
   // True only while remote content is being actively used from this process.
   // After mIsAlive goes to false, some previously scheduled IPC traffic may
   // still pass through.
   bool mIsAlive;
 
+  bool mShuttingDown;
   bool mIsForBrowser;
 
   // Whether this process is recording or replaying its execution, and any
   // associated recording file.
   RecordReplayState mRecordReplayState;
   nsString mRecordingFile;
 
   // When recording or replaying, the child process is a middleman. This vector
--- a/dom/ipc/MemoryReportRequest.cpp
+++ b/dom/ipc/MemoryReportRequest.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MemoryReportRequest.h"
+#include "mozilla/RDDParent.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/gfx/GPUParent.h"
 
 namespace mozilla {
 namespace dom {
 
 MemoryReportRequestHost::MemoryReportRequestHost(uint32_t aGeneration)
@@ -121,16 +122,19 @@ public:
                            aAmount, mGeneration, nsCString(aDescription));
     switch (XRE_GetProcessType()) {
       case GeckoProcessType_Content:
         ContentChild::GetSingleton()->SendAddMemoryReport(memreport);
         break;
       case GeckoProcessType_GPU:
         Unused << gfx::GPUParent::GetSingleton()->SendAddMemoryReport(memreport);
         break;
+      case GeckoProcessType_RDD:
+        Unused << RDDParent::GetSingleton()->SendAddMemoryReport(memreport);
+        break;
       default:
         MOZ_ASSERT_UNREACHABLE("Unhandled process type");
     }
     return NS_OK;
   }
 private:
   ~HandleReportCallback() = default;
 
@@ -158,16 +162,19 @@ public:
     bool sent = false;
     switch (XRE_GetProcessType()) {
       case GeckoProcessType_Content:
         sent = ContentChild::GetSingleton()->SendFinishMemoryReport(mGeneration);
         break;
       case GeckoProcessType_GPU:
         sent = gfx::GPUParent::GetSingleton()->SendFinishMemoryReport(mGeneration);
         break;
+      case GeckoProcessType_RDD:
+        sent = RDDParent::GetSingleton()->SendFinishMemoryReport(mGeneration);
+        break;
       default:
         MOZ_ASSERT_UNREACHABLE("Unhandled process type");
     }
     return sent ? NS_OK : NS_ERROR_FAILURE;
   }
 
 private:
   ~FinishReportingCallback() = default;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -38,16 +38,17 @@ include protocol PJavaScript;
 include protocol PRemoteSpellcheckEngine;
 include protocol PWebBrowserPersistDocument;
 include protocol PWebrtcGlobal;
 include protocol PPresentation;
 include protocol PURLClassifier;
 include protocol PURLClassifierLocal;
 include protocol PVRManager;
 include protocol PVideoDecoderManager;
+include protocol PRemoteDecoderManager;
 include protocol PProfiler;
 include protocol PScriptCache;
 include DOMTypes;
 include JavaScriptTypes;
 include IPCBlob;
 include PTabContext;
 include URIParams;
 include PluginTypes;
@@ -392,16 +393,18 @@ child:
       Endpoint<PVideoDecoderManagerChild> video,
       uint32_t[] namespaces);
 
     async AudioDefaultDeviceChange();
 
     // Re-create the rendering stack for a device reset.
     async ReinitRenderingForDeviceReset();
 
+    async InitRemoteDecoder(Endpoint<PRemoteDecoderManagerChild> decoder);
+
     /**
      * Enable system-level sandboxing features, if available.  Can
      * usually only be performed zero or one times.  The child may
      * abnormally exit if this fails; the details are OS-specific.
      */
     async SetProcessSandbox(MaybeFileDesc aBroker);
 
     async RequestMemoryReport(uint32_t generation,
--- a/dom/media/ipc/MediaIPCUtils.h
+++ b/dom/media/ipc/MediaIPCUtils.h
@@ -39,11 +39,54 @@ namespace IPC {
           ReadParam(aMsg, aIter, &aResult->mImage) &&
           ReadParam(aMsg, aIter, &imageRect)) {
         aResult->SetImageRect(imageRect);
         return true;
       }
       return false;
     }
   };
+
+  template<>
+  struct ParamTraits<mozilla::AudioInfo>
+  {
+    typedef mozilla::AudioInfo paramType;
+
+    static void Write(Message* aMsg, const paramType& aParam)
+    {
+      // TrackInfo
+      WriteParam(aMsg, aParam.mMimeType);
+
+      // AudioInfo
+      WriteParam(aMsg, aParam.mRate);
+      WriteParam(aMsg, aParam.mChannels);
+      WriteParam(aMsg, aParam.mChannelMap);
+      WriteParam(aMsg, aParam.mBitDepth);
+      WriteParam(aMsg, aParam.mProfile);
+      WriteParam(aMsg, aParam.mExtendedProfile);
+    }
+
+    static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+    {
+      if (ReadParam(aMsg, aIter, &aResult->mMimeType) &&
+          ReadParam(aMsg, aIter, &aResult->mRate) &&
+          ReadParam(aMsg, aIter, &aResult->mChannels) &&
+          ReadParam(aMsg, aIter, &aResult->mChannelMap) &&
+          ReadParam(aMsg, aIter, &aResult->mBitDepth) &&
+          ReadParam(aMsg, aIter, &aResult->mProfile) &&
+          ReadParam(aMsg, aIter, &aResult->mExtendedProfile)) {
+        return true;
+      }
+      return false;
+    }
+  };
+
+  template<>
+  struct ParamTraits<mozilla::MediaDataDecoder::ConversionRequired>
+    : public ContiguousEnumSerializerInclusive<
+        mozilla::MediaDataDecoder::ConversionRequired,
+        mozilla::MediaDataDecoder::ConversionRequired(0),
+        mozilla::MediaDataDecoder::ConversionRequired(
+          mozilla::MediaDataDecoder::ConversionRequired::kNeedAnnexB)> {};
+
 } // namespace IPC
 
 #endif // mozilla_dom_media_MediaIPCUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/PMediaDecoderParams.ipdlh
@@ -0,0 +1,23 @@
+/* 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/. */
+
+namespace mozilla {
+
+struct MediaDataIPDL
+{
+  int64_t offset;
+  int64_t time;
+  int64_t timecode;
+  int64_t duration;
+  uint32_t frames;
+  bool keyframe;
+};
+
+struct MediaRawDataIPDL
+{
+  MediaDataIPDL base;
+  Shmem buffer;
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/PRDD.ipdl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; 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 MemoryReportTypes;
+
+include protocol PProfiler;
+include protocol PRemoteDecoderManager;
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
+
+namespace mozilla {
+
+// This protocol allows the UI process to talk to the RDD
+// (RemoteDataDecoder) process. There is one instance of this protocol,
+// with the RDDParent living on the main thread of the RDD process and
+// the RDDChild living on the main thread of the UI process.
+protocol PRDD
+{
+parent:
+
+  // args TBD, sent by UI process to initiate core settings
+  async Init();
+
+  async InitProfiler(Endpoint<PProfilerChild> endpoint);
+
+  async NewContentRemoteDecoderManager(
+            Endpoint<PRemoteDecoderManagerParent> endpoint);
+
+  async RequestMemoryReport(uint32_t generation,
+                            bool anonymize,
+                            bool minimizeMemoryUsage,
+                            MaybeFileDesc DMDFile);
+
+child:
+  // args TBD, sent when init complete. Occurs once, after Init().
+  async InitComplete();
+
+  async InitCrashReporter(Shmem shmem, NativeThreadId threadId);
+
+  async AddMemoryReport(MemoryReport aReport);
+  async FinishMemoryReport(uint32_t aGeneration);
+
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/PRemoteDecoderManager.ipdl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; 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 protocol PRemoteVideoDecoder;
+include "mozilla/dom/MediaIPCUtils.h";
+
+using VideoInfo from "MediaInfo.h";
+using AudioInfo from "MediaInfo.h";
+using mozilla::CreateDecoderParams::OptionSet from "PlatformDecoderModule.h";
+
+namespace mozilla {
+
+sync protocol PRemoteDecoderManager
+{
+  manages PRemoteVideoDecoder;
+
+parent:
+  sync PRemoteVideoDecoder(VideoInfo info,
+                           float framerate,
+                           OptionSet options)
+         returns (bool success,
+                  nsCString aErrorDescription);
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/PRemoteVideoDecoder.ipdl
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/dom/MediaIPCUtils.h";
+
+include protocol PRemoteDecoderManager;
+include PMediaDecoderParams;
+include LayersSurfaces;
+using mozilla::MediaDataDecoder::ConversionRequired from "PlatformDecoderModule.h";
+
+namespace mozilla {
+
+struct RemoteVideoDataIPDL
+{
+  MediaDataIPDL base;
+  IntSize display;
+  IntSize frameSize;
+  SurfaceDescriptorBuffer sdBuffer;
+  int32_t frameID;
+};
+
+// This protocol provides a way to use MediaDataDecoder across processes.
+// The parent side currently is only implemented to work with
+// RemoteDecoderModule.
+// The child side runs in the content process, and the parent side runs
+// in the RDD process. We run a separate IPDL thread for both sides.
+async protocol PRemoteVideoDecoder
+{
+  manager PRemoteDecoderManager;
+parent:
+  async Init();
+
+  async Input(MediaRawDataIPDL data);
+
+  async Flush();
+  async Drain();
+  async Shutdown();
+
+  async SetSeekThreshold(int64_t time);
+
+  async __delete__();
+
+child:
+  async InitComplete(nsCString decoderDescription,
+                     ConversionRequired conversion);
+  async InitFailed(nsresult reason);
+
+  async FlushComplete();
+
+  // Each output includes a SurfaceDescriptorBuffer that represents the decoded
+  // frame.
+  async VideoOutput(RemoteVideoDataIPDL data);
+  async InputExhausted();
+  async DrainComplete();
+  async Error(nsresult error);
+};
+
+} // namespace mozilla
--- a/dom/media/ipc/PVideoDecoder.ipdl
+++ b/dom/media/ipc/PVideoDecoder.ipdl
@@ -1,47 +1,32 @@
 /* -*- Mode: C++; tab-width: 8; 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 "mozilla/dom/MediaIPCUtils.h";
 
 include protocol PVideoDecoderManager;
+include PMediaDecoderParams;
 include LayersSurfaces;
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 
 namespace mozilla {
 namespace dom {
 
-struct MediaDataIPDL
-{
-  int64_t offset;
-  int64_t time;
-  int64_t timecode;
-  int64_t duration;
-  uint32_t frames;
-  bool keyframe;
-};
-
 struct VideoDataIPDL
 {
   MediaDataIPDL base;
   IntSize display;
   IntSize frameSize;
   SurfaceDescriptorGPUVideo sd;
   int32_t frameID;
 };
 
-struct MediaRawDataIPDL
-{
-  MediaDataIPDL base;
-  Shmem buffer;
-};
-
 // This protocol provides a way to use MediaDataDecoder across processes.
 // The parent side currently is only implemented to work with
 // Window Media Foundation, but can be extended easily to support other backends.
 // The child side runs in the content process, and the parent side runs in the
 // GPU process. We run a separate IPDL thread for both sides.
 async protocol PVideoDecoder
 {
   manager PVideoDecoderManager;
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDChild.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RDDChild.h"
+
+#include "mozilla/dom/MemoryReportRequest.h"
+#include "mozilla/ipc/CrashReporterHost.h"
+
+#ifdef MOZ_GECKO_PROFILER
+#include "ProfilerParent.h"
+#endif
+#include "RDDProcessHost.h"
+
+namespace mozilla {
+
+using namespace layers;
+
+RDDChild::RDDChild(RDDProcessHost* aHost)
+ : mHost(aHost),
+   mRDDReady(false)
+{
+  MOZ_COUNT_CTOR(RDDChild);
+}
+
+RDDChild::~RDDChild()
+{
+  MOZ_COUNT_DTOR(RDDChild);
+}
+
+void
+RDDChild::Init()
+{
+  SendInit();
+
+#ifdef MOZ_GECKO_PROFILER
+  Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid()));
+#endif
+}
+
+bool
+RDDChild::EnsureRDDReady()
+{
+  if (mRDDReady) {
+    return true;
+  }
+
+  mRDDReady = true;
+  return true;
+}
+
+mozilla::ipc::IPCResult
+RDDChild::RecvInitComplete()
+{
+  // We synchronously requested RDD parameters before this arrived.
+  if (mRDDReady) {
+    return IPC_OK();
+  }
+
+  mRDDReady = true;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RDDChild::RecvInitCrashReporter(Shmem&& aShmem, const NativeThreadId& aThreadId)
+{
+  mCrashReporter = MakeUnique<ipc::CrashReporterHost>(
+    GeckoProcessType_RDD,
+    aShmem,
+    aThreadId);
+
+  return IPC_OK();
+}
+
+bool
+RDDChild::SendRequestMemoryReport(const uint32_t& aGeneration,
+                                  const bool& aAnonymize,
+                                  const bool& aMinimizeMemoryUsage,
+                                  const MaybeFileDesc& aDMDFile)
+{
+  mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration);
+  Unused << PRDDChild::SendRequestMemoryReport(aGeneration,
+                                               aAnonymize,
+                                               aMinimizeMemoryUsage,
+                                               aDMDFile);
+  return true;
+}
+
+mozilla::ipc::IPCResult
+RDDChild::RecvAddMemoryReport(const MemoryReport& aReport)
+{
+  if (mMemoryReportRequest) {
+    mMemoryReportRequest->RecvReport(aReport);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RDDChild::RecvFinishMemoryReport(const uint32_t& aGeneration)
+{
+  if (mMemoryReportRequest) {
+    mMemoryReportRequest->Finish(aGeneration);
+    mMemoryReportRequest = nullptr;
+  }
+  return IPC_OK();
+}
+
+void
+RDDChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (aWhy == AbnormalShutdown) {
+    if (mCrashReporter) {
+      mCrashReporter->GenerateCrashReport(OtherPid());
+      mCrashReporter = nullptr;
+    }
+  }
+
+  mHost->OnChannelClosed();
+}
+
+class DeferredDeleteRDDChild : public Runnable
+{
+public:
+  explicit DeferredDeleteRDDChild(UniquePtr<RDDChild>&& aChild)
+    : Runnable("gfx::DeferredDeleteRDDChild")
+    , mChild(std::move(aChild))
+  {
+  }
+
+  NS_IMETHODIMP Run() override {
+    return NS_OK;
+  }
+
+private:
+  UniquePtr<RDDChild> mChild;
+};
+
+/* static */ void
+RDDChild::Destroy(UniquePtr<RDDChild>&& aChild)
+{
+  NS_DispatchToMainThread(new DeferredDeleteRDDChild(std::move(aChild)));
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDChild.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _include_dom_media_ipc_RDDChild_h_
+#define _include_dom_media_ipc_RDDChild_h_
+#include "mozilla/PRDDChild.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+namespace ipc {
+class CrashReporterHost;
+} // namespace ipc
+namespace dom {
+class MemoryReportRequestHost;
+} // namespace dom
+
+class RDDProcessHost;
+
+class RDDChild final : public PRDDChild
+{
+  typedef mozilla::dom::MemoryReportRequestHost MemoryReportRequestHost;
+
+public:
+  explicit RDDChild(RDDProcessHost* aHost);
+  ~RDDChild();
+
+  void Init();
+
+  bool EnsureRDDReady();
+
+  // PRDDChild overrides.
+  mozilla::ipc::IPCResult RecvInitComplete() override;
+  mozilla::ipc::IPCResult RecvInitCrashReporter(
+                              Shmem&& shmem,
+                              const NativeThreadId& aThreadId) override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult RecvAddMemoryReport(
+                              const MemoryReport& aReport) override;
+  mozilla::ipc::IPCResult RecvFinishMemoryReport(
+                              const uint32_t& aGeneration) override;
+
+  bool SendRequestMemoryReport(const uint32_t& aGeneration,
+                               const bool& aAnonymize,
+                               const bool& aMinimizeMemoryUsage,
+                               const MaybeFileDesc& aDMDFile);
+
+  static void Destroy(UniquePtr<RDDChild>&& aChild);
+
+private:
+  RDDProcessHost* mHost;
+  UniquePtr<ipc::CrashReporterHost> mCrashReporter;
+  UniquePtr<MemoryReportRequestHost> mMemoryReportRequest;
+  bool mRDDReady;
+};
+
+} // namespace mozilla
+
+#endif // _include_dom_media_ipc_RDDChild_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDParent.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RDDParent.h"
+
+#if defined(XP_WIN)
+# include <process.h>
+# include <dwrite.h>
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/HangDetails.h"
+#include "mozilla/RemoteDecoderManagerChild.h"
+#include "mozilla/RemoteDecoderManagerParent.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/MemoryReportRequest.h"
+#include "mozilla/ipc/CrashReporterClient.h"
+#include "mozilla/ipc/ProcessChild.h"
+
+#ifdef MOZ_GECKO_PROFILER
+#include "ChildProfilerController.h"
+#endif
+#include "nsDebugImpl.h"
+#include "nsThreadManager.h"
+#include "ProcessUtils.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+static RDDParent* sRDDParent;
+
+RDDParent::RDDParent()
+  : mLaunchTime(TimeStamp::Now())
+{
+  sRDDParent = this;
+}
+
+RDDParent::~RDDParent()
+{
+  sRDDParent = nullptr;
+}
+
+/* static */ RDDParent*
+RDDParent::GetSingleton()
+{
+  return sRDDParent;
+}
+
+bool
+RDDParent::Init(base::ProcessId aParentPid,
+                const char* aParentBuildID,
+                MessageLoop* aIOLoop,
+                IPC::Channel* aChannel)
+{
+  // Initialize the thread manager before starting IPC. Otherwise, messages
+  // may be posted to the main thread and we won't be able to process them.
+  if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
+    return false;
+  }
+
+  // Now it's safe to start IPC.
+  if (NS_WARN_IF(!Open(aChannel, aParentPid, aIOLoop))) {
+    return false;
+  }
+
+  nsDebugImpl::SetMultiprocessMode("RDD");
+
+  // This must be checked before any IPDL message, which may hit sentinel
+  // errors due to parent and content processes having different
+  // versions.
+  MessageChannel* channel = GetIPCChannel();
+  if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) {
+    // We need to quit this process if the buildID doesn't match the parent's.
+    // This can occur when an update occurred in the background.
+    ProcessChild::QuickExit();
+  }
+
+  // Init crash reporter support.
+  CrashReporterClient::InitSingleton(this);
+
+  if (NS_FAILED(NS_InitMinimalXPCOM())) {
+    return false;
+  }
+
+  mozilla::ipc::SetThisProcessName("RDD Process");
+  return true;
+}
+
+mozilla::ipc::IPCResult
+RDDParent::RecvInit()
+{
+  Unused << SendInitComplete();
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RDDParent::RecvInitProfiler(Endpoint<PProfilerChild>&& aEndpoint)
+{
+#ifdef MOZ_GECKO_PROFILER
+  mProfilerController = ChildProfilerController::Create(std::move(aEndpoint));
+#endif
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RDDParent::RecvNewContentRemoteDecoderManager(
+               Endpoint<PRemoteDecoderManagerParent>&& aEndpoint)
+{
+  if (!RemoteDecoderManagerParent::CreateForContent(std::move(aEndpoint))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RDDParent::RecvRequestMemoryReport(const uint32_t& aGeneration,
+                                   const bool& aAnonymize,
+                                   const bool& aMinimizeMemoryUsage,
+                                   const MaybeFileDesc& aDMDFile)
+{
+  nsPrintfCString processName("RDD (pid %u)", (unsigned)getpid());
+
+  mozilla::dom::MemoryReportRequestClient::Start(aGeneration,
+                                                 aAnonymize,
+                                                 aMinimizeMemoryUsage,
+                                                 aDMDFile,
+                                                 processName);
+  return IPC_OK();
+}
+
+void
+RDDParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  if (AbnormalShutdown == aWhy) {
+    NS_WARNING("Shutting down RDD process early due to a crash!");
+    ProcessChild::QuickExit();
+  }
+
+#ifndef NS_FREE_PERMANENT_DATA
+  // No point in going through XPCOM shutdown because we don't keep persistent
+  // state.
+  ProcessChild::QuickExit();
+#endif
+
+#ifdef MOZ_GECKO_PROFILER
+  if (mProfilerController) {
+    mProfilerController->Shutdown();
+    mProfilerController = nullptr;
+  }
+#endif
+
+  CrashReporterClient::DestroySingleton();
+  XRE_ShutdownChildProcess();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDParent.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _include_dom_media_ipc_RDDParent_h__
+#define _include_dom_media_ipc_RDDParent_h__
+#include "mozilla/PRDDParent.h"
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+class TimeStamp;
+class ChildProfilerController;
+
+class RDDParent final : public PRDDParent
+{
+public:
+  RDDParent();
+  ~RDDParent();
+
+  static RDDParent* GetSingleton();
+
+  bool Init(base::ProcessId aParentPid,
+            const char* aParentBuildID,
+            MessageLoop* aIOLoop,
+            IPC::Channel* aChannel);
+
+  mozilla::ipc::IPCResult RecvInit() override;
+  mozilla::ipc::IPCResult RecvInitProfiler(
+                              Endpoint<PProfilerChild>&& aEndpoint) override;
+
+  mozilla::ipc::IPCResult RecvNewContentRemoteDecoderManager(
+                    Endpoint<PRemoteDecoderManagerParent>&& aEndpoint) override;
+  mozilla::ipc::IPCResult RecvRequestMemoryReport(
+                                        const uint32_t& generation,
+                                        const bool& anonymize,
+                                        const bool& minimizeMemoryUsage,
+                                        const MaybeFileDesc& DMDFile) override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  const TimeStamp mLaunchTime;
+#ifdef MOZ_GECKO_PROFILER
+  RefPtr<ChildProfilerController> mProfilerController;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // _include_dom_media_ipc_RDDParent_h__
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDProcessHost.cpp
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RDDProcessHost.h"
+
+#include "chrome/common/process_watcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs.h"
+
+#include "RDDChild.h"
+
+
+namespace mozilla {
+
+using namespace ipc;
+
+RDDProcessHost::RDDProcessHost(Listener* aListener)
+ : GeckoChildProcessHost(GeckoProcessType_RDD),
+   mListener(aListener),
+   mTaskFactory(this),
+   mLaunchPhase(LaunchPhase::Unlaunched),
+   mProcessToken(0),
+   mShutdownRequested(false),
+   mChannelClosed(false)
+{
+  MOZ_COUNT_CTOR(RDDProcessHost);
+}
+
+RDDProcessHost::~RDDProcessHost()
+{
+  MOZ_COUNT_DTOR(RDDProcessHost);
+}
+
+bool
+RDDProcessHost::Launch(StringVector aExtraOpts)
+{
+  MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
+  MOZ_ASSERT(!mRDDChild);
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+  mSandboxLevel = Preferences::GetInt("security.sandbox.rdd.level");
+#endif
+
+  mLaunchPhase = LaunchPhase::Waiting;
+  mLaunchTime = TimeStamp::Now();
+
+  if (!GeckoChildProcessHost::AsyncLaunch(aExtraOpts)) {
+    mLaunchPhase = LaunchPhase::Complete;
+    return false;
+  }
+  return true;
+}
+
+bool
+RDDProcessHost::WaitForLaunch()
+{
+  if (mLaunchPhase == LaunchPhase::Complete) {
+    return !!mRDDChild;
+  }
+
+  int32_t timeoutMs = StaticPrefs::MediaRddProcessStartupTimeoutMs();
+
+  // If one of the following environment variables are set we can
+  // effectively ignore the timeout - as we can guarantee the RDD
+  // process will be terminated
+  if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") ||
+      PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) {
+    timeoutMs = 0;
+  }
+
+  // Our caller expects the connection to be finished after we return, so we
+  // immediately set up the IPDL actor and fire callbacks. The IO thread will
+  // still dispatch a notification to the main thread - we'll just ignore it.
+  bool result = GeckoChildProcessHost::WaitUntilConnected(timeoutMs);
+  InitAfterConnect(result);
+  return result;
+}
+
+void
+RDDProcessHost::OnChannelConnected(int32_t peer_pid)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  GeckoChildProcessHost::OnChannelConnected(peer_pid);
+
+  // Post a task to the main thread. Take the lock because mTaskFactory is not
+  // thread-safe.
+  RefPtr<Runnable> runnable;
+  {
+    MonitorAutoLock lock(mMonitor);
+    runnable =
+        mTaskFactory.NewRunnableMethod(&RDDProcessHost::OnChannelConnectedTask);
+  }
+  NS_DispatchToMainThread(runnable);
+}
+
+void
+RDDProcessHost::OnChannelError()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  GeckoChildProcessHost::OnChannelError();
+
+  // Post a task to the main thread. Take the lock because mTaskFactory is not
+  // thread-safe.
+  RefPtr<Runnable> runnable;
+  {
+    MonitorAutoLock lock(mMonitor);
+    runnable =
+        mTaskFactory.NewRunnableMethod(&RDDProcessHost::OnChannelErrorTask);
+  }
+  NS_DispatchToMainThread(runnable);
+}
+
+void
+RDDProcessHost::OnChannelConnectedTask()
+{
+  if (mLaunchPhase == LaunchPhase::Waiting) {
+    InitAfterConnect(true);
+  }
+}
+
+void
+RDDProcessHost::OnChannelErrorTask()
+{
+  if (mLaunchPhase == LaunchPhase::Waiting) {
+    InitAfterConnect(false);
+  }
+}
+
+static uint64_t sRDDProcessTokenCounter = 0;
+
+void
+RDDProcessHost::InitAfterConnect(bool aSucceeded)
+{
+  MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
+  MOZ_ASSERT(!mRDDChild);
+
+  mLaunchPhase = LaunchPhase::Complete;
+
+  if (aSucceeded) {
+    mProcessToken = ++sRDDProcessTokenCounter;
+    mRDDChild = MakeUnique<RDDChild>(this);
+    DebugOnly<bool> rv =
+      mRDDChild->Open(GetChannel(), base::GetProcId(GetChildProcessHandle()));
+    MOZ_ASSERT(rv);
+
+    mRDDChild->Init();
+  }
+
+  if (mListener) {
+    mListener->OnProcessLaunchComplete(this);
+  }
+}
+
+void
+RDDProcessHost::Shutdown()
+{
+  MOZ_ASSERT(!mShutdownRequested);
+
+  mListener = nullptr;
+
+  if (mRDDChild) {
+    // OnChannelClosed uses this to check if the shutdown was expected or
+    // unexpected.
+    mShutdownRequested = true;
+
+    // The channel might already be closed if we got here unexpectedly.
+    if (!mChannelClosed) {
+      mRDDChild->Close();
+    }
+
+#ifndef NS_FREE_PERMANENT_DATA
+    // No need to communicate shutdown, the RDD process doesn't need to
+    // communicate anything back.
+    KillHard("NormalShutdown");
+#endif
+
+    // If we're shutting down unexpectedly, we're in the middle of handling an
+    // ActorDestroy for PRDDChild, which is still on the stack. We'll return
+    // back to OnChannelClosed.
+    //
+    // Otherwise, we'll wait for OnChannelClose to be called whenever PRDDChild
+    // acknowledges shutdown.
+    return;
+  }
+
+  DestroyProcess();
+}
+
+void
+RDDProcessHost::OnChannelClosed()
+{
+  mChannelClosed = true;
+
+  if (!mShutdownRequested && mListener) {
+    // This is an unclean shutdown. Notify our listener that we're going away.
+    mListener->OnProcessUnexpectedShutdown(this);
+  } else {
+    DestroyProcess();
+  }
+
+  // Release the actor.
+  RDDChild::Destroy(std::move(mRDDChild));
+  MOZ_ASSERT(!mRDDChild);
+}
+
+void
+RDDProcessHost::KillHard(const char* aReason)
+{
+  ProcessHandle handle = GetChildProcessHandle();
+  if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER, false)) {
+    NS_WARNING("failed to kill subprocess!");
+  }
+
+  SetAlreadyDead();
+}
+
+uint64_t
+RDDProcessHost::GetProcessToken() const
+{
+  return mProcessToken;
+}
+
+static void
+RDDDelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess)
+{
+  XRE_GetIOMessageLoop()->
+    PostTask(
+        mozilla::MakeAndAddRef<DeleteTask<GeckoChildProcessHost>>(aSubprocess));
+}
+
+void
+RDDProcessHost::KillProcess()
+{
+  KillHard("DiagnosticKill");
+}
+
+void
+RDDProcessHost::DestroyProcess()
+{
+  // Cancel all tasks. We don't want anything triggering after our caller
+  // expects this to go away.
+  {
+    MonitorAutoLock lock(mMonitor);
+    mTaskFactory.RevokeAll();
+  }
+
+  MessageLoop::current()->
+    PostTask(NewRunnableFunction("DestroyProcessRunnable",
+                                 RDDDelayedDeleteSubprocess,
+                                 this));
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDProcessHost.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _include_dom_media_ipc_RDDProcessHost_h_
+#define _include_dom_media_ipc_RDDProcessHost_h_
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/TaskFactory.h"
+
+class nsITimer;
+
+namespace mozilla {
+
+class RDDChild;
+
+// RDDProcessHost is the "parent process" container for a subprocess handle and
+// IPC connection. It owns the parent process IPDL actor, which in this case,
+// is a RDDChild.
+//
+// RDDProcessHosts are allocated and managed by RDDProcessManager. For all
+// intents and purposes it is a singleton, though more than one may be allocated
+// at a time due to its shutdown being asynchronous.
+class RDDProcessHost final : public mozilla::ipc::GeckoChildProcessHost
+{
+  friend class RDDChild;
+
+public:
+  class Listener {
+  public:
+    virtual void OnProcessLaunchComplete(RDDProcessHost* aHost)
+    {}
+
+    // The RDDProcessHost has unexpectedly shutdown or had its connection
+    // severed. This is not called if an error occurs after calling
+    // Shutdown().
+    virtual void OnProcessUnexpectedShutdown(RDDProcessHost* aHost)
+    {}
+  };
+
+  explicit RDDProcessHost(Listener* listener);
+  ~RDDProcessHost();
+
+  // Launch the subprocess asynchronously. On failure, false is returned.
+  // Otherwise, true is returned, and the OnProcessLaunchComplete listener
+  // callback will be invoked either when a connection has been established, or
+  // if a connection could not be established due to an asynchronous error.
+  //
+  // @param aExtraOpts (StringVector)
+  //        Extra options to pass to the subprocess.
+  bool Launch(StringVector aExtraOpts);
+
+  // If the process is being launched, block until it has launched and
+  // connected. If a launch task is pending, it will fire immediately.
+  //
+  // Returns true if the process is successfully connected; false otherwise.
+  bool WaitForLaunch();
+
+  // Inform the process that it should clean up its resources and shut
+  // down. This initiates an asynchronous shutdown sequence. After this
+  // method returns, it is safe for the caller to forget its pointer to
+  // the RDDProcessHost.
+  //
+  // After this returns, the attached Listener is no longer used.
+  void Shutdown();
+
+  // Return the actor for the top-level actor of the process. If the process
+  // has not connected yet, this returns null.
+  RDDChild* GetActor() const {
+    return mRDDChild.get();
+  }
+
+  // Return a unique id for this process, guaranteed not to be shared with any
+  // past or future instance of RDDProcessHost.
+  uint64_t GetProcessToken() const;
+
+  bool IsConnected() const {
+    return !!mRDDChild;
+  }
+
+  // Return the time stamp for when we tried to launch the RDD process.
+  // This is currently used for Telemetry so that we can determine how
+  // long RDD processes take to spin up. Note this doesn't denote a
+  // successful launch, just when we attempted launch.
+  TimeStamp GetLaunchTime() const {
+    return mLaunchTime;
+  }
+
+  // Called on the IO thread.
+  void OnChannelConnected(int32_t peer_pid) override;
+  void OnChannelError() override;
+
+  void SetListener(Listener* aListener);
+
+  // Used for tests and diagnostics
+  void KillProcess();
+
+private:
+  // Called on the main thread.
+  void OnChannelConnectedTask();
+  void OnChannelErrorTask();
+
+  // Called on the main thread after a connection has been established.
+  void InitAfterConnect(bool aSucceeded);
+
+  // Called on the main thread when the mRDDChild actor is shutting down.
+  void OnChannelClosed();
+
+  // Kill the remote process, triggering IPC shutdown.
+  void KillHard(const char* aReason);
+
+  void DestroyProcess();
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(RDDProcessHost);
+
+  Listener* mListener;
+  mozilla::ipc::TaskFactory<RDDProcessHost> mTaskFactory;
+
+  enum class LaunchPhase {
+    Unlaunched,
+    Waiting,
+    Complete
+  };
+  LaunchPhase mLaunchPhase;
+
+  UniquePtr<RDDChild> mRDDChild;
+  uint64_t mProcessToken;
+
+  bool mShutdownRequested;
+  bool mChannelClosed;
+
+  TimeStamp mLaunchTime;
+};
+
+} // namespace mozilla
+
+#endif // _include_dom_media_ipc_RDDProcessHost_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDProcessImpl.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RDDProcessImpl.h"
+
+#include "mozilla/ipc/IOThreadChild.h"
+
+#if defined(OS_WIN) && defined(MOZ_SANDBOX)
+#include "mozilla/sandboxTarget.h"
+#endif
+
+namespace mozilla {
+
+using namespace ipc;
+
+RDDProcessImpl::RDDProcessImpl(ProcessId aParentPid)
+ : ProcessChild(aParentPid)
+{
+}
+
+RDDProcessImpl::~RDDProcessImpl()
+{
+}
+
+bool
+RDDProcessImpl::Init(int aArgc, char* aArgv[])
+{
+#if defined(MOZ_SANDBOX) && defined(OS_WIN)
+  mozilla::SandboxTarget::Instance()->StartSandbox();
+#endif
+  char* parentBuildID = nullptr;
+  for (int i = 1; i < aArgc; i++) {
+    if (strcmp(aArgv[i], "-parentBuildID") == 0) {
+      parentBuildID = aArgv[i + 1];
+    }
+  }
+
+  return mRDD.Init(ParentPid(),
+                   parentBuildID,
+                   IOThreadChild::message_loop(),
+                   IOThreadChild::channel());
+}
+
+void
+RDDProcessImpl::CleanUp()
+{
+  NS_ShutdownXPCOM(nullptr);
+}
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDProcessImpl.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _include_dom_media_ipc_RDDProcessImpl_h__
+#define _include_dom_media_ipc_RDDProcessImpl_h__
+#include "mozilla/ipc/ProcessChild.h"
+
+#if defined(XP_WIN)
+# include "mozilla/mscom/MainThreadRuntime.h"
+#endif
+
+#include "RDDParent.h"
+
+namespace mozilla {
+
+// This class owns the subprocess instance of a PRDD - which in this case,
+// is a RDDParent. It is instantiated as a singleton in XRE_InitChildProcess.
+class RDDProcessImpl final : public ipc::ProcessChild
+{
+public:
+  explicit RDDProcessImpl(ProcessId aParentPid);
+  ~RDDProcessImpl();
+
+  bool Init(int aArgc, char* aArgv[]) override;
+  void CleanUp() override;
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(RDDProcessImpl);
+
+  RDDParent mRDD;
+
+#if defined(XP_WIN)
+  // This object initializes and configures COM.
+  mozilla::mscom::MainThreadRuntime mCOMRuntime;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // _include_dom_media_ipc_RDDProcessImpl_h__
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDProcessManager.cpp
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RDDProcessManager.h"
+
+#include "mozilla/RemoteDecoderManagerChild.h"
+#include "mozilla/RemoteDecoderManagerParent.h"
+#include "mozilla/StaticPrefs.h"
+#include "mozilla/dom/ContentParent.h"
+
+#include "nsAppRunner.h"
+#include "nsContentUtils.h"
+#include "RDDChild.h"
+#include "RDDProcessHost.h"
+
+namespace mozilla {
+
+using namespace mozilla::layers;
+
+static StaticAutoPtr<RDDProcessManager> sRDDSingleton;
+
+RDDProcessManager*
+RDDProcessManager::Get()
+{
+  return sRDDSingleton;
+}
+
+void
+RDDProcessManager::Initialize()
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  sRDDSingleton = new RDDProcessManager();
+}
+
+void
+RDDProcessManager::Shutdown()
+{
+  sRDDSingleton = nullptr;
+}
+
+RDDProcessManager::RDDProcessManager()
+ : mTaskFactory(this),
+   mNumProcessAttempts(0),
+   mProcess(nullptr),
+   mProcessToken(0),
+   mRDDChild(nullptr)
+{
+  MOZ_COUNT_CTOR(RDDProcessManager);
+
+  mObserver = new Observer(this);
+  nsContentUtils::RegisterShutdownObserver(mObserver);
+}
+
+RDDProcessManager::~RDDProcessManager()
+{
+  MOZ_COUNT_DTOR(RDDProcessManager);
+
+  // The RDD process should have already been shut down.
+  MOZ_ASSERT(!mProcess && !mRDDChild);
+
+  // We should have already removed observers.
+  MOZ_ASSERT(!mObserver);
+}
+
+NS_IMPL_ISUPPORTS(RDDProcessManager::Observer, nsIObserver);
+
+RDDProcessManager::Observer::Observer(RDDProcessManager* aManager)
+ : mManager(aManager)
+{
+}
+
+NS_IMETHODIMP
+RDDProcessManager::Observer::Observe(nsISupports* aSubject,
+                                     const char* aTopic,
+                                     const char16_t* aData)
+{
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    mManager->OnXPCOMShutdown();
+  }
+  return NS_OK;
+}
+
+void
+RDDProcessManager::OnXPCOMShutdown()
+{
+  if (mObserver) {
+    nsContentUtils::UnregisterShutdownObserver(mObserver);
+    mObserver = nullptr;
+  }
+
+  CleanShutdown();
+}
+
+void
+RDDProcessManager::LaunchRDDProcess()
+{
+  if (mProcess) {
+    return;
+  }
+
+  mNumProcessAttempts++;
+
+  std::vector<std::string> extraArgs;
+  nsCString parentBuildID(mozilla::PlatformBuildID());
+  extraArgs.push_back("-parentBuildID");
+  extraArgs.push_back(parentBuildID.get());
+
+  // The subprocess is launched asynchronously, so we wait for a callback to
+  // acquire the IPDL actor.
+  mProcess = new RDDProcessHost(this);
+  if (!mProcess->Launch(extraArgs)) {
+    DestroyProcess();
+  }
+}
+
+bool
+RDDProcessManager::EnsureRDDReady()
+{
+  if (mProcess && !mProcess->IsConnected()) {
+    if (!mProcess->WaitForLaunch()) {
+      // If this fails, we should have fired OnProcessLaunchComplete and
+      // removed the process.
+      MOZ_ASSERT(!mProcess && !mRDDChild);
+      return false;
+    }
+  }
+
+  if (mRDDChild) {
+    if (mRDDChild->EnsureRDDReady()) {
+      return true;
+    }
+
+    // If the initialization above fails, we likely have a RDD process teardown
+    // waiting in our message queue (or will soon).
+    DestroyProcess();
+  }
+
+  return false;
+}
+
+void
+RDDProcessManager::OnProcessLaunchComplete(RDDProcessHost* aHost)
+{
+  MOZ_ASSERT(mProcess && mProcess == aHost);
+
+  if (!mProcess->IsConnected()) {
+    DestroyProcess();
+    return;
+  }
+
+  mRDDChild = mProcess->GetActor();
+  mProcessToken = mProcess->GetProcessToken();
+
+  CrashReporter::AnnotateCrashReport(
+    CrashReporter::Annotation::RDDProcessStatus,
+    NS_LITERAL_CSTRING("Running"));
+}
+
+void
+RDDProcessManager::OnProcessUnexpectedShutdown(RDDProcessHost* aHost)
+{
+  MOZ_ASSERT(mProcess && mProcess == aHost);
+
+  DestroyProcess();
+}
+
+void
+RDDProcessManager::NotifyRemoteActorDestroyed(const uint64_t& aProcessToken)
+{
+  if (!NS_IsMainThread()) {
+    RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod(
+      &RDDProcessManager::NotifyRemoteActorDestroyed, aProcessToken);
+    NS_DispatchToMainThread(task.forget());
+    return;
+  }
+
+  if (mProcessToken != aProcessToken) {
+    // This token is for an older process; we can safely ignore it.
+    return;
+  }
+
+  // One of the bridged top-level actors for the RDD process has been
+  // prematurely terminated, and we're receiving a notification. This
+  // can happen if the ActorDestroy for a bridged protocol fires
+  // before the ActorDestroy for PRDDChild.
+  OnProcessUnexpectedShutdown(mProcess);
+}
+
+void
+RDDProcessManager::CleanShutdown()
+{
+  DestroyProcess();
+}
+
+void
+RDDProcessManager::KillProcess()
+{
+  if (!mProcess) {
+    return;
+  }
+
+  mProcess->KillProcess();
+}
+
+void
+RDDProcessManager::DestroyProcess()
+{
+  if (!mProcess) {
+    return;
+  }
+
+  mProcess->Shutdown();
+  mProcessToken = 0;
+  mProcess = nullptr;
+  mRDDChild = nullptr;
+
+  CrashReporter::AnnotateCrashReport(
+    CrashReporter::Annotation::RDDProcessStatus,
+    NS_LITERAL_CSTRING("Destroyed"));
+}
+
+bool
+RDDProcessManager::CreateContentBridge(
+       base::ProcessId aOtherProcess,
+       ipc::Endpoint<PRemoteDecoderManagerChild>* aOutRemoteDecoderManager)
+{
+  if (!EnsureRDDReady() ||
+      !StaticPrefs::MediaRddProcessEnabled()) {
+    return false;
+  }
+
+  ipc::Endpoint<PRemoteDecoderManagerParent> parentPipe;
+  ipc::Endpoint<PRemoteDecoderManagerChild> childPipe;
+
+  nsresult rv = PRemoteDecoderManager::CreateEndpoints(
+    mRDDChild->OtherPid(),
+    aOtherProcess,
+    &parentPipe,
+    &childPipe);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(sPDMLog,
+            LogLevel::Debug,
+            ("Could not create content remote decoder: %d", int(rv)));
+    return false;
+  }
+
+  mRDDChild->SendNewContentRemoteDecoderManager(std::move(parentPipe));
+
+  *aOutRemoteDecoderManager = std::move(childPipe);
+  return true;
+}
+
+base::ProcessId
+RDDProcessManager::RDDProcessPid()
+{
+  base::ProcessId rddPid = mRDDChild
+                           ? mRDDChild->OtherPid()
+                           : -1;
+  return rddPid;
+}
+
+class RDDMemoryReporter : public MemoryReportingProcess
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RDDMemoryReporter, override)
+
+  bool IsAlive() const override {
+    return !!GetChild();
+  }
+
+  bool SendRequestMemoryReport(const uint32_t& aGeneration,
+                               const bool& aAnonymize,
+                               const bool& aMinimizeMemoryUsage,
+                               const dom::MaybeFileDesc& aDMDFile) override
+  {
+    RDDChild* child = GetChild();
+    if (!child) {
+      return false;
+    }
+
+    return child->SendRequestMemoryReport(
+      aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile);
+  }
+
+  int32_t Pid() const override {
+    if (RDDChild* child = GetChild()) {
+      return (int32_t)child->OtherPid();
+    }
+    return 0;
+  }
+
+private:
+  RDDChild* GetChild() const {
+    if (RDDProcessManager* rddpm = RDDProcessManager::Get()) {
+      if (RDDChild* child = rddpm->GetRDDChild()) {
+        return child;
+      }
+    }
+    return nullptr;
+  }
+
+protected:
+  ~RDDMemoryReporter() = default;
+};
+
+RefPtr<MemoryReportingProcess>
+RDDProcessManager::GetProcessMemoryReporter()
+{
+  if (!EnsureRDDReady()) {
+    return nullptr;
+  }
+  return new RDDMemoryReporter();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RDDProcessManager.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _include_dom_media_ipc_RDDProcessManager_h_
+#define _include_dom_media_ipc_RDDProcessManager_h_
+#include "mozilla/RDDProcessHost.h"
+
+#include "mozilla/ipc/TaskFactory.h"
+
+namespace mozilla {
+
+class MemoryReportingProcess;
+class PRemoteDecoderManagerChild;
+class RDDChild;
+
+// The RDDProcessManager is a singleton responsible for creating RDD-bound
+// objects that may live in another process. Currently, it provides access
+// to the RDD process via ContentParent.
+class RDDProcessManager final : public RDDProcessHost::Listener
+{
+public:
+  static void Initialize();
+  static void Shutdown();
+  static RDDProcessManager* Get();
+
+  ~RDDProcessManager();
+
+  // If not using a RDD process, launch a new RDD process asynchronously.
+  void LaunchRDDProcess();
+
+  // Ensure that RDD-bound methods can be used. If no RDD process is being
+  // used, or one is launched and ready, this function returns immediately.
+  // Otherwise it blocks until the RDD process has finished launching.
+  bool EnsureRDDReady();
+
+  bool CreateContentBridge(
+    base::ProcessId aOtherProcess,
+    mozilla::ipc::Endpoint<PRemoteDecoderManagerChild>* 
+                                                     aOutRemoteDecoderManager);
+
+  void OnProcessLaunchComplete(RDDProcessHost* aHost) override;
+  void OnProcessUnexpectedShutdown(RDDProcessHost* aHost) override;
+
+  // Notify the RDDProcessManager that a top-level PRDD protocol has been
+  // terminated. This may be called from any thread.
+  void NotifyRemoteActorDestroyed(const uint64_t& aProcessToken);
+
+  // Used for tests and diagnostics
+  void KillProcess();
+
+  // Returns -1 if there is no RDD process, or the platform pid for it.
+  base::ProcessId RDDProcessPid();
+
+  // If a RDD process is present, create a MemoryReportingProcess object.
+  // Otherwise, return null.
+  RefPtr<MemoryReportingProcess> GetProcessMemoryReporter();
+
+  // Returns access to the PRDD protocol if a RDD process is present.
+  RDDChild* GetRDDChild() {
+    return mRDDChild;
+  }
+
+  // Returns whether or not a RDD process was ever launched.
+  bool AttemptedRDDProcess() const {
+    return mNumProcessAttempts > 0;
+  }
+
+private:
+  // Called from our xpcom-shutdown observer.
+  void OnXPCOMShutdown();
+
+  RDDProcessManager();
+
+  // Shutdown the RDD process.
+  void CleanShutdown();
+  void DestroyProcess();
+
+  DISALLOW_COPY_AND_ASSIGN(RDDProcessManager);
+
+  class Observer final : public nsIObserver {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+    explicit Observer(RDDProcessManager* aManager);
+
+  protected:
+    ~Observer() {}
+
+    RDDProcessManager* mManager;
+  };
+  friend class Observer;
+
+private:
+  RefPtr<Observer> mObserver;
+  mozilla::ipc::TaskFactory<RDDProcessManager> mTaskFactory;
+  uint32_t mNumProcessAttempts;
+
+  // Fields that are associated with the current RDD process.
+  RDDProcessHost* mProcess;
+  uint64_t mProcessToken;
+  RDDChild* mRDDChild;
+};
+
+} // namespace mozilla
+
+#endif // _include_dom_media_ipc_RDDProcessManager_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerChild.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteDecoderManagerChild.h"
+
+#include "base/task.h"
+
+#include "RemoteVideoDecoderChild.h"
+
+namespace mozilla {
+
+// Only modified on the main-thread
+StaticRefPtr<nsIThread> sRemoteDecoderManagerChildThread;
+StaticRefPtr<AbstractThread> sRemoteDecoderManagerChildAbstractThread;
+
+// Only accessed from sRemoteDecoderManagerChildThread
+static StaticRefPtr<RemoteDecoderManagerChild> sRemoteDecoderManagerChild;
+
+/* static */ void
+RemoteDecoderManagerChild::InitializeThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sRemoteDecoderManagerChildThread) {
+    RefPtr<nsIThread> childThread;
+    nsresult rv = NS_NewNamedThread("RemVidChild", getter_AddRefs(childThread));
+    NS_ENSURE_SUCCESS_VOID(rv);
+    sRemoteDecoderManagerChildThread = childThread;
+
+    sRemoteDecoderManagerChildAbstractThread =
+      AbstractThread::CreateXPCOMThreadWrapper(childThread, false);
+  }
+}
+
+/* static */ void
+RemoteDecoderManagerChild::InitForContent(
+    Endpoint<PRemoteDecoderManagerChild>&& aVideoManager)
+{
+  InitializeThread();
+  sRemoteDecoderManagerChildThread->Dispatch(
+      NewRunnableFunction("InitForContentRunnable",
+                          &Open,
+                          std::move(aVideoManager)),
+      NS_DISPATCH_NORMAL);
+}
+
+/* static */ void
+RemoteDecoderManagerChild::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sRemoteDecoderManagerChildThread) {
+    sRemoteDecoderManagerChildThread->Dispatch(
+        NS_NewRunnableFunction("dom::RemoteDecoderManagerChild::Shutdown",
+                               []() {
+                                 if (sRemoteDecoderManagerChild &&
+                                     sRemoteDecoderManagerChild->CanSend()) {
+                                   sRemoteDecoderManagerChild->Close();
+                                   sRemoteDecoderManagerChild = nullptr;
+                                 }
+                               }),
+        NS_DISPATCH_NORMAL);
+
+    sRemoteDecoderManagerChildAbstractThread = nullptr;
+    sRemoteDecoderManagerChildThread->Shutdown();
+    sRemoteDecoderManagerChildThread = nullptr;
+  }
+}
+
+/* static */ RemoteDecoderManagerChild*
+RemoteDecoderManagerChild::GetSingleton()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+  return sRemoteDecoderManagerChild;
+}
+
+/* static */ nsIThread*
+RemoteDecoderManagerChild::GetManagerThread()
+{
+  return sRemoteDecoderManagerChildThread;
+}
+
+/* static */ AbstractThread*
+RemoteDecoderManagerChild::GetManagerAbstractThread()
+{
+  return sRemoteDecoderManagerChildAbstractThread;
+}
+
+PRemoteVideoDecoderChild*
+RemoteDecoderManagerChild::AllocPRemoteVideoDecoderChild(
+    const VideoInfo& /* not used */,
+    const float& /* not used */,
+    const CreateDecoderParams::OptionSet& /* not used */,
+    bool* /* not used */,
+    nsCString* /* not used */)
+{
+  return new RemoteVideoDecoderChild();
+}
+
+bool
+RemoteDecoderManagerChild::DeallocPRemoteVideoDecoderChild(
+    PRemoteVideoDecoderChild* actor)
+{
+  RemoteVideoDecoderChild* child = static_cast<RemoteVideoDecoderChild*>(actor);
+  child->IPDLActorDestroyed();
+  return true;
+}
+
+void
+RemoteDecoderManagerChild::Open(
+    Endpoint<PRemoteDecoderManagerChild>&& aEndpoint)
+{
+  sRemoteDecoderManagerChild = nullptr;
+  if (aEndpoint.IsValid()) {
+    RefPtr<RemoteDecoderManagerChild> manager = new RemoteDecoderManagerChild();
+    if (aEndpoint.Bind(manager)) {
+      sRemoteDecoderManagerChild = manager;
+      manager->InitIPDL();
+    }
+  }
+}
+
+void
+RemoteDecoderManagerChild::InitIPDL()
+{
+  mCanSend = true;
+  mIPDLSelfRef = this;
+}
+
+void
+RemoteDecoderManagerChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mCanSend = false;
+}
+
+void
+RemoteDecoderManagerChild::DeallocPRemoteDecoderManagerChild()
+{
+  mIPDLSelfRef = nullptr;
+}
+
+bool
+RemoteDecoderManagerChild::CanSend()
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+  return mCanSend;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerChild.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteDecoderManagerChild_h
+#define include_dom_media_ipc_RemoteDecoderManagerChild_h
+#include "mozilla/PRemoteDecoderManagerChild.h"
+
+namespace mozilla {
+
+class RemoteDecoderManagerChild final : public PRemoteDecoderManagerChild
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerChild)
+
+  // Can only be called from the manager thread
+  static RemoteDecoderManagerChild* GetSingleton();
+
+  // Can be called from any thread.
+  static nsIThread* GetManagerThread();
+  static AbstractThread* GetManagerAbstractThread();
+
+  // Main thread only
+  static void InitForContent(
+                  Endpoint<PRemoteDecoderManagerChild>&& aVideoManager);
+  static void Shutdown();
+
+  bool CanSend();
+
+protected:
+  void InitIPDL();
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+  void DeallocPRemoteDecoderManagerChild() override;
+
+  PRemoteVideoDecoderChild* AllocPRemoteVideoDecoderChild(
+                                const VideoInfo& aVideoInfo,
+                                const float& aFramerate,
+                                const CreateDecoderParams::OptionSet& aOptions,
+                                bool* aSuccess,
+                                nsCString* aErrorDescription) override;
+  bool DeallocPRemoteVideoDecoderChild(
+           PRemoteVideoDecoderChild* actor) override;
+
+private:
+  // Main thread only
+  static void InitializeThread();
+
+  RemoteDecoderManagerChild() = default;
+  ~RemoteDecoderManagerChild() = default;
+
+  static void Open(Endpoint<PRemoteDecoderManagerChild>&& aEndpoint);
+
+  RefPtr<RemoteDecoderManagerChild> mIPDLSelfRef;
+
+  // Should only ever be accessed on the manager thread.
+  bool mCanSend = false;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteDecoderManagerChild_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerParent.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteDecoderManagerParent.h"
+
+#if XP_WIN
+#include <objbase.h>
+#endif
+
+#include "RemoteVideoDecoderParent.h"
+#include "VideoUtils.h" // for MediaThreadType
+
+namespace mozilla {
+
+StaticRefPtr<nsIThread> sRemoteDecoderManagerParentThread;
+StaticRefPtr<TaskQueue> sRemoteDecoderManagerTaskQueue;
+
+class RemoteDecoderManagerThreadHolder
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerThreadHolder)
+
+public:
+  RemoteDecoderManagerThreadHolder() { }
+
+private:
+  ~RemoteDecoderManagerThreadHolder()
+  {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "dom::RemoteDecoderManagerThreadHolder::~RemoteDecoderManagerThreadHolder",
+      []() {
+        sRemoteDecoderManagerParentThread->Shutdown();
+        sRemoteDecoderManagerParentThread = nullptr;
+      }));
+  }
+};
+
+StaticRefPtr<RemoteDecoderManagerThreadHolder>
+    sRemoteDecoderManagerParentThreadHolder;
+
+class RemoteDecoderManagerThreadShutdownObserver : public nsIObserver
+{
+  virtual ~RemoteDecoderManagerThreadShutdownObserver() = default;
+public:
+  RemoteDecoderManagerThreadShutdownObserver() = default;
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                     const char16_t* aData) override
+  {
+    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+
+    RemoteDecoderManagerParent::ShutdownThreads();
+    return NS_OK;
+  }
+};
+NS_IMPL_ISUPPORTS(RemoteDecoderManagerThreadShutdownObserver, nsIObserver);
+
+bool
+RemoteDecoderManagerParent::StartupThreads()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sRemoteDecoderManagerParentThread) {
+    return true;
+  }
+
+  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+  if (!observerService) {
+    return false;
+  }
+
+  RefPtr<nsIThread> managerThread;
+  nsresult rv =
+      NS_NewNamedThread("RemVidParent", getter_AddRefs(managerThread));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  sRemoteDecoderManagerParentThread = managerThread;
+  sRemoteDecoderManagerParentThreadHolder =
+      new RemoteDecoderManagerThreadHolder();
+#if XP_WIN
+  sRemoteDecoderManagerParentThread->Dispatch(
+      NS_NewRunnableFunction(
+          "RemoteDecoderManagerParent::StartupThreads",
+          []() {
+            DebugOnly<HRESULT> hr = CoInitializeEx(0, COINIT_MULTITHREADED);
+            MOZ_ASSERT(SUCCEEDED(hr));
+          }),
+      NS_DISPATCH_NORMAL);
+#endif
+
+  sRemoteDecoderManagerTaskQueue = new TaskQueue(
+      managerThread.forget(),
+      "RemoteDecoderManagerParent::sRemoteDecoderManagerTaskQueue");
+
+  auto* obs = new RemoteDecoderManagerThreadShutdownObserver();
+  observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  return true;
+}
+
+void
+RemoteDecoderManagerParent::ShutdownThreads()
+{
+  sRemoteDecoderManagerTaskQueue = nullptr;
+
+  sRemoteDecoderManagerParentThreadHolder = nullptr;
+  while (sRemoteDecoderManagerParentThread) {
+    NS_ProcessNextEvent(nullptr, true);
+  }
+}
+
+bool
+RemoteDecoderManagerParent::OnManagerThread()
+{
+  return NS_GetCurrentThread() == sRemoteDecoderManagerParentThread;
+}
+
+bool
+RemoteDecoderManagerParent::CreateForContent(
+    Endpoint<PRemoteDecoderManagerParent>&& aEndpoint)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_RDD);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!StartupThreads()) {
+    return false;
+  }
+
+  RefPtr<RemoteDecoderManagerParent> parent =
+    new RemoteDecoderManagerParent(sRemoteDecoderManagerParentThreadHolder);
+
+  RefPtr<Runnable> task =
+    NewRunnableMethod<Endpoint<PRemoteDecoderManagerParent>&&>(
+      "dom::RemoteDecoderManagerParent::Open",
+      parent,
+      &RemoteDecoderManagerParent::Open,
+      std::move(aEndpoint));
+  sRemoteDecoderManagerParentThread->Dispatch(task.forget(),
+                                              NS_DISPATCH_NORMAL);
+  return true;
+}
+
+RemoteDecoderManagerParent::RemoteDecoderManagerParent(
+    RemoteDecoderManagerThreadHolder* aHolder)
+ : mThreadHolder(aHolder)
+{
+  MOZ_COUNT_CTOR(RemoteDecoderManagerParent);
+}
+
+RemoteDecoderManagerParent::~RemoteDecoderManagerParent()
+{
+  MOZ_COUNT_DTOR(RemoteDecoderManagerParent);
+}
+
+void
+RemoteDecoderManagerParent::ActorDestroy(
+    mozilla::ipc::IProtocol::ActorDestroyReason)
+{
+  mThreadHolder = nullptr;
+}
+
+PRemoteVideoDecoderParent*
+RemoteDecoderManagerParent::AllocPRemoteVideoDecoderParent(
+    const VideoInfo& aVideoInfo,
+    const float& aFramerate,
+    const CreateDecoderParams::OptionSet& aOptions,
+    bool* aSuccess,
+    nsCString* aErrorDescription)
+{
+  RefPtr<TaskQueue> decodeTaskQueue = new TaskQueue(
+      GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+      "RemoteVideoDecoderParent::mDecodeTaskQueue");
+
+  auto* parent = new RemoteVideoDecoderParent(this,
+                                              aVideoInfo,
+                                              aFramerate,
+                                              aOptions,
+                                              sRemoteDecoderManagerTaskQueue,
+                                              decodeTaskQueue,
+                                              aSuccess,
+                                              aErrorDescription);
+
+  return parent;
+}
+
+bool
+RemoteDecoderManagerParent::DeallocPRemoteVideoDecoderParent(
+    PRemoteVideoDecoderParent* actor)
+{
+  RemoteVideoDecoderParent* parent =
+      static_cast<RemoteVideoDecoderParent*>(actor);
+  parent->Destroy();
+  return true;
+}
+
+void
+RemoteDecoderManagerParent::Open(
+    Endpoint<PRemoteDecoderManagerParent>&& aEndpoint)
+{
+  if (!aEndpoint.Bind(this)) {
+    // We can't recover from this.
+    MOZ_CRASH("Failed to bind RemoteDecoderManagerParent to endpoint");
+  }
+  AddRef();
+}
+
+void
+RemoteDecoderManagerParent::DeallocPRemoteDecoderManagerParent()
+{
+  Release();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderManagerParent.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteDecoderManagerParent_h
+#define include_dom_media_ipc_RemoteDecoderManagerParent_h
+#include "mozilla/PRemoteDecoderManagerParent.h"
+
+namespace mozilla {
+
+class RemoteDecoderManagerThreadHolder;
+
+class RemoteDecoderManagerParent final : public PRemoteDecoderManagerParent
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteDecoderManagerParent)
+
+  static bool CreateForContent(
+                  Endpoint<PRemoteDecoderManagerParent>&& aEndpoint);
+
+  static bool StartupThreads();
+  static void ShutdownThreads();
+
+  bool OnManagerThread();
+
+protected:
+  PRemoteVideoDecoderParent* AllocPRemoteVideoDecoderParent(
+                                 const VideoInfo& aVideoInfo,
+                                 const float& aFramerate,
+                                 const CreateDecoderParams::OptionSet& aOptions,
+                                 bool* aSuccess,
+                                 nsCString* aErrorDescription) override;
+  bool DeallocPRemoteVideoDecoderParent(
+           PRemoteVideoDecoderParent* actor) override;
+
+  void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
+
+  void DeallocPRemoteDecoderManagerParent() override;
+
+private:
+  explicit RemoteDecoderManagerParent(
+               RemoteDecoderManagerThreadHolder* aThreadHolder);
+  ~RemoteDecoderManagerParent();
+
+  void Open(Endpoint<PRemoteDecoderManagerParent>&& aEndpoint);
+
+  RefPtr<RemoteDecoderManagerThreadHolder> mThreadHolder;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteDecoderManagerParent_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderModule.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteDecoderModule.h"
+
+#include "base/thread.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/StaticPrefs.h"
+
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
+#include "RemoteDecoderManagerChild.h"
+#include "RemoteMediaDataDecoder.h"
+#include "RemoteVideoDecoderChild.h"
+
+namespace mozilla {
+
+using base::Thread;
+using namespace ipc;
+using namespace layers;
+
+nsresult
+RemoteDecoderModule::Startup()
+{
+  if (!RemoteDecoderManagerChild::GetManagerThread()) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+bool
+RemoteDecoderModule::SupportsMimeType(
+                                const nsACString& aMimeType,
+                                DecoderDoctorDiagnostics* aDiagnostics) const
+{
+  bool supports = false;
+
+#ifdef MOZ_AV1
+  if (StaticPrefs::MediaAv1Enabled()) {
+    supports |= AOMDecoder::IsAV1(aMimeType);
+  }
+#endif
+  MOZ_LOG(sPDMLog, LogLevel::Debug, ("Sandbox decoder %s requested type",
+        supports ? "supports" : "rejects"));
+  return supports;
+}
+
+already_AddRefed<MediaDataDecoder>
+RemoteDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+  RemoteVideoDecoderChild* child = new RemoteVideoDecoderChild();
+  RefPtr<RemoteMediaDataDecoder> object =
+      new RemoteMediaDataDecoder(
+          child,
+          RemoteDecoderManagerChild::GetManagerThread(),
+          RemoteDecoderManagerChild::GetManagerAbstractThread());
+
+  // (per Matt Woodrow) We can't use NS_DISPATCH_SYNC here since that
+  // can spin the event loop while it waits.
+  SynchronousTask task("InitIPDL");
+  MediaResult result(NS_OK);
+  RemoteDecoderManagerChild::GetManagerThread()->Dispatch(
+    NS_NewRunnableFunction(
+      "dom::RemoteDecoderModule::CreateVideoDecoder",
+      [&, child]() {
+        AutoCompleteTask complete(&task);
+        result = child->InitIPDL(
+          aParams.VideoConfig(),
+          aParams.mRate.mValue,
+          aParams.mOptions);
+      }),
+    NS_DISPATCH_NORMAL);
+  task.Wait();
+
+  if (NS_FAILED(result)) {
+    if (aParams.mError) {
+      *aParams.mError = result;
+    }
+    return nullptr;
+  }
+
+  return object.forget();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteDecoderModule.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteDecoderModule_h
+#define include_dom_media_ipc_RemoteDecoderModule_h
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+// A PDM implementation that creates a RemoteMediaDataDecoder (a
+// MediaDataDecoder) that proxies to a RemoteVideoDecoderChild.
+// A decoder child will talk to its respective decoder parent
+// (RemoteVideoDecoderParent) on the RDD process.
+class RemoteDecoderModule : public PlatformDecoderModule
+{
+public:
+  RemoteDecoderModule() = default;
+
+  nsresult Startup() override;
+
+  bool SupportsMimeType(const nsACString& aMimeType,
+                        DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+  already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
+    const CreateDecoderParams& aParams) override;
+
+  already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
+    const CreateDecoderParams& aParams) override
+  {
+    return nullptr;
+  }
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteDecoderModule_h
--- a/dom/media/ipc/RemoteMediaDataDecoder.h
+++ b/dom/media/ipc/RemoteMediaDataDecoder.h
@@ -8,32 +8,34 @@
 #include "PlatformDecoderModule.h"
 
 #include "MediaData.h"
 
 namespace mozilla {
 
 class GpuDecoderModule;
 class IRemoteDecoderChild;
+class RemoteDecoderModule;
 class RemoteMediaDataDecoder;
 
 DDLoggedTypeCustomNameAndBase(RemoteMediaDataDecoder,
                               RemoteMediaDataDecoder,
                               MediaDataDecoder);
 
 // A MediaDataDecoder implementation that proxies through IPDL
 // to a 'real' decoder in the GPU or RDD process.
 // All requests get forwarded to a *DecoderChild instance that
 // operates solely on the provided manager and abstract manager threads.
 class RemoteMediaDataDecoder
   : public MediaDataDecoder
   , public DecoderDoctorLifeLogger<RemoteMediaDataDecoder>
 {
 public:
   friend class GpuDecoderModule;
+  friend class RemoteDecoderModule;
 
   // MediaDataDecoder
   RefPtr<InitPromise> Init() override;
   RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
   RefPtr<DecodePromise> Drain() override;
   RefPtr<FlushPromise> Flush() override;
   RefPtr<ShutdownPromise> Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderChild.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteVideoDecoderChild.h"
+
+#include "base/thread.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+
+#include "ImageContainer.h" // for PlanarYCbCrData and BufferRecycleBin
+#include "RemoteDecoderManagerChild.h"
+
+namespace mozilla {
+
+using base::Thread;
+using namespace layers; // for PlanarYCbCrData and BufferRecycleBin
+
+RemoteVideoDecoderChild::RemoteVideoDecoderChild()
+  : mThread(RemoteDecoderManagerChild::GetManagerThread())
+  , mCanSend(false)
+  , mInitialized(false)
+  , mIsHardwareAccelerated(false)
+  , mConversion(MediaDataDecoder::ConversionRequired::kNeedNone)
+  , mBufferRecycleBin(new BufferRecycleBin)
+{
+}
+
+RemoteVideoDecoderChild::~RemoteVideoDecoderChild()
+{
+  AssertOnManagerThread();
+  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+}
+
+RefPtr<mozilla::layers::Image>
+RemoteVideoDecoderChild::DeserializeImage(
+  const SurfaceDescriptorBuffer& aSdBuffer,
+  const IntSize& aPicSize)
+{
+  MOZ_ASSERT(aSdBuffer.desc().type() == BufferDescriptor::TYCbCrDescriptor);
+  if (aSdBuffer.desc().type() != BufferDescriptor::TYCbCrDescriptor) {
+    return nullptr;
+  }
+  const YCbCrDescriptor& descriptor = aSdBuffer.desc().get_YCbCrDescriptor();
+
+  uint8_t* buffer = nullptr;
+  const MemoryOrShmem& memOrShmem = aSdBuffer.data();
+  switch (memOrShmem.type()) {
+    case MemoryOrShmem::Tuintptr_t:
+      buffer = reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t());
+      break;
+    case MemoryOrShmem::TShmem:
+      buffer = memOrShmem.get_Shmem().get<uint8_t>();
+      break;
+    default:
+      MOZ_ASSERT(false, "Unknown MemoryOrShmem type");
+  }
+  if (!buffer) {
+    return nullptr;
+  }
+
+  PlanarYCbCrData pData;
+  pData.mYSize = descriptor.ySize();
+  pData.mYStride = descriptor.yStride();
+  pData.mCbCrSize = descriptor.cbCrSize();
+  pData.mCbCrStride = descriptor.cbCrStride();
+  // default mYSkip, mCbSkip, mCrSkip because not held in YCbCrDescriptor
+  pData.mYSkip = pData.mCbSkip = pData.mCrSkip = 0;
+  // default mPicX, mPicY because not held in YCbCrDescriptor
+  pData.mPicX = pData.mPicY = 0;
+  pData.mPicSize = aPicSize;
+  pData.mStereoMode = descriptor.stereoMode();
+  pData.mColorDepth = descriptor.colorDepth();
+  pData.mYUVColorSpace = descriptor.yUVColorSpace();
+  pData.mYChannel = ImageDataSerializer::GetYChannel(buffer, descriptor);
+  pData.mCbChannel = ImageDataSerializer::GetCbChannel(buffer, descriptor);
+  pData.mCrChannel = ImageDataSerializer::GetCrChannel(buffer, descriptor);
+
+  // images coming from AOMDecoder are RecyclingPlanarYCbCrImages.
+  RefPtr<RecyclingPlanarYCbCrImage> image =
+      new RecyclingPlanarYCbCrImage(mBufferRecycleBin);
+  image->CopyData(pData);
+
+  switch (memOrShmem.type()) {
+    case MemoryOrShmem::Tuintptr_t:
+      delete [] reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t());
+      break;
+    case MemoryOrShmem::TShmem:
+      DeallocShmem(memOrShmem.get_Shmem());
+      break;
+    default:
+      MOZ_ASSERT(false, "Unknown MemoryOrShmem type");
+  }
+
+  return image;
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvVideoOutput(const RemoteVideoDataIPDL& aData)
+{
+  AssertOnManagerThread();
+
+  RefPtr<Image> image = DeserializeImage(aData.sdBuffer(), aData.frameSize());
+
+  RefPtr<VideoData> video = VideoData::CreateFromImage(
+      aData.display(),
+      aData.base().offset(),
+      media::TimeUnit::FromMicroseconds(aData.base().time()),
+      media::TimeUnit::FromMicroseconds(aData.base().duration()),
+      image,
+      aData.base().keyframe(),
+      media::TimeUnit::FromMicroseconds(aData.base().timecode()));
+
+  mDecodedData.AppendElement(std::move(video));
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvInputExhausted()
+{
+  AssertOnManagerThread();
+  mDecodePromise.ResolveIfExists(std::move(mDecodedData), __func__);
+  mDecodedData = MediaDataDecoder::DecodedData();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvDrainComplete()
+{
+  AssertOnManagerThread();
+  mDrainPromise.ResolveIfExists(std::move(mDecodedData), __func__);
+  mDecodedData = MediaDataDecoder::DecodedData();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvError(const nsresult& aError)
+{
+  AssertOnManagerThread();
+  mDecodedData = MediaDataDecoder::DecodedData();
+  mDecodePromise.RejectIfExists(aError, __func__);
+  mDrainPromise.RejectIfExists(aError, __func__);
+  mFlushPromise.RejectIfExists(aError, __func__);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvInitComplete(const nsCString& aDecoderDescription,
+                                          const ConversionRequired& aConversion)
+{
+  AssertOnManagerThread();
+  mInitPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
+  mInitialized = true;
+  mDescription = aDecoderDescription;
+  mConversion = aConversion;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvInitFailed(const nsresult& aReason)
+{
+  AssertOnManagerThread();
+  mInitPromise.RejectIfExists(aReason, __func__);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderChild::RecvFlushComplete()
+{
+  AssertOnManagerThread();
+  mFlushPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
+void
+RemoteVideoDecoderChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mCanSend = false;
+}
+
+MediaResult
+RemoteVideoDecoderChild::InitIPDL(
+  const VideoInfo& aVideoInfo,
+  float aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions)
+{
+  RefPtr<RemoteDecoderManagerChild> manager =
+      RemoteDecoderManagerChild::GetSingleton();
+
+  // The manager isn't available because RemoteDecoderManagerChild has been
+  // initialized with null end points and we don't want to decode video on RDD
+  // process anymore. Return false here so that we can fallback to other PDMs.
+  if (!manager) {
+    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                       RESULT_DETAIL("RemoteDecoderManager is not available."));
+  }
+
+  if (!manager->CanSend()) {
+    return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                       RESULT_DETAIL("RemoteDecoderManager unable to send."));
+  }
+
+  mIPDLSelfRef = this;
+  bool success = false;
+  nsCString errorDescription;
+  if (manager->SendPRemoteVideoDecoderConstructor(this,
+                                                  aVideoInfo,
+                                                  aFramerate,
+                                                  aOptions,
+                                                  &success,
+                                                  &errorDescription)) {
+    mCanSend = true;
+  }
+
+  return success ? MediaResult(NS_OK) :
+                   MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, errorDescription);
+}
+
+void
+RemoteVideoDecoderChild::DestroyIPDL()
+{
+  if (mCanSend) {
+    PRemoteVideoDecoderChild::Send__delete__(this);
+  }
+}
+
+void
+RemoteVideoDecoderChild::IPDLActorDestroyed()
+{
+  mIPDLSelfRef = nullptr;
+}
+
+// MediaDataDecoder methods
+
+RefPtr<MediaDataDecoder::InitPromise>
+RemoteVideoDecoderChild::Init()
+{
+  AssertOnManagerThread();
+
+  if (!mIPDLSelfRef || !mCanSend) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+
+  SendInit();
+
+  return mInitPromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+RemoteVideoDecoderChild::Decode(MediaRawData* aSample)
+{
+  AssertOnManagerThread();
+
+  if (!mCanSend) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+
+  // TODO: It would be nice to add an allocator method to
+  // MediaDataDecoder so that the demuxer could write directly
+  // into shmem rather than requiring a copy here.
+  Shmem buffer;
+  if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &buffer)) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+
+  memcpy(buffer.get<uint8_t>(), aSample->Data(), aSample->Size());
+
+  MediaRawDataIPDL sample(MediaDataIPDL(aSample->mOffset,
+                                        aSample->mTime.ToMicroseconds(),
+                                        aSample->mTimecode.ToMicroseconds(),
+                                        aSample->mDuration.ToMicroseconds(),
+                                        aSample->mFrames,
+                                        aSample->mKeyframe),
+                          buffer);
+  SendInput(sample);
+  return mDecodePromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise>
+RemoteVideoDecoderChild::Flush()
+{
+  AssertOnManagerThread();
+  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  if (!mCanSend) {
+    return MediaDataDecoder::FlushPromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+  SendFlush();
+  return mFlushPromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+RemoteVideoDecoderChild::Drain()
+{
+  AssertOnManagerThread();
+  if (!mCanSend) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+        NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
+  }
+  SendDrain();
+  return mDrainPromise.Ensure(__func__);
+}
+
+void
+RemoteVideoDecoderChild::Shutdown()
+{
+  AssertOnManagerThread();
+  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  if (mCanSend) {
+    SendShutdown();
+  }
+  mInitialized = false;
+}
+
+bool
+RemoteVideoDecoderChild::IsHardwareAccelerated(nsACString& aFailureReason) const
+{
+  AssertOnManagerThread();
+  aFailureReason = mHardwareAcceleratedReason;
+  return mIsHardwareAccelerated;
+}
+
+nsCString
+RemoteVideoDecoderChild::GetDescriptionName() const
+{
+  AssertOnManagerThread();
+  return mDescription;
+}
+
+void
+RemoteVideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime)
+{
+  AssertOnManagerThread();
+  if (mCanSend) {
+    SendSetSeekThreshold(aTime.ToMicroseconds());
+  }
+}
+
+MediaDataDecoder::ConversionRequired
+RemoteVideoDecoderChild::NeedsConversion() const
+{
+  AssertOnManagerThread();
+  return mConversion;
+}
+
+void
+RemoteVideoDecoderChild::AssertOnManagerThread() const
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mThread);
+}
+
+RemoteDecoderManagerChild*
+RemoteVideoDecoderChild::GetManager()
+{
+  if (!mCanSend) {
+    return nullptr;
+  }
+  return static_cast<RemoteDecoderManagerChild*>(Manager());
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderChild.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteVideoDecoderChild_h
+#define include_dom_media_ipc_RemoteVideoDecoderChild_h
+#include "mozilla/PRemoteVideoDecoderChild.h"
+#include "IRemoteDecoderChild.h"
+
+#include "MediaResult.h"
+
+namespace mozilla {
+namespace layers {
+class BufferRecycleBin;
+}
+}
+
+namespace mozilla {
+
+class RemoteDecoderManagerChild;
+using mozilla::MediaDataDecoder;
+
+class RemoteVideoDecoderChild final : public PRemoteVideoDecoderChild
+                                    , public IRemoteDecoderChild
+{
+public:
+  explicit RemoteVideoDecoderChild();
+
+  // PRemoteVideoDecoderChild
+  mozilla::ipc::IPCResult RecvVideoOutput(
+                              const RemoteVideoDataIPDL& aData) override;
+  mozilla::ipc::IPCResult RecvInputExhausted() override;
+  mozilla::ipc::IPCResult RecvDrainComplete() override;
+  mozilla::ipc::IPCResult RecvError(const nsresult& aError) override;
+  mozilla::ipc::IPCResult RecvInitComplete(
+                              const nsCString& aDecoderDescription,
+                              const ConversionRequired& aConversion) override;
+  mozilla::ipc::IPCResult RecvInitFailed(const nsresult& aReason) override;
+  mozilla::ipc::IPCResult RecvFlushComplete() override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  // IRemoteDecoderChild
+  RefPtr<MediaDataDecoder::InitPromise> Init() override;
+  RefPtr<MediaDataDecoder::DecodePromise> Decode(MediaRawData* aSample) override;
+  RefPtr<MediaDataDecoder::DecodePromise> Drain() override;
+  RefPtr<MediaDataDecoder::FlushPromise> Flush() override;
+  void Shutdown() override;
+  bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
+  nsCString GetDescriptionName() const override;
+  void SetSeekThreshold(const media::TimeUnit& aTime) override;
+  MediaDataDecoder::ConversionRequired NeedsConversion() const override;
+  void DestroyIPDL() override;
+
+  MOZ_IS_CLASS_INIT
+  MediaResult InitIPDL(const VideoInfo& aVideoInfo,
+                       float aFramerate,
+                       const CreateDecoderParams::OptionSet& aOptions);
+
+  // Called from IPDL when our actor has been destroyed
+  void IPDLActorDestroyed();
+
+  RemoteDecoderManagerChild* GetManager();
+
+private:
+  ~RemoteVideoDecoderChild();
+
+  void AssertOnManagerThread() const;
+  RefPtr<mozilla::layers::Image> DeserializeImage(
+                                     const SurfaceDescriptorBuffer& sdBuffer,
+                                     const IntSize& aPicSize);
+
+  RefPtr<RemoteVideoDecoderChild> mIPDLSelfRef;
+  RefPtr<nsIThread> mThread;
+
+  MozPromiseHolder<MediaDataDecoder::InitPromise> mInitPromise;
+  MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
+  MozPromiseHolder<MediaDataDecoder::DecodePromise> mDrainPromise;
+  MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushPromise;
+
+  nsCString mHardwareAcceleratedReason;
+  nsCString mDescription;
+  bool mCanSend;
+  bool mInitialized;
+  bool mIsHardwareAccelerated;
+  MediaDataDecoder::ConversionRequired mConversion;
+  MediaDataDecoder::DecodedData mDecodedData;
+  RefPtr<mozilla::layers::BufferRecycleBin> mBufferRecycleBin;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteVideoDecoderChild_h
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderParent.cpp
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "RemoteVideoDecoderParent.h"
+
+#include "mozilla/Unused.h"
+
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
+#include "ImageContainer.h"
+#include "RemoteDecoderManagerParent.h"
+#include "RemoteDecoderModule.h"
+
+namespace mozilla {
+
+using media::TimeUnit;
+using namespace layers; // for PlanarYCbCrImage and BufferRecycleBin
+
+RemoteVideoDecoderParent::RemoteVideoDecoderParent(
+  RemoteDecoderManagerParent* aParent,
+  const VideoInfo& aVideoInfo,
+  float aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions,
+  TaskQueue* aManagerTaskQueue,
+  TaskQueue* aDecodeTaskQueue,
+  bool* aSuccess,
+  nsCString* aErrorDescription)
+  : mParent(aParent)
+  , mManagerTaskQueue(aManagerTaskQueue)
+  , mDecodeTaskQueue(aDecodeTaskQueue)
+  , mDestroyed(false)
+  , mVideoInfo(aVideoInfo)
+{
+  MOZ_COUNT_CTOR(RemoteVideoDecoderParent);
+  MOZ_ASSERT(OnManagerThread());
+  // We hold a reference to ourselves to keep us alive until IPDL
+  // explictly destroys us. There may still be refs held by
+  // tasks, but no new ones should be added after we're
+  // destroyed.
+  mIPDLSelfRef = this;
+
+  CreateDecoderParams params(mVideoInfo);
+  params.mTaskQueue = mDecodeTaskQueue;
+  params.mImageContainer = new layers::ImageContainer();
+  params.mRate = CreateDecoderParams::VideoFrameRate(aFramerate);
+  params.mOptions = aOptions;
+  MediaResult error(NS_OK);
+  params.mError = &error;
+
+#ifdef MOZ_AV1
+  if (AOMDecoder::IsAV1(params.mConfig.mMimeType)) {
+    mDecoder = new AOMDecoder(params);
+  }
+#endif
+
+  if (NS_FAILED(error)) {
+    MOZ_ASSERT(aErrorDescription);
+    *aErrorDescription = error.Description();
+  }
+
+  *aSuccess = !!mDecoder;
+}
+
+RemoteVideoDecoderParent::~RemoteVideoDecoderParent()
+{
+  MOZ_COUNT_DTOR(RemoteVideoDecoderParent);
+}
+
+void
+RemoteVideoDecoderParent::Destroy()
+{
+  MOZ_ASSERT(OnManagerThread());
+  mDestroyed = true;
+  mIPDLSelfRef = nullptr;
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvInit()
+{
+  MOZ_ASSERT(OnManagerThread());
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Init()->Then(mManagerTaskQueue, __func__,
+    [self] (TrackInfo::TrackType aTrack) {
+      MOZ_ASSERT(aTrack == TrackInfo::kVideoTrack);
+      if (self->mDecoder) {
+        Unused << self->SendInitComplete(self->mDecoder->GetDescriptionName(),
+                                         self->mDecoder->NeedsConversion());
+      }
+    },
+    [self] (MediaResult aReason) {
+      if (!self->mDestroyed) {
+        Unused << self->SendInitFailed(aReason);
+      }
+    });
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvInput(const MediaRawDataIPDL& aData)
+{
+  MOZ_ASSERT(OnManagerThread());
+  // XXX: This copies the data into a buffer owned by the MediaRawData. Ideally
+  // we'd just take ownership of the shmem.
+  RefPtr<MediaRawData> data = new MediaRawData(aData.buffer().get<uint8_t>(),
+                                               aData.buffer().Size<uint8_t>());
+  if (aData.buffer().Size<uint8_t>() && !data->Data()) {
+    // OOM
+    Error(NS_ERROR_OUT_OF_MEMORY);
+    return IPC_OK();
+  }
+  data->mOffset = aData.base().offset();
+  data->mTime = TimeUnit::FromMicroseconds(aData.base().time());
+  data->mTimecode = TimeUnit::FromMicroseconds(aData.base().timecode());
+  data->mDuration = TimeUnit::FromMicroseconds(aData.base().duration());
+  data->mKeyframe = aData.base().keyframe();
+
+  DeallocShmem(aData.buffer());
+
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Decode(data)->Then(
+    mManagerTaskQueue, __func__,
+    [self, this](const MediaDataDecoder::DecodedData& aResults) {
+      if (mDestroyed) {
+        return;
+      }
+      ProcessDecodedData(aResults);
+      Unused << SendInputExhausted();
+    },
+    [self](const MediaResult& aError) { self->Error(aError); });
+  return IPC_OK();
+}
+
+void
+RemoteVideoDecoderParent::ProcessDecodedData(
+                              const MediaDataDecoder::DecodedData& aData)
+{
+  MOZ_ASSERT(OnManagerThread());
+
+  for (const auto& data : aData) {
+    MOZ_ASSERT(data->mType == MediaData::VIDEO_DATA,
+                "Can only decode videos using RemoteVideoDecoderParent!");
+    VideoData* video = static_cast<VideoData*>(data.get());
+
+    MOZ_ASSERT(video->mImage, "Decoded video must output a layer::Image to "
+                              "be used with RemoteVideoDecoderParent");
+
+    PlanarYCbCrImage* image =
+        static_cast<PlanarYCbCrImage*>(video->mImage.get());
+
+    SurfaceDescriptorBuffer sdBuffer;
+    Shmem buffer;
+    if (AllocShmem(image->GetDataSize(),
+                   Shmem::SharedMemory::TYPE_BASIC, &buffer) &&
+        image->GetDataSize() == buffer.Size<uint8_t>()) {
+      sdBuffer.data() = buffer;
+      image->BuildSurfaceDescriptorBuffer(sdBuffer);
+    }
+
+    RemoteVideoDataIPDL output(
+      MediaDataIPDL(data->mOffset, data->mTime.ToMicroseconds(),
+                    data->mTimecode.ToMicroseconds(),
+                    data->mDuration.ToMicroseconds(),
+                    data->mFrames, data->mKeyframe),
+      video->mDisplay,
+      image->GetSize(),
+      sdBuffer,
+      video->mFrameID);
+    Unused << SendVideoOutput(output);
+  }
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvFlush()
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Flush()->Then(
+    mManagerTaskQueue, __func__,
+    [self]() {
+      if (!self->mDestroyed) {
+        Unused << self->SendFlushComplete();
+      }
+    },
+    [self](const MediaResult& aError) { self->Error(aError); });
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvDrain()
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  RefPtr<RemoteVideoDecoderParent> self = this;
+  mDecoder->Drain()->Then(
+    mManagerTaskQueue, __func__,
+    [self, this](const MediaDataDecoder::DecodedData& aResults) {
+      if (!mDestroyed) {
+        ProcessDecodedData(aResults);
+        Unused << SendDrainComplete();
+      }
+    },
+    [self](const MediaResult& aError) { self->Error(aError); });
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvShutdown()
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  if (mDecoder) {
+    mDecoder->Shutdown();
+  }
+  mDecoder = nullptr;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+RemoteVideoDecoderParent::RecvSetSeekThreshold(const int64_t& aTime)
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
+  return IPC_OK();
+}
+
+void
+RemoteVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  MOZ_ASSERT(!mDestroyed);
+  MOZ_ASSERT(OnManagerThread());
+  if (mDecoder) {
+    mDecoder->Shutdown();
+    mDecoder = nullptr;
+  }
+  if (mDecodeTaskQueue) {
+    mDecodeTaskQueue->BeginShutdown();
+  }
+}
+
+void
+RemoteVideoDecoderParent::Error(const MediaResult& aError)
+{
+  MOZ_ASSERT(OnManagerThread());
+  if (!mDestroyed) {
+    Unused << SendError(aError);
+  }
+}
+
+bool
+RemoteVideoDecoderParent::OnManagerThread()
+{
+  return mParent->OnManagerThread();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/ipc/RemoteVideoDecoderParent.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef include_dom_media_ipc_RemoteVideoDecoderParent_h
+#define include_dom_media_ipc_RemoteVideoDecoderParent_h
+#include "mozilla/PRemoteVideoDecoderParent.h"
+
+namespace mozilla {
+
+class RemoteDecoderManagerParent;
+
+class RemoteVideoDecoderParent final : public PRemoteVideoDecoderParent
+{
+public:
+  // We refcount this class since the task queue can have runnables
+  // that reference us.
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteVideoDecoderParent)
+
+  RemoteVideoDecoderParent(RemoteDecoderManagerParent* aParent,
+                           const VideoInfo& aVideoInfo,
+                           float aFramerate,
+                           const CreateDecoderParams::OptionSet& aOptions,
+                           TaskQueue* aManagerTaskQueue,
+                           TaskQueue* aDecodeTaskQueue,
+                           bool* aSuccess,
+                           nsCString* aErrorDescription);
+
+  void Destroy();
+
+  // PRemoteVideoDecoderParent
+  mozilla::ipc::IPCResult RecvInit() override;
+  mozilla::ipc::IPCResult RecvInput(const MediaRawDataIPDL& aData) override;
+  mozilla::ipc::IPCResult RecvFlush() override;
+  mozilla::ipc::IPCResult RecvDrain() override;
+  mozilla::ipc::IPCResult RecvShutdown() override;
+  mozilla::ipc::IPCResult RecvSetSeekThreshold(const int64_t& aTime) override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  bool OnManagerThread();
+  void Error(const MediaResult& aError);
+
+  ~RemoteVideoDecoderParent();
+  void ProcessDecodedData(const MediaDataDecoder::DecodedData& aData);
+
+  RefPtr<RemoteDecoderManagerParent> mParent;
+  RefPtr<RemoteVideoDecoderParent> mIPDLSelfRef;
+  RefPtr<TaskQueue> mManagerTaskQueue;
+  RefPtr<TaskQueue> mDecodeTaskQueue;
+  RefPtr<MediaDataDecoder> mDecoder;
+
+  // Can only be accessed from the manager thread
+  bool mDestroyed;
+  VideoInfo mVideoInfo;
+};
+
+} // namespace mozilla
+
+#endif // include_dom_media_ipc_RemoteVideoDecoderParent_h
--- a/dom/media/ipc/moz.build
+++ b/dom/media/ipc/moz.build
@@ -1,34 +1,56 @@
 # -*- Mode: python; c-basic-offset: 4; 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/.
 
 
 IPDL_SOURCES += [
+    'PMediaDecoderParams.ipdlh',
+    'PRDD.ipdl',
+    'PRemoteDecoderManager.ipdl',
+    'PRemoteVideoDecoder.ipdl',
     'PVideoDecoder.ipdl',
     'PVideoDecoderManager.ipdl',
 ]
 
 EXPORTS.mozilla += [
     'GpuDecoderModule.h',
+    'RDDChild.h',
+    'RDDParent.h',
+    'RDDProcessHost.h',
+    'RDDProcessImpl.h',
+    'RDDProcessManager.h',
+    'RemoteDecoderManagerChild.h',
+    'RemoteDecoderManagerParent.h',
+    'RemoteDecoderModule.h',
     'RemoteMediaDataDecoder.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'MediaIPCUtils.h',
     'VideoDecoderManagerChild.h',
     'VideoDecoderManagerParent.h',
 ]
 
 SOURCES += [
     'GpuDecoderModule.cpp',
+    'RDDChild.cpp',
+    'RDDParent.cpp',
+    'RDDProcessHost.cpp',
+    'RDDProcessImpl.cpp',
+    'RDDProcessManager.cpp',
+    'RemoteDecoderManagerChild.cpp',
+    'RemoteDecoderManagerParent.cpp',
+    'RemoteDecoderModule.cpp',
     'RemoteMediaDataDecoder.cpp',
+    'RemoteVideoDecoderChild.cpp',
+    'RemoteVideoDecoderParent.cpp',
     'VideoDecoderChild.cpp',
     'VideoDecoderManagerChild.cpp',
     'VideoDecoderManagerParent.cpp',
     'VideoDecoderParent.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -41,16 +41,17 @@
 #include "AgnosticDecoderModule.h"
 #include "EMEDecoderModule.h"
 
 #include "DecoderDoctorDiagnostics.h"
 
 #include "MP4Decoder.h"
 #include "VPXDecoder.h"
 #include "mozilla/GpuDecoderModule.h"
+#include "mozilla/RemoteDecoderModule.h"
 
 #include "H264.h"
 
 #include <functional>
 
 namespace mozilla {
 
 extern already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule();
@@ -347,16 +348,21 @@ PDMFactory::CreatePDMs()
     m = CreateBlankDecoderModule();
     StartupPDM(m);
     // The Blank PDM SupportsMimeType reports true for all codecs; the creation
     // of its decoder is infallible. As such it will be used for all media, we
     // can stop creating more PDM from this point.
     return;
   }
 
+  if (StaticPrefs::MediaRddProcessEnabled()) {
+    m = new RemoteDecoderModule;
+    StartupPDM(m);
+  }
+
 #ifdef XP_WIN
   if (StaticPrefs::MediaWmfEnabled() && !IsWin7AndPre2000Compatible()) {
     m = new WMFDecoderModule();
     RefPtr<PlatformDecoderModule> remote = new GpuDecoderModule(m);
     StartupPDM(remote);
     mWMFFailedToLoad = !StartupPDM(m);
   } else {
     mWMFFailedToLoad =
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -30,16 +30,17 @@ class MediaRawData;
 class DecoderDoctorDiagnostics;
 
 namespace layers {
 class ImageContainer;
 } // namespace layers
 
 class GpuDecoderModule;
 class MediaDataDecoder;
+class RemoteDecoderModule;
 class TaskQueue;
 class CDMProxy;
 
 static LazyLogModule sPDMLog("PlatformDecoderModule");
 
 struct MOZ_STACK_CLASS CreateDecoderParams final
 {
   explicit CreateDecoderParams(const TrackInfo& aConfig) : mConfig(aConfig) { }
@@ -208,16 +209,17 @@ public:
 protected:
   PlatformDecoderModule() { }
   virtual ~PlatformDecoderModule() { }
 
   friend class MediaChangeMonitor;
   friend class PDMFactory;
   friend class GpuDecoderModule;
   friend class EMEDecoderModule;
+  friend class RemoteDecoderModule;
 
   // Indicates if the PlatformDecoderModule supports decoding of aColorDepth.
   // Should override this method when the platform can support color depth != 8.
   virtual bool SupportsColorDepth(gfx::ColorDepth aColorDepth,
                                   DecoderDoctorDiagnostics* aDiagnostics) const
   {
     return aColorDepth == gfx::ColorDepth::COLOR_8;
   }
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VPXDecoder.h"
 #include "BitReader.h"
 #include "TimeUnits.h"
 #include "gfx2DGlue.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
 #include "ImageContainer.h"
 #include "nsError.h"
 #include "prsystem.h"
 
 #include <algorithm>
 
 #undef LOG
 #define LOG(arg, ...)                                                          \
@@ -305,69 +306,217 @@ VPXDecoder::IsVP9(const nsACString& aMim
 {
   return IsVPX(aMimeType, VPXDecoder::VP9);
 }
 
 /* static */
 bool
 VPXDecoder::IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec)
 {
-  vpx_codec_stream_info_t si;
-  PodZero(&si);
-  si.sz = sizeof(si);
-
-  if (aCodec == Codec::VP8) {
-    vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
-    return bool(si.is_kf);
-  } else if (aCodec == Codec::VP9) {
-    vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
-    return bool(si.is_kf);
-  }
-
-  return false;
+  VPXStreamInfo info;
+  return GetStreamInfo(aBuffer, info, aCodec) && info.mKeyFrame;
 }
 
 /* static */
 gfx::IntSize
 VPXDecoder::GetFrameSize(Span<const uint8_t> aBuffer, Codec aCodec)
 {
-  vpx_codec_stream_info_t si;
-  PodZero(&si);
-  si.sz = sizeof(si);
+  VPXStreamInfo info;
+  if (!GetStreamInfo(aBuffer, info, aCodec)) {
+    return gfx::IntSize();
+  }
+  return info.mImage;
+}
 
-  if (aCodec == Codec::VP8) {
-    vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
-  } else if (aCodec == Codec::VP9) {
-    vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
+/* static */
+gfx::IntSize
+VPXDecoder::GetDisplaySize(Span<const uint8_t> aBuffer, Codec aCodec)
+{
+  VPXStreamInfo info;
+  if (!GetStreamInfo(aBuffer, info, aCodec)) {
+    return gfx::IntSize();
   }
-
-  return gfx::IntSize(si.w, si.h);
+  return info.mDisplay;
 }
 
 /* static */
 int
 VPXDecoder::GetVP9Profile(Span<const uint8_t> aBuffer)
 {
-  if (aBuffer.Length() < 2) {
-    // Can't be good.
+  VPXStreamInfo info;
+  if (!GetStreamInfo(aBuffer, info, Codec::VP9)) {
     return -1;
   }
+  return info.mProfile;
+}
+
+/* static */
+bool
+VPXDecoder::GetStreamInfo(Span<const uint8_t> aBuffer,
+                          VPXDecoder::VPXStreamInfo& aInfo,
+                          Codec aCodec)
+{
+  if (aBuffer.IsEmpty()) {
+    // Can't be good.
+    return false;
+  }
+
+  aInfo = VPXStreamInfo();
+
+  if (aCodec == Codec::VP8) {
+    aInfo.mKeyFrame =
+      (aBuffer[0] & 1) == 0; // frame type (0 for key frames, 1 for interframes)
+    if (!aInfo.mKeyFrame) {
+      // We can't retrieve the required information from interframes.
+      return true;
+    }
+    if (aBuffer.Length() < 10) {
+      return false;
+    }
+    uint8_t version = (aBuffer[0] >> 1) & 0x7;
+    if (version > 3) {
+      return false;
+    }
+    uint8_t start_code_byte_0 = aBuffer[3];
+    uint8_t start_code_byte_1 = aBuffer[4];
+    uint8_t start_code_byte_2 = aBuffer[5];
+    if (start_code_byte_0 != 0x9d || start_code_byte_1 != 0x01 ||
+        start_code_byte_2 != 0x2a) {
+      return false;
+    }
+    uint16_t width = (aBuffer[6] | aBuffer[7] << 8) & 0x3fff;
+    uint16_t height = (aBuffer[8] | aBuffer[9] << 8) & 0x3fff;
+
+    // aspect ratio isn't found in the VP8 frame header.
+    aInfo.mImage = aInfo.mDisplay = gfx::IntSize(width, height);
+    return true;
+  }
+
   BitReader br(aBuffer.Elements(), aBuffer.Length() * 8);
-
   uint32_t frameMarker = br.ReadBits(2); // frame_marker
   if (frameMarker != 2) {
     // That's not a valid vp9 header.
-    return -1;
+    return false;
   }
   uint32_t profile = br.ReadBits(1); // profile_low_bit
-  profile |= br.ReadBits(1) << 1; // profile_high_bit
+  profile |= br.ReadBits(1) << 1;    // profile_high_bit
   if (profile == 3) {
     profile += br.ReadBits(1); // reserved_zero
     if (profile > 3) {
       // reserved_zero wasn't zero.
-      return -1;
+      return false;
     }
   }
-  return profile;
+
+  aInfo.mProfile = profile;
+
+  bool show_existing_frame = br.ReadBits(1);
+  if (show_existing_frame) {
+    if (profile == 3 && aBuffer.Length() < 2) {
+      return false;
+    }
+    Unused << br.ReadBits(3); // frame_to_show_map_idx
+    return true;
+  }
+
+  if (aBuffer.Length() < 10) {
+    // Header too small;
+    return false;
+  }
+
+  aInfo.mKeyFrame = !br.ReadBits(1);
+  bool show_frame = br.ReadBits(1);
+  bool error_resilient_mode = br.ReadBits(1);
+
+  auto frame_sync_code = [&]() -> bool {
+    uint8_t frame_sync_byte_1 = br.ReadBits(8);
+    uint8_t frame_sync_byte_2 = br.ReadBits(8);
+    uint8_t frame_sync_byte_3 = br.ReadBits(8);
+    return frame_sync_byte_1 == 0x49 && frame_sync_byte_2 == 0x83 &&
+           frame_sync_byte_3 == 0x42;
+  };
+
+  auto color_config = [&]() -> bool {
+    aInfo.mBitDepth = 8;
+    if (profile >= 2) {
+      bool ten_or_twelve_bit = br.ReadBits(1);
+      aInfo.mBitDepth = ten_or_twelve_bit ? 12 : 10;
+    }
+    aInfo.mColorSpace = br.ReadBits(3);
+    if (aInfo.mColorSpace != 7 /* CS_RGB */) {
+      aInfo.mFullRange = br.ReadBits(1);
+      if (profile == 1 || profile == 3) {
+        aInfo.mSubSampling_x = br.ReadBits(1);
+        aInfo.mSubSampling_y = br.ReadBits(1);
+        if (br.ReadBits(1)) { // reserved_zero
+          return false;
+        };
+      } else {
+        aInfo.mSubSampling_x = true;
+        aInfo.mSubSampling_y = true;
+      }
+    } else {
+      aInfo.mFullRange = true;
+      if (profile == 1 || profile == 3) {
+        aInfo.mSubSampling_x = false;
+        aInfo.mSubSampling_y = false;
+        if (br.ReadBits(1)) { // reserved_zero
+          return false;
+        };
+      } else {
+        // sRGB color space is only available with VP9 profile 1.
+        return false;
+      }
+    }
+    return true;
+  };
+
+  auto frame_size = [&]() {
+    aInfo.mImage = gfx::IntSize(br.ReadBits(16) + 1, br.ReadBits(16) + 1);
+  };
+
+  auto render_size = [&]() {
+    bool render_and_frame_size_different = br.ReadBits(1);
+    if (render_and_frame_size_different) {
+      aInfo.mDisplay = gfx::IntSize(br.ReadBits(16) + 1, br.ReadBits(16) + 1);
+    } else {
+      aInfo.mDisplay = aInfo.mImage;
+    }
+  };
+
+  if (aInfo.mKeyFrame) {
+    if (!frame_sync_code()) {
+      return false;
+    }
+    if (!color_config()) {
+      return false;
+    }
+    frame_size();
+    render_size();
+  } else {
+    bool intra_only = show_frame ? false : br.ReadBit();
+    if (!error_resilient_mode) {
+      Unused << br.ReadBits(2); // reset_frame_context
+    }
+    if (intra_only) {
+      if (!frame_sync_code()) {
+        return false;
+      }
+      if (profile > 0) {
+        if (!color_config()) {
+          return false;
+        }
+      } else {
+        aInfo.mColorSpace = 1; // CS_BT_601
+        aInfo.mSubSampling_x = 1;
+        aInfo.mSubSampling_y = 1;
+        aInfo.mBitDepth = 8;
+      }
+      Unused << br.ReadBits(8); // refresh_frame_flags
+      frame_size();
+      render_size();
+    }
+  }
+  return true;
 }
 
 } // namespace mozilla
 #undef LOG
--- a/dom/media/platforms/agnostic/VPXDecoder.h
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -50,21 +50,83 @@ public:
   static bool IsVP8(const nsACString& aMimeType);
   static bool IsVP9(const nsACString& aMimeType);
 
   // Return true if a sample is a keyframe for the specified codec.
   static bool IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec);
 
   // Return the frame dimensions for a sample for the specified codec.
   static gfx::IntSize GetFrameSize(Span<const uint8_t> aBuffer, Codec aCodec);
+  // Return the display dimensions for a sample for the specified codec.
+  static gfx::IntSize GetDisplaySize(Span<const uint8_t> aBuffer, Codec aCodec);
 
   // Return the VP9 profile as per https://www.webmproject.org/vp9/profiles/
   // Return negative value if error.
   static int GetVP9Profile(Span<const uint8_t> aBuffer);
 
+  struct VPXStreamInfo
+  {
+    gfx::IntSize mImage;
+    gfx::IntSize mDisplay;
+    bool mKeyFrame = false;
+
+    uint8_t mProfile = 0;
+    uint8_t mBitDepth = 8;
+    /*
+    0 CS_UNKNOWN Unknown (in this case the color space must be signaled outside
+      the VP9 bitstream).
+    1 CS_BT_601 Rec. ITU-R BT.601-7
+    2 CS_BT_709 Rec. ITU-R BT.709-6
+    3 CS_SMPTE_170 SMPTE-170
+    4 CS_SMPTE_240 SMPTE-240
+    5 CS_BT_2020 Rec. ITU-R BT.2020-2
+    6 CS_RESERVED Reserved
+    7 CS_RGB sRGB (IEC 61966-2-1)
+    */
+    int mColorSpace = 1; // CS_BT_601
+
+    /*
+    mFullRange == false then:
+      For BitDepth equals 8:
+        Y is between 16 and 235 inclusive.
+        U and V are between 16 and 240 inclusive.
+      For BitDepth equals 10:
+        Y is between 64 and 940 inclusive.
+        U and V are between 64 and 960 inclusive.
+      For BitDepth equals 12:
+        Y is between 256 and 3760.
+        U and V are between 256 and 3840 inclusive.
+    mFullRange == true then:
+      No restriction on Y, U, V values.
+    */
+    bool mFullRange = false;
+
+    /*
+      Sub-sampling, used only for non sRGB colorspace.
+      subsampling_x subsampling_y Description
+          0             0         YUV 4:4:4
+          0             1         YUV 4:4:0
+          1             0         YUV 4:2:2
+          1             1         YUV 4:2:0
+    */
+    bool mSubSampling_x = true;
+    bool mSubSampling_y = true;
+
+    bool IsCompatible(const VPXStreamInfo& aOther) const
+    {
+      return mImage == aOther.mImage && mProfile == aOther.mProfile &&
+             mBitDepth == aOther.mBitDepth && mSubSampling_x &&
+             aOther.mSubSampling_x && mSubSampling_y == aOther.mSubSampling_y;
+    }
+  };
+
+  static bool GetStreamInfo(Span<const uint8_t> aBuffer,
+                            VPXStreamInfo& aInfo,
+                            Codec aCodec);
+
 private:
   ~VPXDecoder();
   RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample);
   MediaResult DecodeAlpha(vpx_image_t** aImgAlpha, const MediaRawData* aSample);
 
   const RefPtr<layers::ImageContainer> mImageContainer;
   RefPtr<layers::KnowsCompositor> mImageAllocator;
   const RefPtr<TaskQueue> mTaskQueue;
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -6,16 +6,17 @@
 
 #include <CoreFoundation/CFString.h>
 
 #include "AppleVTDecoder.h"
 #include "AppleCMLinker.h"
 #include "AppleDecoderModule.h"
 #include "AppleUtils.h"
 #include "AppleVTLinker.h"
+#include "MacIOSurfaceImage.h"
 #include "MediaData.h"
 #include "mozilla/ArrayUtils.h"
 #include "H264.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 #include "VideoUtils.h"
 #include "gfxPlatform.h"
@@ -431,17 +432,17 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
     CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
   } else {
 #ifndef MOZ_WIDGET_UIKIT
     IOSurfacePtr surface = MacIOSurfaceLib::CVPixelBufferGetIOSurface(aImage);
     MOZ_ASSERT(surface, "Decoder didn't return an IOSurface backed buffer");
 
     RefPtr<MacIOSurface> macSurface = new MacIOSurface(surface);
 
-    RefPtr<layers::Image> image = new MacIOSurfaceImage(macSurface);
+    RefPtr<layers::Image> image = new layers::MacIOSurfaceImage(macSurface);
 
     data =
       VideoData::CreateFromImage(info.mDisplay,
                                  aFrameRef.byte_offset,
                                  aFrameRef.composition_timestamp,
                                  aFrameRef.duration,
                                  image.forget(),
                                  aFrameRef.is_sync_point,
--- a/dom/media/platforms/wrappers/MediaChangeMonitor.cpp
+++ b/dom/media/platforms/wrappers/MediaChangeMonitor.cpp
@@ -144,16 +144,17 @@ private:
 class VPXChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor
 {
 public:
   explicit VPXChangeMonitor(const VideoInfo& aInfo)
     : mCurrentConfig(aInfo)
     , mCodec(VPXDecoder::IsVP8(aInfo.mMimeType) ? VPXDecoder::Codec::VP8
                                                 : VPXDecoder::Codec::VP9)
   {
+    mTrackInfo = new TrackInfoSharedPtr(mCurrentConfig, mStreamID++);
   }
 
   bool CanBeInstantiated() const override { return true; }
 
   MediaResult CheckForChange(MediaRawData* aSample) override
   {
     // Don't look at encrypted content.
     if (aSample->mCrypto.mValid) {
@@ -161,50 +162,56 @@ public:
     }
     // For both VP8 and VP9, we only look for resolution changes
     // on keyframes. Other resolution changes are invalid.
     if (!aSample->mKeyframe) {
       return NS_OK;
     }
 
     auto dataSpan = MakeSpan<const uint8_t>(aSample->Data(), aSample->Size());
-    auto dimensions = VPXDecoder::GetFrameSize(dataSpan, mCodec);
-    int profile = mCodec == VPXDecoder::Codec::VP9
-                    ? VPXDecoder::GetVP9Profile(dataSpan)
-                    : 0;
 
-    if (!mSize) {
-      mSize = Some(dimensions);
-      mProfile = Some(profile);
+    VPXDecoder::VPXStreamInfo info;
+    if (!VPXDecoder::GetStreamInfo(dataSpan, info, mCodec)) {
+      return NS_ERROR_DOM_MEDIA_DECODE_ERR;
+    }
+
+    if (!mInfo) {
+      mInfo = Some(info);
       return NS_OK;
     }
-    if (mSize.ref() == dimensions && mProfile.ref() == profile) {
+    if (mInfo.ref().IsCompatible(info)) {
       return NS_OK;
     }
-    mSize = Some(dimensions);
-    mProfile = Some(profile);
-    mCurrentConfig.mDisplay = dimensions;
+    mInfo = Some(info);
+    mCurrentConfig.mImage = info.mImage;
+    mCurrentConfig.mDisplay = info.mDisplay;
+    mCurrentConfig.SetImageRect(
+      gfx::IntRect(0, 0, info.mImage.width, info.mImage.height));
+    mTrackInfo = new TrackInfoSharedPtr(mCurrentConfig, mStreamID++);
 
     return NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER;
   }
 
   const TrackInfo& Config() const override { return mCurrentConfig; }
 
   MediaResult PrepareSample(MediaDataDecoder::ConversionRequired aConversion,
                             MediaRawData* aSample,
                             bool aNeedKeyFrame) override
   {
+    aSample->mTrackInfo = mTrackInfo;
+
     return NS_OK;
   }
 
 private:
   VideoInfo mCurrentConfig;
   const VPXDecoder::Codec mCodec;
-  Maybe<gfx::IntSize> mSize;
-  Maybe<int> mProfile;
+  Maybe<VPXDecoder::VPXStreamInfo> mInfo;
+  uint32_t mStreamID = 0;
+  RefPtr<TrackInfoSharedPtr> mTrackInfo;
 };
 
 MediaChangeMonitor::MediaChangeMonitor(PlatformDecoderModule* aPDM,
                                        const CreateDecoderParams& aParams)
   : mPDM(aPDM)
   , mCurrentConfig(aParams.VideoConfig())
   , mKnowsCompositor(aParams.mKnowsCompositor)
   , mImageContainer(aParams.mImageContainer)
--- a/gfx/ipc/VsyncBridgeParent.cpp
+++ b/gfx/ipc/VsyncBridgeParent.cpp
@@ -1,16 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "VsyncBridgeParent.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 
+using mozilla::layers::CompositorBridgeParent;
+using mozilla::layers::CompositorThreadHolder;
+
 namespace mozilla {
 namespace gfx {
 
 RefPtr<VsyncBridgeParent>
 VsyncBridgeParent::Start(Endpoint<PVsyncBridgeParent>&& aEndpoint)
 {
   RefPtr<VsyncBridgeParent> parent = new VsyncBridgeParent();
 
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -12,16 +12,17 @@
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxUtils.h"                   // for gfxUtils
 #include "libyuv.h"
 #include "mozilla/RefPtr.h"             // for already_AddRefed
 #include "mozilla/ipc/CrossProcessMutex.h"  // for CrossProcessMutex, etc
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/ImageBridgeChild.h"  // for ImageBridgeChild
 #include "mozilla/layers/ImageClient.h"  // for ImageClient
+#include "mozilla/layers/ImageDataSerializer.h" // for SurfaceDescriptorBuffer
 #include "mozilla/layers/LayersMessages.h"
 #include "mozilla/layers/SharedPlanarYCbCrImage.h"
 #include "mozilla/layers/SharedSurfacesChild.h" // for SharedSurfacesAnimation
 #include "mozilla/layers/SharedRGBImage.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "nsISupportsUtils.h"           // for NS_IF_ADDREF
 #include "YCbCrUtils.h"                 // for YCbCr conversions
@@ -46,16 +47,20 @@ namespace layers {
 using namespace mozilla::ipc;
 using namespace android;
 using namespace mozilla::gfx;
 
 Atomic<int32_t> Image::sSerialCounter(0);
 
 Atomic<uint32_t> ImageContainer::sGenerationCounter(0);
 
+static void
+CopyPlane(uint8_t* aDst, const uint8_t* aSrc,
+          const gfx::IntSize& aSize, int32_t aStride, int32_t aSkip);
+
 RefPtr<PlanarYCbCrImage>
 ImageFactory::CreatePlanarYCbCrImage(const gfx::IntSize& aScaleHint, BufferRecycleBin *aRecycleBin)
 {
   return new RecyclingPlanarYCbCrImage(aRecycleBin);
 }
 
 BufferRecycleBin::BufferRecycleBin()
   : mLock("mozilla.layers.BufferRecycleBin.mLock")
@@ -477,16 +482,71 @@ ImageContainer::GetD3D11YCbCrRecycleAllo
 
 PlanarYCbCrImage::PlanarYCbCrImage()
   : Image(nullptr, ImageFormat::PLANAR_YCBCR)
   , mOffscreenFormat(SurfaceFormat::UNKNOWN)
   , mBufferSize(0)
 {
 }
 
+nsresult
+PlanarYCbCrImage::BuildSurfaceDescriptorBuffer(
+    SurfaceDescriptorBuffer& aSdBuffer)
+{
+  const PlanarYCbCrData* pdata = GetData();
+  MOZ_ASSERT(pdata, "must have PlanarYCbCrData");
+  MOZ_ASSERT(pdata->mYSkip == 0 && pdata->mCbSkip == 0 && pdata->mCrSkip == 0,
+             "YCbCrDescriptor doesn't hold skip values");
+  MOZ_ASSERT(pdata->mPicX == 0 && pdata->mPicY == 0,
+             "YCbCrDescriptor doesn't hold picx or picy");
+
+  uint32_t yOffset;
+  uint32_t cbOffset;
+  uint32_t crOffset;
+  ImageDataSerializer::ComputeYCbCrOffsets(pdata->mYStride,
+                                           pdata->mYSize.height,
+                                           pdata->mCbCrStride,
+                                           pdata->mCbCrSize.height,
+                                           yOffset, cbOffset, crOffset);
+
+  aSdBuffer.desc() = YCbCrDescriptor(pdata->mYSize, pdata->mYStride,
+                                     pdata->mCbCrSize, pdata->mCbCrStride,
+                                     yOffset, cbOffset, crOffset,
+                                     pdata->mStereoMode,
+                                     pdata->mColorDepth,
+                                     pdata->mYUVColorSpace,
+                                     /*hasIntermediateBuffer*/ false);
+
+  uint8_t* buffer = nullptr;
+  const MemoryOrShmem& memOrShmem = aSdBuffer.data();
+  switch (memOrShmem.type()) {
+    case MemoryOrShmem::Tuintptr_t:
+      buffer = reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t());
+      break;
+    case MemoryOrShmem::TShmem:
+      buffer = memOrShmem.get_Shmem().get<uint8_t>();
+      break;
+    default:
+      MOZ_ASSERT(false, "Unknown MemoryOrShmem type");
+  }
+  MOZ_ASSERT(buffer, "no valid buffer available to copy image data");
+  if (!buffer) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  CopyPlane(buffer+yOffset, pdata->mYChannel,
+            pdata->mYSize, pdata->mYStride, pdata->mYSkip);
+  CopyPlane(buffer+cbOffset, pdata->mCbChannel,
+            pdata->mCbCrSize, pdata->mCbCrStride, pdata->mCbSkip);
+  CopyPlane(buffer+crOffset, pdata->mCrChannel,
+            pdata->mCbCrSize, pdata->mCbCrStride, pdata->mCrSkip);
+  return NS_OK;
+}
+
+
 RecyclingPlanarYCbCrImage::~RecyclingPlanarYCbCrImage()
 {
   if (mBuffer) {
     mRecycleBin->RecycleBuffer(std::move(mBuffer), mBufferSize);
   }
 }
 
 size_t
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -165,16 +165,17 @@ class SharedPlanarYCbCrImage;
 class SharedSurfacesAnimation;
 class PlanarYCbCrImage;
 class TextureClient;
 class KnowsCompositor;
 class NVImage;
 #ifdef XP_WIN
 class D3D11YCbCrRecycleAllocator;
 #endif
+class SurfaceDescriptorBuffer;
 
 struct ImageBackendData
 {
   virtual ~ImageBackendData() {}
 
 protected:
   ImageBackendData() {}
 };
@@ -883,16 +884,24 @@ public:
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const = 0;
 
   PlanarYCbCrImage* AsPlanarYCbCrImage() override { return this; }
 
+  /**
+   * Build a SurfaceDescriptorBuffer with this image.  The provided
+   * SurfaceDescriptorBuffer must already have a valid MemoryOrShmem set
+   * with a capacity large enough to hold |GetDataSize|.
+   */
+  virtual nsresult BuildSurfaceDescriptorBuffer(
+    SurfaceDescriptorBuffer& aSdBuffer);
+
 protected:
   already_AddRefed<gfx::SourceSurface> GetAsSourceSurface() override;
 
   void SetOffscreenFormat(gfxImageFormat aFormat) { mOffscreenFormat = aFormat; }
   gfxImageFormat GetOffscreenFormat() const;
 
   Data mData;
   gfx::IntPoint mOrigin;
--- a/gfx/layers/wr/ClipManager.cpp
+++ b/gfx/layers/wr/ClipManager.cpp
@@ -53,19 +53,24 @@ ClipManager::EndBuild()
   MOZ_ASSERT(mASROverride.empty());
   MOZ_ASSERT(mItemClipStack.empty());
 }
 
 void
 ClipManager::BeginList(const StackingContextHelper& aStackingContext)
 {
   if (aStackingContext.AffectsClipPositioning()) {
-    PushOverrideForASR(
-        mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR,
-        aStackingContext.ReferenceFrameId());
+    if (aStackingContext.ReferenceFrameId()) {
+      PushOverrideForASR(
+          mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR,
+          aStackingContext.ReferenceFrameId().ref());
+    } else {
+      // Start a new cache
+      mCacheStack.emplace();
+    }
   }
 
   ItemClips clips(nullptr, nullptr, false);
   if (!mItemClipStack.empty()) {
     clips.CopyOutputsFrom(mItemClipStack.top());
   }
   mItemClipStack.push(clips);
 }
@@ -73,31 +78,36 @@ ClipManager::BeginList(const StackingCon
 void
 ClipManager::EndList(const StackingContextHelper& aStackingContext)
 {
   MOZ_ASSERT(!mItemClipStack.empty());
   mItemClipStack.top().Unapply(mBuilder);
   mItemClipStack.pop();
 
   if (aStackingContext.AffectsClipPositioning()) {
-    PopOverrideForASR(
+    if (aStackingContext.ReferenceFrameId()) {
+      PopOverrideForASR(
         mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR);
+    } else {
+      MOZ_ASSERT(!mCacheStack.empty());
+      mCacheStack.pop();
+    }
   }
 }
 
 void
 ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                                const Maybe<wr::WrClipId>& aClipId)
+                                const wr::WrClipId& aClipId)
 {
   Maybe<wr::WrClipId> scrollId = GetScrollLayer(aASR);
   MOZ_ASSERT(scrollId.isSome());
 
   CLIP_LOG("Pushing override %zu -> %s\n", scrollId->id,
-      aClipId ? Stringify(aClipId->id).c_str() : "(none)");
-  auto it = mASROverride.insert({ *scrollId, std::stack<Maybe<wr::WrClipId>>() });
+      Stringify(aClipId->id).c_str());
+  auto it = mASROverride.insert({ *scrollId, std::stack<wr::WrClipId>() });
   it.first->second.push(aClipId);
 
   // Start a new cache
   mCacheStack.emplace();
 }
 
 void
 ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR)
@@ -107,17 +117,17 @@ ClipManager::PopOverrideForASR(const Act
 
   Maybe<wr::WrClipId> scrollId = GetScrollLayer(aASR);
   MOZ_ASSERT(scrollId.isSome());
 
   auto it = mASROverride.find(*scrollId);
   MOZ_ASSERT(it != mASROverride.end());
   MOZ_ASSERT(!(it->second.empty()));
   CLIP_LOG("Popping override %zu -> %s\n", scrollId->id,
-      it->second.top() ? Stringify(it->second.top()->id).c_str() : "(none)");
+      Stringify(it->second.top()->id).c_str());
   it->second.pop();
   if (it->second.empty()) {
     mASROverride.erase(it);
   }
 }
 
 Maybe<wr::WrClipId>
 ClipManager::ClipIdAfterOverride(const Maybe<wr::WrClipId>& aClipId)
@@ -127,17 +137,17 @@ ClipManager::ClipIdAfterOverride(const M
   }
   auto it = mASROverride.find(*aClipId);
   if (it == mASROverride.end()) {
     return aClipId;
   }
   MOZ_ASSERT(!it->second.empty());
   CLIP_LOG("Overriding %zu with %s\n", aClipId->id,
       it->second.top() ? Stringify(it->second.top()->id).c_str() : "(none)");
-  return it->second.top();
+  return Some(it->second.top());
 }
 
 void
 ClipManager::BeginItem(nsDisplayItem* aItem,
                        const StackingContextHelper& aStackingContext)
 {
   CLIP_LOG("processing item %p\n", aItem);
 
--- a/gfx/layers/wr/ClipManager.h
+++ b/gfx/layers/wr/ClipManager.h
@@ -61,17 +61,17 @@ public:
   void BeginList(const StackingContextHelper& aStackingContext);
   void EndList(const StackingContextHelper& aStackingContext);
 
   void BeginItem(nsDisplayItem* aItem,
                  const StackingContextHelper& aStackingContext);
   ~ClipManager();
 
   void PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                          const Maybe<wr::WrClipId>& aClipId);
+                          const wr::WrClipId& aClipId);
   void PopOverrideForASR(const ActiveScrolledRoot* aASR);
 
 private:
   Maybe<wr::WrClipId> ClipIdAfterOverride(const Maybe<wr::WrClipId>& aClipId);
 
   Maybe<wr::WrClipId>
   GetScrollLayer(const ActiveScrolledRoot* aASR);
 
@@ -114,17 +114,17 @@ private:
   // Any time ClipManager wants to define a new clip as a child of ASR X, it
   // should first check the cache overrides to see if there is a cache override
   // item ((a) or (b) above) that is already a child of X, and then define that
   // clip as a child of Y instead. This map stores X -> Y, which allows
   // ClipManager to do the necessary lookup. Note that there theoretically might
   // be multiple different "Y" clips (in case of nested cache overrides), which
   // is why we need a stack.
   std::unordered_map<wr::WrClipId,
-                     std::stack<Maybe<wr::WrClipId>>,
+                     std::stack<wr::WrClipId>,
                      wr::WrClipId::HashFn> mASROverride;
 
   // This holds some clip state for a single nsDisplayItem
   struct ItemClips {
     ItemClips(const ActiveScrolledRoot* aASR,
               const DisplayItemClipChain* aChain,
               bool aSeparateLeaf);
 
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -440,17 +440,17 @@ struct DIGroup
       combined = aData->mClip.ApplyNonRoundedIntersection(aData->mGeometry->ComputeInvalidationRegion());
       combined.MoveBy(shift);
       combined.Or(combined, clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion()));
       aData->mGeometry = std::move(geometry);
       */
       combined = clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion());
       aData->mGeometry = std::move(geometry);
 
-      GP("matrix: %f %f\n", aMatrix._31, aMatrix._32); 
+      GP("matrix: %f %f\n", aMatrix._31, aMatrix._32);
       GP("frame invalid invalidate: %s\n", aItem->Name());
       GP("old rect: %d %d %d %d\n",
              aData->mRect.x,
              aData->mRect.y,
              aData->mRect.width,
              aData->mRect.height);
       InvalidateRect(aData->mRect.Intersect(mImageBounds));
       // We want to snap to outside pixels. When should we multiply by the matrix?
@@ -1584,17 +1584,17 @@ WebRenderCommandBuilder::CreateWebRender
   }
 
   mDumpIndent--;
   mClipManager.EndList(aSc);
 }
 
 void
 WebRenderCommandBuilder::PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                                            const Maybe<wr::WrClipId>& aClipId)
+                                            const wr::WrClipId& aClipId)
 {
   mClipManager.PushOverrideForASR(aASR, aClipId);
 }
 
 void
 WebRenderCommandBuilder::PopOverrideForASR(const ActiveScrolledRoot* aASR)
 {
   mClipManager.PopOverrideForASR(aASR);
--- a/gfx/layers/wr/WebRenderCommandBuilder.h
+++ b/gfx/layers/wr/WebRenderCommandBuilder.h
@@ -54,17 +54,17 @@ public:
                               wr::IpcResourceUpdateQueue& aResourceUpdates,
                               nsDisplayList* aDisplayList,
                               nsDisplayListBuilder* aDisplayListBuilder,
                               WebRenderScrollData& aScrollData,
                               wr::LayoutSize& aContentSize,
                               const nsTArray<wr::WrFilterOp>& aFilters);
 
   void PushOverrideForASR(const ActiveScrolledRoot* aASR,
-                          const Maybe<wr::WrClipId>& aClipId);
+                          const wr::WrClipId& aClipId);
   void PopOverrideForASR(const ActiveScrolledRoot* aASR);
 
   Maybe<wr::ImageKey> CreateImageKey(nsDisplayItem* aItem,
                                      ImageContainer* aContainer,
                                      mozilla::wr::DisplayListBuilder& aBuilder,
                                      mozilla::wr::IpcResourceUpdateQueue& aResources,
                                      mozilla::wr::ImageRendering aRendering,
                                      const StackingContextHelper& aSc,
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 20; 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 "mozilla/FontPropertyTypes.h"
+#include "mozilla/RDDProcessManager.h"
 #include "mozilla/image/ImageMemoryReporter.h"
 #include "mozilla/layers/CompositorManagerChild.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/webrender_ffi.h"
@@ -762,31 +763,33 @@ WebRenderMemoryReporter::CollectReports(
   return NS_OK;
 }
 
 
 void
 gfxPlatform::Init()
 {
     MOZ_RELEASE_ASSERT(!XRE_IsGPUProcess(), "GFX: Not allowed in GPU process.");
+    MOZ_RELEASE_ASSERT(!XRE_IsRDDProcess(), "GFX: Not allowed in RDD process.");
     MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
 
     if (gEverInitialized) {
         MOZ_CRASH("Already started???");
     }
     gEverInitialized = true;
 
     // Initialize the preferences by creating the singleton.
     gfxPrefs::GetSingleton();
     gfxVars::Initialize();
 
     gfxConfig::Init();
 
     if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
       GPUProcessManager::Initialize();
+      RDDProcessManager::Initialize();
 
       if (Preferences::GetBool("media.wmf.skip-blacklist")) {
         gfxVars::SetPDMWMFDisableD3D11Dlls(nsCString());
         gfxVars::SetPDMWMFDisableD3D9Dlls(nsCString());
       } else {
         nsAutoCString d3d11;
         Preferences::GetCString("media.wmf.disable-d3d11-for-dlls", d3d11);
         gfxVars::SetPDMWMFDisableD3D11Dlls(d3d11);
@@ -878,16 +881,22 @@ gfxPlatform::Init()
     gPlatform->InitWebRenderConfig();
     gPlatform->InitOMTPConfig();
 
     if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
       GPUProcessManager* gpu = GPUProcessManager::Get();
       gpu->LaunchGPUProcess();
     }
 
+    if (XRE_IsParentProcess() &&
+        Preferences::GetBool("media.rdd-process.enabled", false)) {
+      RDDProcessManager* rdd = RDDProcessManager::Get();
+      if (rdd) { rdd->LaunchRDDProcess(); }
+    }
+
     if (XRE_IsParentProcess() || recordreplay::IsRecordingOrReplaying()) {
       if (gfxPlatform::ForceSoftwareVsync()) {
         gPlatform->mVsyncSource = (gPlatform)->gfxPlatform::CreateHardwareVsyncSource();
       } else {
         gPlatform->mVsyncSource = gPlatform->CreateHardwareVsyncSource();
       }
     }
 
@@ -1156,16 +1165,17 @@ gfxPlatform::Shutdown()
     // could go away. Unfortunately, we currently support WGL (the default) for
     // WebGL on Optimus.
     GLContextProviderEGL::Shutdown();
 #endif
 
     if (XRE_IsParentProcess()) {
       GPUProcessManager::Shutdown();
       VRProcessManager::Shutdown();
+      RDDProcessManager::Shutdown();
     }
 
     gfx::Factory::ShutDown();
 
     delete gGfxPlatformPrefsLock;
 
     gfxVars::Shutdown();
     gfxPrefs::DestroySingleton();
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -1,29 +1,23 @@
 /* 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 api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, DeviceIntSize, LayoutPrimitiveInfo};
-use api::{LayoutRect, LayoutSize, LayoutToDeviceScale, LayoutVector2D, MAX_BLUR_RADIUS};
+use api::{LayoutRect, LayoutSize, LayoutVector2D, MAX_BLUR_RADIUS};
 use clip::ClipItemKey;
 use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BoxShadowStretchMode;
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
 use render_task::RenderTaskCacheEntryHandle;
 use util::RectHelpers;
 
-/// In the majority of cases box-shadow radius in device pixels should be quite
-/// small (< 256), so the choice of max is trade off between panic! in
-/// pathological case of being presented with huge radius and using too much
-/// texture memory for better rendering of such cases.
-const MAX_BOX_SHADOW_RESOLUTION: u32 = 2048;
-
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowClipSource {
     // Parameters that define the shadow and are constant.
     pub shadow_radius: BorderRadius,
     pub blur_radius: f32,
     pub clip_mode: BoxShadowClipMode,
@@ -34,16 +28,20 @@ pub struct BoxShadowClipSource {
     // to the cached clip region and blurred texture.
     pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>,
     pub cache_handle: Option<RenderTaskCacheEntryHandle>,
     pub clip_data_handle: GpuCacheHandle,
 
     // Local-space size of the required render task size.
     pub shadow_rect_alloc_size: LayoutSize,
 
+    // Local-space size of the required render task size without any downscaling
+    // applied. This is needed to stretch the shadow properly.
+    pub original_alloc_size: LayoutSize,
+
     // The minimal shadow rect for the parameters above,
     // used when drawing the shadow rect to be blurred.
     pub minimal_shadow_rect: LayoutRect,
 
     // Local space rect for the shadow to be drawn or
     // stretched in the shadow primitive.
     pub prim_shadow_rect: LayoutRect,
 }
@@ -55,17 +53,19 @@ pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 // and blurred box-shadow rect that can be stored in the
 // texture cache and applied to clip-masks.
 #[derive(Debug, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowCacheKey {
     pub blur_radius_dp: i32,
     pub clip_mode: BoxShadowClipMode,
-    pub rect_size: DeviceIntSize,
+    // NOTE(emilio): Only the original allocation size needs to be in the cache
+    // key, since the actual size is derived from that.
+    pub original_alloc_size: DeviceIntSize,
     pub br_top_left: DeviceIntSize,
     pub br_top_right: DeviceIntSize,
     pub br_bottom_right: DeviceIntSize,
     pub br_bottom_left: DeviceIntSize,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn add_box_shadow(
@@ -245,13 +245,8 @@ fn adjust_corner_for_box_shadow(corner: 
 
 fn adjust_radius_for_box_shadow(border_radius: f32, spread_amount: f32) -> f32 {
     if border_radius > 0.0 {
         (border_radius + spread_amount).max(0.0)
     } else {
         0.0
     }
 }
-
-pub fn get_max_scale_for_box_shadow(rect_size: &LayoutSize) -> LayoutToDeviceScale {
-    let r = rect_size.width.max(rect_size.height);
-    LayoutToDeviceScale::new(MAX_BOX_SHADOW_RESOLUTION as f32 / r)
-}
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -5,17 +5,16 @@
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D};
 use api::{BoxShadowClipMode, LayoutToWorldScale, PicturePixel, WorldPixel};
 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
 use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
 use app_units::Au;
 use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
-use box_shadow::get_max_scale_for_box_shadow;
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode};
 use image::{self, Repetition};
 use intern;
 use internal_types::FastHashSet;
 use prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
@@ -334,18 +333,18 @@ impl ClipNode {
                         }
                         resource_cache.request_image(request, gpu_cache);
                     }
                 }
             }
             ClipItem::BoxShadow(ref mut info) => {
                 if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
                     request.push([
-                        info.shadow_rect_alloc_size.width,
-                        info.shadow_rect_alloc_size.height,
+                        info.original_alloc_size.width,
+                        info.original_alloc_size.height,
                         info.clip_mode as i32 as f32,
                         0.0,
                     ]);
                     request.push([
                         info.stretch_mode_x as i32 as f32,
                         info.stretch_mode_y as i32 as f32,
                         0.0,
                         0.0,
@@ -353,31 +352,25 @@ impl ClipNode {
                     request.push(info.prim_shadow_rect);
                 }
 
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
                 let blur_radius_dp = info.blur_radius * 0.5;
 
-                // Create scaling from requested size to cache size.  If the
-                // requested size exceeds the maximum size of the cache, the
-                // box shadow will be scaled down and stretched when rendered
-                // into the document.
-                let world_scale = LayoutToWorldScale::new(1.0);
-                let mut content_scale = world_scale * device_pixel_scale;
-                let max_scale = get_max_scale_for_box_shadow(&info.shadow_rect_alloc_size);
-                content_scale.0 = content_scale.0.min(max_scale.0);
+                // Create scaling from requested size to cache size.
+                let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
 
                 // Create the cache key for this box-shadow render task.
                 let cache_size = to_cache_size(info.shadow_rect_alloc_size * content_scale);
                 let bs_cache_key = BoxShadowCacheKey {
                     blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
                     clip_mode: info.clip_mode,
-                    rect_size: (info.shadow_rect_alloc_size * content_scale).round().to_i32(),
+                    original_alloc_size: (info.original_alloc_size * content_scale).round().to_i32(),
                     br_top_left: (info.shadow_radius.top_left * content_scale).round().to_i32(),
                     br_top_right: (info.shadow_radius.top_right * content_scale).round().to_i32(),
                     br_bottom_right: (info.shadow_radius.bottom_right * content_scale).round().to_i32(),
                     br_bottom_left: (info.shadow_radius.bottom_left * content_scale).round().to_i32(),
                 };
 
                 info.cache_key = Some((cache_size, bs_cache_key));
 
@@ -876,113 +869,171 @@ impl ClipItemKey {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ClipItem {
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
     Image { mask: ImageMask, visible_tiles: Option<Vec<VisibleMaskImageTile>> },
     BoxShadow(BoxShadowClipSource),
 }
 
+fn compute_box_shadow_parameters(
+    shadow_rect: LayoutRect,
+    mut shadow_radius: BorderRadius,
+    prim_shadow_rect: LayoutRect,
+    blur_radius: f32,
+    clip_mode: BoxShadowClipMode,
+) -> BoxShadowClipSource {
+    // Make sure corners don't overlap.
+    ensure_no_corner_overlap(&mut shadow_radius, &shadow_rect);
+
+    // Get the fractional offsets required to match the
+    // source rect with a minimal rect.
+    let fract_offset = LayoutPoint::new(
+        shadow_rect.origin.x.fract().abs(),
+        shadow_rect.origin.y.fract().abs(),
+    );
+    let fract_size = LayoutSize::new(
+        shadow_rect.size.width.fract().abs(),
+        shadow_rect.size.height.fract().abs(),
+    );
+
+    // Create a minimal size primitive mask to blur. In this
+    // case, we ensure the size of each corner is the same,
+    // to simplify the shader logic that stretches the blurred
+    // result across the primitive.
+    let max_corner_width = shadow_radius.top_left.width
+                                .max(shadow_radius.bottom_left.width)
+                                .max(shadow_radius.top_right.width)
+                                .max(shadow_radius.bottom_right.width);
+    let max_corner_height = shadow_radius.top_left.height
+                                .max(shadow_radius.bottom_left.height)
+                                .max(shadow_radius.top_right.height)
+                                .max(shadow_radius.bottom_right.height);
+
+    // Get maximum distance that can be affected by given blur radius.
+    let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
+
+    // If the largest corner is smaller than the blur radius, we need to ensure
+    // that it's big enough that the corners don't affect the middle segments.
+    let used_corner_width = max_corner_width.max(blur_region);
+    let used_corner_height = max_corner_height.max(blur_region);
+
+    // Minimal nine-patch size, corner + internal + corner.
+    let min_shadow_rect_size = LayoutSize::new(
+        2.0 * used_corner_width + blur_region,
+        2.0 * used_corner_height + blur_region,
+    );
+
+    // The minimal rect to blur.
+    let mut minimal_shadow_rect = LayoutRect::new(
+        LayoutPoint::new(
+            blur_region + fract_offset.x,
+            blur_region + fract_offset.y,
+        ),
+        LayoutSize::new(
+            min_shadow_rect_size.width + fract_size.width,
+            min_shadow_rect_size.height + fract_size.height,
+        ),
+    );
+
+    // If the width or height ends up being bigger than the original
+    // primitive shadow rect, just blur the entire rect along that
+    // axis and draw that as a simple blit. This is necessary for
+    // correctness, since the blur of one corner may affect the blur
+    // in another corner.
+    let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
+    if shadow_rect.size.width < minimal_shadow_rect.size.width {
+        minimal_shadow_rect.size.width = shadow_rect.size.width;
+        stretch_mode_x = BoxShadowStretchMode::Simple;
+    }
+
+    let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
+    if shadow_rect.size.height < minimal_shadow_rect.size.height {
+        minimal_shadow_rect.size.height = shadow_rect.size.height;
+        stretch_mode_y = BoxShadowStretchMode::Simple;
+    }
+
+    // Expand the shadow rect by enough room for the blur to take effect.
+    let shadow_rect_alloc_size = LayoutSize::new(
+        2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
+        2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
+    );
+
+    BoxShadowClipSource {
+        original_alloc_size: shadow_rect_alloc_size,
+        shadow_rect_alloc_size,
+        shadow_radius,
+        prim_shadow_rect,
+        blur_radius,
+        clip_mode,
+        stretch_mode_x,
+        stretch_mode_y,
+        cache_handle: None,
+        cache_key: None,
+        clip_data_handle: GpuCacheHandle::new(),
+        minimal_shadow_rect,
+    }
+}
+
 impl ClipItem {
     pub fn new_box_shadow(
         shadow_rect: LayoutRect,
         mut shadow_radius: BorderRadius,
         prim_shadow_rect: LayoutRect,
         blur_radius: f32,
         clip_mode: BoxShadowClipMode,
     ) -> Self {
-        // Make sure corners don't overlap.
-        ensure_no_corner_overlap(&mut shadow_radius, &shadow_rect);
-
-        // Get the fractional offsets required to match the
-        // source rect with a minimal rect.
-        let fract_offset = LayoutPoint::new(
-            shadow_rect.origin.x.fract().abs(),
-            shadow_rect.origin.y.fract().abs(),
-        );
-        let fract_size = LayoutSize::new(
-            shadow_rect.size.width.fract().abs(),
-            shadow_rect.size.height.fract().abs(),
-        );
-
-        // Create a minimal size primitive mask to blur. In this
-        // case, we ensure the size of each corner is the same,
-        // to simplify the shader logic that stretches the blurred
-        // result across the primitive.
-        let max_corner_width = shadow_radius.top_left.width
-                                    .max(shadow_radius.bottom_left.width)
-                                    .max(shadow_radius.top_right.width)
-                                    .max(shadow_radius.bottom_right.width);
-        let max_corner_height = shadow_radius.top_left.height
-                                    .max(shadow_radius.bottom_left.height)
-                                    .max(shadow_radius.top_right.height)
-                                    .max(shadow_radius.bottom_right.height);
-
-        // Get maximum distance that can be affected by given blur radius.
-        let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
-
-        // If the largest corner is smaller than the blur radius, we need to ensure
-        // that it's big enough that the corners don't affect the middle segments.
-        let used_corner_width = max_corner_width.max(blur_region);
-        let used_corner_height = max_corner_height.max(blur_region);
-
-        // Minimal nine-patch size, corner + internal + corner.
-        let min_shadow_rect_size = LayoutSize::new(
-            2.0 * used_corner_width + blur_region,
-            2.0 * used_corner_height + blur_region,
-        );
-
-        // The minimal rect to blur.
-        let mut minimal_shadow_rect = LayoutRect::new(
-            LayoutPoint::new(
-                blur_region + fract_offset.x,
-                blur_region + fract_offset.y,
-            ),
-            LayoutSize::new(
-                min_shadow_rect_size.width + fract_size.width,
-                min_shadow_rect_size.height + fract_size.height,
-            ),
-        );
-
-        // If the width or height ends up being bigger than the original
-        // primitive shadow rect, just blur the entire rect along that
-        // axis and draw that as a simple blit. This is necessary for
-        // correctness, since the blur of one corner may affect the blur
-        // in another corner.
-        let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
-        if shadow_rect.size.width < minimal_shadow_rect.size.width {
-            minimal_shadow_rect.size.width = shadow_rect.size.width;
-            stretch_mode_x = BoxShadowStretchMode::Simple;
-        }
-
-        let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
-        if shadow_rect.size.height < minimal_shadow_rect.size.height {
-            minimal_shadow_rect.size.height = shadow_rect.size.height;
-            stretch_mode_y = BoxShadowStretchMode::Simple;
-        }
-
-        // Expand the shadow rect by enough room for the blur to take effect.
-        let shadow_rect_alloc_size = LayoutSize::new(
-            2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
-            2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
-        );
-
-        ClipItem::BoxShadow(BoxShadowClipSource {
-            shadow_rect_alloc_size,
+        let mut source = compute_box_shadow_parameters(
+            shadow_rect,
             shadow_radius,
             prim_shadow_rect,
             blur_radius,
             clip_mode,
-            stretch_mode_x,
-            stretch_mode_y,
-            cache_handle: None,
-            cache_key: None,
-            clip_data_handle: GpuCacheHandle::new(),
-            minimal_shadow_rect,
-        })
+        );
+
+        fn needed_downscaling(source: &BoxShadowClipSource) -> Option<f32> {
+            // This size is fairly arbitrary, but it's the same as the size that
+            // we use to avoid caching big blurred stacking contexts.
+            //
+            // If you change it, ensure that the reftests
+            // box-shadow-large-blur-radius-* still hit the downscaling path,
+            // and that they render correctly.
+            const MAX_SIZE: f32 = 2048.;
+
+            let max_dimension =
+                source.shadow_rect_alloc_size.width.max(source.shadow_rect_alloc_size.height);
+
+            if max_dimension > MAX_SIZE {
+                Some(MAX_SIZE / max_dimension)
+            } else {
+                None
+            }
+        }
+
+        if let Some(downscale) = needed_downscaling(&source) {
+            shadow_radius.bottom_left.height *= downscale;
+            shadow_radius.bottom_left.width *= downscale;
+            shadow_radius.bottom_right.height *= downscale;
+            shadow_radius.bottom_right.width *= downscale;
+            shadow_radius.top_left.height *= downscale;
+            shadow_radius.top_left.width *= downscale;
+            shadow_radius.top_right.height *= downscale;
+            shadow_radius.top_right.width *= downscale;
+
+            let original_alloc_size = source.shadow_rect_alloc_size;
+            source = compute_box_shadow_parameters(
+                shadow_rect.scale(downscale, downscale),
+                shadow_radius,
+                prim_shadow_rect,
+                blur_radius * downscale,
+                clip_mode,
+            );
+            source.original_alloc_size = original_alloc_size;
+        }
+        ClipItem::BoxShadow(source)
     }
 
     // Get an optional clip rect that a clip source can provide to
     // reduce the size of a primitive region. This is typically
     // used to eliminate redundant clips, and reduce the size of
     // any clip mask that eventually gets drawn.
     fn get_local_clip_rect(&self) -> Option<LayoutRect> {
         match *self {
--- a/gfx/webrender/src/debug_render.rs
+++ b/gfx/webrender/src/debug_render.rs
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorU, DeviceIntRect, DeviceUintSize, ImageFormat, TextureTarget};
+use api::{ColorU, ImageFormat, TextureTarget};
+use api::{DeviceIntRect, DeviceRect, DevicePoint, DeviceSize, DeviceUintSize};
 use debug_font_data;
 use device::{Device, Program, Texture, TextureSlot, VertexDescriptor, ShaderError, VAO};
 use device::{TextureFilter, VertexAttribute, VertexAttributeKind, VertexUsageHint};
 use euclid::{Point2D, Rect, Size2D, Transform3D};
 use internal_types::{ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE};
 use std::f32;
 
 #[derive(Debug, Copy, Clone)]
@@ -160,17 +161,30 @@ impl DebugRenderer {
         device.delete_vao(self.line_vao);
         device.delete_vao(self.font_vao);
     }
 
     pub fn line_height(&self) -> f32 {
         debug_font_data::FONT_SIZE as f32 * 1.1
     }
 
-    pub fn add_text(&mut self, x: f32, y: f32, text: &str, color: ColorU) -> Rect<f32> {
+    /// Draws a line of text at the provided starting coordinates.
+    ///
+    /// If |bounds| is specified, glyphs outside the bounds are discarded.
+    ///
+    /// Y-coordinates is relative to screen top, along with everything else in
+    /// this file.
+    pub fn add_text(
+        &mut self,
+        x: f32,
+        y: f32,
+        text: &str,
+        color: ColorU,
+        bounds: Option<DeviceRect>,
+    ) -> Rect<f32> {
         let mut x_start = x;
         let ipw = 1.0 / debug_font_data::BMP_WIDTH as f32;
         let iph = 1.0 / debug_font_data::BMP_HEIGHT as f32;
 
         let mut min_x = f32::MAX;
         let mut max_x = -f32::MAX;
         let mut min_y = f32::MAX;
         let mut max_y = -f32::MAX;
@@ -181,16 +195,27 @@ impl DebugRenderer {
                 let glyph = &debug_font_data::GLYPHS[c];
 
                 let x0 = (x_start + glyph.xo + 0.5).floor();
                 let y0 = (y + glyph.yo + 0.5).floor();
 
                 let x1 = x0 + glyph.x1 as f32 - glyph.x0 as f32;
                 let y1 = y0 + glyph.y1 as f32 - glyph.y0 as f32;
 
+                // If either corner of the glyph will end up out of bounds, drop it.
+                if let Some(b) = bounds {
+                    let rect = DeviceRect::new(
+                        DevicePoint::new(x0, y0),
+                        DeviceSize::new(x1 - x0, y1 - y0),
+                    );
+                    if !b.contains_rect(&rect) {
+                        continue;
+                    }
+                }
+
                 let s0 = glyph.x0 as f32 * ipw;
                 let t0 = glyph.y0 as f32 * iph;
                 let s1 = glyph.x1 as f32 * ipw;
                 let t1 = glyph.y1 as f32 * iph;
 
                 x_start += glyph.xa;
 
                 let vertex_count = self.font_vertices.len() as u32;
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -87,16 +87,17 @@ const DEFAULT_TEXTURE: TextureSlot = Tex
 
 #[repr(u32)]
 pub enum DepthFunction {
     #[cfg(feature = "debug_renderer")]
     Less = gl::LESS,
     LessEqual = gl::LEQUAL,
 }
 
+#[repr(u32)]
 #[derive(Copy, Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TextureFilter {
     Nearest,
     Linear,
     Trilinear,
 }
@@ -459,29 +460,38 @@ impl ExternalTexture {
     }
 
     #[cfg(feature = "replay")]
     pub fn internal_id(&self) -> gl::GLuint {
         self.id
     }
 }
 
+bitflags! {
+    #[derive(Default)]
+    pub struct TextureFlags: u32 {
+        /// This texture corresponds to one of the shared texture caches.
+        const IS_SHARED_TEXTURE_CACHE = 1 << 0;
+    }
+}
+
 /// WebRender interface to an OpenGL texture.
 ///
 /// Because freeing a texture requires various device handles that are not
 /// reachable from this struct, manual destruction via `Device` is required.
 /// Our `Drop` implementation asserts that this has happened.
 pub struct Texture {
     id: gl::GLuint,
     target: gl::GLuint,
     layer_count: i32,
     format: ImageFormat,
     width: u32,
     height: u32,
     filter: TextureFilter,
+    flags: TextureFlags,
     /// Framebuffer Objects, one for each layer of the texture, allowing this
     /// texture to be rendered to. Empty if this texture is not used as a render
     /// target.
     fbos: Vec<FBOId>,
     /// Same as the above, but with a depth buffer attached.
     ///
     /// FBOs are cheap to create but expensive to reconfigure (since doing so
     /// invalidates framebuffer completeness caching). Moreover, rendering with
@@ -529,25 +539,40 @@ impl Texture {
     }
 
     /// Returns true if this texture was used within `threshold` frames of
     /// the current frame.
     pub fn used_recently(&self, current_frame_id: GpuFrameId, threshold: usize) -> bool {
         self.last_frame_used + threshold >= current_frame_id
     }
 
-    /// Returns the number of bytes (generally in GPU memory) that this texture
-    /// consumes.
-    pub fn size_in_bytes(&self) -> usize {
+    /// Returns the flags for this texture.
+    pub fn flags(&self) -> &TextureFlags {
+        &self.flags
+    }
+
+    /// Returns a mutable borrow of the flags for this texture.
+    pub fn flags_mut(&mut self) -> &mut TextureFlags {
+        &mut self.flags
+    }
+
+    /// Returns the number of bytes (generally in GPU memory) that each layer of
+    /// this texture consumes.
+    pub fn layer_size_in_bytes(&self) -> usize {
         assert!(self.layer_count > 0 || self.width + self.height == 0);
         let bpp = self.format.bytes_per_pixel() as usize;
         let w = self.width as usize;
         let h = self.height as usize;
-        let count = self.layer_count as usize;
-        bpp * w * h * count
+        bpp * w * h
+    }
+
+    /// Returns the number of bytes (generally in GPU memory) that this texture
+    /// consumes.
+    pub fn size_in_bytes(&self) -> usize {
+        self.layer_size_in_bytes() * (self.layer_count as usize)
     }
 
     #[cfg(feature = "replay")]
     pub fn into_external(mut self) -> ExternalTexture {
         let ext = ExternalTexture {
             id: self.id,
             target: self.target,
         };
@@ -1449,16 +1474,17 @@ impl Device {
             width,
             height,
             layer_count,
             format,
             filter,
             fbos: vec![],
             fbos_with_depth: vec![],
             last_frame_used: self.frame_id,
+            flags: TextureFlags::default(),
         };
         self.bind_texture(DEFAULT_TEXTURE, &texture);
         self.set_texture_parameters(texture.target, filter);
 
         // Allocate storage.
         let desc = self.gl_describe_format(texture.format);
         let is_array = match texture.target {
             gl::TEXTURE_2D_ARRAY => true,
@@ -1738,16 +1764,39 @@ impl Device {
             dest_rect.origin.y,
             dest_rect.origin.x + dest_rect.size.width,
             dest_rect.origin.y + dest_rect.size.height,
             gl::COLOR_BUFFER_BIT,
             gl::LINEAR,
         );
     }
 
+    /// Performs a blit while flipping vertically. Useful for blitting textures
+    /// (which use origin-bottom-left) to the main framebuffer (which uses
+    /// origin-top-left).
+    pub fn blit_render_target_invert_y(
+        &mut self,
+        src_rect: DeviceIntRect,
+        dest_rect: DeviceIntRect,
+    ) {
+        debug_assert!(self.inside_frame);
+        self.gl.blit_framebuffer(
+            src_rect.origin.x,
+            src_rect.origin.y,
+            src_rect.origin.x + src_rect.size.width,
+            src_rect.origin.y + src_rect.size.height,
+            dest_rect.origin.x,
+            dest_rect.origin.y + dest_rect.size.height,
+            dest_rect.origin.x + dest_rect.size.width,
+            dest_rect.origin.y,
+            gl::COLOR_BUFFER_BIT,
+            gl::LINEAR,
+        );
+    }
+
     pub fn delete_texture(&mut self, mut texture: Texture) {
         debug_assert!(self.inside_frame);
         record_gpu_free(texture.size_in_bytes());
         let had_depth = texture.supports_depth();
         self.deinit_fbos(&mut texture.fbos);
         self.deinit_fbos(&mut texture.fbos_with_depth);
         if had_depth {
             self.release_depth_target(texture.get_dimensions());
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -122,16 +122,18 @@ pub struct TextureCacheAllocation {
 /// Information used when allocating / reallocating.
 #[derive(Debug)]
 pub struct TextureCacheAllocInfo {
     pub width: u32,
     pub height: u32,
     pub layer_count: i32,
     pub format: ImageFormat,
     pub filter: TextureFilter,
+    /// Indicates whether this corresponds to one of the shared texture caches.
+    pub is_shared_cache: bool,
 }
 
 /// Sub-operation-specific information for allocation operations.
 #[derive(Debug)]
 pub enum TextureCacheAllocationKind {
     /// Performs an initial texture allocation.
     Alloc(TextureCacheAllocInfo),
     /// Reallocates the texture. The existing live texture with the same id
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2718,18 +2718,18 @@ impl BrushPrimitive {
 
                     // Push a region into the segment builder where the
                     // box-shadow can have an effect on the result. This
                     // ensures clip-mask tasks get allocated for these
                     // pixel regions, even if no other clips affect them.
                     segment_builder.push_mask_region(
                         info.prim_shadow_rect,
                         info.prim_shadow_rect.inflate(
-                            -0.5 * info.shadow_rect_alloc_size.width,
-                            -0.5 * info.shadow_rect_alloc_size.height,
+                            -0.5 * info.original_alloc_size.width,
+                            -0.5 * info.original_alloc_size.height,
                         ),
                         inner_clip_mode,
                     );
 
                     continue;
                 }
                 ClipItem::Image { .. } => {
                     rect_clips_only = false;
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -573,34 +573,38 @@ impl ProfileGraph {
 
         let text_color = ColorU::new(255, 255, 0, 255);
         let text_origin = rect.origin + vec2(rect.size.width, 20.0);
         debug_renderer.add_text(
             text_origin.x,
             text_origin.y,
             description,
             ColorU::new(0, 255, 0, 255),
+            None,
         );
         debug_renderer.add_text(
             text_origin.x,
             text_origin.y + line_height,
             &format!("Min: {:.2} ms", stats.min_value),
             text_color,
+            None,
         );
         debug_renderer.add_text(
             text_origin.x,
             text_origin.y + line_height * 2.0,
             &format!("Mean: {:.2} ms", stats.mean_value),
             text_color,
+            None,
         );
         debug_renderer.add_text(
             text_origin.x,
             text_origin.y + line_height * 3.0,
             &format!("Max: {:.2} ms", stats.max_value),
             text_color,
+            None,
         );
 
         rect.size.width += 140.0;
         debug_renderer.add_quad(
             rect.origin.x,
             rect.origin.y,
             rect.origin.x + rect.size.width + 10.0,
             rect.origin.y + rect.size.height,
@@ -765,16 +769,17 @@ impl GpuFrameCollection {
                 color.into(),
             );
 
             debug_renderer.add_text(
                 x0 + PADDED_LEGEND_SIZE,
                 y0 + LEGEND_SIZE * 0.75,
                 label,
                 ColorU::new(255, 255, 0, 255),
+                None,
             );
         }
 
         bounding_rect
     }
 }
 
 #[cfg(feature = "debug_renderer")]
@@ -835,16 +840,17 @@ impl Profiler {
         ];
 
         for counter in counters {
             let rect = debug_renderer.add_text(
                 current_x,
                 current_y,
                 counter.description(),
                 colors[color_index],
+                None,
             );
             color_index = (color_index + 1) % colors.len();
 
             label_rect = label_rect.union(&rect);
             current_y += line_height;
         }
 
         color_index = 0;
@@ -852,16 +858,17 @@ impl Profiler {
         current_y = if left { draw_state.y_left } else { draw_state.y_right };
 
         for counter in counters {
             let rect = debug_renderer.add_text(
                 current_x,
                 current_y,
                 &counter.value(),
                 colors[color_index],
+                None,
             );
             color_index = (color_index + 1) % colors.len();
 
             value_rect = value_rect.union(&rect);
             current_y += line_height;
         }
 
         let total_rect = label_rect.union(&value_rect).inflate(10.0, 10.0);
@@ -888,16 +895,17 @@ impl Profiler {
         counters: &[(ColorU, &IntProfileCounter)],
         debug_renderer: &mut DebugRenderer,
     ) -> Rect<f32> {
         let mut rect = debug_renderer.add_text(
             self.draw_state.x_left,
             self.draw_state.y_left,
             label,
             label_color,
+            None,
         );
 
         let x_base = rect.origin.x + rect.size.width + 10.0;
         let height = debug_renderer.line_height();
         let width = (self.draw_state.x_right - 30.0 - x_base).max(0.0);
         let total_value = counters.last().unwrap().1.value;
         let scale = width / total_value as f32;
         let mut x_current = x_base;
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -32,17 +32,17 @@ use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use device::{DepthFunction, Device, GpuFrameId, Program, UploadMethod, Texture, PBO};
 use device::{DrawTarget, ExternalTexture, FBOId, ReadTarget, TextureSlot};
-use device::{ShaderError, TextureFilter,
+use device::{ShaderError, TextureFilter, TextureFlags,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 #[cfg(feature = "debug_renderer")]
 use euclid::rect;
 use euclid::Transform3D;
 use frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
@@ -2819,28 +2819,33 @@ impl Renderer {
                     let is_realloc = matches!(allocation.kind, TextureCacheAllocationKind::Realloc(..));
                     match allocation.kind {
                         TextureCacheAllocationKind::Alloc(info) |
                         TextureCacheAllocationKind::Realloc(info) => {
                             // Create a new native texture, as requested by the texture cache.
                             //
                             // Ensure no PBO is bound when creating the texture storage,
                             // or GL will attempt to read data from there.
-                            let texture = self.device.create_texture(
+                            let mut texture = self.device.create_texture(
                                 TextureTarget::Array,
                                 info.format,
                                 info.width,
                                 info.height,
                                 info.filter,
                                 // This needs to be a render target because some render
                                 // tasks get rendered into the texture cache.
                                 Some(RenderTargetInfo { has_depth: false }),
                                 info.layer_count,
                             );
 
+                            if info.is_shared_cache {
+                                texture.flags_mut()
+                                    .insert(TextureFlags::IS_SHARED_TEXTURE_CACHE);
+                            }
+
                             let old = self.texture_resolver.texture_cache_map.insert(allocation.id, texture);
                             assert_eq!(old.is_some(), is_realloc, "Renderer and RenderBackend disagree");
                             if let Some(old) = old {
                                 self.device.blit_renderable_texture(
                                     self.texture_resolver.texture_cache_map.get_mut(&allocation.id).unwrap(),
                                     &old
                                 );
                                 self.device.delete_texture(old);
@@ -4180,95 +4185,143 @@ impl Renderer {
     }
 
     #[cfg(feature = "debug_renderer")]
     fn draw_render_target_debug(&mut self, framebuffer_size: DeviceUintSize) {
         if !self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) {
             return;
         }
 
-        let mut spacing = 16;
-        let mut size = 512;
-        let fb_width = framebuffer_size.width as i32;
-        let num_layers: i32 = self.texture_resolver.render_target_pool
-            .iter()
-            .map(|texture| texture.get_layer_count() as i32)
-            .sum();
-
-        if num_layers * (size + spacing) > fb_width {
-            let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32;
-            size = (size as f32 * factor) as i32;
-            spacing = (spacing as f32 * factor) as i32;
-        }
-
-        let mut target_index = 0;
-        for texture in &self.texture_resolver.render_target_pool {
-            let dimensions = texture.get_dimensions();
-            let src_rect = DeviceIntRect::new(DeviceIntPoint::zero(), dimensions.to_i32());
-
-            let layer_count = texture.get_layer_count() as usize;
-            for layer in 0 .. layer_count {
-                self.device.bind_read_target(ReadTarget::Texture { texture, layer });
-                let x = fb_width - (spacing + size) * (target_index + 1);
-                let y = spacing;
-
-                let dest_rect = rect(x, y, size, size);
-                self.device.blit_render_target(src_rect, dest_rect);
-                target_index += 1;
-            }
-        }
+        let debug_renderer = match self.debug.get_mut(&mut self.device) {
+            Some(render) => render,
+            None => return,
+        };
+
+        let textures =
+            self.texture_resolver.render_target_pool.iter().collect::<Vec<&Texture>>();
+
+        Self::do_debug_blit(
+            &mut self.device,
+            debug_renderer,
+            textures,
+            framebuffer_size,
+            0,
+            &|_| [0.0, 1.0, 0.0, 1.0], // Use green for all RTs.
+        );
     }
 
     #[cfg(feature = "debug_renderer")]
     fn draw_texture_cache_debug(&mut self, framebuffer_size: DeviceUintSize) {
         if !self.debug_flags.contains(DebugFlags::TEXTURE_CACHE_DBG) {
             return;
         }
 
+        let debug_renderer = match self.debug.get_mut(&mut self.device) {
+            Some(render) => render,
+            None => return,
+        };
+
+        let textures =
+            self.texture_resolver.texture_cache_map.values().collect::<Vec<&Texture>>();
+
+        fn select_color(texture: &Texture) -> [f32; 4] {
+            if texture.flags().contains(TextureFlags::IS_SHARED_TEXTURE_CACHE) {
+                [1.0, 0.5, 0.0, 1.0] // Orange for shared.
+            } else {
+                [1.0, 0.0, 1.0, 1.0] // Fuchsia for standalone.
+            }
+        }
+
+        Self::do_debug_blit(
+            &mut self.device,
+            debug_renderer,
+            textures,
+            framebuffer_size,
+            if self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) { 544 } else { 0 },
+            &select_color,
+        );
+    }
+
+    #[cfg(feature = "debug_renderer")]
+    fn do_debug_blit(
+        device: &mut Device,
+        debug_renderer: &mut DebugRenderer,
+        mut textures: Vec<&Texture>,
+        framebuffer_size: DeviceUintSize,
+        bottom: i32,
+        select_color: &Fn(&Texture) -> [f32; 4],
+    ) {
         let mut spacing = 16;
         let mut size = 512;
+
         let fb_width = framebuffer_size.width as i32;
-        let num_layers: i32 = self.texture_resolver
-            .texture_cache_map
-            .values()
+        let fb_height = framebuffer_size.height as i32;
+        let num_layers: i32 = textures.iter()
             .map(|texture| texture.get_layer_count())
             .sum();
 
         if num_layers * (size + spacing) > fb_width {
             let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32;
             size = (size as f32 * factor) as i32;
             spacing = (spacing as f32 * factor) as i32;
         }
 
+        // Sort the display by layer size (in bytes), so that left-to-right is
+        // largest-to-smallest.
+        //
+        // Note that the vec here is in increasing order, because the elements
+        // get drawn right-to-left.
+        textures.sort_by_key(|t| t.layer_size_in_bytes());
+
         let mut i = 0;
-        for texture in self.texture_resolver.texture_cache_map.values() {
-            let y = spacing + if self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) {
-                528
-            } else {
-                0
-            };
+        for texture in textures.iter() {
+            let y = spacing + bottom;
             let dimensions = texture.get_dimensions();
             let src_rect = DeviceIntRect::new(
                 DeviceIntPoint::zero(),
                 DeviceIntSize::new(dimensions.width as i32, dimensions.height as i32),
             );
 
             let layer_count = texture.get_layer_count() as usize;
             for layer in 0 .. layer_count {
-                self.device.bind_read_target(ReadTarget::Texture { texture, layer});
+                device.bind_read_target(ReadTarget::Texture { texture, layer});
 
                 let x = fb_width - (spacing + size) * (i as i32 + 1);
 
                 // If we have more targets than fit on one row in screen, just early exit.
                 if x > fb_width {
                     return;
                 }
 
-                let dest_rect = rect(x, y, size, size);
-                self.device.blit_render_target(src_rect, dest_rect);
+                // Draw the info tag.
+                let text_margin = 1;
+                let text_height = 14; // Visually aproximated.
+                let tag_height = text_height + text_margin * 2;
+                let tag_rect = rect(x, y, size, tag_height);
+                let tag_color = select_color(texture);
+                device.clear_target(Some(tag_color), None, Some(tag_rect));
+
+                // Draw the dimensions onto the tag.
+                let dim = texture.get_dimensions();
+                let mut text_rect = tag_rect;
+                text_rect.origin.y =
+                    fb_height - text_rect.origin.y - text_rect.size.height; // Top-relative.
+                debug_renderer.add_text(
+                    (x + text_margin) as f32,
+                    (fb_height - y - text_margin) as f32, // Top-relative.
+                    &format!("{}x{}", dim.width, dim.height),
+                    ColorU::new(0, 0, 0, 255),
+                    Some(text_rect.to_f32())
+                );
+
+                // Blit the contents of the layer. We need to invert Y because
+                // we're blitting from a texture to the main framebuffer, which
+                // use different conventions.
+                let dest_rect = rect(x, y + tag_height, size, size);
+                device.blit_render_target_invert_y(src_rect, dest_rect);
                 i += 1;
             }
         }
     }
 
     #[cfg(feature = "debug_renderer")]
     fn draw_epoch_debug(&mut self) {
         if !self.debug_flags.contains(DebugFlags::EPOCHS) {
@@ -4286,16 +4339,17 @@ impl Renderer {
         let mut y = y0;
         let mut text_width = 0.0;
         for (pipeline, epoch) in  &self.pipeline_info.epochs {
             y += dy;
             let w = debug_renderer.add_text(
                 x0, y,
                 &format!("{:?}: {:?}", pipeline, epoch),
                 ColorU::new(255, 255, 0, 255),
+                None,
             ).size.width;
             text_width = f32::max(text_width, w);
         }
 
         let margin = 10.0;
         debug_renderer.add_quad(
             &x0 - margin,
             y0 - margin,
--- a/gfx/webrender/src/segment.rs
+++ b/gfx/webrender/src/segment.rs
@@ -229,80 +229,81 @@ impl SegmentBuilder {
     pub fn push_mask_region(
         &mut self,
         outer_rect: LayoutRect,
         inner_rect: LayoutRect,
         inner_clip_mode: Option<ClipMode>,
     ) {
         self.has_interesting_clips = true;
 
-        if inner_rect.is_well_formed_and_nonempty() {
-            debug_assert!(outer_rect.contains_rect(&inner_rect));
-
-            let p0 = outer_rect.origin;
-            let p1 = inner_rect.origin;
-            let p2 = inner_rect.bottom_right();
-            let p3 = outer_rect.bottom_right();
-
-            let segments = &[
-                LayoutRect::new(
-                    LayoutPoint::new(p0.x, p0.y),
-                    LayoutSize::new(p1.x - p0.x, p1.y - p0.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p2.x, p0.y),
-                    LayoutSize::new(p3.x - p2.x, p1.y - p0.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p2.x, p2.y),
-                    LayoutSize::new(p3.x - p2.x, p3.y - p2.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p0.x, p2.y),
-                    LayoutSize::new(p1.x - p0.x, p3.y - p2.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p1.x, p0.y),
-                    LayoutSize::new(p2.x - p1.x, p1.y - p0.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p2.x, p1.y),
-                    LayoutSize::new(p3.x - p2.x, p2.y - p1.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p1.x, p2.y),
-                    LayoutSize::new(p2.x - p1.x, p3.y - p2.y),
-                ),
-                LayoutRect::new(
-                    LayoutPoint::new(p0.x, p1.y),
-                    LayoutSize::new(p1.x - p0.x, p2.y - p1.y),
-                ),
-            ];
-
-            for segment in segments {
-                self.items.push(Item::new(
-                    *segment,
-                    None,
-                    true
-                ));
-            }
-
-            if inner_clip_mode.is_some() {
-                self.items.push(Item::new(
-                    inner_rect,
-                    inner_clip_mode,
-                    false,
-                ));
-            }
-        } else {
+        if !inner_rect.is_well_formed_and_nonempty() {
             self.items.push(Item::new(
                 outer_rect,
                 None,
                 true
             ));
+            return;
+        }
+
+        debug_assert!(outer_rect.contains_rect(&inner_rect));
+
+        let p0 = outer_rect.origin;
+        let p1 = inner_rect.origin;
+        let p2 = inner_rect.bottom_right();
+        let p3 = outer_rect.bottom_right();
+
+        let segments = &[
+            LayoutRect::new(
+                LayoutPoint::new(p0.x, p0.y),
+                LayoutSize::new(p1.x - p0.x, p1.y - p0.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p2.x, p0.y),
+                LayoutSize::new(p3.x - p2.x, p1.y - p0.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p2.x, p2.y),
+                LayoutSize::new(p3.x - p2.x, p3.y - p2.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p0.x, p2.y),
+                LayoutSize::new(p1.x - p0.x, p3.y - p2.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p1.x, p0.y),
+                LayoutSize::new(p2.x - p1.x, p1.y - p0.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p2.x, p1.y),
+                LayoutSize::new(p3.x - p2.x, p2.y - p1.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p1.x, p2.y),
+                LayoutSize::new(p2.x - p1.x, p3.y - p2.y),
+            ),
+            LayoutRect::new(
+                LayoutPoint::new(p0.x, p1.y),
+                LayoutSize::new(p1.x - p0.x, p2.y - p1.y),
+            ),
+        ];
+
+        for segment in segments {
+            self.items.push(Item::new(
+                *segment,
+                None,
+                true
+            ));
+        }
+
+        if inner_clip_mode.is_some() {
+            self.items.push(Item::new(
+                inner_rect,
+                inner_clip_mode,
+                false,
+            ));
         }
     }
 
     // Push some kind of clipping region into the segment builder.
     // If radius is None, it's a simple rect.
     pub fn push_clip_rect(
         &mut self,
         rect: LayoutRect,
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -742,16 +742,17 @@ impl TextureCache {
             self.next_id.0 += 1;
 
             let info = TextureCacheAllocInfo {
                 width: TEXTURE_REGION_DIMENSIONS,
                 height: TEXTURE_REGION_DIMENSIONS,
                 format: params.descriptor.format,
                 filter: texture_array.filter,
                 layer_count: 1,
+                is_shared_cache: true,
             };
             self.pending_updates.push_alloc(texture_id, info);
 
             texture_array.texture_id = Some(texture_id);
             texture_array.regions.push(TextureRegion::new(0));
         }
 
         // Do the allocation. This can fail and return None
@@ -798,16 +799,17 @@ impl TextureCache {
 
         // Push a command to allocate device storage of the right size / format.
         let info = TextureCacheAllocInfo {
             width: params.descriptor.size.width,
             height: params.descriptor.size.height,
             format: params.descriptor.format,
             filter: params.filter,
             layer_count: 1,
+            is_shared_cache: false,
         };
         self.pending_updates.push_alloc(texture_id, info);
 
         return CacheEntry::new_standalone(
             texture_id,
             self.frame_id,
             params,
         );
@@ -852,16 +854,17 @@ impl TextureCache {
             // Add a layer, unless we've hit our limit.
             if num_regions < self.max_texture_layers as usize {
                 let info = TextureCacheAllocInfo {
                     width: TEXTURE_REGION_DIMENSIONS,
                     height: TEXTURE_REGION_DIMENSIONS,
                     format: params.descriptor.format,
                     filter: texture_array.filter,
                     layer_count: (num_regions + 1) as i32,
+                    is_shared_cache: true,
                 };
                 self.pending_updates.push_realloc(texture_array.texture_id.unwrap(), info);
                 texture_array.regions.push(TextureRegion::new(num_regions));
                 true
             } else {
                 false
             }
         };
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-652e3f8a180865abc40c78813668098f2b55bdd3
+02387f7e5c77c415cfa232366c321d6f621cae28
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -19,17 +19,17 @@ clap = { version = "2", features = ["yam
 lazy_static = "1"
 log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
-osmesa-src = { git = "https://github.com/jrmuizel/osmesa-src", optional = true, branch = "serialize" }
+osmesa-src = { git = "https://github.com/servo/osmesa-src", optional = true }
 webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler"]}
 webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
 winit = "0.16"
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.17.1"
 core-foundation = "0.6"
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -570,17 +570,17 @@ impl Wrench {
 
         let color_and_offset = [(ColorF::BLACK, 2.0), (ColorF::WHITE, 0.0)];
         let dr = self.renderer.debug_renderer().unwrap();
 
         for ref co in &color_and_offset {
             let x = self.device_pixel_ratio * (15.0 + co.1);
             let mut y = self.device_pixel_ratio * (15.0 + co.1 + dr.line_height());
             for ref line in &help_lines {
-                dr.add_text(x, y, line, co.0.into());
+                dr.add_text(x, y, line, co.0.into(), None);
                 y += self.device_pixel_ratio * dr.line_height();
             }
         }
     }
 
     pub fn shut_down(self, rx: Receiver<NotifierEvent>) {
         self.api.shut_down();
 
--- a/ipc/glue/CrashReporterHost.cpp
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -89,16 +89,19 @@ CrashReporterHost::FinalizeCrashReport()
       break;
     case GeckoProcessType_Plugin:
     case GeckoProcessType_GMPlugin:
       type = NS_LITERAL_CSTRING("plugin");
       break;
     case GeckoProcessType_GPU:
       type = NS_LITERAL_CSTRING("gpu");
       break;
+    case GeckoProcessType_RDD:
+      type = NS_LITERAL_CSTRING("rdd");
+      break;
     default:
       NS_ERROR("unknown process type");
       break;
   }
   annotations[CrashReporter::Annotation::ProcessType] = type;
 
   char startTime[32];
   SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime));
@@ -161,16 +164,20 @@ CrashReporterHost::NotifyCrashService(Ge
     case GeckoProcessType_GMPlugin:
       processType = nsICrashService::PROCESS_TYPE_GMPLUGIN;
       telemetryKey.AssignLiteral("gmplugin");
       break;
     case GeckoProcessType_GPU:
       processType = nsICrashService::PROCESS_TYPE_GPU;
       telemetryKey.AssignLiteral("gpu");
       break;
+    case GeckoProcessType_RDD:
+      processType = nsICrashService::PROCESS_TYPE_RDD;
+      telemetryKey.AssignLiteral("rdd");
+      break;
     default:
       NS_ERROR("unknown process type");
       return;
   }
 
   RefPtr<Promise> promise;
   crashService->AddCrash(processType, aCrashType, aChildDumpID, getter_AddRefs(promise));
   Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1);
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -138,18 +138,20 @@ GeckoChildProcessHost::~GeckoChildProces
   }
 }
 
 //static
 auto
 GeckoChildProcessHost::GetPathToBinary(FilePath& exePath, GeckoProcessType processType) -> BinaryPathType
 {
   if (sRunSelfAsContentProc &&
-      (processType == GeckoProcessType_Content || processType == GeckoProcessType_GPU ||
-       processType == GeckoProcessType_VR)) {
+      (processType == GeckoProcessType_Content ||
+       processType == GeckoProcessType_GPU ||
+       processType == GeckoProcessType_VR ||
+       processType == GeckoProcessType_RDD)) {
 #if defined(OS_WIN)
     wchar_t exePathBuf[MAXPATHLEN];
     if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) {
       MOZ_CRASH("GetModuleFileNameW failed (FIXME)");
     }
 #if defined(MOZ_SANDBOX)
     // We need to start the child process using the real path, so that the
     // sandbox policy rules will match for DLLs loaded from the bin dir after
@@ -740,19 +742,22 @@ GeckoChildProcessHost::PerformAsyncLaunc
       childArgv.push_back("-appomni");
       childArgv.push_back(path.get());
     }
   }
 
   // Add the application directory path (-appdir path)
   AddAppDirToCommandLine(childArgv);
 
-  // Tmp dir that the GPU process should use for crash reports. This arg is
-  // always populated (but possibly with an empty value) for a GPU child process.
-  if (mProcessType == GeckoProcessType_GPU || mProcessType == GeckoProcessType_VR) {
+  // Tmp dir that the GPU or RDD process should use for crash reports.
+  // This arg is always populated (but possibly with an empty value) for
+  // a GPU or RDD child process.
+  if (mProcessType == GeckoProcessType_GPU ||
+      mProcessType == GeckoProcessType_RDD ||
+      mProcessType == GeckoProcessType_VR) {
     nsCOMPtr<nsIFile> file;
     CrashReporter::GetChildProcessTmpDir(getter_AddRefs(file));
     nsAutoCString path;
     if (file) {
       file->GetNativePath(path);
     }
     childArgv.push_back(path.get());
   }
@@ -984,16 +989,21 @@ GeckoChildProcessHost::PerformAsyncLaunc
         shouldSandboxCurrentProcess = true;
       }
       break;
     case GeckoProcessType_VR:
       if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_VR_SANDBOX")) {
         // TODO: Implement sandbox for VR process, Bug 1430043.
       }
       break;
+    case GeckoProcessType_RDD:
+      if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
+        // TODO: Implement sandbox for RDD process, Bug 1498624.
+      }
+      break;
     case GeckoProcessType_Default:
     default:
       MOZ_CRASH("Bad process type in GeckoChildProcessHost");
       break;
   };
 
   if (shouldSandboxCurrentProcess) {
     for (auto it = mAllowedFilesRead.begin();
@@ -1009,19 +1019,21 @@ GeckoChildProcessHost::PerformAsyncLaunc
 
   // XXX Command line params past this point are expected to be at
   // the end of the command line string, and in a specific order.
   // See XRE_InitChildProcess in nsEmbedFunction.
 
   // Win app model id
   cmdLine.AppendLooseValue(mGroupId.get());
 
-  // Tmp dir that the GPU process should use for crash reports. This arg is
-  // always populated (but possibly with an empty value) for a GPU child process.
-  if (mProcessType == GeckoProcessType_GPU) {
+  // Tmp dir that the GPU or RDD process should use for crash reports.
+  // This arg is always populated (but possibly with an empty value) for
+  // a GPU or RDD child process.
+  if (mProcessType == GeckoProcessType_GPU ||
+      mProcessType == GeckoProcessType_RDD) {
     nsCOMPtr<nsIFile> file;
     CrashReporter::GetChildProcessTmpDir(getter_AddRefs(file));
     nsString path;
     if (file) {
       MOZ_ALWAYS_SUCCEEDS(file->GetPath(path));
     }
     std::wstring wpath(path.get());
     cmdLine.AppendLooseValue(wpath);
@@ -1066,16 +1078,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
   {
     base::LaunchApp(cmdLine, *mLaunchOptions, &process);
 
 # ifdef MOZ_SANDBOX
     // We need to be able to duplicate handles to some types of non-sandboxed
     // child processes.
     if (mProcessType == GeckoProcessType_Content ||
         mProcessType == GeckoProcessType_GPU ||
+        mProcessType == GeckoProcessType_RDD ||
         mProcessType == GeckoProcessType_VR ||
         mProcessType == GeckoProcessType_GMPlugin) {
       if (!mSandboxBroker.AddTargetPeer(process)) {
         NS_WARNING("Failed to add content process as target peer.");
       }
     }
 # endif // MOZ_SANDBOX
   }
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -909,16 +909,18 @@ description =
 [PGMPService::LaunchGMPForNodeId]
 description =
 [PGMPService::GetGMPNodeId]
 description =
 [PGMPVideoDecoder::NeedShmem]
 description =
 [PGMPVideoEncoder::NeedShmem]
 description =
+[PRemoteDecoderManager::PRemoteVideoDecoder]
+description = See Bug 1505976 - investigate changing to async instead of matching GPU pattern
 [PVideoDecoderManager::PVideoDecoder]
 description =
 [PVideoDecoderManager::Readback]
 description =
 [PBackgroundStorage::Preload]
 description =
 [PRemoteSpellcheckEngine::Check]
 description =
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1215,16 +1215,17 @@ BaselineCompiler::emitBody()
         }
 
         switch (op) {
           // ===== NOT Yet Implemented =====
           case JSOP_FORCEINTERPRETER:
             // Intentionally not implemented.
           case JSOP_SETINTRINSIC:
             // Run-once opcode during self-hosting initialization.
+          case JSOP_UNUSED151:
           case JSOP_UNUSED206:
           case JSOP_LIMIT:
             // === !! WARNING WARNING WARNING !! ===
             // Do you really want to sacrifice performance by not implementing
             // this operation in the BaselineCompiler?
             JitSpew(JitSpew_BaselineAbort, "Unhandled op: %s", CodeName[op]);
             return Method_CantCompile;
 
@@ -3894,32 +3895,16 @@ BaselineCompiler::emit_JSOP_THROW()
     frame.popRegsAndSync(1);
 
     prepareVMCall();
     pushArg(R0);
 
     return callVM(ThrowInfo);
 }
 
-typedef bool (*ThrowingFn)(JSContext*, HandleValue);
-static const VMFunction ThrowingInfo =
-    FunctionInfo<ThrowingFn>(js::ThrowingOperation, "ThrowingOperation");
-
-bool
-BaselineCompiler::emit_JSOP_THROWING()
-{
-    // Keep value to throw in R0.
-    frame.popRegsAndSync(1);
-
-    prepareVMCall();
-    pushArg(R0);
-
-    return callVM(ThrowingInfo);
-}
-
 bool
 BaselineCompiler::emit_JSOP_TRY()
 {
     if (!emit_JSOP_JUMPTARGET()) {
         return false;
     }
 
     // Ionmonkey can't inline function with JSOP_TRY.
@@ -4779,32 +4764,30 @@ typedef bool (*NewArgumentsObjectFn)(JSC
 static const VMFunction NewArgumentsObjectInfo =
     FunctionInfo<NewArgumentsObjectFn>(jit::NewArgumentsObject, "NewArgumentsObject");
 
 bool
 BaselineCompiler::emit_JSOP_ARGUMENTS()
 {
     frame.syncStack(0);
 
+    MOZ_ASSERT(script->argumentsHasVarBinding());
+
     Label done;
-    if (!script->argumentsHasVarBinding() || !script->needsArgsObj()) {
+    if (!script->needsArgsObj()) {
         // We assume the script does not need an arguments object. However, this
         // assumption can be invalidated later, see argumentsOptimizationFailed
-        // in JSScript. Because we can't invalidate baseline JIT code, we set a
-        // flag on BaselineScript when that happens and guard on it here.
+        // in JSScript. Guard on the script's NeedsArgsObj flag.
         masm.moveValue(MagicValue(JS_OPTIMIZED_ARGUMENTS), R0);
 
-        // Load script->baseline.
+        // If we don't need an arguments object, skip the VM call.
         Register scratch = R1.scratchReg();
         masm.movePtr(ImmGCPtr(script), scratch);
-        masm.loadPtr(Address(scratch, JSScript::offsetOfBaselineScript()), scratch);
-
-        // If we don't need an arguments object, skip the VM call.
-        masm.branchTest32(Assembler::Zero, Address(scratch, BaselineScript::offsetOfFlags()),
-                          Imm32(BaselineScript::NEEDS_ARGS_OBJ), &done);
+        masm.branchTest32(Assembler::Zero, Address(scratch, JSScript::offsetOfMutableFlags()),
+                          Imm32(uint32_t(JSScript::MutableFlags::NeedsArgsObj)), &done);
     }
 
     prepareVMCall();
 
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
     pushArg(R0.scratchReg());
 
     if (!callVM(NewArgumentsObjectInfo)) {
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -168,17 +168,16 @@ namespace jit {
     _(JSOP_OPTIMIZE_SPREADCALL)\
     _(JSOP_IMPLICITTHIS)       \
     _(JSOP_GIMPLICITTHIS)      \
     _(JSOP_INSTANCEOF)         \
     _(JSOP_TYPEOF)             \
     _(JSOP_TYPEOFEXPR)         \
     _(JSOP_THROWMSG)           \
     _(JSOP_THROW)              \
-    _(JSOP_THROWING)           \
     _(JSOP_TRY)                \
     _(JSOP_FINALLY)            \
     _(JSOP_GOSUB)              \
     _(JSOP_RETSUB)             \
     _(JSOP_PUSHLEXICALENV)     \
     _(JSOP_POPLEXICALENV)      \
     _(JSOP_FRESHENLEXICALENV)  \
     _(JSOP_RECREATELEXICALENV) \
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -272,19 +272,17 @@ struct BaselineScript final
     bool traceLoggerScriptsEnabled_ = false;
     bool traceLoggerEngineEnabled_ = false;
 # endif
     TraceLoggerEvent traceLoggerScriptEvent_ = {};
 #endif
 
   public:
     enum Flag {
-        // Flag set by JSScript::argumentsOptimizationFailed. Similar to
-        // JSScript::needsArgsObj_, but can be read from JIT code.
-        NEEDS_ARGS_OBJ = 1 << 0,
+        // (1 << 0) is unused.
 
         // Flag set when discarding JIT code, to indicate this script is
         // on the stack and should not be discarded.
         ACTIVE = 1 << 1,
 
         // Flag set when the script contains any writes to its on-stack
         // (rather than call object stored) arguments.
         MODIFIES_ARGUMENTS = 1 << 2,
@@ -412,20 +410,16 @@ struct BaselineScript final
     }
     void setActive() {
         flags_ |= ACTIVE;
     }
     void resetActive() {
         flags_ &= ~ACTIVE;
     }
 
-    void setNeedsArgsObj() {
-        flags_ |= NEEDS_ARGS_OBJ;
-    }
-
     void setModifiesArguments() {
         flags_ |= MODIFIES_ARGUMENTS;
     }
     bool modifiesArguments() {
         return flags_ & MODIFIES_ARGUMENTS;
     }
 
     void setHasDebugInstrumentation() {
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2555,17 +2555,16 @@ IonBuilder::inspectOpcode(JSOp op)
       case JSOP_PUSHVARENV:
       case JSOP_POPVARENV:
 
       // Compound assignment
       case JSOP_GETBOUNDNAME:
 
       // Generators / Async (bug 1317690)
       case JSOP_EXCEPTION:
-      case JSOP_THROWING:
       case JSOP_ISGENCLOSING:
       case JSOP_INITIALYIELD:
       case JSOP_YIELD:
       case JSOP_FINALYIELDRVAL:
       case JSOP_RESUME:
       case JSOP_DEBUGAFTERYIELD:
       case JSOP_AWAIT:
       case JSOP_TRYSKIPAWAIT:
@@ -2583,16 +2582,17 @@ IonBuilder::inspectOpcode(JSOp op)
         // Do you really want to sacrifice performance by not implementing this
         // operation in the optimizing compiler?
         break;
 
       case JSOP_FORCEINTERPRETER:
         // Intentionally not implemented.
         break;
 
+      case JSOP_UNUSED151:
       case JSOP_UNUSED206:
       case JSOP_LIMIT:
         break;
     }
 
     // Track a simpler message, since the actionable abort message is a
     // static string, and the internal opcode name isn't an actionable
     // thing anyways.
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -408,27 +408,63 @@ extern JS_PUBLIC_API(bool)
 JS_IsBuiltinEvalFunction(JSFunction* fun);
 
 /** True iff fun is the Function constructor. */
 extern JS_PUBLIC_API(bool)
 JS_IsBuiltinFunctionConstructor(JSFunction* fun);
 
 /************************************************************************/
 
+// [SMDOC] Data Structures (JSContext, JSRuntime, Realm, Compartment, Zone)
+//
+// SpiderMonkey uses some data structures that behave a lot like Russian dolls:
+// runtimes contain zones, zones contain compartments, compartments contain
+// realms. Each layer has its own purpose.
+//
+// Realm
+// -----
+// Data associated with a global object. In the browser each frame has its
+// own global/realm.
+//
+// Compartment
+// -----------
+// Security membrane; when an object from compartment A is used in compartment
+// B, a cross-compartment wrapper (a kind of proxy) is used. In the browser each
+// compartment currently contains one global/realm, but we want to change that
+// so each compartment contains multiple same-origin realms (bug 1357862).
+//
+// Zone
+// ----
+// A Zone is a group of compartments that share GC resources (arenas, strings,
+// etc) for memory usage and performance reasons. Zone is the GC unit: the GC
+// can operate on one or more zones at a time. The browser uses roughly one zone
+// per tab.
+//
+// Context
+// -------
+// JSContext represents a thread: there must be exactly one JSContext for each
+// thread running JS/Wasm. Internally, helper threads have their own JSContext.
+//
+// Runtime
+// -------
+// JSRuntime is very similar to JSContext: each runtime belongs to one context
+// (thread), but helper threads don't have their own runtimes (they're shared by
+// all runtimes in the process and use the runtime of the task they're
+// executing).
+
 /*
  * Locking, contexts, and memory allocation.
  *
  * It is important that SpiderMonkey be initialized, and the first context
  * be created, in a single-threaded fashion.  Otherwise the behavior of the
  * library is undefined.
  * See: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference
  */
 
-// Create a new runtime, with a single cooperative context for this thread.
-// On success, the new context will be the active context for the runtime.
+// Create a new context (and runtime) for this thread.
 extern JS_PUBLIC_API(JSContext*)
 JS_NewContext(uint32_t maxbytes,
               uint32_t maxNurseryBytes = JS::DefaultNurseryBytes,
               JSRuntime* parentRuntime = nullptr);
 
 // The methods below for controlling the active context in a cooperatively
 // multithreaded runtime are not threadsafe, and the caller must ensure they
 // are called serially if there is a chance for contention between threads.
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -2293,16 +2293,17 @@ CASE(EnableInterruptsPseudoOpcode)
     SANITY_CHECKS();
     DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_NOP_DESTRUCTURING)
 CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE)
+CASE(JSOP_UNUSED151)
 CASE(JSOP_UNUSED206)
 CASE(JSOP_CONDSWITCH)
 {
     MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1);
     ADVANCE_AND_DISPATCH(1);
 }
 
 CASE(JSOP_TRY)
@@ -4347,24 +4348,16 @@ CASE(JSOP_EXCEPTION)
     }
 }
 END_CASE(JSOP_EXCEPTION)
 
 CASE(JSOP_FINALLY)
     CHECK_BRANCH();
 END_CASE(JSOP_FINALLY)
 
-CASE(JSOP_THROWING)
-{
-    ReservedRooted<Value> v(&rootValue0);
-    POP_COPY_TO(v);
-    MOZ_ALWAYS_TRUE(ThrowingOperation(cx, v));
-}
-END_CASE(JSOP_THROWING)
-
 CASE(JSOP_THROW)
 {
     CHECK_BRANCH();
     ReservedRooted<Value> v(&rootValue0);
     POP_COPY_TO(v);
     MOZ_ALWAYS_FALSE(Throw(cx, v));
     /* let the code at error try to catch the exception. */
     goto error;
@@ -4874,27 +4867,16 @@ bool
 js::Throw(JSContext* cx, HandleValue v)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     cx->setPendingException(v);
     return false;
 }
 
 bool
-js::ThrowingOperation(JSContext* cx, HandleValue v)
-{
-    // Like js::Throw, but returns |true| instead of |false| to continue
-    // execution instead of calling the (JIT) exception handler.
-
-    MOZ_ASSERT(!cx->isExceptionPending());
-    cx->setPendingException(v);
-    return true;
-}
-
-bool
 js::GetProperty(JSContext* cx, HandleValue v, HandlePropertyName name, MutableHandleValue vp)
 {
     if (name == cx->names().length) {
         // Fast path for strings, arrays and arguments.
         if (GetLengthProperty(v, vp)) {
             return true;
         }
     }
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -421,19 +421,16 @@ bool
 HandleClosingGeneratorReturn(JSContext* cx, AbstractFramePtr frame, bool ok);
 
 /************************************************************************/
 
 bool
 Throw(JSContext* cx, HandleValue v);
 
 bool
-ThrowingOperation(JSContext* cx, HandleValue v);
-
-bool
 GetProperty(JSContext* cx, HandleValue value, HandlePropertyName name, MutableHandleValue vp);
 
 JSObject*
 Lambda(JSContext* cx, HandleFunction fun, HandleObject parent);
 
 JSObject*
 LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue newTargetv);
 
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4572,25 +4572,16 @@ JSScript::argumentsOptimizationFailed(JS
     }
 
     MOZ_ASSERT(!script->isGenerator());
     MOZ_ASSERT(!script->isAsync());
 
     script->setFlag(MutableFlags::NeedsArgsObj);
 
     /*
-     * Since we can't invalidate baseline scripts, set a flag that's checked from
-     * JIT code to indicate the arguments optimization failed and JSOP_ARGUMENTS
-     * should create an arguments object next time.
-     */
-    if (script->hasBaselineScript()) {
-        script->baselineScript()->setNeedsArgsObj();
-    }
-
-    /*
      * By design, the arguments optimization is only made when there are no
      * outstanding cases of MagicValue(JS_OPTIMIZED_ARGUMENTS) at any points
      * where the optimization could fail, other than an active invocation of
      * 'f.apply(x, arguments)'. Thus, there are no outstanding values of
      * MagicValue(JS_OPTIMIZED_ARGUMENTS) on the stack. However, there are
      * three things that need fixup:
      *  - there may be any number of activations of this script that don't have
      *    an argsObj that now need one.
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1699,17 +1699,18 @@ class JSScript : public js::gc::TenuredC
         HasRest = 1 << 20,
 
         // See comments below.
         ArgsHasVarBinding = 1 << 21,
     };
     uint32_t immutableFlags_ = 0;
 
     // Mutable flags typically store information about runtime or deoptimization
-    // behavior of this script.
+    // behavior of this script. This is only public for the JITs.
+  public:
     enum class MutableFlags : uint32_t {
         // Have warned about uses of undefined properties in this script.
         WarnedAboutUndefinedProp = 1 << 0,
 
         // If treatAsRunOnce, whether script has executed.
         HasRunOnce = 1 << 1,
 
         // Script has been reused for a clone.
@@ -1764,16 +1765,17 @@ class JSScript : public js::gc::TenuredC
 
         // See comments below.
         NeedsArgsAnalysis = 1 << 17,
         NeedsArgsObj = 1 << 18,
 
         // Set if the debugger's onNewScript hook has not yet been called.
         HideScriptFromDebugger = 1 << 19,
     };
+  private:
     uint32_t mutableFlags_ = 0;
 
     // 16-bit fields.
 
     /**
      * Number of times the |warmUpCount| was forcibly discarded. The counter is
      * reset when a script is successfully jit-compiled.
      */
@@ -2236,16 +2238,20 @@ class JSScript : public js::gc::TenuredC
     void setDoNotRelazify(bool b) {
         setFlag(MutableFlags::DoNotRelazify, b);
     }
 
     bool hasInnerFunctions() const {
         return hasFlag(ImmutableFlags::HasInnerFunctions);
     }
 
+    static constexpr size_t offsetOfMutableFlags() {
+        return offsetof(JSScript, mutableFlags_);
+    }
+
     bool hasAnyIonScript() const {
         return hasIonScript();
     }
 
     bool hasIonScript() const {
         bool res = ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT &&
                           ion != ION_PENDING_SCRIPT;
         MOZ_ASSERT_IF(res, baseline);
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1622,27 +1622,18 @@
      *
      *   Category: Operators
      *   Type: Arithmetic Operators
      *   Operands:
      *   Stack: lval, rval => (lval ** rval)
      */ \
     macro(JSOP_POW, 150, "pow", "**", 1, 2, 1, JOF_BYTE|JOF_IC) \
     /*
-     * Pops the top of stack value as 'v', sets pending exception as 'v', to
-     * trigger rethrow.
-     *
-     * This opcode is used in conditional catch clauses.
-     *
-     *   Category: Statements
-     *   Type: Exception Handling
-     *   Operands:
-     *   Stack: v =>
      */ \
-    macro(JSOP_THROWING, 151, "throwing", NULL, 1, 1, 0, JOF_BYTE) \
+    macro(JSOP_UNUSED151, 151, "unused151", NULL, 1, 0, 0, JOF_BYTE) \
     /*
      * Pops the top of stack value as 'rval', sets the return value in stack
      * frame as 'rval'.
      *
      *   Category: Statements
      *   Type: Function
      *   Operands:
      *   Stack: rval =>
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -726,26 +726,30 @@ RecomputePosition(nsIFrame* aFrame)
     return true;
   }
 
   // Don't process position changes on frames which have views or the ones which
   // have a view somewhere in their descendants, because the corresponding view
   // needs to be repositioned properly as well.
   if (aFrame->HasView() ||
       (aFrame->GetStateBits() & NS_FRAME_HAS_CHILD_WITH_VIEW)) {
-    StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
+    StyleChangeReflow(aFrame,
+                      nsChangeHint_NeedReflow |
+                      nsChangeHint_ReflowChangesSizeOrPosition);
     return false;
   }
 
   // Flexbox and Grid layout supports CSS Align and the optimizations below
   // don't support that yet.
   if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
     nsIFrame* ph = aFrame->GetPlaceholderFrame();
     if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
-      StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
+      StyleChangeReflow(aFrame,
+                        nsChangeHint_NeedReflow |
+                        nsChangeHint_ReflowChangesSizeOrPosition);
       return false;
     }
   }
 
   aFrame->SchedulePaint();
 
   // For relative positioning, we can simply update the frame rect
   if (display->IsRelativelyPositionedStyle()) {
@@ -900,17 +904,19 @@ RecomputePosition(nsIFrame* aFrame)
                 parentBorder.top + reflowInput.ComputedPhysicalOffsets().top +
                 reflowInput.ComputedPhysicalMargin().top);
     aFrame->SetPosition(pos);
 
     return true;
   }
 
   // Fall back to a reflow
-  StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
+  StyleChangeReflow(aFrame,
+                    nsChangeHint_NeedReflow |
+                    nsChangeHint_ReflowChangesSizeOrPosition);
   return false;
 }
 
 static bool
 HasBoxAncestor(nsIFrame* aFrame)
 {
   for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
     if (f->IsXULBoxFrame()) {
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -180,18 +180,18 @@ enum nsChangeHint : uint32_t {
   /**
    * This will cause rendering observers to be invalidated.
    */
   nsChangeHint_InvalidateRenderingObservers = 1 << 22,
 
   /**
    * Indicates that the reflow changes the size or position of the
    * element, and thus the reflow must start from at least the frame's
-   * parent.  Must be not be set without also setting nsChangeHint_NeedReflow
-   * and nsChangeHint_ClearAncestorIntrinsics.
+   * parent.  Must be not be set without also setting nsChangeHint_NeedReflow.
+   * And consider adding nsChangeHint_ClearAncestorIntrinsics if needed.
    */
   nsChangeHint_ReflowChangesSizeOrPosition = 1 << 23,
 
   /**
    * Indicates that the style changes the computed BSize --- e.g. 'height'.
    * Must not be set without also setting nsChangeHint_NeedReflow.
    */
   nsChangeHint_UpdateComputedBSize = 1 << 24,
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -221,21 +221,22 @@ nsLayoutModuleInitialize()
 
   gInitialized = true;
 
   if (XRE_GetProcessType() == GeckoProcessType_VR) {
     // VR process doesn't need the layout module.
     return;
   }
 
-  if (XRE_GetProcessType() == GeckoProcessType_GPU) {
-    // We mark the layout module as being available in the GPU process so that
-    // XPCOM's component manager initializes the power manager service, which
-    // is needed for nsAppShell. However, we don't actually need anything in
-    // the layout module itself.
+  if (XRE_GetProcessType() == GeckoProcessType_GPU ||
+      XRE_GetProcessType() == GeckoProcessType_RDD) {
+    // We mark the layout module as being available in the GPU and RDD
+    // process so that XPCOM's component manager initializes the power
+    // manager service, which is needed for nsAppShell. However, we
+    // don't actually need anything in the layout module itself.
     return;
   }
 
   if (NS_FAILED(xpcModuleCtor())) {
     MOZ_CRASH("xpcModuleCtor failed");
   }
 
   if (NS_FAILED(nsLayoutStatics::Initialize())) {
@@ -671,17 +672,18 @@ Initialize()
   MOZ_RELEASE_ASSERT(gInitialized);
   return NS_OK;
 }
 
 static void
 LayoutModuleDtor()
 {
   if (XRE_GetProcessType() == GeckoProcessType_GPU ||
-      XRE_GetProcessType() == GeckoProcessType_VR) {
+      XRE_GetProcessType() == GeckoProcessType_VR ||
+      XRE_GetProcessType() == GeckoProcessType_RDD) {
     return;
   }
 
   Shutdown();
   nsContentUtils::XPCOMShutdown();
 
   // Layout depends heavily on gfx and imagelib, so we want to make sure that
   // these modules are shut down after all the layout cleanup runs.
--- a/layout/generic/test/mochitest.ini
+++ b/layout/generic/test/mochitest.ini
@@ -94,16 +94,17 @@ support-files = bug633762_iframe.html
 [test_bug938772.html]
 [test_bug970363.html]
 [test_bug1062406.html]
 [test_bug1174521.html]
 [test_bug1198135.html]
 [test_bug1307853.html]
 support-files = file_bug1307853.html
 [test_bug1408607.html]
+[test_bug1499961.html]
 [test_contained_plugin_transplant.html]
 skip-if = os=='win'
 [test_image_selection.html]
 [test_image_selection_2.html]
 [test_invalidate_during_plugin_paint.html]
 skip-if = toolkit == 'android'
 [test_intrinsic_size_on_loading.html]
 [test_movement_by_characters.html]
new file mode 100644
--- /dev/null
+++ b/layout/generic/test/test_bug1499961.html
@@ -0,0 +1,408 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1499961
+
+Some tests ported from IntersectionObserver/polyfill/intersection-observer-test.html
+
+Original license header:
+
+Copyright 2016 Google Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1499961</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1499961">Mozilla Bug 1499961</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+  var tests = [];
+  var curDescribeMsg = '';
+  var curItMsg = '';
+
+  function beforeEach_fn() { };
+  function afterEach_fn() { };
+
+  function before(fn) {
+    fn();
+  }
+
+  function beforeEach(fn) {
+    beforeEach_fn = fn;
+  }
+
+  function afterEach(fn) {
+    afterEach_fn = fn;
+  }
+
+  function it(msg, fn) {
+    tests.push({
+      msg: `${msg} [${curDescribeMsg}]`,
+      fn: fn
+    });
+  }
+
+  var callbacks = [];
+  function callDelayed(fn) {
+    callbacks.push(fn);
+  }
+
+  requestAnimationFrame(function tick() {
+    var i = callbacks.length;
+    while (i--) {
+      var cb = callbacks[i];
+      SimpleTest.executeSoon(function() { SimpleTest.executeSoon(cb) });
+      callbacks.splice(i, 1);
+    }
+    requestAnimationFrame(tick);
+  });
+
+  function expect(val) {
+    return {
+      to: {
+        throwException: function (regexp) {
+          try {
+            val();
+            ok(false, `${curItMsg} - an exception should have beeen thrown`);
+          } catch (e) {
+            ok(regexp.test(e), `${curItMsg} - supplied regexp should match thrown exception`);
+          }
+        },
+        get be() {
+          var fn = function (expected) {
+            is(val, expected, curItMsg);
+          };
+          fn.ok = function () {
+            ok(val, curItMsg);
+          };
+          fn.greaterThan = function (other) {
+            ok(val > other, `${curItMsg} - ${val} should be greater than ${other}`);
+          };
+          fn.lessThan = function (other) {
+            ok(val < other, `${curItMsg} - ${val} should be less than ${other}`);
+          };
+          return fn;
+        },
+        eql: function (expected) {
+          if (Array.isArray(expected)) {
+            if (!Array.isArray(val)) {
+              ok(false, curItMsg, `${curItMsg} - should be an array,`);
+              return;
+            }
+            is(val.length, expected.length, curItMsg, `${curItMsg} - arrays should be the same length`);
+            if (expected.length != val.length) {
+              return;
+            }
+            for (var i = 0; i < expected.length; i++) {
+              is(val[i], expected[i], `${curItMsg} - array elements at position ${i} should be equal`);
+              if (expected[i] != val[i]) {
+                return;
+              }
+            }
+            ok(true);
+          }
+        },
+      }
+    }
+  }
+
+  function describe(msg, fn) {
+    curDescribeMsg = msg;
+    fn();
+    curDescribeMsg = '';
+  }
+
+  function next() {
+    var test = tests.shift();
+    if (test) {
+      console.log(test.msg);
+      curItMsg = test.msg;
+      var fn = test.fn;
+      beforeEach_fn();
+      if (fn.length) {
+        fn(function () {
+          afterEach_fn();
+          next();
+        });
+      } else {
+        fn();
+        afterEach_fn();
+        next();
+      }
+    } else {
+      SimpleTest.finish();
+    }
+  }
+
+  var sinon = {
+    spy: function () {
+      var callbacks = [];
+      var fn = function () {
+        fn.callCount++;
+        fn.lastCall = { args: arguments };
+        if (callbacks.length) {
+          callbacks.shift()();
+        }
+      };
+      fn.callCount = 0;
+      fn.lastCall = { args: [] };
+      fn.waitForNotification = (fn) => {
+        callbacks.push(fn);
+      };
+      return fn;
+    }
+  };
+
+  var ASYNC_TIMEOUT = 300;
+
+
+  var io;
+  var noop = function() {};
+
+
+  // References to DOM elements, which are accessible to any test
+  // and reset prior to each test so state isn't shared.
+  var rootEl;
+  var grandParentEl;
+  var parentEl;
+  var targetEl1;
+  var targetEl2;
+  var targetEl3;
+  var targetEl4;
+  var targetEl5;
+
+
+  describe('IntersectionObserver', function() {
+
+    before(function() {
+
+    });
+
+
+    beforeEach(function() {
+      addStyles();
+      addFixtures();
+    });
+
+
+    afterEach(function() {
+      if (io && 'disconnect' in io) io.disconnect();
+      io = null;
+
+      window.onmessage = null;
+
+      removeStyles();
+      removeFixtures();
+    });
+
+
+    describe('constructor', function() {
+
+      it('move iframe and check reflow', function(done) {
+
+        var spy = sinon.spy();
+        io = new IntersectionObserver(spy, {root: rootEl});
+
+        runSequence([
+          // Do a first change and wait for its intersection observer
+          // notification, to ensure one full reflow was completed.
+          function(done) {
+            targetEl1.style.top = '0px';
+            io.observe(targetEl1);
+            spy.waitForNotification(function() {
+              var records = sortRecords(spy.lastCall.args[0]);
+              expect(records.length).to.be(1);
+              expect(records[0].target).to.be(targetEl1);
+              done();
+            });
+          },
+          // Do another change, which may trigger an incremental reflow only.
+          function(done) {
+            targetEl4.style.top = '-20px';
+            targetEl4.style.left = '20px';
+            io.observe(targetEl4);
+            spy.waitForNotification(function() {
+              expect(spy.callCount).to.be(2);
+              var records = sortRecords(spy.lastCall.args[0]);
+              expect(records.length).to.be(1);
+              expect(records[0].target).to.be(targetEl4);
+              // After the iframe is moved, reflow should include its parent,
+              // even if the iframe is a reflow root.
+              // If moved correctly (outside of rootEl), the intersection ratio
+              // should now be 0.
+              expect(records[0].intersectionRatio).to.be(0);
+              done();
+            });
+          }
+        ], done);
+
+      });
+
+    });
+
+  });
+
+
+  /**
+   * Runs a sequence of function and when finished invokes the done callback.
+   * Each function in the sequence is invoked with its own done function and
+   * it should call that function once it's complete.
+   * @param {Array<Function>} functions An array of async functions.
+   * @param {Function} done A final callback to be invoked once all function
+   *     have run.
+   */
+  function runSequence(functions, done) {
+    var next = functions.shift();
+    if (next) {
+      next(function() {
+        runSequence(functions, done);
+      });
+    } else {
+      done && done();
+    }
+  }
+
+
+  /**
+   * Sorts an array of records alphebetically by ascending ID. Since the current
+   * native implementation doesn't sort change entries by `observe` order, we do
+   * that ourselves for the non-polyfill case. Since all tests call observe
+   * on targets in sequential order, this should always match.
+   * https://crbug.com/613679
+   * @param {Array<IntersectionObserverEntry>} entries The entries to sort.
+   * @return {Array<IntersectionObserverEntry>} The sorted array.
+   */
+  function sortRecords(entries) {
+    entries = entries.sort(function(a, b) {
+      return a.target.id < b.target.id ? -1 : 1;
+    });
+    return entries;
+  }
+
+
+  /**
+   * Adds the common styles used by all tests to the page.
+   */
+  function addStyles() {
+    var styles = document.createElement('style');
+    styles.id = 'styles';
+    document.documentElement.appendChild(styles);
+
+    var cssText =
+        '#root {' +
+        '  position: relative;' +
+        '  width: 400px;' +
+        '  height: 200px;' +
+        '  background: #eee' +
+        '}' +
+        '#grand-parent {' +
+        '  position: relative;' +
+        '  width: 200px;' +
+        '  height: 200px;' +
+        '}' +
+        '#parent {' +
+        '  position: absolute;' +
+        '  top: 0px;' +
+        '  left: 200px;' +
+        '  overflow: hidden;' +
+        '  width: 200px;' +
+        '  height: 200px;' +
+        '  background: #ddd;' +
+        '}' +
+        '#target1, #target2, #target3, #target4 {' +
+        '  position: absolute;' +
+        '  top: 0px;' +
+        '  left: 0px;' +
+        '  width: 20px;' +
+        '  height: 20px;' +
+        '  transform: translateX(0px) translateY(0px);' +
+        '  transition: transform .5s;' +
+        '  background: #f00;' +
+        '  border: none;' +
+        '}';
+
+    styles.innerHTML = cssText;
+  }
+
+
+  /**
+   * Adds the DOM fixtures used by all tests to the page and assigns them to
+   * global variables so they can be referenced within the tests.
+   */
+  function addFixtures() {
+    var fixtures = document.createElement('div');
+    fixtures.id = 'fixtures';
+
+    fixtures.innerHTML =
+        '<div id="root">' +
+        '  <div id="grand-parent">' +
+        '    <div id="parent">' +
+        '      <div id="target1"></div>' +
+        '      <div id="target2"></div>' +
+        '      <div id="target3"></div>' +
+        '      <iframe id="target4"></iframe>' +
+        '    </div>' +
+        '  </div>' +
+        '</div>';
+
+    document.body.appendChild(fixtures);
+
+    rootEl = document.getElementById('root');
+    grandParentEl = document.getElementById('grand-parent');
+    parentEl = document.getElementById('parent');
+    targetEl1 = document.getElementById('target1');
+    targetEl2 = document.getElementById('target2');
+    targetEl3 = document.getElementById('target3');
+    targetEl4 = document.getElementById('target4');
+  }
+
+
+  /**
+   * Removes the common styles from the page.
+   */
+  function removeStyles() {
+    var styles = document.getElementById('styles');
+    styles.remove();
+  }
+
+
+  /**
+   * Removes the DOM fixtures from the page and resets the global references.
+   */
+  function removeFixtures() {
+    var fixtures = document.getElementById('fixtures');
+    fixtures.remove();
+
+    rootEl = null;
+    grandParentEl = null;
+    parentEl = null;
+    targetEl1 = null;
+    targetEl2 = null;
+    targetEl3 = null;
+    targetEl4 = null;
+  }
+
+  function onLoad() {
+    SpecialPowers.pushPrefEnv({"set": [["dom.IntersectionObserver.enabled", true]]}, next);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7834,17 +7834,17 @@ nsDisplayStickyPosition::CreateWebRender
                                  rightMargin.ptrOr(nullptr),
                                  bottomMargin.ptrOr(nullptr),
                                  leftMargin.ptrOr(nullptr),
                                  vBounds,
                                  hBounds,
                                  applied);
 
     aBuilder.PushClip(id);
-    aManager->CommandBuilder().PushOverrideForASR(mContainerASR, Some(id));
+    aManager->CommandBuilder().PushOverrideForASR(mContainerASR, id);
   }
 
   {
     StackingContextHelper sc(aSc, GetActiveScrolledRoot(), aBuilder);
     nsDisplayWrapList::CreateWebRenderCommands(
       aBuilder, aResources, sc, aManager, aDisplayListBuilder);
   }
 
@@ -9468,17 +9468,17 @@ nsDisplayPerspective::CreateWebRenderCom
 
   Point3D newOrigin = Point3D(
     NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, appUnitsPerPixel),
     NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, appUnitsPerPixel),
     0.0f);
   Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0);
 
   gfx::Matrix4x4 transformForSC = gfx::Matrix4x4::Translation(roundedOrigin);
-  
+
   nsIFrame* perspectiveFrame = mFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
 
   nsTArray<mozilla::wr::WrFilterOp> filters;
   StackingContextHelper sc(aSc,
                            GetActiveScrolledRoot(),
                            aBuilder,
                            filters,
                            LayoutDeviceRect(),
@@ -10179,29 +10179,21 @@ nsDisplayMasksAndClipPaths::CreateWebRen
                   /*aTransform: */ nullptr,
                   /*aPerspective: */ nullptr,
                   /*aMixBlendMode: */ gfx::CompositionOp::OP_OVER,
                   /*aBackfaceVisible: */ true,
                   /*aIsPreserve3D: */ false,
                   /*aTransformForScrollData: */ Nothing(),
                   /*aClipNodeId: */ &clipId);
     sc = layer.ptr();
-    // The whole stacking context will be clipped by us, so no need to have any
-    // parent for the children context's clip.
-    aManager->CommandBuilder().PushOverrideForASR(GetActiveScrolledRoot(),
-                                                  Nothing());
   }
 
   nsDisplayEffectsBase::CreateWebRenderCommands(
     aBuilder, aResources, *sc, aManager, aDisplayListBuilder);
 
-  if (clip) {
-    aManager->CommandBuilder().PopOverrideForASR(GetActiveScrolledRoot());
-  }
-
   return true;
 }
 
 Maybe<nsRect>
 nsDisplayMasksAndClipPaths::GetClipWithRespectToASR(
                               nsDisplayListBuilder* aBuilder,
                               const ActiveScrolledRoot* aASR) const
 {
@@ -10511,25 +10503,19 @@ nsDisplayFilters::CreateWebRenderCommand
                            nullptr,
                            nullptr,
                            gfx::CompositionOp::OP_OVER,
                            true,
                            false,
                            Nothing(),
                            &clipId);
 
-  // The whole stacking context will be clipped by us, so no need to have any
-  // parent for the children context's clip.
-  aManager->CommandBuilder().PushOverrideForASR(GetActiveScrolledRoot(),
-                                                Nothing());
-
   nsDisplayEffectsBase::CreateWebRenderCommands(
     aBuilder, aResources, sc, aManager, aDisplayListBuilder);
 
-  aManager->CommandBuilder().PopOverrideForASR(GetActiveScrolledRoot());
   return true;
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
 nsDisplayFilters::PrintEffects(nsACString& aTo)
 {
   nsIFrame* firstFrame =
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -2885,21 +2885,16 @@ arena_bin_t::Init(SizeClass aSizeClass)
       try_nregs--;
       try_mask_nelms =
         (try_nregs >> (LOG2(sizeof(int)) + 3)) +
         ((try_nregs & ((1U << (LOG2(sizeof(int)) + 3)) - 1)) ? 1 : 0);
       try_reg0_offset = try_run_size - (try_nregs * mSizeClass);
     } while (kFixedHeaderSize + (sizeof(unsigned) * try_mask_nelms) >
              try_reg0_offset);
 
-    // Don't allow runs larger than the largest possible large size class.
-    if (try_run_size > gMaxLargeClass) {
-      break;
-    }
-
     // Try to keep the run overhead below kRunOverhead.
     if (Fraction(try_reg0_offset, try_run_size) <= kRunOverhead) {
       break;
     }
 
     // If the overhead is larger than the size class, it means the size class
     // is small and doesn't align very well with the header. It's desirable to
     // have smaller run sizes for them, so relax the overhead requirement.
@@ -2916,16 +2911,23 @@ arena_bin_t::Init(SizeClass aSizeClass)
     // close to 512 bits to the header, which is 64 bytes.
     // With such overhead, there is no way to get to the wanted overhead above,
     // so we give up if the required size for mRegionsMask more than doubles the
     // size of the run header.
     if (try_mask_nelms * sizeof(unsigned) >= kFixedHeaderSize) {
       break;
     }
 
+    // If next iteration is going to be larger than the largest possible large
+    // size class, then we didn't find a setup where the overhead is small enough,
+    // and we can't do better than the current settings, so just use that.
+    if (try_run_size + gPageSize > gMaxLargeClass) {
+      break;
+    }
+
     // Try more aggressive settings.
     try_run_size += gPageSize;
   }
 
   MOZ_ASSERT(kFixedHeaderSize + (sizeof(unsigned) * try_mask_nelms) <=
              try_reg0_offset);
   MOZ_ASSERT((try_mask_nelms << (LOG2(sizeof(int)) + 3)) >= try_nregs);
 
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -856,33 +856,39 @@ public class GeckoPreferences
 
                     // Mma can only work if Health Report is enabled
                     boolean isHealthReportEnabled = isHealthReportEnabled(this);
                     if (!isHealthReportEnabled) {
                         ((SwitchPreference) pref).setChecked(isHealthReportEnabled);
                         pref.setEnabled(isHealthReportEnabled);
 
                         // Instruct the user on how to enable Health Report
-                        final String RIGHT_CHEVRON_SPACE_PADDED = " > ";
-                        StringBuilder healthReportSettingPath = new StringBuilder()
-                                .append(getString(R.string.pref_category_privacy_short))
-                                .append(RIGHT_CHEVRON_SPACE_PADDED)
-                                .append(getString(R.string.pref_category_datareporting))
-                                .append(RIGHT_CHEVRON_SPACE_PADDED)
-                                .append(getString(R.string.datareporting_fhr_title));
-                        SpannableString boldSettingsLocation = new SpannableString(healthReportSettingPath);
-                        boldSettingsLocation.setSpan(new StyleSpan(Typeface.BOLD),
-                                0, healthReportSettingPath.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+                        final String healthReportSettingPath =
+                                getString(R.string.pref_feature_tips_notification_enabling_path);
+                        final String enableHealthReportHint =
+                                getString(R.string.pref_feature_tips_notification_enabling_hint);
+
+                        final int healthReportPathFirstCharIndex =
+                                enableHealthReportHint.indexOf(healthReportSettingPath);
+                        if (healthReportPathFirstCharIndex < 0) {
+                            return;
+                        }
+                        final int healthReportPathLastCharIndex =
+                                healthReportPathFirstCharIndex + healthReportSettingPath.length();
+
+                        final SpannableString enableHealthReportBoldedHint =
+                                new SpannableString(enableHealthReportHint);
+                        enableHealthReportBoldedHint.setSpan(new StyleSpan(Typeface.BOLD),
+                                healthReportPathFirstCharIndex, healthReportPathLastCharIndex,
+                                Spannable.SPAN_INCLUSIVE_INCLUSIVE);
 
                         SpannableStringBuilder summaryTextBuilder = new SpannableStringBuilder()
-                                .append(getString(R.string.pref_feature_tips_notification_summary))
+                                .append(enableHealthReportBoldedHint)
                                 .append("\n\n")
-                                .append(getString(R.string.pref_feature_tips_notification_enabling_hint))
-                                .append(" ")
-                                .append(boldSettingsLocation);
+                                .append(getString(R.string.pref_feature_tips_notification_summary));
 
                         pref.setSummary(summaryTextBuilder);
                     }
                 }
 
                 // Some Preference UI elements are not actually preferences,
                 // but they require a key to work correctly. For example,
                 // "Clear private data" requires a key for its state to be
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -285,20 +285,22 @@
 <!ENTITY pref_whats_new_notification_summary "Learn about new features after an update">
 
 <!-- Localization note (pref_feature_tips_notification): Title of a new toggleable setting in Settings-Notifications screen.
      Similar to the already existing "pref_whats_new_notification"-->
 <!ENTITY pref_feature_tips_notification "Product and feature tips">
 <!-- Localization note (pref_feature_tips_notification_summary): Description of a new toggleable setting in Settings-Notifications screen.
      Similar to the already existing "pref_whats_new_notification_summary"-->
 <!ENTITY pref_feature_tips_notification_summary "Learn more about using &brandShortName; and other &vendorShortName; products">
+<!-- Localization note (pref_feature_tips_notification_enabling_path):
+     Nothing to translate. Simple concatenation of already localized strings. Result is used below. -->
+<!ENTITY pref_feature_tips_notification_enabling_path "&pref_category_privacy_short; > &pref_category_datareporting; > &datareporting_fhr_title;">
 <!-- Localization note (pref_feature_tips_notification_enabling_hint):
-     Describe the action the user should do to enable this preference
-     Will be used in the context "<Turn on> ... to enable this feature" -->
-<!ENTITY pref_feature_tips_notification_enabling_hint "Turn on">
+     Describe the action the user should do to enable this preference. -->
+<!ENTITY pref_feature_tips_notification_enabling_hint2 "(To enable this option, &pref_feature_tips_notification_enabling_path; must already be enabled.)">
 
 <!-- Localization note (pref_category_experimental): Title of a sub category in the 'advanced' category
      for experimental features. -->
 <!ENTITY pref_category_experimental "Experimental features">
 
 <!-- Custom Tabs is an Android API for allowing third-party apps to open URLs in a customized UI.
      Instead of switching to the browser it appears as if the user stays in the third-party app.
      For more see: https://developer.chrome.com/multidevice/android/customtabs -->
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -219,17 +219,18 @@
   <string name="pref_tracking_protection_enabled_pb">&pref_tracking_protection_enabled_pb;</string>
   <string name="pref_tracking_protection_disabled">&pref_tracking_protection_disabled;</string>
 
   <string name="pref_whats_new_notification">&pref_whats_new_notification;</string>
   <string name="pref_whats_new_notification_summary">&pref_whats_new_notification_summary;</string>
 
   <string name="pref_feature_tips_notification">&pref_feature_tips_notification;</string>
   <string name="pref_feature_tips_notification_summary">&pref_feature_tips_notification_summary;</string>
-  <string name="pref_feature_tips_notification_enabling_hint">&pref_feature_tips_notification_enabling_hint;</string>
+  <string name="pref_feature_tips_notification_enabling_path">&pref_feature_tips_notification_enabling_path;</string>
+  <string name="pref_feature_tips_notification_enabling_hint">&pref_feature_tips_notification_enabling_hint2;</string>
 
   <string name="pref_category_experimental">&pref_category_experimental;</string>
 
   <string name="pref_custom_tabs">&pref_custom_tabs2;</string>
   <string name="pref_custom_tabs_summary">&pref_custom_tabs_summary4;</string>
 
   <string name="custom_tabs_menu_item_open_in">&custom_tabs_menu_item_open_in;</string>
   <string name="custom_tabs_menu_footer">&custom_tabs_menu_footer;</string>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -30,16 +30,17 @@ import android.widget.EditText
 
 import android.widget.FrameLayout
 
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.Before
 import org.junit.After
 import org.junit.runner.RunWith
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 
 const val DISPLAY_WIDTH = 480
 const val DISPLAY_HEIGHT = 640
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @WithDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
@@ -590,16 +591,17 @@ class AccessibilityTest : BaseSessionTes
 
             @AssertCalled(count = 1, order = [3])
             override fun onWinContentChanged(event: AccessibilityEvent) {
                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
             }
         })
     }
 
+    @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
     @Test fun autoFill() {
         // Wait for the accessibility nodes to populate.
         mainSession.loadTestPath(FORMS_HTML_PATH)
         waitForInitialFocus()
 
         val autoFills = mapOf(
                 "#user1" to "bar", "#pass1" to "baz", "#user2" to "bar", "#pass2" to "baz") +
                 if (Build.VERSION.SDK_INT >= 19) mapOf(
@@ -663,16 +665,17 @@ class AccessibilityTest : BaseSessionTes
         autoFillChild(View.NO_ID, createNodeInfo(View.NO_ID))
 
         // Wait on the promises and check for correct values.
         for ((actual, expected) in promises.map { it.value.asJSList<String>() }) {
             assertThat("Auto-filled value must match", actual, equalTo(expected))
         }
     }
 
+    @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
     @Test fun autoFill_navigation() {
         fun countAutoFillNodes(cond: (AccessibilityNodeInfo) -> Boolean =
                                        { it.className == "android.widget.EditText" },
                                id: Int = View.NO_ID): Int {
             val info = createNodeInfo(id)
             return (if (cond(info) && info.className != "android.webkit.WebView" ) 1 else 0) + (if (info.childCount > 0)
                 (0 until info.childCount).sumBy {
                     countAutoFillNodes(cond, info.getChildId(it))
@@ -716,16 +719,17 @@ class AccessibilityTest : BaseSessionTes
             @AssertCalled
             override fun onFocused(event: AccessibilityEvent) {
             }
         })
         assertThat("Should not have focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(0))
     }
 
+    @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
     @Test fun testTree() {
         sessionRule.session.loadString(
                 "<label for='name'>Name:</label><input id='name' type='text' value='Julie'><button>Submit</button>",
                 "text/html")
         waitForInitialFocus()
 
         val rootNode = createNodeInfo(View.NO_ID)
         assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
@@ -751,16 +755,17 @@ class AccessibilityTest : BaseSessionTes
         assertThat("Button has a single text leaf", buttonNode.childCount, equalTo(1))
         assertThat("Button has correct text", buttonNode.text.toString(), equalTo("Submit"))
 
         val textLeaf = createNodeInfo(buttonNode.getChildId(0))
         assertThat("First node is a label", textLeaf.className.toString(), equalTo("android.view.View"))
         assertThat("Text leaf has correct text", textLeaf.text.toString(), equalTo("Submit"))
     }
 
+    @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
     @Test fun testCollection() {
         sessionRule.session.loadString(
                 """<ul>
                   |  <li>One</li>
                   |  <li>Two</li>
                   |</ul>
                   |<ul>
                   |  <li>1<ul><li>1.1</li><li>1.2</li></ul></li>
@@ -791,16 +796,17 @@ class AccessibilityTest : BaseSessionTes
         assertThat("Second list has 1 child", secondList.childCount, equalTo(1))
         if (Build.VERSION.SDK_INT >= 19) {
             assertThat("Second list should have collectionInfo", secondList.collectionInfo, notNullValue())
             assertThat("Second list has 2 rowCount", secondList.collectionInfo.rowCount, equalTo(1))
             assertThat("Second list should be hierarchical", secondList.collectionInfo.isHierarchical, equalTo(true))
         }
     }
 
+    @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
     @Test fun testRange() {
         sessionRule.session.loadString(
                 """<input type="range" aria-label="Rating" min="1" max="10" value="4">
                   |<input type="range" aria-label="Stars" min="1" max="5" step="0.5" value="4.5">
                   |<input type="range" aria-label="Percent" min="0" max="1" step="0.01" value="0.83">
                 """.trimMargin(),
                 "text/html")
         waitForInitialFocus()
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -186,17 +186,18 @@ public class GeckoSessionTestRule implem
     public @interface Setting {
         enum Key {
             CHROME_URI,
             DISPLAY_MODE,
             ALLOW_JAVASCRIPT,
             SCREEN_ID,
             USE_MULTIPROCESS,
             USE_PRIVATE_MODE,
-            USE_TRACKING_PROTECTION;
+            USE_TRACKING_PROTECTION,
+            FULL_ACCESSIBILITY_TREE;
 
             private final GeckoSessionSettings.Key<?> mKey;
             private final Class<?> mType;
 
             Key() {
                 final Field field;
                 try {
                     field = GeckoSessionSettings.class.getField(name());
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
@@ -136,17 +136,17 @@ public final class GeckoSessionSettings 
         mBundle = new GeckoBundle();
         mBundle.putString(CHROME_URI.name, null);
         mBundle.putInt(SCREEN_ID.name, 0);
         mBundle.putBoolean(USE_TRACKING_PROTECTION.name, false);
         mBundle.putBoolean(USE_PRIVATE_MODE.name, false);
         mBundle.putBoolean(USE_MULTIPROCESS.name, true);
         mBundle.putBoolean(SUSPEND_MEDIA_WHEN_INACTIVE.name, false);
         mBundle.putBoolean(ALLOW_JAVASCRIPT.name, true);
-        mBundle.putBoolean(FULL_ACCESSIBILITY_TREE.name, true);
+        mBundle.putBoolean(FULL_ACCESSIBILITY_TREE.name, false);
         mBundle.putInt(USER_AGENT_MODE.name, USER_AGENT_MODE_MOBILE);
         mBundle.putInt(DISPLAY_MODE.name, DISPLAY_MODE_BROWSER);
     }
 
     public void setBoolean(final Key<Boolean> key, final boolean value) {
         synchronized (mBundle) {
             if (valueChangedLocked(key, value)) {
                 mBundle.putBoolean(key.name, value);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -256,35 +256,37 @@ public class SessionAccessibility {
         }
 
         private AccessibilityNodeInfo getNodeFromGecko(final int virtualViewId) {
             AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView, virtualViewId);
             populateNodeFromBundle(node, nativeProvider.getNodeInfo(virtualViewId), false);
             return node;
         }
 
-        private synchronized AccessibilityNodeInfo getNodeFromCache(final int virtualViewId) {
-            AccessibilityNodeInfo node = null;
-            for (SparseArray<GeckoBundle> cache : mCaches) {
-                GeckoBundle bundle = cache.get(virtualViewId);
-                if (bundle == null) {
-                    continue;
+        private AccessibilityNodeInfo getNodeFromCache(final int virtualViewId) {
+            synchronized (SessionAccessibility.this) {
+                AccessibilityNodeInfo node = null;
+                for (SparseArray<GeckoBundle> cache : mCaches) {
+                    GeckoBundle bundle = cache.get(virtualViewId);
+                    if (bundle == null) {
+                        continue;
+                    }
+
+                    if (node == null) {
+                        node = AccessibilityNodeInfo.obtain(mView, virtualViewId);
+                    }
+                    populateNodeFromBundle(node, bundle, true);
                 }
 
                 if (node == null) {
-                    node = AccessibilityNodeInfo.obtain(mView, virtualViewId);
+                    Log.e(LOGTAG, "No cached node for " + virtualViewId);
                 }
-                populateNodeFromBundle(node, bundle, true);
-            }
 
-            if (node == null) {
-                Log.e(LOGTAG, "No cached node for " + virtualViewId);
+                return node;
             }
-
-            return node;
         }
 
         private void populateNodeFromBundle(final AccessibilityNodeInfo node, final GeckoBundle nodeInfo, final boolean fromCache) {
             if (mView == null || nodeInfo == null) {
                 return;
             }
 
             boolean isRoot = nodeInfo.getInt("id") == View.NO_ID;
@@ -644,17 +646,17 @@ public class SessionAccessibility {
             // display is attached and we must be in a junit test.
             return;
         }
 
         GeckoBundle cachedBundle = null;
         if (!mSession.getSettings().getBoolean(GeckoSessionSettings.FULL_ACCESSIBILITY_TREE)) {
             cachedBundle = getMostRecentBundle(sourceId);
             // Suppress events from non cached nodes if cache is enabled.
-            if (cachedBundle == null) {
+            if (cachedBundle == null && sourceId != View.NO_ID) {
                 return;
             }
         }
 
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
         event.setSource(mView, sourceId);
         event.setClassName(getClassName(className));
@@ -746,42 +748,52 @@ public class SessionAccessibility {
                 @Override
                 public void run() {
                     sendEvent(eventType, sourceId, className, eventData);
                 }
             });
         }
 
         @WrapForJNI(calledFrom = "gecko")
-        private synchronized void replaceViewportCache(final GeckoBundle[] bundles) {
-            mViewportCache.clear();
-            for (GeckoBundle bundle : bundles) {
-                if (bundle == null) { continue; }
-                mViewportCache.append(bundle.getInt("id"), bundle);
+        private void replaceViewportCache(final GeckoBundle[] bundles) {
+            synchronized (SessionAccessibility.this) {
+                mViewportCache.clear();
+                for (GeckoBundle bundle : bundles) {
+                    if (bundle == null) {
+                        continue;
+                    }
+                    mViewportCache.append(bundle.getInt("id"), bundle);
+                }
+                mCaches.remove(mViewportCache);
+                mCaches.add(mViewportCache);
             }
-            mCaches.remove(mViewportCache);
-            mCaches.add(mViewportCache);
         }
 
         @WrapForJNI(calledFrom = "gecko")
-        private synchronized void replaceFocusPathCache(final GeckoBundle[] bundles) {
-            mFocusPathCache.clear();
-            for (GeckoBundle bundle : bundles) {
-                if (bundle == null) { continue; }
-                mFocusPathCache.append(bundle.getInt("id"), bundle);
+        private void replaceFocusPathCache(final GeckoBundle[] bundles) {
+            synchronized (SessionAccessibility.this) {
+                mFocusPathCache.clear();
+                for (GeckoBundle bundle : bundles) {
+                    if (bundle == null) {
+                        continue;
+                    }
+                    mFocusPathCache.append(bundle.getInt("id"), bundle);
+                }
+                mCaches.remove(mFocusPathCache);
+                mCaches.add(mFocusPathCache);
             }
-            mCaches.remove(mFocusPathCache);
-            mCaches.add(mFocusPathCache);
         }
 
         @WrapForJNI(calledFrom = "gecko")
-        private synchronized void updateCachedBounds(final GeckoBundle[] bundles) {
-            for (GeckoBundle bundle : bundles) {
-                GeckoBundle cachedBundle = getMostRecentBundle(bundle.getInt("id"));
-                if (cachedBundle == null) {
-                    Log.e(LOGTAG, "Can't update bounds of uncached node " + bundle.getInt("id"));
-                    continue;
+        private void updateCachedBounds(final GeckoBundle[] bundles) {
+            synchronized (SessionAccessibility.this) {
+                for (GeckoBundle bundle : bundles) {
+                    GeckoBundle cachedBundle = getMostRecentBundle(bundle.getInt("id"));
+                    if (cachedBundle == null) {
+                        Log.e(LOGTAG, "Can't update bounds of uncached node " + bundle.getInt("id"));
+                        continue;
+                    }
+                    cachedBundle.putIntArray("bounds", bundle.getIntArray("bounds"));
                 }
-                cachedBundle.putIntArray("bounds", bundle.getIntArray("bounds"));
             }
         }
     }
 }
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -102,17 +102,17 @@ public class GeckoViewActivity extends A
         mLocationView = new LocationView(this);
         mLocationView.setId(R.id.url_bar);
         getSupportActionBar().setCustomView(mLocationView,
                 new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT,
                         ActionBar.LayoutParams.WRAP_CONTENT));
         getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
 
         mUseMultiprocess = getIntent().getBooleanExtra(USE_MULTIPROCESS_EXTRA, true);
-        mFullAccessibilityTree = getIntent().getBooleanExtra(FULL_ACCESSIBILITY_TREE_EXTRA, true);
+        mFullAccessibilityTree = getIntent().getBooleanExtra(FULL_ACCESSIBILITY_TREE_EXTRA, false);
         mProgressView = (ProgressBar) findViewById(R.id.page_progress);
 
         if (sGeckoRuntime == null) {
             final GeckoRuntimeSettings.Builder runtimeSettingsBuilder =
                 new GeckoRuntimeSettings.Builder();
 
             if (BuildConfig.DEBUG) {
                 // In debug builds, we want to load JavaScript resources fresh with
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -179,17 +179,17 @@ VARCACHE_PREF(
 )
 #undef PREF_VALUE
 
 // How long a content process can take before closing its IPC channel
 // after shutdown is initiated.  If the process exceeds the timeout,
 // we fear the worst and kill it.
 #if !defined(DEBUG) && !defined(MOZ_ASAN) && !defined(MOZ_VALGRIND) && \
     !defined(MOZ_TSAN)
-# define PREF_VALUE 10
+# define PREF_VALUE 5
 #else
 # define PREF_VALUE 0
 #endif
 VARCACHE_PREF(
   "dom.ipc.tabs.shutdownTimeoutSecs",
    dom_ipc_tabs_shutdownTimeoutSecs,
   RelaxedAtomicUint32, PREF_VALUE
 )
@@ -1064,16 +1064,28 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "media.gpu-process-decoder",
    MediaGpuProcessDecoder,
   RelaxedAtomicBool, PREF_VALUE
 )
 #undef PREF_VALUE
 
+VARCACHE_PREF(
+  "media.rdd-process.enabled",
+   MediaRddProcessEnabled,
+  RelaxedAtomicBool, false
+)
+
+VARCACHE_PREF(
+  "media.rdd-process.startup_timeout_ms",
+   MediaRddProcessStartupTimeoutMs,
+  RelaxedAtomicInt32, 5000
+)
+
 #ifdef ANDROID
 
 // Enable the MediaCodec PlatformDecoderModule by default.
 VARCACHE_PREF(
   "media.android-media-codec.enabled",
    MediaAndroidMediaCodecEnabled,
   RelaxedAtomicBool, true
 )
--- a/python/mozbuild/mozbuild/generated_sources.py
+++ b/python/mozbuild/mozbuild/generated_sources.py
@@ -44,18 +44,17 @@ def get_generated_sources():
     # Next, return all the files in $objdir/ipc/ipdl/_ipdlheaders.
     base = 'ipc/ipdl/_ipdlheaders'
     finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
     for p, f in finder.find('**/*.h'):
         yield mozpath.join(base, p), f
     # Next, return any Rust source files that were generated into the Rust
     # object directory.
     rust_build_kind = 'debug' if buildconfig.substs.get('MOZ_DEBUG_RUST') else 'release'
-    base = mozpath.join('toolkit/library',
-                        buildconfig.substs['RUST_TARGET'],
+    base = mozpath.join(buildconfig.substs['RUST_TARGET'],
                         rust_build_kind,
                         'build')
     finder = FileFinder(mozpath.join(buildconfig.topobjdir, base))
     for p, f in finder.find('**/*.rs'):
         yield mozpath.join(base, p), f
 
 
 def get_s3_region_and_bucket():
--- a/security/manager/ssl/OSReauthenticator.cpp
+++ b/security/manager/ssl/OSReauthenticator.cpp
@@ -83,131 +83,141 @@ GetUserTokenInfo()
 // Use the Windows credential prompt to ask the user to authenticate the
 // currently used account.
 static nsresult
 ReauthenticateUserWindows(const nsACString& aPrompt,
                           /* out */ bool& reauthenticated)
 {
   reauthenticated = false;
 
-  HANDLE lsa;
-  // Get authentication handle for future user authentications.
-  // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsaconnectuntrusted
-  if (LsaConnectUntrusted(&lsa) != ERROR_SUCCESS) {
-    MOZ_LOG(gCredentialManagerSecretLog,
-            LogLevel::Debug,
-            ("Error aquiring lsa. Authentication attempts will fail."));
-    return NS_ERROR_FAILURE;
-  }
-  ScopedHANDLE scopedLsa(lsa);
-
+  // Is used in next iteration if the previous login failed.
+  DWORD err = 0;
+  uint8_t numAttempts = 3;
   std::unique_ptr<char[]> userTokenInfo = GetUserTokenInfo();
-  if (!userTokenInfo || lsa == INVALID_HANDLE_VALUE) {
-    MOZ_LOG(gCredentialManagerSecretLog,
-            LogLevel::Debug,
-            ("Error setting up login and user token."));
-    return NS_ERROR_FAILURE;
-  }
 
   // CredUI prompt.
   CREDUI_INFOW credui = {};
   credui.cbSize = sizeof(credui);
   // TODO: maybe set parent (Firefox) here.
   credui.hwndParent = nullptr;
   const nsString& prompt = PromiseFlatString(NS_ConvertUTF8toUTF16(aPrompt));
   credui.pszMessageText = prompt.get();
   credui.pszCaptionText = nullptr;
   credui.hbmBanner = nullptr; // ignored
 
-  ULONG authPackage = 0;
-  LPVOID outCredBuffer = nullptr;
-  ULONG outCredSize = 0;
-  BOOL save = false;
-  // Could be used in next iteration if the previous login failed.
-  DWORD err = 0;
+  while (!reauthenticated && numAttempts > 0) {
+    --numAttempts;
+
+    HANDLE lsa;
+    // Get authentication handle for future user authentications.
+    // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsaconnectuntrusted
+    if (LsaConnectUntrusted(&lsa) != ERROR_SUCCESS) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Error aquiring lsa. Authentication attempts will fail."));
+      return NS_ERROR_FAILURE;
+    }
+    ScopedHANDLE scopedLsa(lsa);
 
-  // Get user's Windows credentials.
-  // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creduipromptforwindowscredentialsw
-  err = CredUIPromptForWindowsCredentialsW(&credui, err, &authPackage,
-                          nullptr, 0, &outCredBuffer, &outCredSize, &save,
-                          CREDUIWIN_ENUMERATE_CURRENT_USER);
-  ScopedBuffer scopedOutCredBuffer(outCredBuffer);
-  if (err != ERROR_SUCCESS) {
-    MOZ_LOG(gCredentialManagerSecretLog,
-            LogLevel::Debug,
-            ("Error getting authPackage for user login"));
-    return NS_ERROR_FAILURE;
-  }
+    if (!userTokenInfo || lsa == INVALID_HANDLE_VALUE) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Error setting up login and user token."));
+      return NS_ERROR_FAILURE;
+    }
+
+    ULONG authPackage = 0;
+    ULONG outCredSize = 0;
+    LPVOID outCredBuffer = nullptr;
+    BOOL save = false;
 
-  // Verify the credentials.
-  TOKEN_SOURCE source;
-  PCHAR contextName = const_cast<PCHAR>("Mozilla");
-  size_t nameLength = std::min(TOKEN_SOURCE_LENGTH,
-                               static_cast<int>(strlen(contextName)));
-  // Note that the string must not be longer than TOKEN_SOURCE_LENGTH.
-  memcpy(source.SourceName, contextName, nameLength);
-  // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-allocatelocallyuniqueid
-  if (!AllocateLocallyUniqueId(&source.SourceIdentifier)) {
+    // Get user's Windows credentials.
+    // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creduipromptforwindowscredentialsw
+    err = CredUIPromptForWindowsCredentialsW(&credui, err, &authPackage,
+                            nullptr, 0, &outCredBuffer, &outCredSize, &save,
+                            CREDUIWIN_ENUMERATE_CURRENT_USER);
+    ScopedBuffer scopedOutCredBuffer(outCredBuffer);
+    if (err == ERROR_CANCELLED) {
+      MOZ_LOG(gCredentialManagerSecretLog,
+              LogLevel::Debug,
+              ("Error getting authPackage for user login, user cancel."));
+      return NS_OK; 
+    }
+    if (err != ERROR_SUCCESS) {
       MOZ_LOG(gCredentialManagerSecretLog,
               LogLevel::Debug,
-              ("Error allocating ID for logon process."));
+              ("Error getting authPackage for user login."));
       return NS_ERROR_FAILURE;
-  }
+    }
+
+    // Verify the credentials.
+    TOKEN_SOURCE source;
+    PCHAR contextName = const_cast<PCHAR>("Mozilla");
+    size_t nameLength = std::min(TOKEN_SOURCE_LENGTH,
+                                 static_cast<int>(strlen(contextName)));
+    // Note that the string must not be longer than TOKEN_SOURCE_LENGTH.
+    memcpy(source.SourceName, contextName, nameLength);
+    // https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-allocatelocallyuniqueid
+    if (!AllocateLocallyUniqueId(&source.SourceIdentifier)) {
+        MOZ_LOG(gCredentialManagerSecretLog,
+                LogLevel::Debug,
+                ("Error allocating ID for logon process."));
+        return NS_ERROR_FAILURE;
+    }
 
-  NTSTATUS substs;
-  void* profileBuffer = nullptr;
-  ULONG profileBufferLength = 0;
-  QUOTA_LIMITS limits = {0};
-  LUID luid;
-  HANDLE token;
-  LSA_STRING name;
-  name.Buffer = contextName;
-  name.Length = strlen(name.Buffer);
-  name.MaximumLength = name.Length;
-  // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsalogonuser
-  NTSTATUS sts = LsaLogonUser(scopedLsa.get(), &name, (SECURITY_LOGON_TYPE)Interactive,
-                      authPackage, scopedOutCredBuffer.get(),
-                      outCredSize, nullptr, &source, &profileBuffer,
-                      &profileBufferLength, &luid, &token, &limits,
-                      &substs);
-  ScopedHANDLE scopedToken(token);
-  LsaFreeReturnBuffer(profileBuffer);
-  LsaDeregisterLogonProcess(scopedLsa.get());
-  if (sts == ERROR_SUCCESS) {
-      MOZ_LOG(gCredentialManagerSecretLog,
-              LogLevel::Debug,
-              ("User logged in successfully."));
-  } else {
-      MOZ_LOG(gCredentialManagerSecretLog,
-              LogLevel::Debug,
-              ("Login failed with %lx (%lx).", sts, LsaNtStatusToWinError(sts)));
-      return NS_ERROR_FAILURE;
+    NTSTATUS substs;
+    void* profileBuffer = nullptr;
+    ULONG profileBufferLength = 0;
+    QUOTA_LIMITS limits = {0};
+    LUID luid;
+    HANDLE token;
+    LSA_STRING name;
+    name.Buffer = contextName;
+    name.Length = strlen(name.Buffer);
+    name.MaximumLength = name.Length;
+    // https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/nf-ntsecapi-lsalogonuser
+    NTSTATUS sts = LsaLogonUser(scopedLsa.get(), &name, (SECURITY_LOGON_TYPE)Interactive,
+                        authPackage, scopedOutCredBuffer.get(),
+                        outCredSize, nullptr, &source, &profileBuffer,
+                        &profileBufferLength, &luid, &token, &limits,
+                        &substs);
+    ScopedHANDLE scopedToken(token);
+    LsaFreeReturnBuffer(profileBuffer);
+    LsaDeregisterLogonProcess(scopedLsa.get());
+    if (sts == ERROR_SUCCESS) {
+        MOZ_LOG(gCredentialManagerSecretLog,
+                LogLevel::Debug,
+                ("User logged in successfully."));
+    } else {
+        MOZ_LOG(gCredentialManagerSecretLog,
+                LogLevel::Debug,
+                ("Login failed with %lx (%lx).", sts, LsaNtStatusToWinError(sts)));
+        continue;
+    }
+
+    // The user can select any user to log-in on the authentication prompt.
+    // Make sure that the logged in user is the current user.
+    std::unique_ptr<char[]> logonTokenInfo = GetTokenInfo(scopedToken);
+    if (!logonTokenInfo) {
+        MOZ_LOG(gCredentialManagerSecretLog,
+                LogLevel::Debug,
+                ("Error getting logon token info."));
+        return NS_ERROR_FAILURE;
+    }
+    PSID logonSID = reinterpret_cast<TOKEN_USER*>(logonTokenInfo.get())->User.Sid;
+    PSID userSID = reinterpret_cast<TOKEN_USER*>(userTokenInfo.get())->User.Sid;
+    if (EqualSid(userSID, logonSID)) {
+        MOZ_LOG(gCredentialManagerSecretLog,
+                LogLevel::Debug,
+                ("Login successfully (correct user)."));
+        reauthenticated = true;
+        break;
+    }
   }
-
-  // The user can select any user to log-in on the authentication prompt.
-  // Make sure that the logged in user is the current user.
-  std::unique_ptr<char[]> logonTokenInfo = GetTokenInfo(scopedToken);
-  if (!logonTokenInfo) {
-      MOZ_LOG(gCredentialManagerSecretLog,
-              LogLevel::Debug,
-              ("Error getting logon token info."));
-      return NS_ERROR_FAILURE;
-  }
-  PSID logonSID = reinterpret_cast<TOKEN_USER*>(logonTokenInfo.get())->User.Sid;
-  PSID userSID = reinterpret_cast<TOKEN_USER*>(userTokenInfo.get())->User.Sid;
-  if (EqualSid(userSID, logonSID)) {
-      MOZ_LOG(gCredentialManagerSecretLog,
-              LogLevel::Debug,
-              ("Login successfully (correct user)."));
-      reauthenticated = true;
-      return NS_OK;
-  }
-  MOZ_LOG(gCredentialManagerSecretLog, LogLevel::Debug,
-          ("Login failed (wrong user)."));
-  return NS_ERROR_FAILURE;
+  return NS_OK;
 }
 #endif // XP_WIN
 
 #ifdef XP_MACOSX
 #include <CoreFoundation/CoreFoundation.h>
 #include <Security/Security.h>
 
 static nsresult
--- a/storage/test/unit/test_locale_collation.js
+++ b/storage/test/unit/test_locale_collation.js
@@ -282,9 +282,10 @@ var gTests = [
 ];
 
 function run_test() {
   setup();
   gTests.forEach(function(test) {
     print("-- Running test: " + test.desc);
     test.run();
   });
+  cleanupLocaleTests();
 }
--- a/taskcluster/docs/attributes.rst
+++ b/taskcluster/docs/attributes.rst
@@ -250,8 +250,19 @@ In automation, full crashsymbol package 
 build kinds where the full crashsymbols should be enabled, set this attribute
 to True. The full symbol packages will then be generated and uploaded on
 release branches and on try.
 
 cron
 ====
 Indicates that a task is meant to be run via cron tasks, and should not be run
 on push.
+
+cache_digest
+============
+Some tasks generate artifacts that are cached between pushes. This is the unique string used
+to identify the current version of the artifacts. See :py:mod:`taskgraph.util.cached_task`.
+
+cache_type
+==========
+Some tasks generate artifacts that are cached between pushes. This is the type of cache that is
+used for the this task. See :py:mod:`taskgraph.util.cached_task`.
+
--- a/taskcluster/taskgraph/transforms/docker_image.py
+++ b/taskcluster/taskgraph/transforms/docker_image.py
@@ -86,25 +86,20 @@ def order_image_tasks(config, tasks):
 
 @transforms.add
 def fill_template(config, tasks):
     available_packages = {}
     for task in config.kind_dependencies_tasks:
         if task.kind != 'packages':
             continue
         name = task.label.replace('packages-', '')
-        for route in task.task.get('routes', []):
-            if route.startswith('index.') and '.hash.' in route:
-                # Only keep the hash part of the route.
-                h = route.rsplit('.', 1)[1]
-                assert DIGEST_RE.match(h)
-                available_packages[name] = h
-                break
+        available_packages[name] = task.attributes['cache_digest']
 
     context_hashes = {}
+    image_digests = {}
 
     for task in order_image_tasks(config, tasks):
         image_name = task.pop('name')
         job_symbol = task.pop('symbol')
         args = task.pop('args', {})
         definition = task.pop('definition', image_name)
         packages = task.pop('packages', [])
         parent = task.pop('parent', None)
@@ -125,16 +120,19 @@ def fill_template(config, tasks):
             args['DOCKER_IMAGE_PARENT'] = '{}:{}'.format(parent, context_hashes[parent])
 
         context_path = os.path.join('taskcluster', 'docker', definition)
         context_hash = generate_context_hash(
             GECKO, context_path, image_name, args)
         digest_data = [context_hash]
         context_hashes[image_name] = context_hash
 
+        if parent:
+            digest_data += [image_digests[parent]]
+
         description = 'Build the docker image {} for use by dependent tasks'.format(
             image_name)
 
         # Adjust the zstandard compression level based on the execution level.
         # We use faster compression for level 1 because we care more about
         # end-to-end times. We use slower/better compression for other levels
         # because images are read more often and it is worth the trade-off to
         # burn more CPU once to reduce image size.
@@ -242,9 +240,11 @@ def fill_template(config, tasks):
             kwargs = {'digest': digest_data[0]}
         add_optimization(
             config, taskdesc,
             cache_type="docker-images.v1",
             cache_name=image_name,
             **kwargs
         )
 
+        image_digests[image_name] = taskdesc['attributes']['cache_digest']
+
         yield taskdesc
--- a/taskcluster/taskgraph/util/cached_tasks.py
+++ b/taskcluster/taskgraph/util/cached_tasks.py
@@ -63,16 +63,19 @@ def add_optimization(config, taskdesc, c
     # ... and add some extra routes for humans
     subs['build_date_long'] = time.strftime("%Y.%m.%d.%Y%m%d%H%M%S",
                                             time.gmtime(config.params['build_date']))
     taskdesc['routes'].extend([
         'index.{}'.format(route.format(**subs))
         for route in EXTRA_CACHE_INDEXES
     ])
 
+    taskdesc['attributes']['cache_digest'] = digest
+    taskdesc['attributes']['cache_type'] = cache_type
+
 
 def cached_index_path(level, trust_domain, cache_type, cache_name, digest=None, digest_data=None):
     """
     Get the index path needed to locate the task that would be created by
     :func:`add_optimization`.
 
     :param int level: The SCM level of the task to look for.
     :param str trust_domain: The trust domain to look for the task in.
--- a/testing/firefox-ui/tests/puppeteer/test_windows.py
+++ b/testing/firefox-ui/tests/puppeteer/test_windows.py
@@ -20,17 +20,17 @@ class TestWindows(PuppeteerMixin, Marion
         finally:
             super(TestWindows, self).tearDown()
 
     def test_switch_to(self):
         url = self.marionette.absolute_url('layout/mozilla.html')
 
         # Open two more windows
         for index in range(0, 2):
-            self.marionette.execute_script(""" window.open(); """)
+            self.marionette.execute_script(""" OpenBrowserWindow(); """)
 
         windows = self.puppeteer.windows.all
         self.assertEquals(len(windows), 3)
 
         # Switch to the 2nd window
         self.puppeteer.windows.switch_to(windows[1].handle)
         self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)
 
@@ -125,17 +125,17 @@ class TestBaseWindow(PuppeteerMixin, Mar
         with self.assertRaises(NoSuchWindowException):
             self.marionette.current_chrome_window_handle
         Wait(self.marionette).until(lambda _: win1.focused)  # catch the no focused window
 
         win1.focus()
 
         # Open and close a new window by a custom callback
         def opener(window):
-            window.marionette.execute_script(""" window.open(); """)
+            window.marionette.execute_script(""" OpenBrowserWindow(); """)
 
         def closer(window):
             window.marionette.execute_script(""" window.close(); """)
 
         win2 = win1.open_window(callback=opener)
 
         # force BaseWindow instance
         win2 = BaseWindow(self.marionette, win2.handle)
--- a/testing/jsshell/benchmark.py
+++ b/testing/jsshell/benchmark.py
@@ -257,16 +257,17 @@ class SunSpider(RunOnceBenchmark):
 
 
 class WebToolingBenchmark(Benchmark):
     name = 'web-tooling-benchmark'
     path = os.path.join('third_party', 'webkit', 'PerformanceTests', 'web-tooling-benchmark')
     main_js = 'cli.js'
     units = 'score'
     lower_is_better = False
+    subtests_lower_is_better = False
 
     @property
     def command(self):
         cmd = super(WebToolingBenchmark, self).command
         return cmd + [self.main_js]
 
     def reset(self):
         super(WebToolingBenchmark, self).reset()
@@ -286,17 +287,21 @@ class WebToolingBenchmark(Benchmark):
         self.scores[self.name][subtest].append(float(score))
 
     def collect_results(self):
         # NOTE: for this benchmark we run the test once, so we have a single value array
         for bench, scores in self.scores.items():
             for score_name, values in scores.items():
                 test_name = "{}-{}".format(self.name, score_name)
                 mean = sum(values) / len(values)
-                self.suite['subtests'].append({'name': test_name, 'value': mean})
+                self.suite['subtests'].append({
+                    'lower_is_better': self.subtests_lower_is_better,
+                    'name': test_name,
+                    'value': mean,
+                });
                 if score_name == 'mean':
                     bench_mean = mean
         self.suite['value'] = bench_mean
 
     def run(self):
         self._provision_benchmark_script()
         return super(WebToolingBenchmark, self).run()
 
--- a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
+++ b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
@@ -94,17 +94,17 @@ class WindowManagerMixin(object):
                   return win.document.readyState == "complete";
                 """, script_args=[handle])
 
         try:
             if callable(trigger):
                 trigger()
             else:
                 with self.marionette.using_context("chrome"):
-                    self.marionette.execute_script("window.open();")
+                    self.marionette.execute_script("OpenBrowserWindow();")
         except Exception:
             exc, val, tb = sys.exc_info()
             reraise(exc, 'Failed to trigger opening a new window: {}'.format(val), tb)
         else:
             Wait(self.marionette).until(
                 lambda mn: len(mn.chrome_window_handles) == len(current_windows) + 1,
                 message="No new window has been opened"
             )
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_window_chrome.py
@@ -25,29 +25,51 @@ class TestSwitchWindowChrome(TestSwitchT
 
     def tearDown(self):
         self.close_all_windows()
 
         super(TestSwitchWindowChrome, self).tearDown()
 
     def open_window_in_background(self):
         with self.marionette.using_context("chrome"):
-            self.marionette.execute_script("""
-              window.open("about:blank", null, "location=1,toolbar=1");
-              window.focus();
+            self.marionette.execute_async_script("""
+              let callback = arguments[0];
+              (async function() {
+                function promiseEvent(target, type, args) {
+                  return new Promise(r => {
+                    let params = Object.assign({once: true}, args);
+                    target.addEventListener(type, r, params);
+                  });
+                }
+                function promiseWindowFocus(w) {
+                  return Promise.all([
+                    promiseEvent(w, "focus", {capture: true}),
+                    promiseEvent(w, "activate"),
+                  ]);
+                }
+                // Open a window, wait for it to receive focus
+                let win = OpenBrowserWindow();
+                await promiseWindowFocus(win);
+
+                // Now refocus our original window and wait for that to happen.
+                let windowFocusPromise = promiseWindowFocus(window);
+                window.focus();
+                return windowFocusPromise;
+              })().then(() => {
+                // can't just pass `callback`, as we can't JSON-ify the events it'd get passed.
+                callback()
+              });
             """)
 
     def open_window_in_foreground(self):
         with self.marionette.using_context("content"):
             self.marionette.navigate(self.test_page)
             link = self.marionette.find_element(By.ID, "new-window")
             link.click()
 
-    @skipIf(sys.platform.startswith("linux") or sys.platform == "darwin",
-            "Bug 1335457 - Fails to open a background window on Linux / MacOS")
     def test_switch_tabs_for_new_background_window_without_focus_change(self):
         # Open an addition tab in the original window so we can better check
         # the selected index in thew new window to be opened.
         second_tab = self.open_tab(trigger=self.open_tab_in_foreground)
         self.marionette.switch_to_window(second_tab, focus=True)
         second_tab_index = self.get_selected_tab_index()
         self.assertNotEqual(second_tab_index, self.selected_tab_index)
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
@@ -39,17 +39,17 @@ class TestWindowHandles(WindowManagerMix
             self.assertIsInstance(handle, types.StringTypes)
 
         for handle in self.marionette.window_handles:
             self.assertIsInstance(handle, types.StringTypes)
 
     def test_chrome_window_handles_with_scopes(self):
         # Open a browser and a non-browser (about window) chrome window
         self.open_window(
-            trigger=lambda: self.marionette.execute_script("window.open();"))
+            trigger=lambda: self.marionette.execute_script("OpenBrowserWindow();"))
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
         self.open_window(
             trigger=lambda: self.marionette.find_element(By.ID, "aboutName").click())
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 2)
--- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/windows.py
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/windows.py
@@ -277,17 +277,17 @@ class BaseWindow(BaseLib):
 
     def close(self, callback=None, force=False):
         """Closes the current chrome window.
 
         If this is the last remaining window, the Marionette session is ended.
 
         :param callback: Optional, function to trigger the window to open. It is
          triggered with the current :class:`BaseWindow` as parameter.
-         Defaults to `window.open()`.
+         Defaults to `OpenBrowserWindow()` (from browser.js).
 
         :param force: Optional, forces the closing of the window by using the Gecko API.
          Defaults to `False`.
         """
         self.switch_to()
 
         # Bug 1121698
         # For more stable tests register an observer topic first
@@ -331,31 +331,31 @@ class BaseWindow(BaseLib):
         """
         return self._l10n.localize_property(self.properties, property_id)
 
     def open_window(self, callback=None, expected_window_class=None, focus=True):
         """Opens a new top-level chrome window.
 
         :param callback: Optional, function to trigger the window to open. It is
          triggered with the current :class:`BaseWindow` as parameter.
-         Defaults to `window.open()`.
+         Defaults to `OpenBrowserWindow()` (from browser.js).
         :param expected_class: Optional, check for the correct window class.
         :param focus: Optional, if true, focus the new window.
          Defaults to `True`.
         """
         # Bug 1121698
         # For more stable tests register an observer topic first
         start_handles = self.marionette.chrome_window_handles
 
         self.switch_to()
         with self.marionette.using_context('chrome'):
             if callback is not None:
                 callback(self)
             else:
-                self.marionette.execute_script(""" window.open(); """)
+                self.marionette.execute_script(""" OpenBrowserWindow(); """)
 
         # TODO: Needs to be replaced with observer handling code (bug 1121698)
         def window_opened(mn):
             return len(mn.chrome_window_handles) == len(start_handles) + 1
         Wait(self.marionette).until(
             window_opened,
             message='No new chrome window has been opened.')
 
--- a/testing/mozharness/mozharness/base/errors.py
+++ b/testing/mozharness/mozharness/base/errors.py
@@ -128,20 +128,25 @@ PythonErrorList = BaseErrorList + [
 VirtualenvErrorList = [
     {'substr': r'''not found or a compiler error:''', 'level': WARNING},
     {'regex': re.compile('''\d+: error: '''), 'level': ERROR},
     {'regex': re.compile('''\d+: warning: '''), 'level': WARNING},
     {'regex': re.compile(
         r'''Downloading .* \(.*\): *([0-9]+%)? *[0-9\.]+[kmKM]b'''), 'level': DEBUG},
 ] + PythonErrorList
 
+RustErrorList = [
+    {'regex': re.compile(r'''error\[E\d+\]:'''), 'level': ERROR},
+    {'substr': r'''error: Could not compile''', 'level': ERROR},
+    {'substr': r'''error: aborting due to previous error''', 'level': ERROR},
+]
 
 # We may need to have various MakefileErrorLists for differing amounts of
 # warning-ignoring-ness.
-MakefileErrorList = BaseErrorList + PythonErrorList + [
+MakefileErrorList = BaseErrorList + PythonErrorList + RustErrorList + [
     {'substr': r'''No rule to make target ''', 'level': ERROR},
     {'regex': re.compile(r'''akefile.*was not found\.'''), 'level': ERROR},
     {'regex': re.compile(r'''Stop\.$'''), 'level': ERROR},
     {'regex': re.compile(r''':\d+: error:'''), 'level': ERROR},
     {'regex': re.compile(r'''make\[\d+\]: \*\*\* \[.*\] Error \d+'''), 'level': ERROR},
     {'regex': re.compile(r''':\d+: warning:'''), 'level': WARNING},
     {'regex': re.compile(r'''make(?:\[\d+\])?: \*\*\*/'''), 'level': ERROR},
     {'substr': r'''Warning: ''', 'level': WARNING},
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -21,16 +21,17 @@ import glob
 
 # import the power of mozharness ;)
 import sys
 from datetime import datetime
 import re
 from mozharness.base.config import (
     BaseConfig, parse_config_file, DEFAULT_CONFIG_PATH,
 )
+from mozharness.base.errors import MakefileErrorList
 from mozharness.base.log import ERROR, OutputParser, FATAL
 from mozharness.base.script import PostScriptRun
 from mozharness.base.vcs.vcsbase import MercurialScript
 from mozharness.mozilla.automation import (
     AutomationMixin,
     EXIT_STATUS_DICT,
     TBPL_STATUS_DICT,
     TBPL_FAILURE,
@@ -1141,16 +1142,17 @@ or run without that action (ie: --no-{ac
             import subprocess
             return_code = subprocess.call(mach + ['--log-no-times'] + args,
                                           env=env, cwd=dirs['abs_src_dir'])
         else:
             return_code = self.run_command(
                 command=mach + ['--log-no-times'] + args,
                 cwd=dirs['abs_src_dir'],
                 env=env,
+                error_list=MakefileErrorList,
                 output_timeout=self.config.get('max_build_output_timeout',
                                                60 * 40)
             )
 
         if return_code:
             self.return_code = self.worst_level(
                 EXIT_STATUS_DICT[TBPL_FAILURE], self.return_code,
                 AUTOMATION_EXIT_CODES[::-1]
deleted file mode 100644
--- a/testing/web-platform/meta/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js.ini
+++ /dev/null
@@ -1,45 +0,0 @@
-[cors-preflight-not-cors-safelisted.any.html]
-  [Need CORS-preflight for accept/" header]
-    expected: FAIL
-
-  [Need CORS-preflight for accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 header]
-    expected: FAIL
-
-  [Need CORS-preflight for accept-language/\x01 header]
-    expected: FAIL
-
-  [Need CORS-preflight for accept-language/@ header]
-    expected: FAIL
-
-  [Need CORS-preflight for content-language/\x01 header]
-    expected: FAIL
-
-  [Need CORS-preflight for content-language/@ header]
-    expected: FAIL
-
-  [Need CORS-preflight for content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 header]
-    expected: FAIL
-
-
-[cors-preflight-not-cors-safelisted.any.worker.html]
-  [Need CORS-preflight for accept/" header]
-    expected: FAIL
-
-  [Need CORS-preflight for accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 header]
-    expected: FAIL
-
-  [Need CORS-preflight for accept-language/\x01 header]
-    expected: FAIL
-
-  [Need CORS-preflight for accept-language/@ header]
-    expected: FAIL
-
-  [Need CORS-preflight for content-language/\x01 header]
-    expected: FAIL
-
-  [Need CORS-preflight for content-language/@ header]
-    expected: FAIL
-
-  [Need CORS-preflight for content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 header]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/fetch/api/headers/headers-no-cors.window.js.ini
+++ /dev/null
@@ -1,22 +0,0 @@
-[headers-no-cors.window.html]
-  ["no-cors" Headers object cannot have accept/" as header]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 as header]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept-language/\x01 as header]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have accept-language/@ as header]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have content-language/\x01 as header]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have content-language/@ as header]
-    expected: FAIL
-
-  ["no-cors" Headers object cannot have content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 as header]
-    expected: FAIL
-
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -172,16 +172,19 @@ this.CrashManager.prototype = Object.fre
   PROCESS_TYPE_PLUGIN: "plugin",
 
   // A crash in a Gecko media plugin process.
   PROCESS_TYPE_GMPLUGIN: "gmplugin",
 
   // A crash in the GPU process.
   PROCESS_TYPE_GPU: "gpu",
 
+  // A crash in the RDD process.
+  PROCESS_TYPE_RDD: "rdd",
+
   // A real crash.
   CRASH_TYPE_CRASH: "crash",
 
   // A hang.
   CRASH_TYPE_HANG: "hang",
 
   // Submission result values.
   SUBMISSION_RESULT_OK: "ok",
@@ -455,17 +458,18 @@ this.CrashManager.prototype = Object.fre
 
       if (deferred) {
         this._crashPromises.delete(id);
         deferred.resolve();
       }
 
       // Send a telemetry ping for each non-main process crash
       if (processType === this.PROCESS_TYPE_CONTENT ||
-          processType === this.PROCESS_TYPE_GPU) {
+          processType === this.PROCESS_TYPE_GPU ||
+          processType === this.PROCESS_TYPE_RDD) {
         this._sendCrashPing(id, processType, date, metadata);
       }
     })();
 
     return promise;
   },
 
   /**
--- a/toolkit/components/crashes/CrashService.js
+++ b/toolkit/components/crashes/CrashService.js
@@ -174,16 +174,19 @@ CrashService.prototype = Object.freeze({
       processType = Services.crashmanager.PROCESS_TYPE_PLUGIN;
       break;
     case Ci.nsICrashService.PROCESS_TYPE_GMPLUGIN:
       processType = Services.crashmanager.PROCESS_TYPE_GMPLUGIN;
       break;
     case Ci.nsICrashService.PROCESS_TYPE_GPU:
       processType = Services.crashmanager.PROCESS_TYPE_GPU;
       break;
+    case Ci.nsICrashService.PROCESS_TYPE_RDD:
+      processType = Services.crashmanager.PROCESS_TYPE_RDD;
+      break;
     default:
       throw new Error("Unrecognized PROCESS_TYPE: " + processType);
     }
 
     let allThreads = false;
 
     switch (crashType) {
     case Ci.nsICrashService.CRASH_TYPE_CRASH:
--- a/toolkit/components/crashes/nsICrashService.idl
+++ b/toolkit/components/crashes/nsICrashService.idl
@@ -21,12 +21,13 @@ interface nsICrashService : nsISupports
    */
   Promise addCrash(in long processType, in long crashType, in AString id);
 
   const long PROCESS_TYPE_MAIN = 0;
   const long PROCESS_TYPE_CONTENT = 1;
   const long PROCESS_TYPE_PLUGIN = 2;
   const long PROCESS_TYPE_GMPLUGIN = 3;
   const long PROCESS_TYPE_GPU = 4;
+  const long PROCESS_TYPE_RDD = 5;
 
   const long CRASH_TYPE_CRASH = 0;
   const long CRASH_TYPE_HANG = 1;
 };
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -389,24 +389,26 @@ add_task(async function test_addCrash() 
   await m.addCrash(m.PROCESS_TYPE_PLUGIN, m.CRASH_TYPE_CRASH,
                    "plugin-crash", DUMMY_DATE);
   await m.addCrash(m.PROCESS_TYPE_PLUGIN, m.CRASH_TYPE_HANG,
                    "plugin-hang", DUMMY_DATE);
   await m.addCrash(m.PROCESS_TYPE_GMPLUGIN, m.CRASH_TYPE_CRASH,
                    "gmplugin-crash", DUMMY_DATE);
   await m.addCrash(m.PROCESS_TYPE_GPU, m.CRASH_TYPE_CRASH,
                    "gpu-crash", DUMMY_DATE);
+  await m.addCrash(m.PROCESS_TYPE_RDD, m.CRASH_TYPE_CRASH,
+                   "rdd-crash", DUMMY_DATE);
 
   await m.addCrash(m.PROCESS_TYPE_MAIN, m.CRASH_TYPE_CRASH,
                    "changing-item", DUMMY_DATE);
   await m.addCrash(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_HANG,
                    "changing-item", DUMMY_DATE_2);
 
   crashes = await m.getCrashes();
-  Assert.equal(crashes.length, 9);
+  Assert.equal(crashes.length, 10);
 
   let map = new Map(crashes.map(crash => [crash.id, crash]));
 
   let crash = map.get("main-crash");
   Assert.ok(!!crash);
   Assert.equal(crash.crashDate, DUMMY_DATE);
   Assert.equal(crash.type, m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH);
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_MAIN, m.CRASH_TYPE_CRASH));
@@ -448,28 +450,35 @@ add_task(async function test_addCrash() 
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_GMPLUGIN, m.CRASH_TYPE_CRASH));
 
   crash = map.get("gpu-crash");
   Assert.ok(!!crash);
   Assert.equal(crash.crashDate, DUMMY_DATE);
   Assert.equal(crash.type, m.PROCESS_TYPE_GPU + "-" + m.CRASH_TYPE_CRASH);
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_GPU, m.CRASH_TYPE_CRASH));
 
+  crash = map.get("rdd-crash");
+  Assert.ok(!!crash);
+  Assert.equal(crash.crashDate, DUMMY_DATE);
+  Assert.equal(crash.type, m.PROCESS_TYPE_RDD + "-" + m.CRASH_TYPE_CRASH);
+  Assert.ok(crash.isOfType(m.PROCESS_TYPE_RDD, m.CRASH_TYPE_CRASH));
+
   crash = map.get("changing-item");
   Assert.ok(!!crash);
   Assert.equal(crash.crashDate, DUMMY_DATE_2);
   Assert.equal(crash.type, m.PROCESS_TYPE_CONTENT + "-" + m.CRASH_TYPE_HANG);
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_CONTENT, m.CRASH_TYPE_HANG));
 });
 
 add_task(async function test_child_process_crash_ping() {
   let m = await getManager();
   const EXPECTED_PROCESSES = [
     m.PROCESS_TYPE_CONTENT,
     m.PROCESS_TYPE_GPU,
+    m.PROCESS_TYPE_RDD,
   ];
 
   const UNEXPECTED_PROCESSES = [
     m.PROCESS_TYPE_PLUGIN,
     m.PROCESS_TYPE_GMPLUGIN,
     null,
     12, // non-string process type
   ];
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
@@ -17,16 +17,17 @@ const DUMMY_DATE_2 = new Date(Date.now()
 DUMMY_DATE_2.setMilliseconds(0);
 
 const {
   PROCESS_TYPE_MAIN,
   PROCESS_TYPE_CONTENT,
   PROCESS_TYPE_PLUGIN,
   PROCESS_TYPE_GMPLUGIN,
   PROCESS_TYPE_GPU,
+  PROCESS_TYPE_RDD,
   CRASH_TYPE_CRASH,
   CRASH_TYPE_HANG,
   SUBMISSION_RESULT_OK,
   SUBMISSION_RESULT_FAILED,
 } = CrashManager.prototype;
 
 var STORE_DIR_COUNT = 0;
 
@@ -348,40 +349,68 @@ add_task(async function test_add_gpu_cra
     s.addCrash(PROCESS_TYPE_GPU, CRASH_TYPE_CRASH, "id1", new Date())
   );
   Assert.equal(s.crashesCount, 2);
 
   let crashes = s.getCrashesOfType(PROCESS_TYPE_GPU, CRASH_TYPE_CRASH);
   Assert.equal(crashes.length, 2);
 });
 
+add_task(async function test_add_rdd_crash() {
+  let s = await getStore();
+
+  Assert.ok(
+    s.addCrash(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH, "id1", new Date())
+  );
+  Assert.equal(s.crashesCount, 1);
+
+  let c = s.crashes[0];
+  Assert.ok(c.crashDate);
+  Assert.equal(c.type, PROCESS_TYPE_RDD + "-" + CRASH_TYPE_CRASH);
+  Assert.ok(c.isOfType(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH));
+
+  Assert.ok(
+    s.addCrash(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH, "id2", new Date())
+  );
+  Assert.equal(s.crashesCount, 2);
+
+  Assert.ok(
+    s.addCrash(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH, "id1", new Date())
+  );
+  Assert.equal(s.crashesCount, 2);
+
+  let crashes = s.getCrashesOfType(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH);
+  Assert.equal(crashes.length, 2);
+});
+
 add_task(async function test_add_mixed_types() {
   let s = await getStore();
 
   Assert.ok(
     s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "mcrash", new Date()) &&
     s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_HANG, "mhang", new Date()) &&
     s.addCrash(PROCESS_TYPE_CONTENT, CRASH_TYPE_CRASH, "ccrash", new Date()) &&
     s.addCrash(PROCESS_TYPE_CONTENT, CRASH_TYPE_HANG, "chang", new Date()) &&
     s.addCrash(PROCESS_TYPE_PLUGIN, CRASH_TYPE_CRASH, "pcrash", new Date()) &&
     s.addCrash(PROCESS_TYPE_PLUGIN, CRASH_TYPE_HANG, "phang", new Date()) &&
     s.addCrash(PROCESS_TYPE_GMPLUGIN, CRASH_TYPE_CRASH, "gmpcrash", new Date()) &&
-    s.addCrash(PROCESS_TYPE_GPU, CRASH_TYPE_CRASH, "gpucrash", new Date())
+    s.addCrash(PROCESS_TYPE_GPU, CRASH_TYPE_CRASH, "gpucrash", new Date()) &&
+    s.addCrash(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH, "rddcrash", new Date())
   );
 
-  Assert.equal(s.crashesCount, 8);
+  Assert.equal(s.crashesCount, 9);
 
   await s.save();
 
   s._data.crashes.clear();
   Assert.equal(s.crashesCount, 0);
 
   await s.load();
 
-  Assert.equal(s.crashesCount, 8);
+  Assert.equal(s.crashesCount, 9);
 
   let crashes = s.getCrashesOfType(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH);
   Assert.equal(crashes.length, 1);
   crashes = s.getCrashesOfType(PROCESS_TYPE_MAIN, CRASH_TYPE_HANG);
   Assert.equal(crashes.length, 1);
   crashes = s.getCrashesOfType(PROCESS_TYPE_CONTENT, CRASH_TYPE_CRASH);
   Assert.equal(crashes.length, 1);
   crashes = s.getCrashesOfType(PROCESS_TYPE_CONTENT, CRASH_TYPE_HANG);
@@ -389,16 +418,18 @@ add_task(async function test_add_mixed_t
   crashes = s.getCrashesOfType(PROCESS_TYPE_PLUGIN, CRASH_TYPE_CRASH);
   Assert.equal(crashes.length, 1);
   crashes = s.getCrashesOfType(PROCESS_TYPE_PLUGIN, CRASH_TYPE_HANG);
   Assert.equal(crashes.length, 1);
   crashes = s.getCrashesOfType(PROCESS_TYPE_GMPLUGIN, CRASH_TYPE_CRASH);
   Assert.equal(crashes.length, 1);
   crashes = s.getCrashesOfType(PROCESS_TYPE_GPU, CRASH_TYPE_CRASH);
   Assert.equal(crashes.length, 1);
+  crashes = s.getCrashesOfType(PROCESS_TYPE_RDD, CRASH_TYPE_CRASH);
+  Assert.equal(crashes.length, 1);
 });
 
 // Crashes added beyond the high water mark behave properly.
 add_task(async function test_high_water() {
   let s = await getStore();
 
   let d1 = new Date(2014, 0, 1, 0, 0, 0);
   let d2 = new Date(2014, 0, 2, 0, 0, 0);
--- a/toolkit/components/normandy/lib/PrefUtils.jsm
+++ b/toolkit/components/normandy/lib/PrefUtils.jsm
@@ -1,17 +1,23 @@
 /* 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";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "LogManager", "resource://normandy/lib/LogManager.jsm");
 
 var EXPORTED_SYMBOLS = ["PrefUtils"];
 
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  return LogManager.getLogger("preference-experiments");
+});
+
 const kPrefBranches = {
   user: Services.prefs,
   default: Services.prefs.getDefaultBranch(""),
 };
 
 var PrefUtils = {
   /**
    * Get a preference from the named branch
@@ -89,19 +95,12 @@ var PrefUtils = {
    * Remove a preference from a branch.
    * @param {string} branchName One of "default" or "user"
    * @param {string} pref
    */
   clearPref(branchName, pref) {
     if (branchName === "user") {
       kPrefBranches.user.clearUserPref(pref);
     } else if (branchName === "default") {
-      // deleteBranch will affect the user branch as well. Get the user-branch
-      // value, and re-set it after clearing the pref.
-      const hadUserValue = Services.prefs.prefHasUserValue(pref);
-      const originalValue = this.getPref("user", pref, null);
-      kPrefBranches.default.deleteBranch(pref);
-      if (hadUserValue) {
-        this.setPref(branchName, pref, originalValue);
-      }
+      log.warn(`Cannot not reset pref ${pref} on the default branch. Pref will be cleared at next restart.`);
     }
   },
 };
--- a/toolkit/components/normandy/lib/PreferenceExperiments.jsm
+++ b/toolkit/components/normandy/lib/PreferenceExperiments.jsm
@@ -211,17 +211,19 @@ var PreferenceExperiments = {
    * This is needed because the default branch does not persist between Firefox
    * restarts. To compensate for that, Normandy sets the default branch to the
    * experiment values again every startup. The values to set the preferences
    * to are stored in user-branch preferences because preferences have minimal
    * impact on the performance of startup.
    */
   async saveStartupPrefs() {
     const prefBranch = Services.prefs.getBranch(STARTUP_EXPERIMENT_PREFS_BRANCH);
-    prefBranch.deleteBranch("");
+    for (const pref of prefBranch.getChildList("")) {
+      prefBranch.clearUserPref(pref);
+    }
 
     // Filter out non-default-branch experiments (user-branch), because they
     // don't need to be set on the default branch during early startup. Doing so
     // would make the user branch and the default branch the same, which would
     // cause the user branch to not be saved, and the user branch preference
     // would be erased.
     const defaultBranchExperiments = (await this.getAllActive()).filter(exp => exp.preferenceBranchType === "default");
     for (const {preferenceName, preferenceValue} of defaultBranchExperiments) {
@@ -515,22 +517,22 @@ var PreferenceExperiments = {
       const preferences = PreferenceBranchType[preferenceBranchType];
 
       if (previousPreferenceValue !== null) {
         setPref(preferences, preferenceName, preferenceType, previousPreferenceValue);
       } else if (preferenceBranchType === "user") {
         // Remove the "user set" value (which Shield set), but leave the default intact.
         preferences.clearUserPref(preferenceName);
       } else {
-        // Remove both the user and default branch preference. This
-        // is ok because we only do this when studies expire, not
-        // when users actively leave a study by changing the
-        // preference, so there should not be a user branch value at
-        // this point.
-        Services.prefs.getDefaultBranch("").deleteBranch(preferenceName);
+        log.warn(
+          `Can't revert pref for experiment ${experimentName} because it had no default value. `
+          + `Preference will be reset at the next restart.`
+        );
+        // It would seem that Services.prefs.deleteBranch() could be used for
+        // this, but in Normandy's case it does not work. See bug 1502410.
       }
     }
 
     experiment.expired = true;
     store.saveSoon();
 
     TelemetryEnvironment.setExperimentInactive(experimentName);
     TelemetryEvents.sendEvent("unenroll", "preference_study", experimentName, {
--- a/toolkit/components/normandy/lib/PreferenceRollouts.jsm
+++ b/toolkit/components/normandy/lib/PreferenceRollouts.jsm
@@ -220,17 +220,19 @@ var PreferenceRollouts = {
   },
 
   /**
    * Save in-progress preference rollouts in a sub-branch of the normandy prefs.
    * On startup, we read these to set the rollout values.
    */
   async saveStartupPrefs() {
     const prefBranch = Services.prefs.getBranch(STARTUP_PREFS_BRANCH);
-    prefBranch.deleteBranch("");
+    for (const pref of prefBranch.getChildList("")) {
+      prefBranch.clearUserPref(pref);
+    }
 
     for (const rollout of await this.getAllActive()) {
       for (const prefSpec of rollout.preferences) {
         PrefUtils.setPref("user", STARTUP_PREFS_BRANCH + prefSpec.preferenceName, prefSpec.value);
       }
     }
   },
 };
--- a/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
+++ b/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
@@ -1072,17 +1072,17 @@ decorate_task(
     // Now stop the experiment. It should remove the preference
     await PreferenceExperiments.stop("test");
     is(
       Services.prefs.getCharPref(prefName, "DEFAULT"),
       "DEFAULT",
       "Preference should be absent",
     );
   },
-);
+).skip(/* bug 1502410 and bug 1505941 */);
 
 // stop should pass "unknown" to telemetry event for `reason` if none is specified
 decorate_task(
   withMockExperiments([experimentFactory({ name: "test", preferenceName: "fake.preference" })]),
   withMockPreferences,
   withStub(PreferenceExperiments, "stopObserver"),
   withSendEventStub,
   async function testStopUnknownReason(experiments, mockPreferences, stopObserverStub, sendEventStub) {
--- a/toolkit/components/normandy/test/browser/browser_actions_PreferenceRolloutAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_PreferenceRolloutAction.js
@@ -106,17 +106,18 @@ decorate_task(
       {preferenceName: "test.pref2", value: 2},
       // pref3 is added
       {preferenceName: "test.pref3", value: 2},
     ];
     action = new PreferenceRolloutAction();
     await action.runRecipe(recipe);
     await action.finalize();
 
-    is(Services.prefs.getPrefType("test.pref1"), Services.prefs.PREF_INVALID, "pref1 should be removed");
+    /* Todo because of bug 1502410 and bug 1505941 */
+    todo_is(Services.prefs.getPrefType("test.pref1"), Services.prefs.PREF_INVALID, "pref1 should be removed");
     is(Services.prefs.getIntPref("test.pref2"), 2, "pref2 should be updated");
     is(Services.prefs.getIntPref("test.pref3"), 2, "pref3 should be added");
 
     is(Services.prefs.getPrefType(
       "app.normandy.startupRolloutPrefs.test.pref1"),
       Services.prefs.PREF_INVALID,
       "startup pref1 should be removed",
     );
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6778,16 +6778,25 @@
     "description": "The input method the user used to select a result in the searchbar. 'enter' => The user hit the Enter key without choosing a result in the popup. 'enterSelection' => The user chose a result and then hit the Enter key. 'click' => The user clicked a result with the mouse."
   },
   "INNERWINDOWS_WITH_MUTATION_LISTENERS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Deleted or to-be-reused innerwindow which has had mutation event listeners."
   },
+  "INNERWINDOWS_WITH_TEXT_EVENT_LISTENERS": {
+    "record_in_processes": ["content"],
+    "alert_emails": ["mnakano@mozilla.com"],
+    "bug_numbers": [1506434,1288640],
+    "expires_in_version": "67",
+    "kind": "boolean",
+    "releaseChannelCollection": "opt-out",
+    "description": "Deleted or to-be-reused innerwindow which has had text event listeners in the default group."
+  },
   "CHARSET_OVERRIDE_SITUATION": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 8,
     "description": "Labeling status of top-level page when overriding charset (0: unlabeled file URL without detection, 1: unlabeled non-TLD-guessed non-file URL without detection, 2: unlabeled file URL with detection, 3: unlabeled non-file URL with detection, 4: labeled, 5: already overridden, 6: bug, 7: unlabeled with TLD guessing)"
   },
   "CHARSET_OVERRIDE_USED": {
@@ -11471,17 +11480,19 @@
       "main-hang",
       "content-crash",
       "content-hang",
       "plugin-crash",
       "plugin-hang",
       "gmplugin-crash",
       "gmplugin-hang",
       "gpu-crash",
-      "gpu-hang"
+      "gpu-hang",
+      "rdd-crash",
+      "rdd-hang"
     ],
     "releaseChannelCollection": "opt-out",
     "description": "An attempt to submit a crash. Keyed on the CrashManager Crash.type."
   },
   "PROCESS_CRASH_SUBMIT_SUCCESS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "boolean",
--- a/toolkit/components/telemetry/app/TelemetryController.jsm
+++ b/toolkit/components/telemetry/app/TelemetryController.jsm
@@ -1105,26 +1105,16 @@ var Impl = {
       });
     } catch (ex) {
       this._log.error(`registerScalarProbes - there was an error loading ${scalarProbeFilename}`,
                       ex);
     }
 
     // Register the builtin probes.
     for (let category in scalarJSProbes) {
-      // Expire the expired scalars
-      for (let name in scalarJSProbes[category]) {
-        let def = scalarJSProbes[category][name];
-        if (!def || !def.expires || def.expires == "never" || def.expires == "default") {
-          continue;
-        }
-        if (Services.vc.compare(AppConstants.MOZ_APP_VERSION, def.expires) >= 0) {
-          def.expired = true;
-        }
-      }
       Telemetry.registerBuiltinScalars(category, scalarJSProbes[category]);
     }
   },
 
   async registerEventProbes() {
     this._log.trace("registerEventProbes - registering builtin JS Event probes");
 
     // Load the event probes JSON file.
@@ -1135,21 +1125,12 @@ var Impl = {
       eventJSProbes = JSON.parse(fileContent);
     } catch (ex) {
       this._log.error(`registerEventProbes - there was an error loading ${eventProbeFilename}`,
                       ex);
     }
 
     // Register the builtin probes.
     for (let category in eventJSProbes) {
-      for (let name in eventJSProbes[category]) {
-        let def = eventJSProbes[category][name];
-        if (!def || !def.expires || def.expires == "never" || def.expires == "default") {
-          continue;
-        }
-        if (Services.vc.compare(AppConstants.MOZ_APP_VERSION, def.expires) >= 0) {
-          def.expired = true;
-        }
-      }
       Telemetry.registerBuiltinEvents(category, eventJSProbes[category]);
     }
   },
 };
--- a/toolkit/components/telemetry/build_scripts/gen_event_data.py
+++ b/toolkit/components/telemetry/build_scripts/gen_event_data.py
@@ -155,17 +155,16 @@ def generate_JSON_definitions(output, *f
 
         event_definitions[category][event.name] = OrderedDict({
             'methods': event.methods,
             'objects': event.objects,
             'extra_keys': event.extra_keys,
             'record_on_release': True if event.dataset_short == 'opt-out' else False,
             # We don't expire dynamic-builtin scalars: they're only meant for
             # use in local developer builds anyway. They will expire when rebuilding.
-            'expires': event.expiry_version,
             'expired': False,
         })
 
     json.dump(event_definitions, output)
 
 
 def main(output, *filenames):
     # Load the event data.
--- a/toolkit/components/telemetry/build_scripts/gen_scalar_data.py
+++ b/toolkit/components/telemetry/build_scripts/gen_scalar_data.py
@@ -142,17 +142,16 @@ def generate_JSON_definitions(output, *f
 
         scalar_definitions[category][scalar.name] = OrderedDict({
             'kind': scalar.nsITelemetry_kind,
             'keyed': scalar.keyed,
             'record_on_release': True if scalar.dataset_short == 'opt-out' else False,
             # We don't expire dynamic-builtin scalars: they're only meant for
             # use in local developer builds anyway. They will expire when rebuilding.
             'expired': False,
-            'expires': scalar.expires,
         })
 
     json.dump(scalar_definitions, output)
 
 
 def main(output, *filenames):
     # Load the scalars first.
     scalars = parse_scalar_definitions(filenames)
--- a/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py
+++ b/toolkit/components/telemetry/tests/python/test_gen_event_data_json.py
@@ -47,27 +47,25 @@ with.optin:
       message: a message 2
         """
 
         EXPECTED_JSON = {
             "with.optout": {
                 "testme1": {
                     "objects": ["test1"],
                     "expired": False,
-                    "expires": "never",
                     "methods": ["testme1"],
                     "extra_keys": ["message"],
                     "record_on_release": True
                 }
             },
             "with.optin": {
                 "testme2": {
                     "objects": ["test2"],
                     "expired": False,
-                    "expires": "never",
                     "methods": ["testme2"],
                     "extra_keys": ["message"],
                     "record_on_release": False
                 }
             },
         }
 
         io = StringIO()
--- a/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py
+++ b/toolkit/components/telemetry/tests/python/test_gen_scalar_data_json.py
@@ -45,24 +45,22 @@ newscalar:
     keyed: false
         """
 
         EXPECTED_JSON = {
             "newscalar": {
                 "withoptout": {
                     "kind": "nsITelemetry::SCALAR_TYPE_STRING",
                     "expired": False,
-                    "expires": "never",
                     "record_on_release": True,
                     "keyed": False
                 },
                 "withoptin": {
                     "kind": "nsITelemetry::SCALAR_TYPE_COUNT",
                     "expired": False,
-                    "expires": "never",
                     "record_on_release": False,
                     "keyed": False
                 }
             }
         }
 
         io = StringIO()
         try:
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryChildEvents_buildFaster.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryChildEvents_buildFaster.js
@@ -51,29 +51,23 @@ add_task(async function test_setup() {
   // Enable recording for the test event category.
 
   // Register some dynamic builtin test events.
   Telemetry.registerBuiltinEvents(TEST_EVENT_NAME, {
     "dynamic": {
       methods: ["dynamic", "child"],
       objects: ["builtin", "anotherone"],
     },
-    "dynamic_expired": {
-      methods: ["check"],
-      objects: ["expiry"],
-      expired: true,
-    },
   });
   Telemetry.setEventRecordingEnabled(TEST_STATIC_EVENT_NAME, true);
   Telemetry.setEventRecordingEnabled(TEST_EVENT_NAME, true);
 
   Telemetry.recordEvent(TEST_EVENT_NAME, "dynamic", "builtin");
   Telemetry.recordEvent(TEST_STATIC_EVENT_NAME, "main_and_content", "object1");
   Telemetry.recordEvent(TEST_EVENT_NAME, "dynamic", "anotherone");
-  Telemetry.recordEvent(TEST_EVENT_NAME, "check", "expiry");
 
   // Run test in child, don't wait for it to finish: just wait for the
   // MESSAGE_CHILD_TEST_DONE.
   run_test_in_child("test_TelemetryChildEvents_buildFaster.js");
   await do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
 
   // Once events are set by the content process, they don't immediately get
   // sent to the parent process. Wait for the Telemetry IPC Timer to trigger
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEvents_buildFaster.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents_buildFaster.js
@@ -46,91 +46,59 @@ add_task({
   }, async function test_dynamicBuiltin() {
   const DYNAMIC_EVENT_SPEC =  {
     "telemetry.test.builtin": {
       "test": {
         "objects": [
           "object1",
           "object2",
         ],
-        "expires": "never",
+        "expired": false,
         "methods": [
           "test1",
           "test2",
         ],
         "extra_keys": [
           "key2",
           "key1",
         ],
         "record_on_release": false,
       },
     },
-    // Test a new, expired event
-    "telemetry.test.expired": {
-      "expired": {
-        "objects": ["object1"],
-        "methods": ["method1"],
-        "expires": AppConstants.MOZ_APP_VERSION,
-        "record_on_release": false,
-      },
-    },
-    // Test overwriting static expiries
-    "telemetry.test": {
-      "expired_version": {
-        "objects": ["object1"],
-        "methods": ["expired_version"],
-        "expires": "never",
-        "record_on_release": false,
-      },
-      "not_expired_optout": {
-        "objects": ["object1"],
-        "methods": ["not_expired_optout"],
-        "expires": AppConstants.MOZ_APP_VERSION,
-        "record_on_release": true,
-      },
-    },
   };
 
   Telemetry.clearEvents();
 
   // Let's write to the definition file to also cover the file
   // loading part.
   const FILE_PATH = getDefinitionsPath();
   await CommonUtils.writeJSON(DYNAMIC_EVENT_SPEC, FILE_PATH);
 
   // Start TelemetryController to trigger loading the specs.
   await TelemetryController.testReset();
   await TelemetryController.testPromiseJsProbeRegistration();
 
   // Record the events
   const TEST_EVENT_NAME = "telemetry.test.builtin";
-  const DYNAMIC_EVENT_CATEGORY = "telemetry.test.expired";
-  const STATIC_EVENT_CATEGORY = "telemetry.test";
   Telemetry.setEventRecordingEnabled(TEST_EVENT_NAME, true);
-  Telemetry.setEventRecordingEnabled(DYNAMIC_EVENT_CATEGORY, true);
-  Telemetry.setEventRecordingEnabled(STATIC_EVENT_CATEGORY, true);
   Telemetry.recordEvent(TEST_EVENT_NAME, "test1", "object1");
   Telemetry.recordEvent(TEST_EVENT_NAME, "test2", "object1", null,
                         {"key1": "foo", "key2": "bar"});
   Telemetry.recordEvent(TEST_EVENT_NAME, "test2", "object2", null,
                         {"key2": "bar"});
-  Telemetry.recordEvent(DYNAMIC_EVENT_CATEGORY, "method1", "object1");
-  Telemetry.recordEvent(STATIC_EVENT_CATEGORY, "expired_version", "object1");
-  Telemetry.recordEvent(STATIC_EVENT_CATEGORY, "not_expired_optout", "object1");
 
   // Check the values we tried to store.
   const snapshot =
     Telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   Assert.ok(("parent" in snapshot), "Should have parent events in the snapshot.");
 
   let expected = [
     [TEST_EVENT_NAME, "test1", "object1"],
     [TEST_EVENT_NAME, "test2", "object1", null, {key1: "foo", key2: "bar"}],
     [TEST_EVENT_NAME, "test2", "object2", null, {key2: "bar"}],
-    [STATIC_EVENT_CATEGORY, "expired_version", "object1"],
   ];
   let events = snapshot.parent;
   Assert.equal(events.length, expected.length, "Should have recorded the right amount of events.");
   for (let i = 0; i < expected.length; ++i) {
     Assert.deepEqual(events[i].slice(1), expected[i],
                      "Should have recorded the expected event data.");
   }
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars_buildFaster.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars_buildFaster.js
@@ -48,29 +48,23 @@ add_task({
     // The test needs to write a file, and that fails in tests on Android.
     // We don't really need the Android coverage, so skip on Android.
     skip_if: () => AppConstants.platform == "android",
   }, async function test_dynamicBuiltin() {
   const DYNAMIC_SCALAR_SPEC =  {
     "telemetry.test": {
       "builtin_dynamic": {
         "kind": "nsITelemetry::SCALAR_TYPE_COUNT",
-        "expires": "never",
+        "expired": false,
         "record_on_release": false,
         "keyed": false,
       },
       "builtin_dynamic_other": {
         "kind": "nsITelemetry::SCALAR_TYPE_BOOLEAN",
-        "expires": "never",
-        "record_on_release": false,
-        "keyed": false,
-      },
-      "builtin_dynamic_expired": {
-        "kind": "nsITelemetry::SCALAR_TYPE_BOOLEAN",
-        "expires": AppConstants.MOZ_APP_VERSION,
+        "expired": false,
         "record_on_release": false,
         "keyed": false,
       },
     },
   };
 
   Telemetry.clearScalars();
 
@@ -81,20 +75,18 @@ add_task({
 
   // Start TelemetryController to trigger loading the specs.
   await TelemetryController.testReset();
   await TelemetryController.testPromiseJsProbeRegistration();
 
   // Store to that scalar.
   const TEST_SCALAR1 = "telemetry.test.builtin_dynamic";
   const TEST_SCALAR2 = "telemetry.test.builtin_dynamic_other";
-  const TEST_SCALAR3 = "telemetry.test.builtin_dynamic_expired";
   Telemetry.scalarSet(TEST_SCALAR1, 3785);
   Telemetry.scalarSet(TEST_SCALAR2, true);
-  Telemetry.scalarSet(TEST_SCALAR3, true);
 
   // Check the values we tried to store.
   const scalars = Telemetry.getSnapshotForScalars("main", false).parent;
 
   // Check that they are serialized to the correct format.
   Assert.equal(typeof(scalars[TEST_SCALAR1]), "number",
                TEST_SCALAR1 + " must be serialized to the correct format.");
   Assert.ok(Number.isInteger(scalars[TEST_SCALAR1]),
--- a/toolkit/crashreporter/CrashAnnotations.yaml
+++ b/toolkit/crashreporter/CrashAnnotations.yaml
@@ -543,18 +543,18 @@ PluginName:
 PluginVersion:
   description: >
     Version of a plugin, only the process holding the plugin has this
     annotation.
   type: string
 
 ProcessType:
   description: >
-    Type of the process that crashed, can hold the values "content", "plugin" or
-    "gpu" currently.
+    Type of the process that crashed, can hold the values "content", "plugin",
+    "gpu" or "rdd" currently.
   type: string
 
 ProductName:
   description: >
     Application name (e.g. Firefox).
   type: string
   ping: true
 
@@ -581,16 +581,21 @@ ProxyStreamUnmarshalStatus:
     the various value this annotation can take.
   type: string
 
 ProxyStreamValid:
   description: >
     Set to "false" when encountering an invalid IPC proxy stream.
   type: string
 
+RDDProcessStatus:
+  description: >
+    Status of the RDD process, can be set to "Running" or "Destroyed"
+  type: string
+
 RecordReplay:
   description: >
     Set to 1 if this crash happened in a Web Replay middleman, recording,
     or replaying process.
   type: boolean
 
 RecordReplayError:
   description: >
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -780,19 +780,20 @@ nsXULAppInfo::GetWidgetToolkit(nsACStrin
 SYNC_ENUMS(DEFAULT, Default)
 SYNC_ENUMS(PLUGIN, Plugin)
 SYNC_ENUMS(CONTENT, Content)
 SYNC_ENUMS(IPDLUNITTEST, IPDLUnitTest)
 SYNC_ENUMS(GMPLUGIN, GMPlugin)
 SYNC_ENUMS(GPU, GPU)
 SYNC_ENUMS(PDFIUM, PDFium)
 SYNC_ENUMS(VR, VR)
+SYNC_ENUMS(RDD, RDD)
 
 // .. and ensure that that is all of them:
-static_assert(GeckoProcessType_VR + 1 == GeckoProcessType_End,
+static_assert(GeckoProcessType_RDD + 1 == GeckoProcessType_End,
               "Did not find the final GeckoProcessType");
 
 NS_IMETHODIMP
 nsXULAppInfo::GetProcessType(uint32_t* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = XRE_GetProcessType();
   return NS_OK;
@@ -5123,16 +5124,22 @@ XRE_GetProcessType()
 
 bool
 XRE_IsGPUProcess()
 {
   return XRE_GetProcessType() == GeckoProcessType_GPU;
 }
 
 bool
+XRE_IsRDDProcess()
+{
+  return XRE_GetProcessType() == GeckoProcessType_RDD;
+}
+
+bool
 XRE_IsVRProcess()
 {
   return XRE_GetProcessType() == GeckoProcessType_VR;
 }
 
 /**
  * Returns true in the e10s parent process and in the main process when e10s
  * is disabled.
@@ -5159,16 +5166,21 @@ bool
 XRE_IsPluginProcess()
 {
   return XRE_GetProcessType() == GeckoProcessType_Plugin;
 }
 
 bool
 XRE_UseNativeEventProcessing()
 {
+#ifdef XP_MACOSX
+  if (XRE_IsRDDProcess()) {
+    return false;
+  }
+#endif
   if (XRE_IsContentProcess()) {
     static bool sInited = false;
     static bool sUseNativeEventProcessing = false;
     if (!sInited) {
       Preferences::AddBoolVarCache(&sUseNativeEventProcessing,
                                    "dom.ipc.useNativeEventProcessing.content");
       sInited = true;
     }
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -54,16 +54,17 @@
 #include "chrome/common/child_process.h"
 #if defined(MOZ_WIDGET_ANDROID)
 #include "chrome/common/ipc_channel.h"
 #include "mozilla/jni/Utils.h"
 #endif //  defined(MOZ_WIDGET_ANDROID)
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/FilePreferences.h"
+#include "mozilla/RDDProcessImpl.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/IOThreadChild.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/recordreplay/ChildIPC.h"
 #include "mozilla/recordreplay/ParentIPC.h"
 #include "ScopedXREEmbed.h"
@@ -620,17 +621,18 @@ XRE_InitChildProcess(int aArgc,
   MOZ_ASSERT(parentPIDString, "NULL parent PID");
   --aArgc;
 
   char* end = 0;
   base::ProcessId parentPID = strtol(parentPIDString, &end, 10);
   MOZ_ASSERT(!*end, "invalid parent PID");
 
   nsCOMPtr<nsIFile> crashReportTmpDir;
-  if (XRE_GetProcessType() == GeckoProcessType_GPU) {
+  if (XRE_GetProcessType() == GeckoProcessType_GPU ||
+      XRE_GetProcessType() == GeckoProcessType_RDD) {
     aArgc--;
     if (strlen(aArgv[aArgc])) { // if it's empty, ignore it
       nsresult rv = XRE_GetFileFromPath(aArgv[aArgc], getter_AddRefs(crashReportTmpDir));
       if (NS_FAILED(rv)) {
         // If we don't have a valid tmp dir we can probably still run ok, but
         // crash report .extra files might not get picked up by the parent
         // process. Debug-assert because this shouldn't happen in practice.
         MOZ_ASSERT(false, "GPU process started without valid tmp dir!");
@@ -671,16 +673,17 @@ XRE_InitChildProcess(int aArgc,
     return NS_ERROR_FAILURE;
   }
 
   MessageLoop::Type uiLoopType;
   switch (XRE_GetProcessType()) {
   case GeckoProcessType_Content:
   case GeckoProcessType_GPU:
   case GeckoProcessType_VR:
+  case GeckoProcessType_RDD:
       // Content processes need the XPCOM/chromium frankenventloop
       uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD;
       break;
   case GeckoProcessType_GMPlugin:
   case GeckoProcessType_PDFium:
       uiLoopType = MessageLoop::TYPE_DEFAULT;
       break;
   default:
@@ -742,16 +745,20 @@ XRE_InitChildProcess(int aArgc,
       case GeckoProcessType_GPU:
         process = new gfx::GPUProcessImpl(parentPID);
         break;
 
       case GeckoProcessType_VR:
         process = new gfx::VRProcessChild(parentPID);
         break;
 
+      case GeckoProcessType_RDD:
+        process = new RDDProcessImpl(parentPID);
+        break;
+
       default:
         MOZ_CRASH("Unknown main thread class");
       }
 
       if (!process->Init(aArgc, aArgv)) {
         return NS_ERROR_FAILURE;
       }
 
--- a/widget/cocoa/nsAppShell.mm
+++ b/widget/cocoa/nsAppShell.mm
@@ -309,39 +309,41 @@ nsAppShell::Init()
 
   // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
   // by |this|.  CFArray is used instead of NSArray because NSArray wants to
   // retain each object you add to it, and you can't retain an
   // NSAutoreleasePool.
   mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
   NS_ENSURE_STATE(mAutoreleasePools);
 
-  // Get the path of the nib file, which lives in the GRE location
-  nsCOMPtr<nsIFile> nibFile;
-  nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (XRE_GetProcessType() != GeckoProcessType_RDD) {
+    // Get the path of the nib file, which lives in the GRE location
+    nsCOMPtr<nsIFile> nibFile;
+    nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
+    NS_ENSURE_SUCCESS(rv, rv);
 
-  nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
-  nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
+    nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
+    nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
 
-  nsAutoCString nibPath;
-  rv = nibFile->GetNativePath(nibPath);
-  NS_ENSURE_SUCCESS(rv, rv);
+    nsAutoCString nibPath;
+    rv = nibFile->GetNativePath(nibPath);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-  // This call initializes NSApplication unless:
-  // 1) we're using xre -- NSApp's already been initialized by
-  //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
-  // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
-  //    already been initialized and its main run loop is already running.
-  [NSBundle loadNibFile:
-                     [NSString stringWithUTF8String:(const char*)nibPath.get()]
-      externalNameTable:
-           [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
-                                       forKey:@"NSOwner"]
-               withZone:NSDefaultMallocZone()];
+    // This call initializes NSApplication unless:
+    // 1) we're using xre -- NSApp's already been initialized by
+    //    MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
+    // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
+    //    already been initialized and its main run loop is already running.
+    [NSBundle loadNibFile:
+                       [NSString stringWithUTF8String:(const char*)nibPath.get()]
+        externalNameTable:
+             [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
+                                         forKey:@"NSOwner"]
+                 withZone:NSDefaultMallocZone()];
+  }
 
   mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
   NS_ENSURE_STATE(mDelegate);
 
   // Add a CFRunLoopSource to the main native run loop.  The source is
   // responsible for interrupting the run loop when Gecko events are ready.
 
   mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
@@ -366,17 +368,17 @@ nsAppShell::Init()
 
     if (gfxPlatform::IsHeadless()) {
       screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
     } else {
       screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperCocoa>());
     }
   }
 
-  rv = nsBaseAppShell::Init();
+  nsresult rv = nsBaseAppShell::Init();
 
   if (!gAppShellMethodsSwizzled) {
     // We should only replace the original terminate: method if we're not
     // running in a Cocoa embedder. See bug 604901.
     if (!mRunningCocoaEmbedded) {
       nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
                                 @selector(nsAppShell_NSApplication_terminate:));
     }
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -25,16 +25,17 @@
 #include "nsMemoryInfoDumper.h"
 #endif
 #include "nsNetCID.h"
 #include "nsThread.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReportingProcess.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/RDDProcessManager.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/dom/MemoryReportTypes.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
@@ -1920,16 +1921,22 @@ nsMemoryReporterManager::StartGettingRep
   }
 
   if (gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get()) {
     if (RefPtr<MemoryReportingProcess> proc = gpu->GetProcessMemoryReporter()) {
       s->mChildrenPending.AppendElement(proc.forget());
     }
   }
 
+  if (RDDProcessManager* rdd = RDDProcessManager::Get()) {
+    if (RefPtr<MemoryReportingProcess> proc = rdd->GetProcessMemoryReporter()) {
+      s->mChildrenPending.AppendElement(proc.forget());
+    }
+  }
+
   if (!s->mChildrenPending.IsEmpty()) {
     nsCOMPtr<nsITimer> timer;
     rv = NS_NewTimerWithFuncCallback(
       getter_AddRefs(timer),
       TimeoutCallback,
       this,
       kTimeoutLengthMS,
       nsITimer::TYPE_ONE_SHOT,
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Poison.h"
+#include "mozilla/RemoteDecoderManagerChild.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/XPCOM.h"
 #include "nsXULAppAPI.h"
 
 #ifndef ANDROID
 #include "nsTerminator.h"
 #endif
 
@@ -895,16 +896,17 @@ ShutdownXPCOM(nsIServiceManager* aServMg
 #endif
     }
 
     // This must happen after the shutdown of media and widgets, which
     // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification.
     NS_ProcessPendingEvents(thread);
     gfxPlatform::ShutdownLayersIPC();
     mozilla::dom::VideoDecoderManagerChild::Shutdown();
+    mozilla::RemoteDecoderManagerChild::Shutdown();
 
     mozilla::scache::StartupCache::DeleteSingleton();
     if (observerService)
     {
       mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownThreads);
       observerService->NotifyObservers(nullptr,
                                        NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
                                        nullptr);