Merge autoland to mozilla-central. a=merge
authorNoemi Erli <nerli@mozilla.com>
Fri, 11 Jan 2019 23:53:31 +0200
changeset 453517 0ce024c9151142de96f1009cd2e5a7678281c443
parent 453417 485de31371e04407432d932a039cbeb40fa88727 (current diff)
parent 453516 b5b1f341206d87c70036b2f35f8ae9604e29535f (diff)
child 453520 c1894cbb4e7150a3a4a16e5d0702e038e7bd466f
push id35357
push usernerli@mozilla.com
push dateFri, 11 Jan 2019 21:54:07 +0000
treeherdermozilla-central@0ce024c91511 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.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 autoland to mozilla-central. a=merge
build/autoconf/subconfigure.m4
dom/base/nsContentIterator.cpp
dom/base/nsIContentIterator.h
editor/spellchecker/nsFilteredContentIterator.cpp
editor/spellchecker/nsFilteredContentIterator.h
testing/web-platform/meta/FileAPI/FileReader/__dir__.ini
testing/web-platform/meta/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js.ini
toolkit/locales/en-US/chrome/global/languageNames.properties
toolkit/locales/en-US/chrome/global/regionNames.properties
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1539,17 +1539,17 @@ pref("browser.contentblocking.reportBrea
 #endif
 // Show report breakage for tracking cookies in all channels.
 pref("browser.contentblocking.rejecttrackers.reportBreakage.enabled", true);
 
 pref("browser.contentblocking.reportBreakage.url", "https://tracking-protection-issues.herokuapp.com/new");
 
 pref("browser.contentblocking.introCount", 0);
 
-pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
+pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/content-blocking/start/");
 
 // Workaround for Google Recaptcha
 pref("urlclassifier.trackingAnnotationSkipURLs", "google.com/recaptcha/,*.google.com/recaptcha/");
 
 // Always enable newtab segregation using containers
 pref("privacy.usercontext.about_newtab_segregation.enabled", true);
 // Enable Contextual Identity Containers
 #ifdef NIGHTLY_BUILD
--- a/browser/base/content/aboutNetError-new.xhtml
+++ b/browser/base/content/aboutNetError-new.xhtml
@@ -192,17 +192,17 @@
           <p>&prefReset.longDesc;</p>
           <button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
         </div>
 
         <div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
           <button id="returnButton" class="primary" autocomplete="off" data-telemetry-id="return_button_top">&returnToPreviousPage1.label;</button>
           <button id="openPortalLoginPageButton" class="primary" autocomplete="off">&openPortalLoginPage.label2;</button>
           <button id="errorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
-          <button id="advancedButton" data-telemetry-id="advanced_button" autocomplete="off">&continue2.label;</button>
+          <button id="advancedButton" data-telemetry-id="advanced_button" autocomplete="off">&advanced2.label;</button>
           <button id="moreInformationButton" autocomplete="off">&moreInformation.label;</button>
         </div>
       </div>
 
       <div id="netErrorButtonContainer" class="button-container">
         <button id="errorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
       </div>
 
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -59,19 +59,18 @@ with Files("test/popupNotifications/**")
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("test/popups/**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("test/referrer/**"):
     BUG_COMPONENT = ("Core", "Document Navigation")
 
-# TODO: Update this after bug 1435528 is resolved.
 with Files("test/sanitize/**"):
-    BUG_COMPONENT = ("Firefox", "General")
+    BUG_COMPONENT = ("Toolkit", "Data Sanitization")
 
 with Files("test/siteIdentity/**"):
     BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
 with Files("test/sidebar/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/static/**"):
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1254,17 +1254,17 @@
       <handler event="dblclick"><![CDATA[
         // When the tabbar has an unified appearance with the titlebar
         // and menubar, a double-click in it should have the same behavior
         // as double-clicking the titlebar
         if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
           return;
 
         if (event.button != 0 ||
-            event.originalTarget.localName != "box")
+            event.originalTarget.localName != "scrollbox")
           return;
 
         if (!this._blockDblClick)
           BrowserOpenTab();
 
         event.preventDefault();
       ]]></handler>
 
@@ -1327,17 +1327,17 @@
           return;
         }
 
         if (event.target.localName == "tab") {
           gBrowser.removeTab(event.target, {
             animate: true,
             byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
           });
-        } else if (event.originalTarget.localName == "box") {
+        } else if (event.originalTarget.localName == "scrollbox") {
           // The user middleclicked an open space on the tabstrip. This could
           // be because they intend to open a new tab, but it could also be
           // because they just removed a tab and they now middleclicked on the
           // resulting space while that tab is closing. In that case, we don't
           // want to open a tab. So if we're removing one or more tabs, and
           // the tab click is before the end of the last visible tab, we do
           // nothing.
           if (gBrowser._removingTabs.length) {
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -1,12 +1,13 @@
 /* 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/. */
 
+pref("startup.homepage_override_nightly.20190114", "https://www.mozilla.org/firefox/dedicated-profiles/");
 pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 7200); // 2 hours
 // Give the user x seconds to react before showing the big UI. default=12 hours
 pref("app.update.promptWaitTime", 43200);
 // URL user can browse to manually if for some reason all update installation
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -17,16 +17,21 @@ ChromeUtils.defineModuleGetter(this, "Pr
                                "resource://gre/modules/Preferences.jsm");
 ChromeUtils.defineModuleGetter(this, "PlacesUtils",
                                "resource://gre/modules/PlacesUtils.jsm");
 
 function DistributionCustomizer() {
 }
 
 DistributionCustomizer.prototype = {
+  // These prefixes must only contain characters
+  // allowed by PlacesUtils.isValidGuid
+  BOOKMARK_GUID_PREFIX: "DstB-",
+  FOLDER_GUID_PREFIX: "DstF-",
+
   get _iniFile() {
     // For parallel xpcshell testing purposes allow loading the distribution.ini
     // file from the profile folder through an hidden pref.
     let loadFromProfile = Services.prefs.getBoolPref("distribution.testing.loadFromProfile", false);
 
     let iniFile;
     try {
       iniFile = loadFromProfile ? Services.dirsvc.get("ProfD", Ci.nsIFile)
@@ -142,16 +147,17 @@ DistributionCustomizer.prototype = {
         break;
 
       case "folder":
         if (itemIndex < defaultIndex)
           index = prependIndex++;
 
         let folder = await PlacesUtils.bookmarks.insert({
           type: PlacesUtils.bookmarks.TYPE_FOLDER,
+          guid: PlacesUtils.generateGuidWithPrefix(this.FOLDER_GUID_PREFIX),
           parentGuid, index, title: item.title,
         });
 
         await this._parseBookmarksSection(folder.guid,
                                           "BookmarksFolder-" + item.folderId);
         break;
 
       case "separator":
@@ -180,16 +186,17 @@ DistributionCustomizer.prototype = {
         break;
 
       case "bookmark":
       default:
         if (itemIndex < defaultIndex)
           index = prependIndex++;
 
         await PlacesUtils.bookmarks.insert({
+          guid: PlacesUtils.generateGuidWithPrefix(this.BOOKMARK_GUID_PREFIX),
           parentGuid, index, title: item.title, url: item.link,
         });
 
         if (item.icon && item.iconData) {
           try {
             let faviconURI = Services.io.newURI(item.icon);
             PlacesUtils.favicons.replaceFaviconDataFromDataURL(
               faviconURI, item.iconData, 0,
@@ -199,25 +206,16 @@ DistributionCustomizer.prototype = {
               Services.io.newURI(item.link), faviconURI, false,
               PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
               Services.scriptSecurityManager.getSystemPrincipal());
           } catch (e) {
             Cu.reportError(e);
           }
         }
 
-        if (item.keyword) {
-          try {
-            await PlacesUtils.keywords.insert({ keyword: item.keyword,
-                                                url: item.link });
-          } catch (e) {
-            Cu.reportError(e);
-          }
-        }
-
         break;
       }
     }
   },
 
   _newProfile: false,
   _customizationsApplied: false,
   applyCustomizations: function DIST_applyCustomizations() {
--- a/browser/components/enterprisepolicies/helpers/BookmarksPolicies.jsm
+++ b/browser/components/enterprisepolicies/helpers/BookmarksPolicies.jsm
@@ -190,17 +190,17 @@ async function calculateLists(specifiedB
 
 async function insertBookmark(bookmark) {
   let parentGuid = await getParentGuid(bookmark.Placement,
                                        bookmark.Folder);
 
   await PlacesUtils.bookmarks.insert({
     url: Services.io.newURI(bookmark.URL.href),
     title: bookmark.Title,
-    guid: generateGuidWithPrefix(BookmarksPolicies.BOOKMARK_GUID_PREFIX),
+    guid: PlacesUtils.generateGuidWithPrefix(BookmarksPolicies.BOOKMARK_GUID_PREFIX),
     parentGuid,
   });
 
   if (bookmark.Favicon) {
     await setFaviconForBookmark(bookmark).catch(
       () => log.error(`Error setting favicon for ${bookmark.Title}`));
   }
 }
@@ -241,23 +241,16 @@ async function setFaviconForBookmark(boo
       false, /* forceReload */
       PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
       resolve,
       nullPrincipal
     );
   });
 }
 
-function generateGuidWithPrefix(prefix) {
-  // Generates a random GUID and replace its beginning with the given
-  // prefix. We do this instead of just prepending the prefix to keep
-  // the correct character length.
-  return prefix + PlacesUtils.history.makeGuid().substring(prefix.length);
-}
-
 // Cache of folder names to guids to be used by the getParentGuid
 // function. The name consists in the parentGuid (which should always
 // be the menuGuid or the toolbarGuid) + the folder title. This is to
 // support having the same folder name in both the toolbar and menu.
 XPCOMUtils.defineLazyGetter(this, "gFoldersMapPromise", () => {
   return new Promise(resolve => {
     let foldersMap = new Map();
     return PlacesUtils.bookmarks.fetch(
@@ -285,17 +278,17 @@ async function getParentGuid(placement, 
 
   let foldersMap = await gFoldersMapPromise;
   let folderName = `${parentGuid}|${folderTitle}`;
 
   if (foldersMap.has(folderName)) {
     return foldersMap.get(folderName);
   }
 
-  let guid = generateGuidWithPrefix(BookmarksPolicies.FOLDER_GUID_PREFIX);
+  let guid = PlacesUtils.generateGuidWithPrefix(BookmarksPolicies.FOLDER_GUID_PREFIX);
   await PlacesUtils.bookmarks.insert({
     type: PlacesUtils.bookmarks.TYPE_FOLDER,
     title: folderTitle,
     guid,
     parentGuid,
   });
 
   foldersMap.set(folderName, guid);
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -56,31 +56,47 @@ function resolveURIInternal(aCmdLine, aA
     Cu.reportError(e);
   }
 
   return uri;
 }
 
 var gFirstWindow = false;
 
+function getNormalizedDate() {
+  let pad = num => ("" + num).padStart(2, "0");
+
+  let date = new Date();
+  return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}`;
+}
+
 const OVERRIDE_NONE        = 0;
 const OVERRIDE_NEW_PROFILE = 1;
 const OVERRIDE_NEW_MSTONE  = 2;
 const OVERRIDE_NEW_BUILD_ID = 3;
+const OVERRIDE_NIGHTLY     = 4;
 /**
  * Determines whether a home page override is needed.
  * Returns:
  *  OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
  *  OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
  *                      Gecko milestone (i.e. right after an upgrade).
  *  OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
  *                        same Gecko milestone (i.e. after a nightly upgrade).
  *  OVERRIDE_NONE otherwise.
  */
 function needHomepageOverride(prefb) {
+  if (AppConstants.NIGHTLY_BUILD && !Cu.isInAutomation) {
+    let pref = `startup.homepage_override_nightly.${getNormalizedDate()}`;
+    let url = Services.prefs.getCharPref(pref, "");
+    if (url) {
+      return OVERRIDE_NIGHTLY;
+    }
+  }
+
   var savedmstone = prefb.getCharPref("browser.startup.homepage_override.mstone", "");
 
   if (savedmstone == "ignore")
     return OVERRIDE_NONE;
 
   var mstone = Services.appinfo.platformVersion;
 
   var savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID", "");
@@ -539,16 +555,22 @@ nsBrowserContentHandler.prototype = {
             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
             break;
           case OVERRIDE_NEW_BUILD_ID:
             if (prefb.prefHasUserValue("app.update.postupdate")) {
               // Send the update ping to signal that the update was successful.
               UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
             }
             break;
+          case OVERRIDE_NIGHTLY:
+            // Opens a page on the first startup on a particular day.
+            let pref = `startup.homepage_override_nightly.${getNormalizedDate()}`;
+            overridePage = Services.prefs.getCharPref(pref);
+            Services.prefs.setCharPref(pref, "");
+            break;
         }
       }
     } catch (ex) {}
 
     // formatURLPref might return "about:blank" if getting the pref fails
     if (overridePage == "about:blank")
       overridePage = "";
 
--- a/browser/components/places/tests/unit/distribution.ini
+++ b/browser/components/places/tests/unit/distribution.ini
@@ -4,24 +4,27 @@
 [Global]
 id=516444
 version=1.0
 about=Test distribution file
 
 [BookmarksToolbar]
 item.1.title=Toolbar Link Before
 item.1.link=https://example.org/toolbar/before/
-item.1.keyword=e:t:b
 item.1.icon=https://example.org/favicon.png
 item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==
 item.2.type=default
-item.3.title=Toolbar Link After
-item.3.link=https://example.org/toolbar/after/
-item.3.keyword=e:t:a
+item.3.type=folder
+item.3.title=Toolbar Folder After
+item.3.folderId=1
 
 [BookmarksMenu]
 item.1.title=Menu Link Before
 item.1.link=https://example.org/menu/before/
 item.1.icon=https://example.org/favicon.png
 item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==
 item.2.type=default
 item.3.title=Menu Link After
 item.3.link=https://example.org/menu/after/
+
+[BookmarksFolder-1]
+item.1.title=Toolbar Link Folder
+item.1.link=https://example.org/toolbar/folder/
--- a/browser/components/places/tests/unit/test_browserGlue_distribution.js
+++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js
@@ -42,16 +42,19 @@ registerCleanupFunction(function() {
   iniFile.append("distribution.ini");
   if (iniFile.exists()) {
     iniFile.remove(false);
   }
   Assert.ok(!iniFile.exists());
 });
 
 add_task(async function() {
+  let {DistributionCustomizer} = ChromeUtils.import("resource:///modules/distribution.js", {});
+  let distribution = new DistributionCustomizer();
+
   let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
   // Initialize Places through the History Service and check that a new
   // database has been created.
   Assert.equal(PlacesUtils.history.databaseStatus,
                PlacesUtils.history.DATABASE_STATUS_CREATE);
   // Force distribution.
   glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
 
@@ -59,62 +62,55 @@ add_task(async function() {
   await promiseTopicObserved(TOPIC_CUSTOMIZATION_COMPLETE);
 
   // Check the custom bookmarks exist on menu.
   let menuItem = await PlacesUtils.bookmarks.fetch({
     parentGuid: PlacesUtils.bookmarks.menuGuid,
     index: 0,
   });
   Assert.equal(menuItem.title, "Menu Link Before");
+  Assert.ok(menuItem.guid.startsWith(distribution.BOOKMARK_GUID_PREFIX),
+    "Guid of this bookmark has expected prefix");
 
   menuItem = await PlacesUtils.bookmarks.fetch({
     parentGuid: PlacesUtils.bookmarks.menuGuid,
     index: 1 + DEFAULT_BOOKMARKS_ON_MENU,
   });
   Assert.equal(menuItem.title, "Menu Link After");
 
-  // Check no favicon or keyword exists for this bookmark
+  // Check no favicon exists for this bookmark
   await Assert.rejects(waitForResolvedPromise(() => {
     return PlacesUtils.promiseFaviconData(menuItem.url.href);
   }, "Favicon not found", 10), /Favicon\snot\sfound/, "Favicon not found");
 
-  let keywordItem = await PlacesUtils.keywords.fetch({
-    url: menuItem.url.href,
-  });
-  Assert.strictEqual(keywordItem, null);
-
   // Check the custom bookmarks exist on toolbar.
   let toolbarItem = await PlacesUtils.bookmarks.fetch({
     parentGuid: PlacesUtils.bookmarks.toolbarGuid,
     index: 0,
   });
   Assert.equal(toolbarItem.title, "Toolbar Link Before");
 
-  // Check the custom favicon and keyword exist for this bookmark
+  // Check the custom favicon exist for this bookmark
   let faviconItem = await waitForResolvedPromise(() => {
     return PlacesUtils.promiseFaviconData(toolbarItem.url.href);
   }, "Favicon not found", 10);
   Assert.equal(faviconItem.uri.spec, "https://example.org/favicon.png");
   Assert.greater(faviconItem.dataLen, 0);
   Assert.equal(faviconItem.mimeType, "image/png");
 
   let base64Icon = "data:image/png;base64," +
       base64EncodeString(String.fromCharCode.apply(String, faviconItem.data));
   Assert.equal(base64Icon, SMALLPNG_DATA_URI.spec);
 
-  keywordItem = await PlacesUtils.keywords.fetch({
-    url: toolbarItem.url.href,
-  });
-  Assert.notStrictEqual(keywordItem, null);
-  Assert.equal(keywordItem.keyword, "e:t:b");
-
   toolbarItem = await PlacesUtils.bookmarks.fetch({
     parentGuid: PlacesUtils.bookmarks.toolbarGuid,
     index: 1 + DEFAULT_BOOKMARKS_ON_TOOLBAR,
   });
-  Assert.equal(toolbarItem.title, "Toolbar Link After");
+  Assert.equal(toolbarItem.title, "Toolbar Folder After");
+  Assert.ok(toolbarItem.guid.startsWith(distribution.FOLDER_GUID_PREFIX),
+    "Guid of this folder has expected prefix");
 
   // Check the bmprocessed pref has been created.
   Assert.ok(Services.prefs.getBoolPref(PREF_BMPROCESSED));
 
   // Check distribution prefs have been created.
   Assert.equal(Services.prefs.getCharPref(PREF_DISTRIBUTION_ID), "516444");
 });
--- a/browser/config/mozconfigs/win64-aarch64/common-win64
+++ b/browser/config/mozconfigs/win64-aarch64/common-win64
@@ -1,7 +1,4 @@
 # This file is used by all AArch64 Win64 builds
 
 ac_add_options --target=aarch64-windows-mingw32
 ac_add_options --host=x86_64-pc-mingw32
-
-# Accessibility doesn't work.
-ac_add_options --disable-accessibility
--- a/browser/extensions/formautofill/FormAutofill.jsm
+++ b/browser/extensions/formautofill/FormAutofill.jsm
@@ -70,11 +70,19 @@ XPCOMUtils.defineLazyPreferenceGetter(Fo
 XPCOMUtils.defineLazyPreferenceGetter(FormAutofill,
                                       "isAutofillAddressesFirstTimeUse", ADDRESSES_FIRST_TIME_USE_PREF);
 XPCOMUtils.defineLazyPreferenceGetter(FormAutofill,
                                       "AutofillCreditCardsUsedStatus", CREDITCARDS_USED_STATUS_PREF);
 XPCOMUtils.defineLazyPreferenceGetter(FormAutofill,
                                       "supportedCountries", SUPPORTED_COUNTRIES_PREF, null, null,
                                       val => val.split(","));
 
+// XXX: This should be invalidated on intl:app-locales-changed.
 XPCOMUtils.defineLazyGetter(FormAutofill, "countries", () => {
-  return Services.intl.getRegions(undefined);
+  let availableRegionCodes = Services.intl.getAvailableLocaleDisplayNames("region");
+  let displayNames = Services.intl.getRegionDisplayNames(undefined, availableRegionCodes);
+  let result = new Map();
+  for (let i = 0; i < availableRegionCodes.length; i++) {
+    result.set(availableRegionCodes[i].toUpperCase(), displayNames[i]);
+  }
+  return result;
 });
+
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -888,20 +888,34 @@ this.FormAutofillUtils = {
    *           {string} addressLevel2Label
    *           {string} addressLevel1Label
    *           {string} postalCodeLabel
    *           {object} fieldsOrder
    *           {string} postalCodePattern
    *         }
    */
   getFormFormat(country) {
-    const dataset = this.getCountryAddressData(country);
+    let dataset = this.getCountryAddressData(country);
+    // We hit a country fallback in `getCountryAddressRawData` but it's not relevant here.
+    if (country != dataset.key) {
+      // Use a sparse object so the below default values take effect.
+      dataset = {
+        /**
+         * Even though data/ZZ only has address-level2, include the other levels
+         * in case they are needed for unknown countries. Users can leave the
+         * unnecessary fields blank which is better than forcing users to enter
+         * the data in incorrect fields.
+         */
+        fmt: "%N%n%O%n%A%n%C %S %Z",
+      };
+    }
     return {
       // When particular values are missing for a country, the
-      // data/ZZ value should be used instead.
+      // data/ZZ value should be used instead:
+      // https://chromium-i18n.appspot.com/ssl-aggregate-address/data/ZZ
       addressLevel3Label: dataset.sublocality_name_type || "suburb",
       addressLevel2Label: dataset.locality_name_type || "city",
       addressLevel1Label: dataset.state_name_type || "province",
       addressLevel1Options: this.buildRegionMapIfAvailable(dataset.sub_keys, dataset.sub_isoids, dataset.sub_names, dataset.sub_lnames),
       countryRequiredFields: this.parseRequireString(dataset.require || "AC"),
       fieldsOrder: this.parseAddressFormat(dataset.fmt || "%N%n%O%n%A%n%C"),
       postalCodeLabel: dataset.zip_name_type || "postalCode",
       postalCodePattern: dataset.zip,
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -1,10 +1,12 @@
 "use strict";
 
+const {FormAutofillUtils} = ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm", {});
+
 requestLongerTimeout(6);
 
 add_task(async function setup_supportedCountries() {
   await SpecialPowers.pushPrefEnv({set: [
     [SUPPORTED_COUNTRIES_PREF, "US,CA,DE"],
   ]});
 });
 
@@ -272,16 +274,84 @@ add_task(async function test_saveAddress
   });
   let addresses = await getAddresses();
   for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_DE_1)) {
     is(addresses[0][fieldName], fieldValue, "check " + fieldName);
   }
   await removeAllRecords();
 });
 
+/**
+ * Test saving an address for a region from regionNames.properties but not in
+ * addressReferences.js (libaddressinput).
+ */
+add_task(async function test_saveAddress_nolibaddressinput() {
+  const TEST_ADDRESS = {
+    ...TEST_ADDRESS_IE_1,
+    ...{
+      "address-level3": undefined,
+      country: "XG",
+    },
+  };
+
+  isnot(FormAutofillUtils.getCountryAddressData("XG").key, "XG",
+        "Check that the region we're testing with isn't in libaddressinput");
+
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+    let doc = win.document;
+
+    // Change country to verify labels
+    doc.querySelector("#country").focus();
+    EventUtils.synthesizeKey("Gaza Strip", {}, win);
+    await TestUtils.waitForCondition(() => {
+      return doc.querySelector("#postal-code-container > .label-text").textContent == "Postal Code";
+    }, "Wait for the mutation observer to change the labels");
+    is(doc.querySelector("#postal-code-container > .label-text").textContent, "Postal Code",
+                         "XG postal-code label should be 'Postal Code'");
+    isnot(doc.querySelector("#address-level1-container").style.display, "none",
+          "XG address-level1 should be hidden");
+    is(doc.querySelector("#address-level2").localName, "input",
+       "XG address-level2 should be an <input>");
+    // Input address info and verify move through form with tab keys
+    doc.querySelector("#given-name").focus();
+    const keyInputs = [
+      TEST_ADDRESS["given-name"],
+      "VK_TAB",
+      TEST_ADDRESS["additional-name"],
+      "VK_TAB",
+      TEST_ADDRESS["family-name"],
+      "VK_TAB",
+      TEST_ADDRESS.organization,
+      "VK_TAB",
+      TEST_ADDRESS["street-address"],
+      "VK_TAB",
+      TEST_ADDRESS["address-level2"],
+      "VK_TAB",
+      TEST_ADDRESS["address-level1"],
+      "VK_TAB",
+      TEST_ADDRESS["postal-code"],
+      "VK_TAB",
+      // TEST_ADDRESS_1.country, // Country is already selected above
+      "VK_TAB",
+      TEST_ADDRESS.tel,
+      "VK_TAB",
+      TEST_ADDRESS.email,
+      "VK_TAB",
+      "VK_TAB",
+      "VK_RETURN",
+    ];
+    keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+  });
+  let addresses = await getAddresses();
+  for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS)) {
+    is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+  }
+  await removeAllRecords();
+});
+
 add_task(async function test_saveAddressIE() {
   await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
     let doc = win.document;
     // Change country to verify labels
     doc.querySelector("#country").focus();
     EventUtils.synthesizeKey("Ireland", {}, win);
     await TestUtils.waitForCondition(() => {
       return doc.querySelector("#postal-code-container > .label-text").textContent == "Eircode";
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -18,16 +18,46 @@ ChromeUtils.import("resource://testing-c
 ChromeUtils.defineModuleGetter(this, "DownloadPaths",
                                "resource://gre/modules/DownloadPaths.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ExtensionParent",
                                "resource://gre/modules/ExtensionParent.jsm");
 
+{
+  // We're going to register a mock file source
+  // with region names based on en-US. This is
+  // necessary for tests that expect to match
+  // on region code display names.
+  const {L10nRegistry, FileSource} = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
+
+  const fs = {
+    "toolkit/intl/regionNames.ftl": `
+region-name-us = United States
+region-name-nz = New Zeland
+region-name-au = Australia
+region-name-ca = Canada
+region-name-tw = Taiwan
+    `,
+  };
+
+  L10nRegistry.loadSync = function(url) {
+    if (!fs.hasOwnProperty(url)) {
+      return false;
+    }
+    return fs[url];
+  };
+
+  let locales = Services.locale.packagedLocales;
+  const mockSource = new FileSource("mock", locales, "");
+  L10nRegistry.registerSource(mockSource);
+}
+
+
 do_get_profile();
 
 // ================================================
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/releases/v2.3.2/
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
 /* globals sinon */
--- a/browser/locales/en-US/chrome/browser/translation.dtd
+++ b/browser/locales/en-US/chrome/browser/translation.dtd
@@ -4,17 +4,17 @@
 
 <!-- LOCALIZATION NOTE (translation.thisPageIsIn.label,
   -                     translation.translateThisPage.label):
   -  These 2 strings are used to construct a sentence that contains a dropdown
   -  showing the detected language of the current web page.
   -  In en-US it looks like this:
   -    This page is in [detected language] Translate this page?
   -  "detected language" here is a language name coming from the
-  -  global/languageNames.properties file; for some locales it may not be in
+  -  toolkit/intl/languageNames.ftl file; for some locales it may not be in
   -  the correct grammar case to keep the same structure of the original
   -  sentence. -->
 <!ENTITY translation.thisPageIsIn.label         "This page is in">
 <!ENTITY translation.translateThisPage.label    "Translate this page?">
 <!ENTITY translation.translate.button           "Translate">
 <!ENTITY translation.notNow.button              "Not Now">
 
 <!ENTITY translation.translatingContent.label   "Translating page content…">
@@ -22,17 +22,17 @@
 <!-- LOCALIZATION NOTE (translation.translatedFrom.label,
   -                     translation.translatedTo.label,
   -                     translation.translatedToSuffix.label):
   -  These 3 strings are used to construct a sentence that contains 2 dropdowns
   -  showing the source and target language of a translated web page.
   -  In en-US it looks like this:
   -    This page has been translated from [from language] to [to language]
   -  "from language" and "to language" here are language names coming from the
-  -  global/languageNames.properties file; for some locales they may not be in
+  -  toolkit/intl/languageNames.ftl file; for some locales they may not be in
   -  the correct grammar case to keep the same structure of the original
   -  sentence.
   -
   -  translation.translatedToSuffix.label (empty in en-US) is for locales that
   -  need to display some text after the second drop down for the sentence to
   -  be grammatically correct. -->
 <!ENTITY translation.translatedFrom.label       "This page has been translated from">
 <!ENTITY translation.translatedTo.label         "to">
--- a/browser/locales/en-US/chrome/browser/translation.properties
+++ b/browser/locales/en-US/chrome/browser/translation.properties
@@ -1,12 +1,12 @@
 # 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/.
 
 # LOCALIZATION NOTE (translation.options.neverForLanguage.label):
-#  %S is a language name coming from the global/languageNames.properties file.
+#  %S is a language name coming from the toolkit/intl/languageNames.ftl file.
 translation.options.neverForLanguage.label=Never translate %S
 
 # LOCALIZATION NOTE (translation.options.neverForLanguage.accesskey):
 # The accesskey value used here should not clash with the values used for
 # translation.options.*.accesskey in translation.dtd
 translation.options.neverForLanguage.accesskey=N
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -5,17 +5,17 @@
 <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
 %brandDTD;
 
 <!ENTITY loadError.label "Problem loading page">
 <!ENTITY retry.label "Try Again">
 <!ENTITY returnToPreviousPage.label "Go Back">
 <!ENTITY returnToPreviousPage1.label "Go Back (Recommended)">
 <!ENTITY advanced.label "Advanced">
-<!ENTITY continue2.label "More…">
+<!ENTITY advanced2.label "Advanced…">
 <!ENTITY moreInformation.label "More Information">
 <!ENTITY viewCertificate.label "View Certificate">
 
 <!-- Specific error messages -->
 
 <!ENTITY connectionFailure.title "Unable to connect">
 <!ENTITY connectionFailure.longDesc "&sharedLongDesc;">
 
--- a/build/autoconf/hooks.m4
+++ b/build/autoconf/hooks.m4
@@ -7,17 +7,16 @@ define([_MOZ_AC_INIT_PREPARE], defn([AC_
 define([AC_INIT_PREPARE],
 [_MOZ_AC_INIT_PREPARE($1)
 
 test "x$prefix" = xNONE && prefix=$ac_default_prefix
 # Let make expand exec_prefix.
 test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
 
 > subconfigures
-> skip_subconfigures
 ])
 
 define([AC_OUTPUT_SUBDIRS],
 [for moz_config_dir in $1; do
   _CONFIG_SHELL=${CONFIG_SHELL-/bin/sh}
   case "$moz_config_dir" in
   *:*)
     objdir=$(echo $moz_config_dir | awk -F: '{print [$]2}')
@@ -49,34 +48,25 @@ define([AC_OUTPUT_SUBDIRS],
   esac
 
   eval $dumpenv $PYTHON $_topsrcdir/build/subconfigure.py --prepare "$srcdir" "$moz_config_dir" "$_CONFIG_SHELL" $ac_configure_args ifelse($2,,,--cache-file="$2")
 
   dnl Actual subconfigure execution happens in MOZ_RUN_CONFIG_STATUS
 done
 ])
 
-define([AC_OUTPUT_SUBDIRS_NOW],
-[
-for moz_config_dir_ in $1; do
-  AC_OUTPUT_SUBDIRS($moz_config_dir_,$2)
-  tail -1 subconfigures >> skip_subconfigures
-  MOZ_RUN_SUBCONFIGURES(`tail -1 skip_subconfigures`)
-done
-])
-
 define([MOZ_RUN_SUBCONFIGURES],
 [dnl Execute subconfigure, unless --no-recursion was passed to configure.
 if test "$no_recursion" != yes; then
   trap '' EXIT
   if ! $PYTHON $_topsrcdir/build/subconfigure.py $1; then
       exit 1
   fi
 fi
 ])
 
 define([MOZ_RUN_ALL_SUBCONFIGURES],[
-MOZ_RUN_SUBCONFIGURES([--list subconfigures --skip skip_subconfigures])
+MOZ_RUN_SUBCONFIGURES([--list subconfigures])
 ])
 
 dnl Print error messages in config.log as well as stderr
 define([AC_MSG_ERROR],
 [{ echo "configure: error: $1" 1>&2; echo "configure: error: $1" 1>&5; exit 1; }])
deleted file mode 100644
--- a/build/autoconf/subconfigure.m4
+++ /dev/null
@@ -1,46 +0,0 @@
-dnl We are not running in a real autoconf environment. So we're using real m4
-dnl here, not the crazier environment that autoconf provides.
-
-dnl Autoconf expects [] for quotes; give it to them
-changequote([, ])
-
-dnl AC_DEFUN is provided to use instead of define in autoconf. Provide it too.
-define([AC_DEFUN], [define($1, [$2])])
-
-dnl AC_ARG_ENABLE(FEATURE, HELP-STRING, IF-TRUE[, IF-FALSE])
-dnl We have to ignore the help string due to how help works in autoconf...
-AC_DEFUN([MOZ_AC_ARG_ENABLE],
-[#] Check whether --enable-[$1] or --disable-[$1] was given.
-[if test "[${enable_]patsubst([$1], -, _)+set}" = set; then
-  enableval="[$enable_]patsubst([$1], -, _)"
-  $3
-ifelse([$4], , , [else
-  $4
-])dnl
-fi
-])
-
-dnl AC_MSG_ERROR(error-description)
-AC_DEFUN([AC_MSG_ERROR], [{ echo "configure: error: $1" 1>&2; exit 1; }])
-
-AC_DEFUN([AC_MSG_WARN],  [ echo "configure: warning: $1" 1>&2 ])
-
-dnl Add the variable to the list of substitution variables
-AC_DEFUN([AC_SUBST],
-[
-_subconfigure_ac_subst_args="$_subconfigure_ac_subst_args $1"
-])
-
-dnl Override for AC_DEFINE.
-AC_DEFUN([AC_DEFINE],
-[
-cat >>confdefs.h <<\EOF
-[#define] $1 ifelse($#, 2, [$2], $#, 3, [$2], 1)
-EOF
-cat >> confdefs.pytmp <<\EOF
-    (''' $1 ''', ifelse($#, 2, [r''' $2 '''], $#, 3, [r''' $2 '''], ' 1 '))
-EOF
-])
-
-dnl AC_OUTPUT_SUBDIRS(subdirectory)
-AC_DEFUN([AC_OUTPUT_SUBDIRS], [do_output_subdirs "$1"])
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -691,110 +691,133 @@ def get_vc_paths(topsrcdir):
     for install in vswhere(['-products', '*', '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64']):
         path = install['installationPath']
         tools_version = open(os.path.join(
             path, r'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt'), 'rb').read().strip()
         tools_path = os.path.join(
             path, r'VC\Tools\MSVC', tools_version, r'bin\HostX64')
         yield (Version(install['installationVersion']), {
             'x64': [os.path.join(tools_path, 'x64')],
-            # The x64->x86 cross toolchain requires DLLs from the native x64 toolchain.
+            # The cross toolchains require DLLs from the native x64 toolchain.
             'x86': [os.path.join(tools_path, 'x86'), os.path.join(tools_path, 'x64')],
-            'arm64': [os.path.join(tools_path, 'x64')],
+            'arm64': [os.path.join(tools_path, 'arm64'), os.path.join(tools_path, 'x64')],
         })
 
 
 js_option('--with-visual-studio-version', nargs=1,
           choices=('2017',),
           help='Select a specific Visual Studio version to use')
 
 
 @depends('--with-visual-studio-version')
 def vs_major_version(value):
     if value:
         return {'2017': 15}[value[0]]
 
 
-@depends(host, target, vs_major_version, check_build_environment, '--with-visual-studio-version')
-@imports(_from='__builtin__', _import='sorted')
-@imports(_from='operator', _import='itemgetter')
-@imports('platform')
-def vc_compiler_path(host, target, vs_major_version, env, vs_release_name):
-    if host.kernel != 'WINNT':
-        return
-    vc_target = {
-        'x86': 'x86',
-        'x86_64': 'x64',
-        'arm': 'arm',
-        'aarch64': 'arm64'
-    }.get(target.cpu)
-    if vc_target is None:
-        return
-
-    all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0))
-    if not all_versions:
-        return
-    if vs_major_version:
-        versions = [d for (v, d) in all_versions if v.major ==
-                    vs_major_version]
-        if not versions:
-            die('Visual Studio %s could not be found!' % vs_release_name)
-        data = versions[0]
-    else:
-        # Choose the newest version.
-        data = all_versions[-1][1]
-    paths = data.get(vc_target)
-    if not paths:
-        return
-    return paths
+@template
+def vc_compiler_path_for(host_or_target):
+    @depends(host, host_or_target, vs_major_version, check_build_environment,
+             '--with-visual-studio-version')
+    @imports(_from='__builtin__', _import='sorted')
+    @imports(_from='operator', _import='itemgetter')
+    @imports('platform')
+    def vc_compiler_path(host, target, vs_major_version, env, vs_release_name):
+        if host.kernel != 'WINNT':
+            return
+        vc_target = {
+            'x86': 'x86',
+            'x86_64': 'x64',
+            'arm': 'arm',
+            'aarch64': 'arm64'
+        }.get(target.cpu)
+        if vc_target is None:
+            return
+
+        all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0))
+        if not all_versions:
+            return
+        if vs_major_version:
+            versions = [d for (v, d) in all_versions if v.major ==
+                        vs_major_version]
+            if not versions:
+                die('Visual Studio %s could not be found!' % vs_release_name)
+            data = versions[0]
+        else:
+            # Choose the newest version.
+            data = all_versions[-1][1]
+        paths = data.get(vc_target)
+        if not paths:
+            return
+        return paths
+    return vc_compiler_path
+
+
+vc_compiler_path = vc_compiler_path_for(target)
+host_vc_compiler_path = vc_compiler_path_for(host)
 
 
 @dependable
 @imports('os')
 @imports(_from='os', _import='environ')
 def original_path():
     return environ['PATH'].split(os.pathsep)
 
 
-@depends(vc_compiler_path, original_path)
-@imports('os')
-@imports(_from='os', _import='environ')
-def toolchain_search_path(vc_compiler_path, original_path):
-    result = list(original_path)
-
-    if vc_compiler_path:
-        # The second item, if there is one, is necessary to have in $PATH for
-        # Windows to load the required DLLs from there.
-        if len(vc_compiler_path) > 1:
-            environ['PATH'] = os.pathsep.join(result + vc_compiler_path[1:])
-
-        # The first item is where the programs are going to be
-        result.append(vc_compiler_path[0])
-
-    # Also add in the location to which `mach bootstrap` or
-    # `mach artifact toolchain` installs clang.
-    mozbuild_state_dir = environ.get('MOZBUILD_STATE_PATH',
-                                     os.path.expanduser(os.path.join('~', '.mozbuild')))
-    bootstrap_clang_path = os.path.join(mozbuild_state_dir, 'clang', 'bin')
-    result.append(bootstrap_clang_path)
-
-    bootstrap_cbindgen_path = os.path.join(mozbuild_state_dir, 'cbindgen')
-    result.append(bootstrap_cbindgen_path)
-
-    return result
+@template
+def toolchain_search_path_for(host_or_target):
+    vc_path = {
+        host: host_vc_compiler_path,
+        target: vc_compiler_path,
+    }[host_or_target]
+
+    @depends(vc_path, original_path)
+    @imports('os')
+    @imports(_from='os', _import='environ')
+    def toolchain_search_path(vc_compiler_path, original_path):
+        result = list(original_path)
+
+        if vc_compiler_path:
+            # The second item, if there is one, is necessary to have in $PATH for
+            # Windows to load the required DLLs from there.
+            if len(vc_compiler_path) > 1:
+                environ['PATH'] = os.pathsep.join(result + vc_compiler_path[1:])
+
+            # The first item is where the programs are going to be
+            result.append(vc_compiler_path[0])
+
+        # Also add in the location to which `mach bootstrap` or
+        # `mach artifact toolchain` installs clang.
+        mozbuild_state_dir = environ.get('MOZBUILD_STATE_PATH',
+                                         os.path.expanduser(os.path.join('~', '.mozbuild')))
+        bootstrap_clang_path = os.path.join(mozbuild_state_dir, 'clang', 'bin')
+        result.append(bootstrap_clang_path)
+
+        bootstrap_cbindgen_path = os.path.join(mozbuild_state_dir, 'cbindgen')
+        result.append(bootstrap_cbindgen_path)
+
+        return result
+    return toolchain_search_path
+
+
+toolchain_search_path = toolchain_search_path_for(target)
+host_toolchain_search_path = toolchain_search_path_for(host)
 
 
 # As a workaround until bug 1516228 and bug 1516253 are fixed, set the PATH
 # variable for the build to contain the toolchain search path.
-@depends(toolchain_search_path)
+@depends(toolchain_search_path, host_toolchain_search_path)
 @imports('os')
 @imports(_from='os', _import='environ')
-def altered_path(toolchain_search_path):
+def altered_path(toolchain_search_path, host_toolchain_search_path):
     path = environ['PATH'].split(os.pathsep)
     altered_path = list(toolchain_search_path)
+    for p in host_toolchain_search_path:
+        if p not in altered_path:
+            altered_path.append(p)
     for p in path:
         if p not in altered_path:
             altered_path.append(p)
     return os.pathsep.join(altered_path)
 
 
 set_config('PATH', altered_path)
 
@@ -811,17 +834,21 @@ def default_c_compilers(host_or_target, 
 
     other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)
 
     @depends(host_or_target, target, toolchain_prefix, android_clang_compiler,
              *other_c_compiler)
     def default_c_compilers(host_or_target, target, toolchain_prefix,
                             android_clang_compiler, *other_c_compiler):
         if host_or_target.kernel == 'WINNT':
-            supported = types = ('clang-cl', 'msvc', 'gcc', 'clang')
+            # Prefer MSVC for aarch64 for now.
+            if host_or_target.cpu == 'aarch64':
+                supported = types = ('msvc', 'clang-cl', 'gcc', 'clang')
+            else:
+                supported = types = ('clang-cl', 'msvc', 'gcc', 'clang')
         elif host_or_target.kernel == 'Darwin':
             types = ('clang',)
             supported = ('clang', 'gcc')
         else:
             supported = types = ('clang', 'gcc')
 
         info = other_c_compiler[0] if other_c_compiler else None
         if info and info.type in supported:
@@ -836,16 +863,20 @@ def default_c_compilers(host_or_target, 
                     # If the target C compiler is GCC, and it can't be used with
                     # -m32/-m64 for the host, it's probably toolchain-prefixed,
                     # so we prioritize a raw 'gcc' instead.
                     prioritized = info.type
             elif info.type == 'clang' and android_clang_compiler:
                 # Android NDK clangs do not function as host compiler, so
                 # prioritize a raw 'clang' instead.
                 prioritized = info.type
+            elif info.type == 'msvc' and target.cpu != host_or_target.cpu:
+                # MSVC compilers only support one architecture, so we'll
+                # want a cl in another (detected) path.
+                prioritized = 'cl'
 
             types = [prioritized] + [t for t in types if t != info.type]
 
         gcc = ('gcc',)
         if toolchain_prefix and host_or_target is target:
             gcc = tuple('%sgcc' % p for p in toolchain_prefix) + gcc
 
         result = []
@@ -979,23 +1010,28 @@ def compiler(language, host_or_target, c
     what = 'the %s %s compiler' % (host_or_target_str, language)
 
     option(env=var, nargs=1, help='Path to %s' % what)
 
     # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
     # HOST_CXX variables.
     provided_compiler = provided_program(var)
 
+    search_path = {
+        host: host_toolchain_search_path,
+        target: toolchain_search_path,
+    }[host_or_target]
+
     # Normally, we'd use `var` instead of `_var`, but the interaction with
     # old-configure complicates things, and for now, we a) can't take the plain
     # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
     # old-configure AC_SUBST it (because it's autoconf doing it, not us)
     compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
                           input=provided_compiler.program,
-                          paths=toolchain_search_path)
+                          paths=search_path)
 
     @depends(compiler, provided_compiler, compiler_wrapper, host_or_target, macos_sdk)
     @checking('whether %s can be used' % what, lambda x: bool(x))
     @imports(_from='mozbuild.shellutil', _import='quote')
     def valid_compiler(compiler, provided_compiler, compiler_wrapper,
                        host_or_target, macos_sdk):
         wrapper = list(compiler_wrapper or ())
         if provided_compiler:
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -338,76 +338,101 @@ def include_path(vc_path, windows_sdk_di
     includes = os.pathsep.join(includes)
     os.environ['INCLUDE'] = includes
     return includes
 
 
 set_config('INCLUDE', include_path)
 
 
-@depends(target, c_compiler, vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
-@imports('os')
-def lib_path(target, c_compiler, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir):
-    if not vc_path:
-        return
-    sdk_target = {
-        'x86': 'x86',
-        'x86_64': 'x64',
-        'arm': 'arm',
-        'aarch64': 'arm64',
-    }.get(target.cpu)
+@template
+def lib_path_for(host_or_target):
+    compiler = {
+        host: host_c_compiler,
+        target: c_compiler,
+    }[host_or_target]
+
+    @depends(host_or_target, dependable(host_or_target is host), compiler, vc_path,
+             valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
+    @imports('os')
+    def lib_path(target, is_host, c_compiler, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir):
+        if not vc_path:
+            return
+        sdk_target = {
+            'x86': 'x86',
+            'x86_64': 'x64',
+            'arm': 'arm',
+            'aarch64': 'arm64',
+        }.get(target.cpu)
 
-    old_target = {
-        'x86': '',
-        'x86_64': 'amd64',
-        'arm': 'arm',
-        'aarch64': 'arm64'
-    }.get(target.cpu)
-    if old_target is None:
-        return
-    # As old_target can be '', and os.path.join will happily use the empty
-    # string, leading to a string ending with a backslash, that Make will
-    # interpret as a "string continues on next line" indicator, use variable
-    # args.
-    old_target = (old_target,) if old_target else ()
-    if c_compiler.version < '19.10':
-        # MSVC2015
-        vc_target = old_target
-    else:
-        # MSVC2017 switched to use the same target naming as the sdk.
-        vc_target = (sdk_target,)
+        old_target = {
+            'x86': '',
+            'x86_64': 'amd64',
+            'arm': 'arm',
+            'aarch64': 'arm64'
+        }.get(target.cpu)
+        if old_target is None:
+            return
+        # As old_target can be '', and os.path.join will happily use the empty
+        # string, leading to a string ending with a backslash, that Make will
+        # interpret as a "string continues on next line" indicator, use variable
+        # args.
+        old_target = (old_target,) if old_target else ()
+        if c_compiler.version < '19.10':
+            # MSVC2015
+            vc_target = old_target
+        else:
+            # MSVC2017 switched to use the same target naming as the sdk.
+            vc_target = (sdk_target,)
 
-    atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'lib', *vc_target)
-    if not os.path.isdir(atlmfc_dir):
-        die('Cannot find the ATL/MFC libraries in the Visual C++ directory '
-            '(%s). Please install them.' % vc_path)
+        atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'lib', *vc_target)
+        if not os.path.isdir(atlmfc_dir):
+            die('Cannot find the ATL/MFC libraries in the Visual C++ directory '
+                '(%s). Please install them.' % vc_path)
 
-    libs = []
-    lib_env = os.environ.get('LIB')
-    if lib_env:
-        libs.append(lib_env)
-    libs.extend((
-        os.path.join(vc_path, 'lib', *vc_target),
-        atlmfc_dir,
-        os.path.join(windows_sdk_dir.lib, 'um', sdk_target),
-        os.path.join(ucrt_sdk_dir.lib, 'ucrt', sdk_target),
-    ))
-    if dia_sdk_dir:
-        # For some reason the DIA SDK still uses the old-style targets
-        # even in a newer MSVC.
-        libs.append(os.path.join(dia_sdk_dir, 'lib', *old_target))
+        libs = []
+        lib_env = os.environ.get('LIB')
+        if lib_env and not is_host:
+            libs.extend(lib_env.split(os.pathsep))
+        libs.extend((
+            os.path.join(vc_path, 'lib', *vc_target),
+            atlmfc_dir,
+            os.path.join(windows_sdk_dir.lib, 'um', sdk_target),
+            os.path.join(ucrt_sdk_dir.lib, 'ucrt', sdk_target),
+        ))
+        if dia_sdk_dir:
+            # For some reason the DIA SDK still uses the old-style targets
+            # even in a newer MSVC.
+            libs.append(os.path.join(dia_sdk_dir, 'lib', *old_target))
+        return libs
+
+    return lib_path
+
+
+@depends(lib_path_for(target))
+@imports('os')
+def lib_path(libs):
     # Set in the environment for old-configure
     libs = os.pathsep.join(libs)
     os.environ['LIB'] = libs
     return libs
 
 
 set_config('LIB', lib_path)
 
 
+@depends(lib_path_for(host))
+@imports(_from='mozbuild.shellutil', _import='quote')
+def host_linker_libpaths(libs):
+    return ['-LIBPATH:%s' % quote(l) for l in libs]
+
+
+set_config('HOST_LINKER_LIBPATHS', host_linker_libpaths)
+
+
 option(env='MT', nargs=1, help='Path to the Microsoft Manifest Tool')
 
 
 @depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir)
 @imports(_from='os', _import='environ')
 @imports('platform')
 def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir):
     if not valid_windows_sdk_dir:
@@ -450,21 +475,37 @@ def valid_mt(path):
     except subprocess.CalledProcessError:
         pass
     raise FatalCheckError('%s is not Microsoft Manifest Tool')
 
 
 set_config('MSMANIFEST_TOOL', depends(valid_mt)(lambda x: bool(x)))
 
 
-link = check_prog('LINKER', ('lld-link.exe', 'link.exe'),
+@template
+def linker_progs_for(host_or_target):
+    compiler = {
+        host: host_c_compiler,
+        target: c_compiler,
+    }[host_or_target]
+
+    @depends(compiler)
+    def linker_progs(compiler):
+        if compiler.type == 'msvc':
+            return ('link', 'lld-link')
+        if compiler.type == 'clang-cl':
+            return ('lld-link', 'link')
+    return linker_progs
+
+
+link = check_prog('LINKER', linker_progs_for(target),
                   paths=toolchain_search_path)
 
-host_link = check_prog('HOST_LINKER', ('lld-link.exe', 'link.exe'),
-                       paths=toolchain_search_path)
+host_link = check_prog('HOST_LINKER', linker_progs_for(host),
+                       paths=host_toolchain_search_path)
 
 add_old_configure_assignment('LINKER', link)
 
 
 check_prog('MAKECAB', ('makecab.exe',))
 
 
 @depends(c_compiler, using_sccache)
--- a/build/subconfigure.py
+++ b/build/subconfigure.py
@@ -377,27 +377,22 @@ def run(objdir):
 
     return ret
 
 
 def subconfigure(args):
     parser = argparse.ArgumentParser()
     parser.add_argument('--list', type=str,
                         help='File containing a list of subconfigures to run')
-    parser.add_argument('--skip', type=str,
-                        help='File containing a list of Subconfigures to skip')
     parser.add_argument('subconfigures', type=str, nargs='*',
                         help='Subconfigures to run if no list file is given')
     args, others = parser.parse_known_args(args)
     subconfigures = args.subconfigures
     if args.list:
         subconfigures.extend(open(args.list, 'rb').read().splitlines())
-    if args.skip:
-        skips = set(open(args.skip, 'rb').read().splitlines())
-        subconfigures = [s for s in subconfigures if s not in skips]
 
     if not subconfigures:
         return 0
 
     ret = 0
     for subconfigure in subconfigures:
         returncode = run(subconfigure)
         ret = max(returncode, ret)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -577,17 +577,17 @@ ifdef ENABLE_STRIP
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
 endif
 
 $(HOST_PROGRAM): $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS) $(call mkdir_deps,$(DEPTH)/dist/host/bin)
 	$(REPORT_BUILD)
 ifeq (_WINNT,$(GNU_CC)_$(HOST_OS_ARCH))
-	$(HOST_LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $($(notdir $@)_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $($(notdir $@)_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LINKER_LIBPATHS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 ifdef MSMANIFEST_TOOL
 	@if test -f $@.manifest; then \
 		if test -f '$(srcdir)/$(notdir $@).manifest'; then \
 			echo 'Embedding manifest from $(srcdir_rel)/$(notdir $@).manifest and $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST '$(srcdir_rel)/$(notdir $@).manifest' $@.manifest -OUTPUTRESOURCE:$@\;1; \
 		else \
 			echo 'Embedding manifest from $@.manifest'; \
 			$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
@@ -638,17 +638,17 @@ ifdef ENABLE_STRIP
 endif
 ifdef MOZ_POST_PROGRAM_COMMAND
 	$(MOZ_POST_PROGRAM_COMMAND) $@
 endif
 
 $(HOST_SIMPLE_PROGRAMS): host_%$(HOST_BIN_SUFFIX): $(HOST_LIBS) $(HOST_EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
 ifeq (WINNT_,$(HOST_OS_ARCH)_$(GNU_CC))
-	$(HOST_LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $($(notdir $@)_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_LINKER) -NOLOGO -OUT:$@ -PDB:$(HOST_PDBFILE) $($(notdir $@)_OBJS) $(WIN32_EXE_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LINKER_LIBPATHS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 ifneq (,$(HOST_CPPSRCS)$(USE_HOST_CXX))
 	$(HOST_CXX) $(HOST_OUTOPTION)$@ $(HOST_CXX_LDFLAGS) $($(notdir $@)_OBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 	$(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_C_LDFLAGS) $($(notdir $@)_OBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 endif
 ifndef CROSS_COMPILE
@@ -670,17 +670,17 @@ ifeq ($(OS_ARCH),WINNT)
 # See bug 795204.
 $(IMPORT_LIBRARY): $(SHARED_LIBRARY) ;
 endif
 
 $(HOST_SHARED_LIBRARY): Makefile
 	$(REPORT_BUILD)
 	$(RM) $@
 ifdef _MSC_VER
-	$(HOST_LINKER) -NOLOGO -DLL -OUT:$@ $($(notdir $@)_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
+	$(HOST_LINKER) -NOLOGO -DLL -OUT:$@ $($(notdir $@)_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LINKER_LIBPATHS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 	$(HOST_CXX) $(HOST_OUTOPTION)$@ $($(notdir $@)_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 
 # On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files,
 # so instead of deleting .o files after repacking them into a dylib, we make
 # symlinks back to the originals. The symlinks are a no-op for stabs debugging,
 # so no need to conditionalize on OS version or debugging format.
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -8,16 +8,19 @@ const Services = require("Services");
 
 const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const { render, unmountComponentAtNode } =
   require("devtools/client/shared/vendor/react-dom");
 const Provider =
   createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
 
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
+
 const actions = require("./src/actions/index");
 const { configureStore } = require("./src/create-store");
 const {
   setDebugTargetCollapsibilities,
 } = require("./src/modules/debug-target-collapsibilities");
 
 const { l10n } = require("./src/modules/l10n");
 
@@ -54,22 +57,23 @@ const AboutDebugging = {
 
     await l10n.init();
 
     render(
       Provider(
         {
           store: this.store,
         },
-        Router(
-          {},
-          App(
-            {
-              fluentBundles: l10n.getBundles(),
-            }
+        LocalizationProvider(
+          { messages: l10n.getBundles() },
+          Router(
+            {},
+            App(
+              {}
+            )
           )
         )
       ),
       this.mount
     );
 
     this.onNetworkLocationsUpdated();
     addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
--- a/devtools/client/aboutdebugging-new/index.html
+++ b/devtools/client/aboutdebugging-new/index.html
@@ -1,14 +1,15 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8" />
+    <title>Debugging</title>
     <link rel="stylesheet" href="chrome://devtools/content/aboutdebugging-new/aboutdebugging.css"/>
     <script type="application/javascript" src="resource://devtools/client/aboutdebugging-new/initializer.js"></script>
   </head>
   <body>
     <div id="mount"></div>
   </body>
 </html>
--- a/devtools/client/aboutdebugging-new/src/components/App.js
+++ b/devtools/client/aboutdebugging-new/src/components/App.js
@@ -5,50 +5,70 @@
 "use strict";
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const FluentReact = require("devtools/client/shared/vendor/fluent-react");
-const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
+const Localized = createFactory(FluentReact.Localized);
 
 const Route = createFactory(require("devtools/client/shared/vendor/react-router-dom").Route);
 const Switch = createFactory(require("devtools/client/shared/vendor/react-router-dom").Switch);
 const Redirect = createFactory(require("devtools/client/shared/vendor/react-router-dom").Redirect);
-const { withRouter } = require("devtools/client/shared/vendor/react-router-dom");
 
 const Types = require("../types/index");
 const { RUNTIMES } = require("../constants");
 
 const ConnectPage = createFactory(require("./connect/ConnectPage"));
 const RuntimePage = createFactory(require("./RuntimePage"));
 const Sidebar = createFactory(require("./sidebar/Sidebar"));
 
 class App extends PureComponent {
   static get propTypes() {
     return {
       adbAddonStatus: PropTypes.string,
       // The "dispatch" helper is forwarded to the App component via connect.
       // From that point, components are responsible for forwarding the dispatch
       // property to all components who need to dispatch actions.
       dispatch: PropTypes.func.isRequired,
-      fluentBundles: PropTypes.arrayOf(PropTypes.object).isRequired,
+      // getString prop is injected by the withLocalization wrapper
+      getString: PropTypes.func.isRequired,
       isScanningUsb: PropTypes.bool.isRequired,
       networkEnabled: PropTypes.bool.isRequired,
       networkLocations: PropTypes.arrayOf(PropTypes.string).isRequired,
       networkRuntimes: PropTypes.arrayOf(Types.runtime).isRequired,
       selectedPage: PropTypes.string,
       selectedRuntime: PropTypes.string,
       usbRuntimes: PropTypes.arrayOf(Types.runtime).isRequired,
       wifiEnabled: PropTypes.bool.isRequired,
     };
   }
 
+  componentDidUpdate() {
+    this.updateTitle();
+  }
+
+  updateTitle() {
+    const { getString, selectedPage, selectedRuntime } = this.props;
+
+    const runtimeTitle = selectedRuntime ?
+                          getString(
+                            "about-debugging-page-title-with-runtime",
+                            { selectedPage, selectedRuntime }
+                          )
+                          : getString(
+                            "about-debugging-page-title",
+                            { selectedPage }
+                          );
+
+    document.title = runtimeTitle;
+  }
+
   renderConnect() {
     const {
       adbAddonStatus,
       dispatch,
       networkEnabled,
       networkLocations,
       wifiEnabled,
     } = this.props;
@@ -131,26 +151,25 @@ class App extends PureComponent {
       })
     );
   }
 
   render() {
     const {
       adbAddonStatus,
       dispatch,
-      fluentBundles,
       isScanningUsb,
       networkRuntimes,
       selectedPage,
       selectedRuntime,
       usbRuntimes,
     } = this.props;
 
-    return LocalizationProvider(
-      { messages: fluentBundles },
+    return Localized(
+      { },
       dom.div(
         { className: "app" },
         Sidebar({
           adbAddonStatus,
           className: "app__sidebar",
           dispatch,
           isScanningUsb,
           networkRuntimes,
@@ -177,9 +196,11 @@ const mapStateToProps = state => {
     wifiEnabled: state.ui.wifiEnabled,
   };
 };
 
 const mapDispatchToProps = dispatch => ({
   dispatch,
 });
 
-module.exports = withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
+module.exports = FluentReact
+  .withLocalization(
+      connect(mapStateToProps, mapDispatchToProps)(App));
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_routes.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_routes.js
@@ -13,49 +13,64 @@ add_task(async function() {
   info("Check root route redirects to 'This Firefox'");
   const { document, tab } = await openAboutDebugging();
   is(document.location.hash, "#/runtime/this-firefox");
 
   await removeTab(tab);
 });
 
 /**
- * Test that the routes in about:debugging show the proper views
+ * Test that the routes in about:debugging show the proper views and document.title
  */
 add_task(async function() {
   // enable USB devices mocks
   const mocks = new Mocks();
 
   const { document, tab } = await openAboutDebugging();
 
   info("Check 'This Firefox' route");
   document.location.hash = "#/runtime/this-firefox";
   await waitUntil(() => document.querySelector(".js-runtime-page"));
   const infoLabel = document.querySelector(".js-runtime-info").textContent;
   // NOTE: when using USB Mocks, we see only "Firefox" as the device name
   ok(infoLabel.includes("Firefox"), "Runtime is displayed as Firefox");
   ok(!infoLabel.includes(" on "), "Runtime is not associated to any device");
+  is(
+      document.title,
+      "Debugging - Runtime / this-firefox",
+      "Checking title for 'runtime' page"
+    );
 
   info("Check 'Connect' page");
   document.location.hash = "#/connect";
   await waitUntil(() => document.querySelector(".js-connect-page"));
   ok(true, "Connect page has been shown");
+  is(
+      document.title,
+      "Debugging - Connect",
+      "Checking title for 'connect' page"
+    );
 
   info("Check 'USB device runtime' page");
   // connect to a mocked USB runtime
   mocks.createUSBRuntime("1337id", {
     deviceName: "Fancy Phone",
     name: "Lorem ipsum",
   });
   mocks.emitUSBUpdate();
   await connectToRuntime("Fancy Phone", document);
   // navigate to it via URL
   document.location.hash = "#/runtime/1337id";
   await waitUntil(() => document.querySelector(".js-runtime-page"));
   const runtimeLabel = document.querySelector(".js-runtime-info").textContent;
+  is(
+      document.title,
+      "Debugging - Runtime / 1337id",
+      "Checking title for 'runtime' page with USB device"
+    );
   ok(runtimeLabel.includes("Lorem ipsum"), "Runtime is displayed with the mocked name");
 
   await removeTab(tab);
 });
 
 /**
  * Test that an invalid route redirects to / (currently This Firefox page)
  */
@@ -65,12 +80,17 @@ add_task(async function() {
 
   info("Waiting for a non-runtime page to load");
   document.location.hash = "#/connect";
   await waitUntil(() => document.querySelector(".js-connect-page"));
 
   info("Update hash & wait for a redirect to root ('This Firefox')");
   document.location.hash = "#/lorem-ipsum";
   await waitUntil(() => document.querySelector(".js-runtime-page"));
+  is(
+      document.title,
+      "Debugging - Runtime / this-firefox",
+      "Checking title for 'runtime' page after redirect to root"
+    );
   is(document.location.hash, "#/runtime/this-firefox", "Redirected to root");
 
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
+++ b/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
@@ -237,8 +237,25 @@ about-debugging-runtime-info-with-model 
 
 # Text of the connection prompt button displayed in Runtime pages, when the preference
 # "devtools.debugger.prompt-connection" is false on the target runtime.
 about-debugging-connection-prompt-enable-button = Enable connection prompt
 
 # Text of the connection prompt button displayed in Runtime pages, when the preference
 # "devtools.debugger.prompt-connection" is true on the target runtime.
 about-debugging-connection-prompt-disable-button = Disable connection prompt
+
+# Title of the application displayed in the tab
+-application-title = Debugging
+
+# Page title of connect / runtime page
+# Part of "about-debugging-page-title" string defined below
+about-debugging-page-title-selected-page = 
+  { $selectedPage -> 
+     [connect] Connect
+     *[runtime] Runtime
+  }
+
+# Page title with the runtime displayed in the tab
+about-debugging-page-title-with-runtime = { -application-title } - { about-debugging-page-title-selected-page } / { $selectedRuntime }
+
+# Page title without the runtime displayed in the tab
+about-debugging-page-title = { -application-title } - { about-debugging-page-title-selected-page }
--- a/devtools/client/inspector/changes/components/ChangesApp.js
+++ b/devtools/client/inspector/changes/components/ChangesApp.js
@@ -5,39 +5,30 @@
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const CSSDeclaration = createFactory(require("./CSSDeclaration"));
+const { getChangesTree } = require("../selectors/changes");
 const { getSourceForDisplay } = require("../utils/changes-utils");
 const { getStr } = require("../utils/l10n");
 
 class ChangesApp extends PureComponent {
   static get propTypes() {
     return {
-      // Redux state slice assigned to Track Changes feature; passed as prop by connect()
-      changes: PropTypes.object.isRequired,
+      // Nested CSS rule tree structure of CSS changes grouped by source (stylesheet)
+      changesTree: PropTypes.object.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
-    // In the Redux store, all rules exist in a collection at the same level of nesting.
-    // Parent rules come before child rules. Parent/child dependencies are set
-    // via parameters in each rule pointing to the corresponding rule ids.
-    //
-    // To render rules, we traverse the descendant rule tree and render each child rule
-    // found. This means we get into situations where we can render the same rule multiple
-    // times: once as a child of its parent and once standalone.
-    //
-    // By keeping a log of rules previously rendered we prevent needless multi-rendering.
-    this.renderedRules = [];
   }
 
   renderDeclarations(remove = [], add = []) {
     const removals = remove
       // Sorting changed declarations in the order they appear in the Rules view.
       .sort((a, b) => a.index > b.index)
       .map(({property, value, index}) => {
         return CSSDeclaration({
@@ -58,26 +49,19 @@ class ChangesApp extends PureComponent {
           property,
           value,
         });
       });
 
     return [removals, additions];
   }
 
-  renderRule(ruleId, rule, rules, level = 0) {
+  renderRule(ruleId, rule, level = 0) {
     const selector = rule.selector;
 
-    if (this.renderedRules.includes(ruleId)) {
-      return null;
-    }
-
-    // Mark this rule as rendered so we don't render it again.
-    this.renderedRules.push(ruleId);
-
     let diffClass = "";
     if (rule.changeType === "rule-add") {
       diffClass = "diff-add";
     } else if (rule.changeType === "rule-remove") {
       diffClass = "diff-remove";
     }
 
     return dom.div(
@@ -92,18 +76,18 @@ class ChangesApp extends PureComponent {
         {
           className: `level selector ${diffClass}`,
           title: selector,
         },
         selector,
         dom.span({ className: "bracket-open" }, "{")
       ),
       // Render any nested child rules if they exist.
-      rule.children.map(childRuleId => {
-        return this.renderRule(childRuleId, rules[childRuleId], rules, level + 1);
+      rule.children.map(childRule => {
+        return this.renderRule(childRule.ruleId, childRule, level + 1);
       }),
       // Render any changed CSS declarations.
       this.renderDeclarations(rule.remove, rule.add),
       dom.div({ className: `level bracket-close ${diffClass}` }, "}")
     );
   }
 
   renderDiff(changes = {}) {
@@ -122,17 +106,17 @@ class ChangesApp extends PureComponent {
             className: "href",
             title: href,
           },
           dom.span({}, path),
           isFramed && this.renderFrameBadge(href)
         ),
         // Render changed rules within this source.
         Object.entries(rules).map(([ruleId, rule]) => {
-          return this.renderRule(ruleId, rule, rules);
+          return this.renderRule(ruleId, rule);
         })
       );
     });
   }
 
   renderFrameBadge(href = "") {
     return dom.span(
       {
@@ -146,24 +130,27 @@ class ChangesApp extends PureComponent {
   renderEmptyState() {
     return dom.div({ className: "devtools-sidepanel-no-result" },
       dom.p({}, getStr("changes.noChanges")),
       dom.p({}, getStr("changes.noChangesDescription"))
     );
   }
 
   render() {
-    // Reset log of rendered rules.
-    this.renderedRules = [];
-    const hasChanges = Object.keys(this.props.changes).length > 0;
-
+    const hasChanges = Object.keys(this.props.changesTree).length > 0;
     return dom.div(
       {
         className: "theme-sidebar inspector-tabpanel",
         id: "sidebar-panel-changes",
       },
       !hasChanges && this.renderEmptyState(),
-      hasChanges && this.renderDiff(this.props.changes)
+      hasChanges && this.renderDiff(this.props.changesTree)
     );
   }
 }
 
-module.exports = connect(state => state)(ChangesApp);
+const mapStateToProps = state => {
+  return {
+    changesTree: getChangesTree(state.changes),
+  };
+};
+
+module.exports = connect(mapStateToProps)(ChangesApp);
--- a/devtools/client/inspector/changes/moz.build
+++ b/devtools/client/inspector/changes/moz.build
@@ -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/.
 
 DIRS += [
     'actions',
     'components',
     'reducers',
+    'selectors',
     'utils',
 ]
 
 DevToolsModules(
     'ChangesView.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/changes/reducers/changes.js
+++ b/devtools/client/inspector/changes/reducers/changes.js
@@ -81,17 +81,17 @@ function createRule(ruleData, rules) {
     })
     // Then, create new entries in the rules collection and assign dependencies.
     .map((ruleId, index, array) => {
       const { selector } = ruleAncestry[index];
       const prevRuleId = array[index - 1];
       const nextRuleId = array[index + 1];
 
       // Copy or create an entry for this rule.
-      const defaults = { selector, add: [], remove: [], children: [] };
+      const defaults = { selector, ruleId, add: [], remove: [], children: [] };
       rules[ruleId] = Object.assign(defaults, rules[ruleId]);
 
       // The next ruleId is lower in the rule tree, therefore it's a child of this rule.
       if (nextRuleId && !rules[ruleId].children.includes(nextRuleId)) {
         rules[ruleId].children.push(nextRuleId);
       }
 
       // The previous ruleId is higher in the rule tree, therefore it's the parent.
@@ -128,16 +128,17 @@ function removeRule(ruleId, rules) {
  * which contain collections of added and removed CSS declarations.
  *
  * Structure:
  *    <sourceId>: {
  *      type: // {String} One of: "stylesheet", "inline" or "element"
  *      href: // {String|null} Stylesheet or document URL; null for inline stylesheets
  *      rules: {
  *        <ruleId>: {
+ *          ruleId:      // {String} <ruleId> of this rule
  *          selector:    // {String} CSS selector or CSS at-rule text
  *          changeType:  // {String} Optional; one of: "rule-add" or "rule-remove"
  *          children: [] // {Array} of <ruleId> for child rules of this rule
  *          parent:      // {String} <ruleId> of the parent rule
  *          add: [       // {Array} of objects with CSS declarations
  *            {
  *              property:    // {String} CSS property name
  *              value:       // {String} CSS property value
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/selectors/changes.js
@@ -0,0 +1,79 @@
+/* -*- 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";
+
+/**
+ * In the Redux state, changed CSS rules are grouped by source (stylesheet) and stored in
+ * a single level array, regardless of nesting.
+ * This method returns a nested tree structure of the changed CSS rules so the React
+ * consumer components can traverse it easier when rendering the nested CSS rules view.
+ * Keeping this interface updated allows the Redux state structure to change without
+ * affecting the consumer components.
+ *
+ * @param {Object} state
+ *        Redux slice for tracked changes.
+ * @return {Object}
+ */
+function getChangesTree(state) {
+  /**
+   * Recursively replace a rule's array of child rule ids with the referenced child rules.
+   * Mark visited rules so as not to handle them (and their children) again.
+   *
+   * Returns the rule object with expanded children or null if previously visited.
+   *
+   * @param  {String} ruleId
+   * @param  {Object} rule
+   * @param  {Array} rules
+   * @param  {Set} visitedRules
+   * @return {Object|null}
+   */
+  function expandRuleChildren(ruleId, rule, rules, visitedRules) {
+    if (visitedRules.has(ruleId)) {
+      return null;
+    }
+
+    visitedRules.add(ruleId);
+
+    return {
+      ...rule,
+      children: rule.children.map(childRuleId =>
+          expandRuleChildren(childRuleId, rules[childRuleId], rules, visitedRules)),
+    };
+  }
+
+  return Object.entries(state).reduce((sourcesObj, [sourceId, source]) => {
+    const { rules } = source;
+    // Log of visited rules in this source. Helps avoid duplication when traversing the
+    // descendant rule tree. This Set is unique per source. It will be passed down to
+    // be populated with ids of rules once visited. This ensures that only visited rules
+    // unique to this source will be skipped and prevents skipping identical rules from
+    // other sources (ex: rules with the same selector and the same index).
+    const visitedRules = new Set();
+
+    // Build a new collection of sources keyed by source id.
+    sourcesObj[sourceId] = {
+      ...source,
+      // Build a new collection of rules keyed by rule id.
+      rules: Object.entries(rules).reduce((rulesObj, [ruleId, rule]) => {
+        // Expand the rule's array of child rule ids with the referenced child rules.
+        // Skip exposing null values which mean the rule was previously visited as part
+        // of an ancestor descendant tree.
+        const expandedRule = expandRuleChildren(ruleId, rule, rules, visitedRules);
+        if (expandedRule !== null) {
+          rulesObj[ruleId] = expandedRule;
+        }
+
+        return rulesObj;
+      }, {}),
+    };
+
+    return sourcesObj;
+  }, {});
+}
+
+module.exports = {
+  getChangesTree,
+};
copy from devtools/client/inspector/changes/moz.build
copy to devtools/client/inspector/changes/selectors/moz.build
--- a/devtools/client/inspector/changes/moz.build
+++ b/devtools/client/inspector/changes/selectors/moz.build
@@ -1,18 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += [
-    'actions',
-    'components',
-    'reducers',
-    'utils',
-]
-
 DevToolsModules(
-    'ChangesView.js',
+    'changes.js',
 )
-
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/changes/test/browser.ini
+++ b/devtools/client/inspector/changes/test/browser.ini
@@ -10,13 +10,14 @@ support-files =
   !/devtools/client/shared/test/shared-redux-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_changes_declaration_disable.js]
 [browser_changes_declaration_duplicate.js]
 [browser_changes_declaration_edit_value.js]
+[browser_changes_declaration_identical_rules.js]
 [browser_changes_declaration_remove_ahead.js]
 [browser_changes_declaration_remove_disabled.js]
 [browser_changes_declaration_remove.js]
 [browser_changes_declaration_rename.js]
 [browser_changes_rule_selector.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/changes/test/browser_changes_declaration_identical_rules.js
@@ -0,0 +1,57 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test tracking changes to CSS declarations in different stylesheets but in rules
+// with identical selectors.
+
+const TEST_URI = `
+  <style type='text/css'>
+    div {
+      color: red;
+    }
+  </style>
+  <style type='text/css'>
+    div {
+      font-size: 1em;
+    }
+  </style>
+  <div></div>
+`;
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const { inspector, view: ruleView } = await openRuleView();
+  const { document: doc, store } = selectChangesView(inspector);
+
+  await selectNode("div", inspector);
+  const rule1 = getRuleViewRuleEditor(ruleView, 1).rule;
+  const rule2 = getRuleViewRuleEditor(ruleView, 2).rule;
+  const prop1 = rule1.textProps[0];
+  const prop2 = rule2.textProps[0];
+  let onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Disable the declaration in the first rule");
+  await togglePropStatus(ruleView, prop1);
+  info("Wait for change to be tracked");
+  await onTrackChange;
+
+  onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
+  info("Disable the declaration in the second rule");
+  await togglePropStatus(ruleView, prop2);
+  info("Wait for change to be tracked");
+  await onTrackChange;
+
+  const removeDecl = getRemovedDeclarations(doc);
+  is(removeDecl.length, 2, "Two declarations tracked as removed");
+  // The last of the two matching rules shows up first in Rule view given that the
+  // specificity is the same. This is correct. If the properties were the same, the latest
+  // declaration would overwrite the first and thus show up on top.
+  is(removeDecl[0].property, "font-size", "Correct property name for second declaration");
+  is(removeDecl[0].value, "1em", "Correct property value for second declaration");
+  is(removeDecl[1].property, "color", "Correct property name for first declaration");
+  is(removeDecl[1].value, "red", "Correct property value for first declaration");
+});
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -381,17 +381,18 @@ nsDocShell::nsDocShell(BrowsingContext* 
       mIsExecutingOnLoadHandler(false),
       mIsPrintingOrPP(false),
       mSavingOldViewer(false),
       mDynamicallyCreated(false),
       mAffectPrivateSessionLifetime(true),
       mInvisible(false),
       mHasLoadedNonBlankURI(false),
       mBlankTiming(false),
-      mTitleValidForCurrentURI(false) {
+      mTitleValidForCurrentURI(false),
+      mIsFrame(false) {
   mHistoryID.m0 = 0;
   mHistoryID.m1 = 0;
   mHistoryID.m2 = 0;
   AssertOriginAttributesMatchPrivateBrowsing();
 
   nsContentUtils::GenerateUUIDInPlace(mHistoryID);
 
   if (gDocShellCount++ == 0) {
@@ -12093,21 +12094,17 @@ nsresult nsDocShell::EnsureFind() {
   rv = findInFrames->SetCurrentSearchFrame(windowToSearch);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return NS_OK;
 }
 
-bool nsDocShell::IsFrame() {
-  nsCOMPtr<nsIDocShellTreeItem> parent;
-  GetSameTypeParent(getter_AddRefs(parent));
-  return !!parent;
-}
+bool nsDocShell::IsFrame() { return mIsFrame; }
 
 NS_IMETHODIMP
 nsDocShell::IsBeingDestroyed(bool* aDoomed) {
   NS_ENSURE_ARG(aDoomed);
   *aDoomed = mIsBeingDestroyed;
   return NS_OK;
 }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -279,16 +279,18 @@ class nsDocShell final : public nsDocLoa
   // Notify Scroll observers when an async panning/zooming transform
   // is no longer applied
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void NotifyAsyncPanZoomStopped();
 
   void SetInFrameSwap(bool aInSwap) { mInFrameSwap = aInSwap; }
   bool InFrameSwap();
 
+  void SetIsFrame() { mIsFrame = true; };
+
   const mozilla::Encoding* GetForcedCharset() { return mForcedCharset; }
 
   mozilla::HTMLEditor* GetHTMLEditorInternal();
   nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor);
 
   // Handle page navigation due to charset changes
   nsresult CharsetChangeReloadDocument(const char* aCharset = nullptr,
                                        int32_t aSource = kCharsetUninitialized);
@@ -1149,11 +1151,13 @@ class nsDocShell final : public nsDocLoa
 
   // This flag means that mTiming has been initialized but nulled out.
   // We will check the innerWin's timing before creating a new one
   // in MaybeInitTiming()
   bool mBlankTiming : 1;
 
   // This flag indicates when the title is valid for the current URI.
   bool mTitleValidForCurrentURI : 1;
+
+  bool mIsFrame : 1;
 };
 
 #endif /* nsDocShell_h__ */
rename from dom/base/nsContentIterator.cpp
rename to dom/base/ContentIterator.cpp
--- a/dom/base/nsContentIterator.cpp
+++ b/dom/base/ContentIterator.cpp
@@ -1,30 +1,25 @@
 /* -*- 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 "ContentIterator.h"
+
 #include "mozilla/DebugOnly.h"
-#include "nsISupports.h"
-#include "nsIContentIterator.h"
-#include "nsRange.h"
-#include "nsIContent.h"
-#include "nsCOMPtr.h"
-#include "nsTArray.h"
+#include "mozilla/RangeBoundary.h"
+
 #include "nsContentUtils.h"
-#include "nsINode.h"
-#include "nsCycleCollectionParticipant.h"
 #include "nsElementTable.h"
+#include "nsIContent.h"
+#include "nsRange.h"
 
-using mozilla::DebugOnly;
-using mozilla::RawRangeBoundary;
-
-// couple of utility static functs
+namespace mozilla {
 
 ///////////////////////////////////////////////////////////////////////////
 // NodeIsInTraversalRange: returns true if content is visited during
 // the traversal of the range in the specified mode.
 //
 static bool NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode,
                                    const RawRangeBoundary& aStart,
                                    const RawRangeBoundary& aEnd) {
@@ -68,149 +63,24 @@ static bool NodeIsInTraversalRange(nsINo
   }
 
   // Pre mode: start <= node < end.
   RawRangeBoundary beforeNode(parent, aNode->GetPreviousSibling());
   return nsContentUtils::ComparePoints(aStart, beforeNode) <= 0 &&
          nsContentUtils::ComparePoints(aEnd, beforeNode) > 0;
 }
 
-/*
- *  A simple iterator class for traversing the content in "close tag" order
- */
-class nsContentIterator : public nsIContentIterator {
- public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(nsContentIterator)
-
-  explicit nsContentIterator(bool aPre);
-
-  // nsIContentIterator interface methods ------------------------------
-
-  virtual nsresult Init(nsINode* aRoot) override;
-
-  virtual nsresult Init(nsRange* aRange) override;
-
-  virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
-                        nsINode* aEndContainer, uint32_t aEndOffset) override;
-
-  virtual nsresult Init(const RawRangeBoundary& aStart,
-                        const RawRangeBoundary& aEnd) override;
-
-  virtual void First() override;
-
-  virtual void Last() override;
-
-  virtual void Next() override;
-
-  virtual void Prev() override;
-
-  virtual nsINode* GetCurrentNode() override;
-
-  virtual bool IsDone() override;
-
-  virtual nsresult PositionAt(nsINode* aCurNode) override;
-
- protected:
-  virtual ~nsContentIterator();
-
-  /**
-   * Callers must guarantee that:
-   * - Neither aStartContainer nor aEndContainer is nullptr.
-   * - aStartOffset and aEndOffset are valid for its container.
-   * - The start point and the end point are in document order.
-   */
-  nsresult InitInternal(const RawRangeBoundary& aStart,
-                        const RawRangeBoundary& aEnd);
-
-  // Recursively get the deepest first/last child of aRoot.  This will return
-  // aRoot itself if it has no children.
-  nsINode* GetDeepFirstChild(nsINode* aRoot);
-  nsIContent* GetDeepFirstChild(nsIContent* aRoot);
-  nsINode* GetDeepLastChild(nsINode* aRoot);
-  nsIContent* GetDeepLastChild(nsIContent* aRoot);
-
-  // Get the next/previous sibling of aNode, or its parent's, or grandparent's,
-  // etc.  Returns null if aNode and all its ancestors have no next/previous
-  // sibling.
-  nsIContent* GetNextSibling(nsINode* aNode);
-  nsIContent* GetPrevSibling(nsINode* aNode);
-
-  nsINode* NextNode(nsINode* aNode);
-  nsINode* PrevNode(nsINode* aNode);
-
-  void MakeEmpty();
-
-  virtual void LastRelease();
-
-  nsCOMPtr<nsINode> mCurNode;
-  nsCOMPtr<nsINode> mFirst;
-  nsCOMPtr<nsINode> mLast;
-  nsCOMPtr<nsINode> mCommonParent;
-
-  bool mIsDone;
-  bool mPre;
-
- private:
-  // no copies or assigns  FIX ME
-  nsContentIterator(const nsContentIterator&);
-  nsContentIterator& operator=(const nsContentIterator&);
-};
-
-/******************************************************
- * repository cruft
- ******************************************************/
-
-already_AddRefed<nsIContentIterator> NS_NewContentIterator() {
-  nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(false);
-  return iter.forget();
-}
-
-already_AddRefed<nsIContentIterator> NS_NewPreContentIterator() {
-  nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(true);
-  return iter.forget();
-}
-
-/******************************************************
- * XPCOM cruft
- ******************************************************/
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentIterator)
-NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsContentIterator,
-                                                   LastRelease())
-
-NS_INTERFACE_MAP_BEGIN(nsContentIterator)
-  NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentIterator)
-  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsContentIterator)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTION(nsContentIterator, mCurNode, mFirst, mLast,
-                         mCommonParent)
-
-void nsContentIterator::LastRelease() {
-  mCurNode = nullptr;
-  mFirst = nullptr;
-  mLast = nullptr;
-  mCommonParent = nullptr;
-}
-
-/******************************************************
- * constructor/destructor
- ******************************************************/
-
-nsContentIterator::nsContentIterator(bool aPre) : mIsDone(false), mPre(aPre) {}
-
-nsContentIterator::~nsContentIterator() {}
+ContentIteratorBase::ContentIteratorBase(bool aPre)
+    : mIsDone(false), mPre(aPre) {}
 
 /******************************************************
  * Init routines
  ******************************************************/
 
-nsresult nsContentIterator::Init(nsINode* aRoot) {
+nsresult ContentIteratorBase::Init(nsINode* aRoot) {
   if (NS_WARN_IF(!aRoot)) {
     return NS_ERROR_NULL_POINTER;
   }
 
   mIsDone = false;
 
   if (mPre) {
     mFirst = aRoot;
@@ -222,58 +92,59 @@ nsresult nsContentIterator::Init(nsINode
     mLast = aRoot;
   }
 
   mCommonParent = aRoot;
   mCurNode = mFirst;
   return NS_OK;
 }
 
-nsresult nsContentIterator::Init(nsRange* aRange) {
+nsresult ContentIteratorBase::Init(nsRange* aRange) {
   mIsDone = false;
 
   if (NS_WARN_IF(!aRange)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (NS_WARN_IF(!aRange->IsPositioned())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   return InitInternal(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw());
 }
 
-nsresult nsContentIterator::Init(nsINode* aStartContainer,
-                                 uint32_t aStartOffset, nsINode* aEndContainer,
-                                 uint32_t aEndOffset) {
+nsresult ContentIteratorBase::Init(nsINode* aStartContainer,
+                                   uint32_t aStartOffset,
+                                   nsINode* aEndContainer,
+                                   uint32_t aEndOffset) {
   mIsDone = false;
 
   if (NS_WARN_IF(!nsRange::IsValidPoints(aStartContainer, aStartOffset,
                                          aEndContainer, aEndOffset))) {
     return NS_ERROR_INVALID_ARG;
   }
 
   return InitInternal(RawRangeBoundary(aStartContainer, aStartOffset),
                       RawRangeBoundary(aEndContainer, aEndOffset));
 }
 
-nsresult nsContentIterator::Init(const RawRangeBoundary& aStart,
-                                 const RawRangeBoundary& aEnd) {
+nsresult ContentIteratorBase::Init(const RawRangeBoundary& aStart,
+                                   const RawRangeBoundary& aEnd) {
   mIsDone = false;
 
   if (NS_WARN_IF(!nsRange::IsValidPoints(aStart.Container(), aStart.Offset(),
                                          aEnd.Container(), aEnd.Offset()))) {
     return NS_ERROR_INVALID_ARG;
   }
 
   return InitInternal(aStart, aEnd);
 }
 
-nsresult nsContentIterator::InitInternal(const RawRangeBoundary& aStart,
-                                         const RawRangeBoundary& aEnd) {
+nsresult ContentIteratorBase::InitInternal(const RawRangeBoundary& aStart,
+                                           const RawRangeBoundary& aEnd) {
   // get common content parent
   mCommonParent =
       nsContentUtils::GetCommonAncestor(aStart.Container(), aEnd.Container());
   if (NS_WARN_IF(!mCommonParent)) {
     return NS_ERROR_FAILURE;
   }
 
   bool startIsData = aStart.Container()->IsCharacterData();
@@ -415,17 +286,17 @@ nsresult nsContentIterator::InitInternal
         mLast = aEnd.Container()->AsContent();
       }
     }
   } else {
     cChild = aEnd.Ref();
 
     if (NS_WARN_IF(!cChild)) {
       // No child at offset!
-      MOZ_ASSERT_UNREACHABLE("nsContentIterator::nsContentIterator");
+      MOZ_ASSERT_UNREACHABLE("ContentIterator::ContentIterator");
       return NS_ERROR_FAILURE;
     }
 
     if (mPre) {
       mLast = GetDeepLastChild(cChild);
       NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
 
       if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre, aStart, aEnd))) {
@@ -445,71 +316,71 @@ nsresult nsContentIterator::InitInternal
   }
 
   mCurNode = mFirst;
   mIsDone = !mCurNode;
 
   return NS_OK;
 }
 
-void nsContentIterator::MakeEmpty() {
+void ContentIteratorBase::MakeEmpty() {
   mCurNode = nullptr;
   mFirst = nullptr;
   mLast = nullptr;
   mCommonParent = nullptr;
   mIsDone = true;
 }
 
-nsINode* nsContentIterator::GetDeepFirstChild(nsINode* aRoot) {
+nsINode* ContentIteratorBase::GetDeepFirstChild(nsINode* aRoot) {
   if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
     return aRoot;
   }
 
   return GetDeepFirstChild(aRoot->GetFirstChild());
 }
 
-nsIContent* nsContentIterator::GetDeepFirstChild(nsIContent* aRoot) {
+nsIContent* ContentIteratorBase::GetDeepFirstChild(nsIContent* aRoot) {
   if (NS_WARN_IF(!aRoot)) {
     return nullptr;
   }
 
   nsIContent* node = aRoot;
   nsIContent* child = node->GetFirstChild();
 
   while (child) {
     node = child;
     child = node->GetFirstChild();
   }
 
   return node;
 }
 
-nsINode* nsContentIterator::GetDeepLastChild(nsINode* aRoot) {
+nsINode* ContentIteratorBase::GetDeepLastChild(nsINode* aRoot) {
   if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
     return aRoot;
   }
 
   return GetDeepLastChild(aRoot->GetLastChild());
 }
 
-nsIContent* nsContentIterator::GetDeepLastChild(nsIContent* aRoot) {
+nsIContent* ContentIteratorBase::GetDeepLastChild(nsIContent* aRoot) {
   if (NS_WARN_IF(!aRoot)) {
     return nullptr;
   }
 
   nsIContent* node = aRoot;
   while (node->HasChildren()) {
     nsIContent* child = node->GetLastChild();
     node = child;
   }
   return node;
 }
 
 // Get the next sibling, or parent's next sibling, or grandpa's next sibling...
-nsIContent* nsContentIterator::GetNextSibling(nsINode* aNode) {
+nsIContent* ContentIteratorBase::GetNextSibling(nsINode* aNode) {
   if (NS_WARN_IF(!aNode)) {
     return nullptr;
   }
 
   if (aNode->GetNextSibling()) {
     return aNode->GetNextSibling();
   }
 
@@ -524,17 +395,17 @@ nsIContent* nsContentIterator::GetNextSi
   if (parent->GetLastChild() && parent->GetLastChild() != aNode) {
     return parent->GetFirstChild();
   }
 
   return GetNextSibling(parent);
 }
 
 // Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling...
-nsIContent* nsContentIterator::GetPrevSibling(nsINode* aNode) {
+nsIContent* ContentIteratorBase::GetPrevSibling(nsINode* aNode) {
   if (NS_WARN_IF(!aNode)) {
     return nullptr;
   }
 
   if (aNode->GetPreviousSibling()) {
     return aNode->GetPreviousSibling();
   }
 
@@ -548,17 +419,17 @@ nsIContent* nsContentIterator::GetPrevSi
   // the last child of our parent.
   if (parent->GetFirstChild() && parent->GetFirstChild() != aNode) {
     return parent->GetLastChild();
   }
 
   return GetPrevSibling(parent);
 }
 
-nsINode* nsContentIterator::NextNode(nsINode* aNode) {
+nsINode* ContentIteratorBase::NextNode(nsINode* aNode) {
   nsINode* node = aNode;
 
   // if we are a Pre-order iterator, use pre-order
   if (mPre) {
     // if it has children then next node is first child
     if (node->HasChildren()) {
       nsIContent* firstChild = node->GetFirstChild();
       MOZ_ASSERT(firstChild);
@@ -582,17 +453,17 @@ nsINode* nsContentIterator::NextNode(nsI
   if (sibling) {
     // next node is sibling's "deep left" child
     return GetDeepFirstChild(sibling);
   }
 
   return parent;
 }
 
-nsINode* nsContentIterator::PrevNode(nsINode* aNode) {
+nsINode* ContentIteratorBase::PrevNode(nsINode* aNode) {
   nsINode* node = aNode;
 
   // if we are a Pre-order iterator, use pre-order
   if (mPre) {
     nsINode* parent = node->GetParentNode();
     if (NS_WARN_IF(!parent)) {
       MOZ_ASSERT(parent, "The node is the root node but not the first node");
       mIsDone = true;
@@ -612,73 +483,73 @@ nsINode* nsContentIterator::PrevNode(nsI
     return node->GetLastChild();
   }
 
   // else prev sibling is previous
   return GetPrevSibling(node);
 }
 
 /******************************************************
- * ContentIterator routines
+ * ContentIteratorBase routines
  ******************************************************/
 
-void nsContentIterator::First() {
+void ContentIteratorBase::First() {
   if (mFirst) {
     mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
     NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
   }
 
   mIsDone = mFirst == nullptr;
 }
 
-void nsContentIterator::Last() {
+void ContentIteratorBase::Last() {
   // Note that mLast can be nullptr if MakeEmpty() is called in Init() since
   // at that time, Init() returns NS_OK.
   if (!mLast) {
     MOZ_ASSERT(mIsDone);
     return;
   }
 
   mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
   NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
 
   mIsDone = mLast == nullptr;
 }
 
-void nsContentIterator::Next() {
+void ContentIteratorBase::Next() {
   if (mIsDone || NS_WARN_IF(!mCurNode)) {
     return;
   }
 
   if (mCurNode == mLast) {
     mIsDone = true;
     return;
   }
 
   mCurNode = NextNode(mCurNode);
 }
 
-void nsContentIterator::Prev() {
+void ContentIteratorBase::Prev() {
   if (NS_WARN_IF(mIsDone) || NS_WARN_IF(!mCurNode)) {
     return;
   }
 
   if (mCurNode == mFirst) {
     mIsDone = true;
     return;
   }
 
   mCurNode = PrevNode(mCurNode);
 }
 
-bool nsContentIterator::IsDone() { return mIsDone; }
+bool ContentIteratorBase::IsDone() { return mIsDone; }
 
 // Keeping arrays of indexes for the stack of nodes makes PositionAt
 // interesting...
-nsresult nsContentIterator::PositionAt(nsINode* aCurNode) {
+nsresult ContentIteratorBase::PositionAt(nsINode* aCurNode) {
   if (NS_WARN_IF(!aCurNode)) {
     return NS_ERROR_NULL_POINTER;
   }
 
   // take an early out if this doesn't actually change the position
   if (mCurNode == aCurNode) {
     mIsDone = false;
     return NS_OK;
@@ -727,149 +598,58 @@ nsresult nsContentIterator::PositionAt(n
     mIsDone = true;
     return NS_ERROR_FAILURE;
   }
 
   mIsDone = false;
   return NS_OK;
 }
 
-nsINode* nsContentIterator::GetCurrentNode() {
+nsINode* ContentIteratorBase::GetCurrentNode() {
   if (mIsDone) {
     return nullptr;
   }
 
   NS_ASSERTION(mCurNode, "Null current node in an iterator that's not done!");
 
   return mCurNode;
 }
 
-/*====================================================================================*/
-/*====================================================================================*/
-
 /******************************************************
- * nsContentSubtreeIterator
+ * ContentSubtreeIterator init routines
  ******************************************************/
 
-/*
- *  A simple iterator class for traversing the content in "top subtree" order
- */
-class nsContentSubtreeIterator : public nsContentIterator {
- public:
-  nsContentSubtreeIterator() : nsContentIterator(false) {}
-
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsContentSubtreeIterator,
-                                           nsContentIterator)
-
-  // nsContentIterator overrides ------------------------------
-
-  virtual nsresult Init(nsINode* aRoot) override;
-
-  virtual nsresult Init(nsRange* aRange) override;
-
-  virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
-                        nsINode* aEndContainer, uint32_t aEndOffset) override;
-
-  virtual nsresult Init(const RawRangeBoundary& aStart,
-                        const RawRangeBoundary& aEnd) override;
-
-  virtual void Next() override;
-
-  virtual void Prev() override;
-
-  virtual nsresult PositionAt(nsINode* aCurNode) override;
-
-  // Must override these because we don't do PositionAt
-  virtual void First() override;
-
-  // Must override these because we don't do PositionAt
-  virtual void Last() override;
-
- protected:
-  virtual ~nsContentSubtreeIterator() {}
-
-  /**
-   * Callers must guarantee that mRange isn't nullptr and is positioned.
-   */
-  nsresult InitWithRange();
-
-  // Returns the highest inclusive ancestor of aNode that's in the range
-  // (possibly aNode itself).  Returns null if aNode is null, or is not itself
-  // in the range.  A node is in the range if (node, 0) comes strictly after
-  // the range endpoint, and (node, node.length) comes strictly before it, so
-  // the range's start and end nodes will never be considered "in" it.
-  nsIContent* GetTopAncestorInRange(nsINode* aNode);
-
-  // no copy's or assigns  FIX ME
-  nsContentSubtreeIterator(const nsContentSubtreeIterator&);
-  nsContentSubtreeIterator& operator=(const nsContentSubtreeIterator&);
-
-  virtual void LastRelease() override;
-
-  RefPtr<nsRange> mRange;
-
-  AutoTArray<nsIContent*, 8> mEndNodes;
-};
-
-NS_IMPL_ADDREF_INHERITED(nsContentSubtreeIterator, nsContentIterator)
-NS_IMPL_RELEASE_INHERITED(nsContentSubtreeIterator, nsContentIterator)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSubtreeIterator)
-NS_INTERFACE_MAP_END_INHERITING(nsContentIterator)
-
-NS_IMPL_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator, nsContentIterator,
-                                   mRange)
-
-void nsContentSubtreeIterator::LastRelease() {
-  mRange = nullptr;
-  nsContentIterator::LastRelease();
-}
-
-/******************************************************
- * repository cruft
- ******************************************************/
-
-already_AddRefed<nsIContentIterator> NS_NewContentSubtreeIterator() {
-  nsCOMPtr<nsIContentIterator> iter = new nsContentSubtreeIterator();
-  return iter.forget();
-}
-
-/******************************************************
- * Init routines
- ******************************************************/
-
-nsresult nsContentSubtreeIterator::Init(nsINode* aRoot) {
+nsresult ContentSubtreeIterator::Init(nsINode* aRoot) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-nsresult nsContentSubtreeIterator::Init(nsRange* aRange) {
+nsresult ContentSubtreeIterator::Init(nsRange* aRange) {
   MOZ_ASSERT(aRange);
 
   mIsDone = false;
 
   if (NS_WARN_IF(!aRange->IsPositioned())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mRange = aRange;
 
   return InitWithRange();
 }
 
-nsresult nsContentSubtreeIterator::Init(nsINode* aStartContainer,
-                                        uint32_t aStartOffset,
-                                        nsINode* aEndContainer,
-                                        uint32_t aEndOffset) {
+nsresult ContentSubtreeIterator::Init(nsINode* aStartContainer,
+                                      uint32_t aStartOffset,
+                                      nsINode* aEndContainer,
+                                      uint32_t aEndOffset) {
   return Init(RawRangeBoundary(aStartContainer, aStartOffset),
               RawRangeBoundary(aEndContainer, aEndOffset));
 }
 
-nsresult nsContentSubtreeIterator::Init(const RawRangeBoundary& aStart,
-                                        const RawRangeBoundary& aEnd) {
+nsresult ContentSubtreeIterator::Init(const RawRangeBoundary& aStart,
+                                      const RawRangeBoundary& aEnd) {
   mIsDone = false;
 
   RefPtr<nsRange> range;
   nsresult rv = nsRange::CreateRange(aStart, aEnd, getter_AddRefs(range));
   if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!range) ||
       NS_WARN_IF(!range->IsPositioned())) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -879,17 +659,17 @@ nsresult nsContentSubtreeIterator::Init(
     return NS_ERROR_UNEXPECTED;
   }
 
   mRange = std::move(range);
 
   return InitWithRange();
 }
 
-nsresult nsContentSubtreeIterator::InitWithRange() {
+nsresult ContentSubtreeIterator::InitWithRange() {
   MOZ_ASSERT(mRange);
   MOZ_ASSERT(mRange->IsPositioned());
 
   // get the start node and offset, convert to nsINode
   mCommonParent = mRange->GetCommonAncestor();
   nsINode* startContainer = mRange->GetStartContainer();
   int32_t startOffset = mRange->StartOffset();
   nsINode* endContainer = mRange->GetEndContainer();
@@ -978,17 +758,17 @@ nsresult nsContentSubtreeIterator::InitW
     offset = numChildren;
   }
   if (!offset || !numChildren) {
     node = endContainer;
   } else {
     lastCandidate = mRange->EndRef().Ref();
     MOZ_ASSERT(lastCandidate == endContainer->GetChildAt_Deprecated(--offset));
     NS_ASSERTION(lastCandidate,
-                 "tree traversal trouble in nsContentSubtreeIterator::Init");
+                 "tree traversal trouble in ContentSubtreeIterator::Init");
   }
 
   if (!lastCandidate) {
     // then lastCandidate is prev node before node
     lastCandidate = GetPrevSibling(node);
   }
 
   if (!lastCandidate) {
@@ -1015,34 +795,34 @@ nsresult nsContentSubtreeIterator::InitW
   mLast = GetTopAncestorInRange(lastCandidate);
 
   mCurNode = mFirst;
 
   return NS_OK;
 }
 
 /****************************************************************
- * nsContentSubtreeIterator overrides of ContentIterator routines
+ * ContentSubtreeIterator overrides of ContentIterator routines
  ****************************************************************/
 
 // we can't call PositionAt in a subtree iterator...
-void nsContentSubtreeIterator::First() {
+void ContentSubtreeIterator::First() {
   mIsDone = mFirst == nullptr;
 
   mCurNode = mFirst;
 }
 
 // we can't call PositionAt in a subtree iterator...
-void nsContentSubtreeIterator::Last() {
+void ContentSubtreeIterator::Last() {
   mIsDone = mLast == nullptr;
 
   mCurNode = mLast;
 }
 
-void nsContentSubtreeIterator::Next() {
+void ContentSubtreeIterator::Next() {
   if (mIsDone || !mCurNode) {
     return;
   }
 
   if (mCurNode == mLast) {
     mIsDone = true;
     return;
   }
@@ -1067,17 +847,17 @@ void nsContentSubtreeIterator::Next() {
   mCurNode = nextNode;
 
   // This shouldn't be needed, but since our selection code can put us
   // in a situation where mLast is in generated content, we need this
   // to stop the iterator when we've walked past past the last node!
   mIsDone = mCurNode == nullptr;
 }
 
-void nsContentSubtreeIterator::Prev() {
+void ContentSubtreeIterator::Prev() {
   // Prev should be optimized to use the mStartNodes, just as Next
   // uses mEndNodes.
   if (mIsDone || !mCurNode) {
     return;
   }
 
   if (mCurNode == mFirst) {
     mIsDone = true;
@@ -1095,27 +875,27 @@ void nsContentSubtreeIterator::Prev() {
   mCurNode = GetTopAncestorInRange(prevNode);
 
   // This shouldn't be needed, but since our selection code can put us
   // in a situation where mFirst is in generated content, we need this
   // to stop the iterator when we've walked past past the first node!
   mIsDone = mCurNode == nullptr;
 }
 
-nsresult nsContentSubtreeIterator::PositionAt(nsINode* aCurNode) {
+nsresult ContentSubtreeIterator::PositionAt(nsINode* aCurNode) {
   NS_ERROR("Not implemented!");
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 /****************************************************************
- * nsContentSubtreeIterator helper routines
+ * ContentSubtreeIterator helper routines
  ****************************************************************/
 
-nsIContent* nsContentSubtreeIterator::GetTopAncestorInRange(nsINode* aNode) {
+nsIContent* ContentSubtreeIterator::GetTopAncestorInRange(nsINode* aNode) {
   if (!aNode || !aNode->GetParentNode()) {
     return nullptr;
   }
 
   // aNode has a parent, so it must be content.
   nsIContent* content = aNode->AsContent();
 
   // sanity check: aNode is itself in the range
@@ -1145,8 +925,10 @@ nsIContent* nsContentSubtreeIterator::Ge
     if (nodeBefore || nodeAfter) {
       return content;
     }
     content = parent;
   }
 
   MOZ_CRASH("This should only be possible if aNode was null");
 }
+
+}  // namespace mozilla
copy from dom/base/nsContentIterator.cpp
copy to dom/base/ContentIterator.h
--- a/dom/base/nsContentIterator.cpp
+++ b/dom/base/ContentIterator.h
@@ -1,121 +1,61 @@
 /* -*- 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 "mozilla/DebugOnly.h"
-#include "nsISupports.h"
-#include "nsIContentIterator.h"
-#include "nsRange.h"
-#include "nsIContent.h"
-#include "nsCOMPtr.h"
-#include "nsTArray.h"
-#include "nsContentUtils.h"
-#include "nsINode.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsElementTable.h"
-
-using mozilla::DebugOnly;
-using mozilla::RawRangeBoundary;
-
-// couple of utility static functs
+#ifndef mozilla_ContentIterator_h
+#define mozilla_ContentIterator_h
 
-///////////////////////////////////////////////////////////////////////////
-// NodeIsInTraversalRange: returns true if content is visited during
-// the traversal of the range in the specified mode.
-//
-static bool NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode,
-                                   const RawRangeBoundary& aStart,
-                                   const RawRangeBoundary& aEnd) {
-  if (NS_WARN_IF(!aStart.IsSet()) || NS_WARN_IF(!aEnd.IsSet()) ||
-      NS_WARN_IF(!aNode)) {
-    return false;
-  }
+#include "mozilla/RangeBoundary.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContent.h"
+#include "nsRange.h"
+#include "nsTArray.h"
 
-  // If a leaf node contains an end point of the traversal range, it is
-  // always in the traversal range.
-  if (aNode == aStart.Container() || aNode == aEnd.Container()) {
-    if (aNode->IsCharacterData()) {
-      return true;  // text node or something
-    }
-    if (!aNode->HasChildren()) {
-      MOZ_ASSERT(
-          aNode != aStart.Container() || aStart.IsStartOfContainer(),
-          "aStart.Container() doesn't have children and not a data node, "
-          "aStart should be at the beginning of its container");
-      MOZ_ASSERT(aNode != aEnd.Container() || aEnd.IsStartOfContainer(),
-                 "aEnd.Container() doesn't have children and not a data node, "
-                 "aEnd should be at the beginning of its container");
-      return true;
-    }
-  }
-
-  nsINode* parent = aNode->GetParentNode();
-  if (!parent) {
-    return false;
-  }
+namespace mozilla {
 
-  if (!aIsPreMode) {
-    // aNode should always be content, as we have a parent, but let's just be
-    // extra careful and check.
-    nsIContent* content =
-        NS_WARN_IF(!aNode->IsContent()) ? nullptr : aNode->AsContent();
-    // Post mode: start < node <= end.
-    RawRangeBoundary afterNode(parent, content);
-    return nsContentUtils::ComparePoints(aStart, afterNode) < 0 &&
-           nsContentUtils::ComparePoints(aEnd, afterNode) >= 0;
-  }
+/**
+ * ContentIteratorBase is a base class of PostContentIterator,
+ * PreContentIterator and ContentSubtreeIterator.  Making each concrete
+ * classes "final", compiler can avoid virtual calls if they are treated
+ * by the users directly.
+ */
+class ContentIteratorBase {
+ public:
+  ContentIteratorBase() = delete;
+  ContentIteratorBase(const ContentIteratorBase&) = delete;
+  ContentIteratorBase& operator=(const ContentIteratorBase&) = delete;
+  virtual ~ContentIteratorBase() = default;
 
-  // Pre mode: start <= node < end.
-  RawRangeBoundary beforeNode(parent, aNode->GetPreviousSibling());
-  return nsContentUtils::ComparePoints(aStart, beforeNode) <= 0 &&
-         nsContentUtils::ComparePoints(aEnd, beforeNode) > 0;
-}
-
-/*
- *  A simple iterator class for traversing the content in "close tag" order
- */
-class nsContentIterator : public nsIContentIterator {
- public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(nsContentIterator)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentIteratorBase)
 
-  explicit nsContentIterator(bool aPre);
-
-  // nsIContentIterator interface methods ------------------------------
-
-  virtual nsresult Init(nsINode* aRoot) override;
-
-  virtual nsresult Init(nsRange* aRange) override;
-
+  virtual nsresult Init(nsINode* aRoot);
+  virtual nsresult Init(nsRange* aRange);
   virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
-                        nsINode* aEndContainer, uint32_t aEndOffset) override;
-
+                        nsINode* aEndContainer, uint32_t aEndOffset);
   virtual nsresult Init(const RawRangeBoundary& aStart,
-                        const RawRangeBoundary& aEnd) override;
+                        const RawRangeBoundary& aEnd);
 
-  virtual void First() override;
-
-  virtual void Last() override;
-
-  virtual void Next() override;
+  virtual void First();
+  virtual void Last();
+  virtual void Next();
+  virtual void Prev();
 
-  virtual void Prev() override;
-
-  virtual nsINode* GetCurrentNode() override;
+  virtual nsINode* GetCurrentNode();
 
-  virtual bool IsDone() override;
+  virtual bool IsDone();
 
-  virtual nsresult PositionAt(nsINode* aCurNode) override;
+  virtual nsresult PositionAt(nsINode* aCurNode);
 
  protected:
-  virtual ~nsContentIterator();
+  explicit ContentIteratorBase(bool aPre);
 
   /**
    * Callers must guarantee that:
    * - Neither aStartContainer nor aEndContainer is nullptr.
    * - aStartOffset and aEndOffset are valid for its container.
    * - The start point and the end point are in document order.
    */
   nsresult InitInternal(const RawRangeBoundary& aStart,
@@ -134,1019 +74,158 @@ class nsContentIterator : public nsICont
   nsIContent* GetNextSibling(nsINode* aNode);
   nsIContent* GetPrevSibling(nsINode* aNode);
 
   nsINode* NextNode(nsINode* aNode);
   nsINode* PrevNode(nsINode* aNode);
 
   void MakeEmpty();
 
-  virtual void LastRelease();
-
   nsCOMPtr<nsINode> mCurNode;
   nsCOMPtr<nsINode> mFirst;
   nsCOMPtr<nsINode> mLast;
   nsCOMPtr<nsINode> mCommonParent;
 
   bool mIsDone;
   bool mPre;
-
- private:
-  // no copies or assigns  FIX ME
-  nsContentIterator(const nsContentIterator&);
-  nsContentIterator& operator=(const nsContentIterator&);
+  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+                                          ContentIteratorBase&, const char*,
+                                          uint32_t);
+  friend void ImplCycleCollectionUnlink(ContentIteratorBase&);
 };
 
-/******************************************************
- * repository cruft
- ******************************************************/
-
-already_AddRefed<nsIContentIterator> NS_NewContentIterator() {
-  nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(false);
-  return iter.forget();
-}
-
-already_AddRefed<nsIContentIterator> NS_NewPreContentIterator() {
-  nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(true);
-  return iter.forget();
-}
-
-/******************************************************
- * XPCOM cruft
- ******************************************************/
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentIterator)
-NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsContentIterator,
-                                                   LastRelease())
-
-NS_INTERFACE_MAP_BEGIN(nsContentIterator)
-  NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentIterator)
-  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsContentIterator)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTION(nsContentIterator, mCurNode, mFirst, mLast,
-                         mCommonParent)
-
-void nsContentIterator::LastRelease() {
-  mCurNode = nullptr;
-  mFirst = nullptr;
-  mLast = nullptr;
-  mCommonParent = nullptr;
-}
-
-/******************************************************
- * constructor/destructor
- ******************************************************/
-
-nsContentIterator::nsContentIterator(bool aPre) : mIsDone(false), mPre(aPre) {}
-
-nsContentIterator::~nsContentIterator() {}
-
-/******************************************************
- * Init routines
- ******************************************************/
-
-nsresult nsContentIterator::Init(nsINode* aRoot) {
-  if (NS_WARN_IF(!aRoot)) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
-  mIsDone = false;
-
-  if (mPre) {
-    mFirst = aRoot;
-    mLast = GetDeepLastChild(aRoot);
-    NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
-  } else {
-    mFirst = GetDeepFirstChild(aRoot);
-    NS_WARNING_ASSERTION(mFirst, "GetDeepFirstChild returned null");
-    mLast = aRoot;
-  }
-
-  mCommonParent = aRoot;
-  mCurNode = mFirst;
-  return NS_OK;
-}
-
-nsresult nsContentIterator::Init(nsRange* aRange) {
-  mIsDone = false;
-
-  if (NS_WARN_IF(!aRange)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if (NS_WARN_IF(!aRange->IsPositioned())) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  return InitInternal(aRange->StartRef().AsRaw(), aRange->EndRef().AsRaw());
-}
-
-nsresult nsContentIterator::Init(nsINode* aStartContainer,
-                                 uint32_t aStartOffset, nsINode* aEndContainer,
-                                 uint32_t aEndOffset) {
-  mIsDone = false;
-
-  if (NS_WARN_IF(!nsRange::IsValidPoints(aStartContainer, aStartOffset,
-                                         aEndContainer, aEndOffset))) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  return InitInternal(RawRangeBoundary(aStartContainer, aStartOffset),
-                      RawRangeBoundary(aEndContainer, aEndOffset));
-}
-
-nsresult nsContentIterator::Init(const RawRangeBoundary& aStart,
-                                 const RawRangeBoundary& aEnd) {
-  mIsDone = false;
-
-  if (NS_WARN_IF(!nsRange::IsValidPoints(aStart.Container(), aStart.Offset(),
-                                         aEnd.Container(), aEnd.Offset()))) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  return InitInternal(aStart, aEnd);
+// Each concreate class of ContentIteratorBase may be owned by another class
+// which may be owned by JS.  Therefore, all of them should be in the cycle
+// collection.  However, we cannot make non-refcountable classes only with the
+// macros.  So, we need to make them cycle collectable without the macros.
+inline void ImplCycleCollectionTraverse(
+    nsCycleCollectionTraversalCallback& aCallback, ContentIteratorBase& aField,
+    const char* aName, uint32_t aFlags = 0) {
+  ImplCycleCollectionTraverse(aCallback, aField.mCurNode, aName, aFlags);
+  ImplCycleCollectionTraverse(aCallback, aField.mFirst, aName, aFlags);
+  ImplCycleCollectionTraverse(aCallback, aField.mLast, aName, aFlags);
+  ImplCycleCollectionTraverse(aCallback, aField.mCommonParent, aName, aFlags);
 }
 
-nsresult nsContentIterator::InitInternal(const RawRangeBoundary& aStart,
-                                         const RawRangeBoundary& aEnd) {
-  // get common content parent
-  mCommonParent =
-      nsContentUtils::GetCommonAncestor(aStart.Container(), aEnd.Container());
-  if (NS_WARN_IF(!mCommonParent)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  bool startIsData = aStart.Container()->IsCharacterData();
-
-  // Check to see if we have a collapsed range, if so, there is nothing to
-  // iterate over.
-  //
-  // XXX: CharacterDataNodes (text nodes) are currently an exception, since
-  //      we always want to be able to iterate text nodes at the end points
-  //      of a range.
-
-  if (!startIsData && aStart == aEnd) {
-    MakeEmpty();
-    return NS_OK;
-  }
-
-  // Handle ranges within a single character data node.
-  if (startIsData && aStart.Container() == aEnd.Container()) {
-    mFirst = aStart.Container()->AsContent();
-    mLast = mFirst;
-    mCurNode = mFirst;
-
-    return NS_OK;
-  }
-
-  // Find first node in range.
-
-  nsIContent* cChild = nullptr;
-
-  // Try to get the child at our starting point. This might return null if
-  // aStart is immediately after the last node in aStart.Container().
-  if (!startIsData) {
-    cChild = aStart.GetChildAtOffset();
-  }
-
-  if (!cChild) {
-    // No children (possibly a <br> or text node), or index is after last child.
-
-    if (mPre) {
-      // XXX: In the future, if start offset is after the last
-      //      character in the cdata node, should we set mFirst to
-      //      the next sibling?
-
-      // Normally we would skip the start node because the start node is outside
-      // of the range in pre mode. However, if aStartOffset == 0, and the node
-      // is a non-container node (e.g. <br>), we don't skip the node in this
-      // case in order to address bug 1215798.
-      bool startIsContainer = true;
-      if (aStart.Container()->IsHTMLElement()) {
-        nsAtom* name = aStart.Container()->NodeInfo()->NameAtom();
-        startIsContainer =
-            nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name));
-      }
-      if (!startIsData && (startIsContainer || !aStart.IsStartOfContainer())) {
-        mFirst = GetNextSibling(aStart.Container());
-        NS_WARNING_ASSERTION(mFirst, "GetNextSibling returned null");
-
-        // Does mFirst node really intersect the range?  The range could be
-        // 'degenerate', i.e., not collapsed but still contain no content.
-        if (mFirst &&
-            NS_WARN_IF(!NodeIsInTraversalRange(mFirst, mPre, aStart, aEnd))) {
-          mFirst = nullptr;
-        }
-      } else {
-        mFirst = aStart.Container()->AsContent();
-      }
-    } else {
-      // post-order
-      if (NS_WARN_IF(!aStart.Container()->IsContent())) {
-        // What else can we do?
-        mFirst = nullptr;
-      } else {
-        mFirst = aStart.Container()->AsContent();
-      }
-    }
-  } else {
-    if (mPre) {
-      mFirst = cChild;
-    } else {
-      // post-order
-      mFirst = GetDeepFirstChild(cChild);
-      NS_WARNING_ASSERTION(mFirst, "GetDeepFirstChild returned null");
-
-      // Does mFirst node really intersect the range?  The range could be
-      // 'degenerate', i.e., not collapsed but still contain no content.
-
-      if (mFirst && !NodeIsInTraversalRange(mFirst, mPre, aStart, aEnd)) {
-        mFirst = nullptr;
-      }
-    }
-  }
-
-  // Find last node in range.
-
-  bool endIsData = aEnd.Container()->IsCharacterData();
-
-  if (endIsData || !aEnd.Container()->HasChildren() ||
-      aEnd.IsStartOfContainer()) {
-    if (mPre) {
-      if (NS_WARN_IF(!aEnd.Container()->IsContent())) {
-        // Not much else to do here...
-        mLast = nullptr;
-      } else {
-        // If the end node is a non-container element and the end offset is 0,
-        // the last element should be the previous node (i.e., shouldn't
-        // include the end node in the range).
-        bool endIsContainer = true;
-        if (aEnd.Container()->IsHTMLElement()) {
-          nsAtom* name = aEnd.Container()->NodeInfo()->NameAtom();
-          endIsContainer =
-              nsHTMLElement::IsContainer(nsHTMLTags::AtomTagToId(name));
-        }
-        if (!endIsData && !endIsContainer && aEnd.IsStartOfContainer()) {
-          mLast = PrevNode(aEnd.Container());
-          NS_WARNING_ASSERTION(mLast, "PrevNode returned null");
-          if (mLast && mLast != mFirst &&
-              NS_WARN_IF(!NodeIsInTraversalRange(
-                  mLast, mPre, RawRangeBoundary(mFirst, 0), aEnd))) {
-            mLast = nullptr;
-          }
-        } else {
-          mLast = aEnd.Container()->AsContent();
-        }
-      }
-    } else {
-      // post-order
-      //
-      // XXX: In the future, if end offset is before the first character in the
-      //      cdata node, should we set mLast to the prev sibling?
-
-      if (!endIsData) {
-        mLast = GetPrevSibling(aEnd.Container());
-        NS_WARNING_ASSERTION(mLast, "GetPrevSibling returned null");
-
-        if (!NodeIsInTraversalRange(mLast, mPre, aStart, aEnd)) {
-          mLast = nullptr;
-        }
-      } else {
-        mLast = aEnd.Container()->AsContent();
-      }
-    }
-  } else {
-    cChild = aEnd.Ref();
-
-    if (NS_WARN_IF(!cChild)) {
-      // No child at offset!
-      MOZ_ASSERT_UNREACHABLE("nsContentIterator::nsContentIterator");
-      return NS_ERROR_FAILURE;
-    }
-
-    if (mPre) {
-      mLast = GetDeepLastChild(cChild);
-      NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
-
-      if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre, aStart, aEnd))) {
-        mLast = nullptr;
-      }
-    } else {
-      // post-order
-      mLast = cChild;
-    }
-  }
-
-  // If either first or last is null, they both have to be null!
-
-  if (!mFirst || !mLast) {
-    mFirst = nullptr;
-    mLast = nullptr;
-  }
-
-  mCurNode = mFirst;
-  mIsDone = !mCurNode;
-
-  return NS_OK;
-}
-
-void nsContentIterator::MakeEmpty() {
-  mCurNode = nullptr;
-  mFirst = nullptr;
-  mLast = nullptr;
-  mCommonParent = nullptr;
-  mIsDone = true;
+inline void ImplCycleCollectionUnlink(ContentIteratorBase& aField) {
+  ImplCycleCollectionUnlink(aField.mCurNode);
+  ImplCycleCollectionUnlink(aField.mFirst);
+  ImplCycleCollectionUnlink(aField.mLast);
+  ImplCycleCollectionUnlink(aField.mCommonParent);
 }
 
-nsINode* nsContentIterator::GetDeepFirstChild(nsINode* aRoot) {
-  if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
-    return aRoot;
-  }
-
-  return GetDeepFirstChild(aRoot->GetFirstChild());
-}
-
-nsIContent* nsContentIterator::GetDeepFirstChild(nsIContent* aRoot) {
-  if (NS_WARN_IF(!aRoot)) {
-    return nullptr;
-  }
-
-  nsIContent* node = aRoot;
-  nsIContent* child = node->GetFirstChild();
-
-  while (child) {
-    node = child;
-    child = node->GetFirstChild();
-  }
+/**
+ * A simple iterator class for traversing the content in "close tag" order.
+ */
+class PostContentIterator final : public ContentIteratorBase {
+ public:
+  PostContentIterator() : ContentIteratorBase(false) {}
+  PostContentIterator(const PostContentIterator&) = delete;
+  PostContentIterator& operator=(const PostContentIterator&) = delete;
+  virtual ~PostContentIterator() = default;
+  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+                                          PostContentIterator&, const char*,
+                                          uint32_t);
+  friend void ImplCycleCollectionUnlink(PostContentIterator&);
+};
 
-  return node;
-}
-
-nsINode* nsContentIterator::GetDeepLastChild(nsINode* aRoot) {
-  if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
-    return aRoot;
-  }
-
-  return GetDeepLastChild(aRoot->GetLastChild());
-}
-
-nsIContent* nsContentIterator::GetDeepLastChild(nsIContent* aRoot) {
-  if (NS_WARN_IF(!aRoot)) {
-    return nullptr;
-  }
-
-  nsIContent* node = aRoot;
-  while (node->HasChildren()) {
-    nsIContent* child = node->GetLastChild();
-    node = child;
-  }
-  return node;
+inline void ImplCycleCollectionTraverse(
+    nsCycleCollectionTraversalCallback& aCallback, PostContentIterator& aField,
+    const char* aName, uint32_t aFlags = 0) {
+  ImplCycleCollectionTraverse(
+      aCallback, static_cast<ContentIteratorBase&>(aField), aName, aFlags);
 }
 
-// Get the next sibling, or parent's next sibling, or grandpa's next sibling...
-nsIContent* nsContentIterator::GetNextSibling(nsINode* aNode) {
-  if (NS_WARN_IF(!aNode)) {
-    return nullptr;
-  }
-
-  if (aNode->GetNextSibling()) {
-    return aNode->GetNextSibling();
-  }
-
-  nsINode* parent = aNode->GetParentNode();
-  if (NS_WARN_IF(!parent)) {
-    return nullptr;
-  }
-
-  // XXX This is a hack to preserve previous behaviour: This should be fixed
-  // in bug 1404916. If we were positioned on anonymous content, move to
-  // the first child of our parent.
-  if (parent->GetLastChild() && parent->GetLastChild() != aNode) {
-    return parent->GetFirstChild();
-  }
-
-  return GetNextSibling(parent);
-}
-
-// Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling...
-nsIContent* nsContentIterator::GetPrevSibling(nsINode* aNode) {
-  if (NS_WARN_IF(!aNode)) {
-    return nullptr;
-  }
-
-  if (aNode->GetPreviousSibling()) {
-    return aNode->GetPreviousSibling();
-  }
-
-  nsINode* parent = aNode->GetParentNode();
-  if (NS_WARN_IF(!parent)) {
-    return nullptr;
-  }
-
-  // XXX This is a hack to preserve previous behaviour: This should be fixed
-  // in bug 1404916. If we were positioned on anonymous content, move to
-  // the last child of our parent.
-  if (parent->GetFirstChild() && parent->GetFirstChild() != aNode) {
-    return parent->GetLastChild();
-  }
-
-  return GetPrevSibling(parent);
-}
-
-nsINode* nsContentIterator::NextNode(nsINode* aNode) {
-  nsINode* node = aNode;
-
-  // if we are a Pre-order iterator, use pre-order
-  if (mPre) {
-    // if it has children then next node is first child
-    if (node->HasChildren()) {
-      nsIContent* firstChild = node->GetFirstChild();
-      MOZ_ASSERT(firstChild);
-
-      return firstChild;
-    }
-
-    // else next sibling is next
-    return GetNextSibling(node);
-  }
-
-  // post-order
-  nsINode* parent = node->GetParentNode();
-  if (NS_WARN_IF(!parent)) {
-    MOZ_ASSERT(parent, "The node is the root node but not the last node");
-    mIsDone = true;
-    return node;
-  }
-
-  nsIContent* sibling = node->GetNextSibling();
-  if (sibling) {
-    // next node is sibling's "deep left" child
-    return GetDeepFirstChild(sibling);
-  }
-
-  return parent;
+inline void ImplCycleCollectionUnlink(PostContentIterator& aField) {
+  ImplCycleCollectionUnlink(static_cast<ContentIteratorBase&>(aField));
 }
 
-nsINode* nsContentIterator::PrevNode(nsINode* aNode) {
-  nsINode* node = aNode;
-
-  // if we are a Pre-order iterator, use pre-order
-  if (mPre) {
-    nsINode* parent = node->GetParentNode();
-    if (NS_WARN_IF(!parent)) {
-      MOZ_ASSERT(parent, "The node is the root node but not the first node");
-      mIsDone = true;
-      return aNode;
-    }
-
-    nsIContent* sibling = node->GetPreviousSibling();
-    if (sibling) {
-      return GetDeepLastChild(sibling);
-    }
-
-    return parent;
-  }
-
-  // post-order
-  if (node->HasChildren()) {
-    return node->GetLastChild();
-  }
-
-  // else prev sibling is previous
-  return GetPrevSibling(node);
-}
-
-/******************************************************
- * ContentIterator routines
- ******************************************************/
-
-void nsContentIterator::First() {
-  if (mFirst) {
-    mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
-    NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
-  }
+/**
+ * A simple iterator class for traversing the content in "start tag" order.
+ */
+class PreContentIterator final : public ContentIteratorBase {
+ public:
+  PreContentIterator() : ContentIteratorBase(true) {}
+  PreContentIterator(const PreContentIterator&) = delete;
+  PreContentIterator& operator=(const PreContentIterator&) = delete;
+  virtual ~PreContentIterator() = default;
+  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+                                          PreContentIterator&, const char*,
+                                          uint32_t);
+  friend void ImplCycleCollectionUnlink(PreContentIterator&);
+};
 
-  mIsDone = mFirst == nullptr;
-}
-
-void nsContentIterator::Last() {
-  // Note that mLast can be nullptr if MakeEmpty() is called in Init() since
-  // at that time, Init() returns NS_OK.
-  if (!mLast) {
-    MOZ_ASSERT(mIsDone);
-    return;
-  }
-
-  mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
-  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
-
-  mIsDone = mLast == nullptr;
-}
-
-void nsContentIterator::Next() {
-  if (mIsDone || NS_WARN_IF(!mCurNode)) {
-    return;
-  }
-
-  if (mCurNode == mLast) {
-    mIsDone = true;
-    return;
-  }
-
-  mCurNode = NextNode(mCurNode);
-}
-
-void nsContentIterator::Prev() {
-  if (NS_WARN_IF(mIsDone) || NS_WARN_IF(!mCurNode)) {
-    return;
-  }
-
-  if (mCurNode == mFirst) {
-    mIsDone = true;
-    return;
-  }
-
-  mCurNode = PrevNode(mCurNode);
+inline void ImplCycleCollectionTraverse(
+    nsCycleCollectionTraversalCallback& aCallback, PreContentIterator& aField,
+    const char* aName, uint32_t aFlags = 0) {
+  ImplCycleCollectionTraverse(
+      aCallback, static_cast<ContentIteratorBase&>(aField), aName, aFlags);
 }
 
-bool nsContentIterator::IsDone() { return mIsDone; }
-
-// Keeping arrays of indexes for the stack of nodes makes PositionAt
-// interesting...
-nsresult nsContentIterator::PositionAt(nsINode* aCurNode) {
-  if (NS_WARN_IF(!aCurNode)) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
-  // take an early out if this doesn't actually change the position
-  if (mCurNode == aCurNode) {
-    mIsDone = false;
-    return NS_OK;
-  }
-  mCurNode = aCurNode;
-
-  // Check to see if the node falls within the traversal range.
-
-  RawRangeBoundary first(mFirst, 0);
-  RawRangeBoundary last(mLast, 0);
-
-  if (mFirst && mLast) {
-    if (mPre) {
-      // In pre we want to record the point immediately before mFirst, which is
-      // the point immediately after mFirst's previous sibling.
-      first.SetAfterRef(mFirst->GetParentNode(), mFirst->GetPreviousSibling());
-
-      // If mLast has no children, then we want to make sure to include it.
-      if (!mLast->HasChildren()) {
-        last.SetAfterRef(mLast->GetParentNode(), mLast->AsContent());
-      }
-    } else {
-      // If the first node has any children, we want to be immediately after the
-      // last. Otherwise we want to be immediately before mFirst.
-      if (mFirst->HasChildren()) {
-        first.SetAfterRef(mFirst, mFirst->GetLastChild());
-      } else {
-        first.SetAfterRef(mFirst->GetParentNode(),
-                          mFirst->GetPreviousSibling());
-      }
-
-      // Set the last point immediately after the final node.
-      last.SetAfterRef(mLast->GetParentNode(), mLast->AsContent());
-    }
-  }
-
-  NS_WARNING_ASSERTION(first.IsSetAndValid(), "first is not valid");
-  NS_WARNING_ASSERTION(last.IsSetAndValid(), "last is not valid");
-
-  // The end positions are always in the range even if it has no parent.  We
-  // need to allow that or 'iter->Init(root)' would assert in Last() or First()
-  // for example, bug 327694.
-  if (mFirst != mCurNode && mLast != mCurNode &&
-      (NS_WARN_IF(!first.IsSet()) || NS_WARN_IF(!last.IsSet()) ||
-       NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mPre, first, last)))) {
-    mIsDone = true;
-    return NS_ERROR_FAILURE;
-  }
-
-  mIsDone = false;
-  return NS_OK;
+inline void ImplCycleCollectionUnlink(PreContentIterator& aField) {
+  ImplCycleCollectionUnlink(static_cast<ContentIteratorBase&>(aField));
 }
 
-nsINode* nsContentIterator::GetCurrentNode() {
-  if (mIsDone) {
-    return nullptr;
-  }
-
-  NS_ASSERTION(mCurNode, "Null current node in an iterator that's not done!");
-
-  return mCurNode;
-}
-
-/*====================================================================================*/
-/*====================================================================================*/
-
-/******************************************************
- * nsContentSubtreeIterator
- ******************************************************/
-
-/*
- *  A simple iterator class for traversing the content in "top subtree" order
+/**
+ *  A simple iterator class for traversing the content in "top subtree" order.
  */
-class nsContentSubtreeIterator : public nsContentIterator {
+class ContentSubtreeIterator final : public ContentIteratorBase {
  public:
-  nsContentSubtreeIterator() : nsContentIterator(false) {}
-
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsContentSubtreeIterator,
-                                           nsContentIterator)
-
-  // nsContentIterator overrides ------------------------------
+  ContentSubtreeIterator() : ContentIteratorBase(true) {}
+  ContentSubtreeIterator(const ContentSubtreeIterator&) = delete;
+  ContentSubtreeIterator& operator=(const ContentSubtreeIterator&) = delete;
+  virtual ~ContentSubtreeIterator() = default;
 
   virtual nsresult Init(nsINode* aRoot) override;
-
   virtual nsresult Init(nsRange* aRange) override;
-
   virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
                         nsINode* aEndContainer, uint32_t aEndOffset) override;
-
   virtual nsresult Init(const RawRangeBoundary& aStart,
                         const RawRangeBoundary& aEnd) override;
 
   virtual void Next() override;
-
   virtual void Prev() override;
+  // Must override these because we don't do PositionAt
+  virtual void First() override;
+  // Must override these because we don't do PositionAt
+  virtual void Last() override;
 
   virtual nsresult PositionAt(nsINode* aCurNode) override;
 
-  // Must override these because we don't do PositionAt
-  virtual void First() override;
-
-  // Must override these because we don't do PositionAt
-  virtual void Last() override;
+  friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
+                                          ContentSubtreeIterator&, const char*,
+                                          uint32_t);
+  friend void ImplCycleCollectionUnlink(ContentSubtreeIterator&);
 
  protected:
-  virtual ~nsContentSubtreeIterator() {}
-
   /**
    * Callers must guarantee that mRange isn't nullptr and is positioned.
    */
   nsresult InitWithRange();
 
   // Returns the highest inclusive ancestor of aNode that's in the range
   // (possibly aNode itself).  Returns null if aNode is null, or is not itself
   // in the range.  A node is in the range if (node, 0) comes strictly after
   // the range endpoint, and (node, node.length) comes strictly before it, so
   // the range's start and end nodes will never be considered "in" it.
   nsIContent* GetTopAncestorInRange(nsINode* aNode);
 
-  // no copy's or assigns  FIX ME
-  nsContentSubtreeIterator(const nsContentSubtreeIterator&);
-  nsContentSubtreeIterator& operator=(const nsContentSubtreeIterator&);
-
-  virtual void LastRelease() override;
-
   RefPtr<nsRange> mRange;
 
   AutoTArray<nsIContent*, 8> mEndNodes;
 };
 
-NS_IMPL_ADDREF_INHERITED(nsContentSubtreeIterator, nsContentIterator)
-NS_IMPL_RELEASE_INHERITED(nsContentSubtreeIterator, nsContentIterator)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSubtreeIterator)
-NS_INTERFACE_MAP_END_INHERITING(nsContentIterator)
-
-NS_IMPL_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator, nsContentIterator,
-                                   mRange)
-
-void nsContentSubtreeIterator::LastRelease() {
-  mRange = nullptr;
-  nsContentIterator::LastRelease();
-}
-
-/******************************************************
- * repository cruft
- ******************************************************/
-
-already_AddRefed<nsIContentIterator> NS_NewContentSubtreeIterator() {
-  nsCOMPtr<nsIContentIterator> iter = new nsContentSubtreeIterator();
-  return iter.forget();
-}
-
-/******************************************************
- * Init routines
- ******************************************************/
-
-nsresult nsContentSubtreeIterator::Init(nsINode* aRoot) {
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-nsresult nsContentSubtreeIterator::Init(nsRange* aRange) {
-  MOZ_ASSERT(aRange);
-
-  mIsDone = false;
-
-  if (NS_WARN_IF(!aRange->IsPositioned())) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  mRange = aRange;
-
-  return InitWithRange();
-}
-
-nsresult nsContentSubtreeIterator::Init(nsINode* aStartContainer,
-                                        uint32_t aStartOffset,
-                                        nsINode* aEndContainer,
-                                        uint32_t aEndOffset) {
-  return Init(RawRangeBoundary(aStartContainer, aStartOffset),
-              RawRangeBoundary(aEndContainer, aEndOffset));
-}
-
-nsresult nsContentSubtreeIterator::Init(const RawRangeBoundary& aStart,
-                                        const RawRangeBoundary& aEnd) {
-  mIsDone = false;
-
-  RefPtr<nsRange> range;
-  nsresult rv = nsRange::CreateRange(aStart, aEnd, getter_AddRefs(range));
-  if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!range) ||
-      NS_WARN_IF(!range->IsPositioned())) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if (NS_WARN_IF(range->StartRef() != aStart) ||
-      NS_WARN_IF(range->EndRef() != aEnd)) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  mRange = std::move(range);
-
-  return InitWithRange();
+inline void ImplCycleCollectionTraverse(
+    nsCycleCollectionTraversalCallback& aCallback,
+    ContentSubtreeIterator& aField, const char* aName, uint32_t aFlags = 0) {
+  ImplCycleCollectionTraverse(aCallback, aField.mRange, aName, aFlags);
+  ImplCycleCollectionTraverse(
+      aCallback, static_cast<ContentIteratorBase&>(aField), aName, aFlags);
 }
 
-nsresult nsContentSubtreeIterator::InitWithRange() {
-  MOZ_ASSERT(mRange);
-  MOZ_ASSERT(mRange->IsPositioned());
-
-  // get the start node and offset, convert to nsINode
-  mCommonParent = mRange->GetCommonAncestor();
-  nsINode* startContainer = mRange->GetStartContainer();
-  int32_t startOffset = mRange->StartOffset();
-  nsINode* endContainer = mRange->GetEndContainer();
-  int32_t endOffset = mRange->EndOffset();
-  MOZ_ASSERT(mCommonParent && startContainer && endContainer);
-  // Bug 767169
-  MOZ_ASSERT(uint32_t(startOffset) <= startContainer->Length() &&
-             uint32_t(endOffset) <= endContainer->Length());
-
-  // short circuit when start node == end node
-  if (startContainer == endContainer) {
-    nsINode* child = startContainer->GetFirstChild();
-
-    if (!child || startOffset == endOffset) {
-      // Text node, empty container, or collapsed
-      MakeEmpty();
-      return NS_OK;
-    }
-  }
-
-  // cache ancestors
-  mEndNodes.Clear();
-  nsIContent* endNode =
-      endContainer->IsContent() ? endContainer->AsContent() : nullptr;
-  while (endNode) {
-    mEndNodes.AppendElement(endNode);
-    endNode = endNode->GetParent();
-  }
-
-  nsIContent* firstCandidate = nullptr;
-  nsIContent* lastCandidate = nullptr;
-
-  // find first node in range
-  int32_t offset = mRange->StartOffset();
-
-  nsINode* node = nullptr;
-  if (!startContainer->GetChildCount()) {
-    // no children, start at the node itself
-    node = startContainer;
-  } else {
-    nsIContent* child = mRange->GetChildAtStartOffset();
-    MOZ_ASSERT(child == startContainer->GetChildAt_Deprecated(offset));
-    if (!child) {
-      // offset after last child
-      node = startContainer;
-    } else {
-      firstCandidate = child;
-    }
-  }
-
-  if (!firstCandidate) {
-    // then firstCandidate is next node after node
-    firstCandidate = GetNextSibling(node);
-
-    if (!firstCandidate) {
-      MakeEmpty();
-      return NS_OK;
-    }
-  }
-
-  firstCandidate = GetDeepFirstChild(firstCandidate);
-
-  // confirm that this first possible contained node is indeed contained.  Else
-  // we have a range that does not fully contain any node.
-
-  bool nodeBefore, nodeAfter;
-  MOZ_ALWAYS_SUCCEEDS(nsRange::CompareNodeToRange(firstCandidate, mRange,
-                                                  &nodeBefore, &nodeAfter));
-
-  if (nodeBefore || nodeAfter) {
-    MakeEmpty();
-    return NS_OK;
-  }
-
-  // cool, we have the first node in the range.  Now we walk up its ancestors
-  // to find the most senior that is still in the range.  That's the real first
-  // node.
-  mFirst = GetTopAncestorInRange(firstCandidate);
-
-  // now to find the last node
-  offset = mRange->EndOffset();
-  int32_t numChildren = endContainer->GetChildCount();
-
-  if (offset > numChildren) {
-    // Can happen for text nodes
-    offset = numChildren;
-  }
-  if (!offset || !numChildren) {
-    node = endContainer;
-  } else {
-    lastCandidate = mRange->EndRef().Ref();
-    MOZ_ASSERT(lastCandidate == endContainer->GetChildAt_Deprecated(--offset));
-    NS_ASSERTION(lastCandidate,
-                 "tree traversal trouble in nsContentSubtreeIterator::Init");
-  }
-
-  if (!lastCandidate) {
-    // then lastCandidate is prev node before node
-    lastCandidate = GetPrevSibling(node);
-  }
-
-  if (!lastCandidate) {
-    MakeEmpty();
-    return NS_OK;
-  }
-
-  lastCandidate = GetDeepLastChild(lastCandidate);
-
-  // confirm that this last possible contained node is indeed contained.  Else
-  // we have a range that does not fully contain any node.
-
-  MOZ_ALWAYS_SUCCEEDS(nsRange::CompareNodeToRange(lastCandidate, mRange,
-                                                  &nodeBefore, &nodeAfter));
-
-  if (nodeBefore || nodeAfter) {
-    MakeEmpty();
-    return NS_OK;
-  }
-
-  // cool, we have the last node in the range.  Now we walk up its ancestors to
-  // find the most senior that is still in the range.  That's the real first
-  // node.
-  mLast = GetTopAncestorInRange(lastCandidate);
-
-  mCurNode = mFirst;
-
-  return NS_OK;
+inline void ImplCycleCollectionUnlink(ContentSubtreeIterator& aField) {
+  ImplCycleCollectionUnlink(aField.mRange);
+  ImplCycleCollectionUnlink(static_cast<ContentIteratorBase&>(aField));
 }
 
-/****************************************************************
- * nsContentSubtreeIterator overrides of ContentIterator routines
- ****************************************************************/
-
-// we can't call PositionAt in a subtree iterator...
-void nsContentSubtreeIterator::First() {
-  mIsDone = mFirst == nullptr;
-
-  mCurNode = mFirst;
-}
-
-// we can't call PositionAt in a subtree iterator...
-void nsContentSubtreeIterator::Last() {
-  mIsDone = mLast == nullptr;
-
-  mCurNode = mLast;
-}
-
-void nsContentSubtreeIterator::Next() {
-  if (mIsDone || !mCurNode) {
-    return;
-  }
-
-  if (mCurNode == mLast) {
-    mIsDone = true;
-    return;
-  }
-
-  nsINode* nextNode = GetNextSibling(mCurNode);
-  NS_ASSERTION(nextNode, "No next sibling!?! This could mean deadlock!");
-
-  int32_t i = mEndNodes.IndexOf(nextNode);
-  while (i != -1) {
-    // as long as we are finding ancestors of the endpoint of the range,
-    // dive down into their children
-    nextNode = nextNode->GetFirstChild();
-    NS_ASSERTION(nextNode, "Iterator error, expected a child node!");
-
-    // should be impossible to get a null pointer.  If we went all the way
-    // down the child chain to the bottom without finding an interior node,
-    // then the previous node should have been the last, which was
-    // was tested at top of routine.
-    i = mEndNodes.IndexOf(nextNode);
-  }
-
-  mCurNode = nextNode;
-
-  // This shouldn't be needed, but since our selection code can put us
-  // in a situation where mLast is in generated content, we need this
-  // to stop the iterator when we've walked past past the last node!
-  mIsDone = mCurNode == nullptr;
-}
-
-void nsContentSubtreeIterator::Prev() {
-  // Prev should be optimized to use the mStartNodes, just as Next
-  // uses mEndNodes.
-  if (mIsDone || !mCurNode) {
-    return;
-  }
-
-  if (mCurNode == mFirst) {
-    mIsDone = true;
-    return;
-  }
+}  // namespace mozilla
 
-  // If any of these function calls return null, so will all succeeding ones,
-  // so mCurNode will wind up set to null.
-  nsINode* prevNode = GetDeepFirstChild(mCurNode);
-
-  prevNode = PrevNode(prevNode);
-
-  prevNode = GetDeepLastChild(prevNode);
-
-  mCurNode = GetTopAncestorInRange(prevNode);
-
-  // This shouldn't be needed, but since our selection code can put us
-  // in a situation where mFirst is in generated content, we need this
-  // to stop the iterator when we've walked past past the first node!
-  mIsDone = mCurNode == nullptr;
-}
-
-nsresult nsContentSubtreeIterator::PositionAt(nsINode* aCurNode) {
-  NS_ERROR("Not implemented!");
-
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-/****************************************************************
- * nsContentSubtreeIterator helper routines
- ****************************************************************/
-
-nsIContent* nsContentSubtreeIterator::GetTopAncestorInRange(nsINode* aNode) {
-  if (!aNode || !aNode->GetParentNode()) {
-    return nullptr;
-  }
-
-  // aNode has a parent, so it must be content.
-  nsIContent* content = aNode->AsContent();
-
-  // sanity check: aNode is itself in the range
-  bool nodeBefore, nodeAfter;
-  nsresult res =
-      nsRange::CompareNodeToRange(aNode, mRange, &nodeBefore, &nodeAfter);
-  NS_ASSERTION(NS_SUCCEEDED(res) && !nodeBefore && !nodeAfter,
-               "aNode isn't in mRange, or something else weird happened");
-  if (NS_FAILED(res) || nodeBefore || nodeAfter) {
-    return nullptr;
-  }
-
-  while (content) {
-    nsIContent* parent = content->GetParent();
-    // content always has a parent.  If its parent is the root, however --
-    // i.e., either it's not content, or it is content but its own parent is
-    // null -- then we're finished, since we don't go up to the root.
-    //
-    // We have to special-case this because CompareNodeToRange treats the root
-    // node differently -- see bug 765205.
-    if (!parent || !parent->GetParentNode()) {
-      return content;
-    }
-    MOZ_ALWAYS_SUCCEEDS(
-        nsRange::CompareNodeToRange(parent, mRange, &nodeBefore, &nodeAfter));
-
-    if (nodeBefore || nodeAfter) {
-      return content;
-    }
-    content = parent;
-  }
-
-  MOZ_CRASH("This should only be possible if aNode was null");
-}
+#endif  // #ifndef mozilla_ContentIterator_h
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1641,32 +1641,31 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(Document)
   return Element::CanSkipInCC(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Document)
   return Element::CanSkipThis(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 
-static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)", "(xhtml)",
-                                "(XLink)",  "(XSLT)",  "(XBL)", "(MathML)",
-                                "(RDF)",    "(XUL)"};
-
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     char name[512];
     nsAutoCString loadedAsData;
     if (tmp->IsLoadedAsData()) {
       loadedAsData.AssignLiteral("data");
     } else {
       loadedAsData.AssignLiteral("normal");
     }
     uint32_t nsid = tmp->GetDefaultNamespaceID();
     nsAutoCString uri;
     if (tmp->mDocumentURI) uri = tmp->mDocumentURI->GetSpecOrDefault();
+    static const char* kNSURIs[] = {"([none])", "(xmlns)", "(xml)", "(xhtml)",
+                                    "(XLink)",  "(XSLT)",  "(XBL)", "(MathML)",
+                                    "(RDF)",    "(XUL)"};
     if (nsid < ArrayLength(kNSURIs)) {
       SprintfLiteral(name, "Document %s %s %s", loadedAsData.get(),
                      kNSURIs[nsid], uri.get());
     } else {
       SprintfLiteral(name, "Document %s %s", loadedAsData.get(), uri.get());
     }
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   } else {
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -22,17 +22,16 @@
 #include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/gfx/Matrix.h"
 #include "nsAtom.h"
 #include "nsDOMAttributeMap.h"
 #include "nsIContentInlines.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "mozilla/dom/DocumentTimeline.h"
-#include "nsIContentIterator.h"
 #include "nsFlexContainerFrame.h"
 #include "nsFocusManager.h"
 #include "nsILinkHandler.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIURL.h"
 #include "nsContainerFrame.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsIPresShell.h"
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -31,17 +31,16 @@
 #include "nsAtom.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/DocumentInlines.h"
 #include "nsIDocumentEncoder.h"
-#include "nsIContentIterator.h"
 #include "nsFocusManager.h"
 #include "nsILinkHandler.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 #include "nsIFrame.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsIPresShell.h"
@@ -1744,21 +1743,16 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(FragmentOrElement)
   return FragmentOrElement::CanSkipInCC(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(FragmentOrElement)
   return FragmentOrElement::CanSkipThis(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 
-static const char* kNSURIs[] = {" ([none])", " (xmlns)",  " (xml)",
-                                " (xhtml)",  " (XLink)",  " (XSLT)",
-                                " (XBL)",    " (MathML)", " (RDF)",
-                                " (XUL)",    " (SVG)",    " (XML Events)"};
-
 // We purposefully don't TRAVERSE_BEGIN_INHERITED here.  All the bits
 // we should traverse should be added here or in nsINode::Traverse.
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
     char name[512];
     uint32_t nsid = tmp->GetNameSpaceID();
     nsAtomCString localName(tmp->NodeInfo()->NameAtom());
     nsAutoCString uri;
@@ -1789,16 +1783,20 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     nsAutoCString orphan;
     if (!tmp->IsInComposedDoc() &&
         // Ignore xbl:content, which is never in the document and hence always
         // appears to be orphaned.
         !tmp->NodeInfo()->Equals(nsGkAtoms::content, kNameSpaceID_XBL)) {
       orphan.AppendLiteral(" (orphan)");
     }
 
+    static const char* kNSURIs[] = {" ([none])", " (xmlns)",  " (xml)",
+                                    " (xhtml)",  " (XLink)",  " (XSLT)",
+                                    " (XBL)",    " (MathML)", " (RDF)",
+                                    " (XUL)",    " (SVG)",    " (XML Events)"};
     const char* nsuri = nsid < ArrayLength(kNSURIs) ? kNSURIs[nsid] : "";
     SprintfLiteral(name, "FragmentOrElement%s %s%s%s%s %s", nsuri,
                    localName.get(), NS_ConvertUTF16toUTF8(id).get(),
                    NS_ConvertUTF16toUTF8(classes).get(), orphan.get(),
                    uri.get());
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
   } else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(FragmentOrElement, tmp->mRefCnt.get())
--- a/dom/base/ScriptableContentIterator.cpp
+++ b/dom/base/ScriptableContentIterator.cpp
@@ -1,47 +1,91 @@
 /* -*- 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 "ScriptableContentIterator.h"
+
+#include "mozilla/ContentIterator.h"
 #include "nsINode.h"
 #include "nsRange.h"
 
 namespace mozilla {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptableContentIterator)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptableContentIterator)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptableContentIterator)
   NS_INTERFACE_MAP_ENTRY(nsIScriptableContentIterator)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION(ScriptableContentIterator, mContentIterator)
+NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptableContentIterator)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptableContentIterator)
+  if (tmp->mContentIterator) {
+    switch (tmp->mIteratorType) {
+      case POST_ORDER_ITERATOR:
+      default:
+        ImplCycleCollectionUnlink(
+            static_cast<PostContentIterator&>(*tmp->mContentIterator));
+        break;
+      case PRE_ORDER_ITERATOR:
+        ImplCycleCollectionUnlink(
+            static_cast<PreContentIterator&>(*tmp->mContentIterator));
+        break;
+      case SUBTREE_ITERATOR:
+        ImplCycleCollectionUnlink(
+            static_cast<ContentSubtreeIterator&>(*tmp->mContentIterator));
+        break;
+    }
+  }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptableContentIterator)
+  if (tmp->mContentIterator) {
+    switch (tmp->mIteratorType) {
+      case POST_ORDER_ITERATOR:
+      default:
+        ImplCycleCollectionTraverse(
+            cb, static_cast<PostContentIterator&>(*tmp->mContentIterator),
+            "mContentIterator");
+        break;
+      case PRE_ORDER_ITERATOR:
+        ImplCycleCollectionTraverse(
+            cb, static_cast<PreContentIterator&>(*tmp->mContentIterator),
+            "mContentIterator");
+        break;
+      case SUBTREE_ITERATOR:
+        ImplCycleCollectionTraverse(
+            cb, static_cast<ContentSubtreeIterator&>(*tmp->mContentIterator),
+            "mContentIterator");
+        break;
+    }
+  }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 ScriptableContentIterator::ScriptableContentIterator()
     : mIteratorType(NOT_INITIALIZED) {}
 
 void ScriptableContentIterator::EnsureContentIterator() {
   if (mContentIterator) {
     return;
   }
   switch (mIteratorType) {
     case POST_ORDER_ITERATOR:
     default:
-      mContentIterator = NS_NewContentIterator();
+      mContentIterator = MakeUnique<PostContentIterator>();
       break;
     case PRE_ORDER_ITERATOR:
-      mContentIterator = NS_NewPreContentIterator();
+      mContentIterator = MakeUnique<PreContentIterator>();
       break;
     case SUBTREE_ITERATOR:
-      mContentIterator = NS_NewContentSubtreeIterator();
+      mContentIterator = MakeUnique<ContentSubtreeIterator>();
       break;
   }
 }
 
 NS_IMETHODIMP
 ScriptableContentIterator::InitWithRootNode(IteratorType aType,
                                             nsINode* aRoot) {
   if (aType == NOT_INITIALIZED ||
--- a/dom/base/ScriptableContentIterator.h
+++ b/dom/base/ScriptableContentIterator.h
@@ -3,32 +3,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_scriptablecontentiterator_h
 #define mozilla_scriptablecontentiterator_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
-#include "nsIContentIterator.h"
 #include "nsIScriptableContentIterator.h"
 
 namespace mozilla {
 
 class ScriptableContentIterator final : public nsIScriptableContentIterator {
  public:
   ScriptableContentIterator();
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(ScriptableContentIterator)
   NS_DECL_NSISCRIPTABLECONTENTITERATOR
 
  protected:
   virtual ~ScriptableContentIterator() = default;
   void EnsureContentIterator();
 
   IteratorType mIteratorType;
-  nsCOMPtr<nsIContentIterator> mContentIterator;
+  UniquePtr<ContentIteratorBase> mContentIterator;
 };
 
 }  // namespace mozilla
 
 #endif  // #ifndef mozilla_scriptablecontentiterator_h
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -9,16 +9,17 @@
  */
 
 #include "mozilla/dom/Selection.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoCopyListener.h"
 #include "mozilla/AutoRestore.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/SelectionBinding.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/RangeBoundary.h"
 #include "mozilla/Telemetry.h"
@@ -32,17 +33,16 @@
 #include "nsIContent.h"
 #include "nsRange.h"
 #include "nsITableCellLayout.h"
 #include "nsTArray.h"
 #include "nsTableWrapperFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsCCUncollectableMarker.h"
-#include "nsIContentIterator.h"
 #include "nsIDocumentEncoder.h"
 #include "nsTextFragment.h"
 #include <algorithm>
 #include "nsContentUtils.h"
 
 #include "nsGkAtoms.h"
 #include "nsLayoutUtils.h"
 #include "nsBidiPresUtils.h"
@@ -1476,32 +1476,31 @@ void Selection::SelectFramesForContent(n
     textFrame->SetSelectedRange(0, aContent->GetText()->GetLength(), aSelected,
                                 mSelectionType);
   } else {
     frame->InvalidateFrameSubtree();  // frame continuations?
   }
 }
 
 // select all content children of aContent
-nsresult Selection::SelectAllFramesForContent(nsIContentIterator* aInnerIter,
-                                              nsIContent* aContent,
-                                              bool aSelected) {
+nsresult Selection::SelectAllFramesForContent(
+    PostContentIterator& aPostOrderIter, nsIContent* aContent, bool aSelected) {
   // If aContent doesn't have children, we should avoid to use the content
   // iterator for performance reason.
   if (!aContent->HasChildren()) {
     SelectFramesForContent(aContent, aSelected);
     return NS_OK;
   }
 
-  if (NS_WARN_IF(NS_FAILED(aInnerIter->Init(aContent)))) {
+  if (NS_WARN_IF(NS_FAILED(aPostOrderIter.Init(aContent)))) {
     return NS_ERROR_FAILURE;
   }
 
-  for (; !aInnerIter->IsDone(); aInnerIter->Next()) {
-    nsINode* node = aInnerIter->GetCurrentNode();
+  for (; !aPostOrderIter.IsDone(); aPostOrderIter.Next()) {
+    nsINode* node = aPostOrderIter.GetCurrentNode();
     MOZ_ASSERT(node);
     nsIContent* innercontent = node->IsContent() ? node->AsContent() : nullptr;
     SelectFramesForContent(innercontent, aSelected);
   }
 
   return NS_OK;
 }
 
@@ -1571,28 +1570,28 @@ nsresult Selection::SelectFrames(nsPresC
   if (aRange->Collapsed() ||
       (startNode == endNode && !startNode->HasChildren())) {
     if (!isFirstContentTextNode) {
       SelectFramesForContent(startContent, aSelect);
     }
     return NS_OK;
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
-  iter->Init(aRange);
-  if (isFirstContentTextNode && !iter->IsDone() &&
-      iter->GetCurrentNode() == startNode) {
-    iter->Next();  // first content has already been handled.
+  ContentSubtreeIterator subtreeIter;
+  subtreeIter.Init(aRange);
+  if (isFirstContentTextNode && !subtreeIter.IsDone() &&
+      subtreeIter.GetCurrentNode() == startNode) {
+    subtreeIter.Next();  // first content has already been handled.
   }
-  nsCOMPtr<nsIContentIterator> inneriter = NS_NewContentIterator();
-  for (; !iter->IsDone(); iter->Next()) {
-    nsINode* node = iter->GetCurrentNode();
+  PostContentIterator postOrderIter;
+  for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+    nsINode* node = subtreeIter.GetCurrentNode();
     MOZ_ASSERT(node);
     nsIContent* content = node->IsContent() ? node->AsContent() : nullptr;
-    SelectAllFramesForContent(inneriter, content, aSelect);
+    SelectAllFramesForContent(postOrderIter, content, aSelect);
   }
 
   // We must now do the last one if it is not the same as the first
   if (endNode != startNode) {
     nsIContent* endContent =
         endNode->IsContent() ? endNode->AsContent() : nullptr;
     // XXX The range can end at a document node and such range can be
     //     added to Selection with JS.  Therefore, even in such cases,
--- a/dom/base/Selection.h
+++ b/dom/base/Selection.h
@@ -22,28 +22,28 @@
 #include "nsISelectionListener.h"
 #include "nsRange.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsThreadUtils.h"
 #include "nsWrapperCache.h"
 
 struct CachedOffsetForFrame;
 class nsAutoScrollTimer;
-class nsIContentIterator;
 class nsIFrame;
 class nsFrameSelection;
 class nsPIDOMWindowOuter;
 struct SelectionDetails;
 struct SelectionCustomColors;
 class nsCopySupport;
 class nsHTMLCopyEncoder;
 
 namespace mozilla {
 class ErrorResult;
 class HTMLEditor;
+class PostContentIterator;
 enum class TableSelection : uint32_t;
 struct AutoPrepareFocusRange;
 namespace dom {
 class DocGroup;
 }  // namespace dom
 }  // namespace mozilla
 
 struct RangeData {
@@ -587,17 +587,17 @@ class Selection final : public nsSupport
   /**
    * Set mAnchorFocusRange to mRanges[aIndex] if aIndex is a valid index.
    * Set mAnchorFocusRange to nullptr if aIndex is negative.
    * Otherwise, i.e., if aIndex is positive but out of bounds of mRanges, do
    * nothing.
    */
   void SetAnchorFocusRange(int32_t aIndex);
   void SelectFramesForContent(nsIContent* aContent, bool aSelected);
-  nsresult SelectAllFramesForContent(nsIContentIterator* aInnerIter,
+  nsresult SelectAllFramesForContent(PostContentIterator& aPostOrderIter,
                                      nsIContent* aContent, bool aSelected);
   nsresult SelectFrames(nsPresContext* aPresContext, nsRange* aRange,
                         bool aSelect);
 
   /**
    * Test whether the supplied range points to a single table element.
    * Result is one of the TableSelection constants. "None" means
    * a table element isn't selected.
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -69,17 +69,16 @@ EXPORTS += [
     'nsFrameLoader.h',  # Because binding headers include it.
     'nsFrameMessageManager.h',
     'nsGlobalWindow.h',  # Because binding headers include it.
     'nsGlobalWindowInner.h',  # Because binding headers include it.
     'nsGlobalWindowOuter.h',  # Because binding headers include it.
     'nsIAnimationObserver.h',
     'nsIContent.h',
     'nsIContentInlines.h',
-    'nsIContentIterator.h',
     'nsIContentSerializer.h',
     'nsIDocumentObserver.h',
     'nsIGlobalObject.h',
     'nsImageLoadingContent.h',
     'nsIMutationObserver.h',
     'nsINode.h',
     'nsINodeList.h',
     'nsIScriptContext.h',
@@ -120,16 +119,17 @@ EXPORTS += [
 
 if CONFIG['MOZ_WEBRTC']:
     EXPORTS += [
         'nsDOMDataChannel.h',
         'nsDOMDataChannelDeclarations.h',
     ]
 
 EXPORTS.mozilla += [
+    'ContentIterator.h',
     'CORSMode.h',
     'FlushType.h',
     'FullscreenChange.h',
     'IdentifierMapEntry.h',
     'RangeBoundary.h',
     'ScriptableContentIterator.h',
     'SelectionChangeEventDispatcher.h',
     'TextInputProcessor.h',
@@ -256,16 +256,17 @@ UNIFIED_SOURCES += [
     'CharacterData.cpp',
     'ChildIterator.cpp',
     'ChromeMessageBroadcaster.cpp',
     'ChromeMessageSender.cpp',
     'ChromeNodeList.cpp',
     'ChromeUtils.cpp',
     'Comment.cpp',
     'ContentFrameMessageManager.cpp',
+    'ContentIterator.cpp',
     'ContentProcessMessageManager.cpp',
     'Crypto.cpp',
     'CustomElementRegistry.cpp',
     'DirectionalityUtils.cpp',
     'DispatcherTrait.cpp',
     'DocGroup.cpp',
     'Document.cpp',
     'DocumentFragment.cpp',
@@ -305,17 +306,16 @@ UNIFIED_SOURCES += [
     'Navigator.cpp',
     'NodeInfo.cpp',
     'NodeIterator.cpp',
     'NodeUbiReporting.cpp',
     'nsAttrValue.cpp',
     'nsAttrValueOrString.cpp',
     'nsCCUncollectableMarker.cpp',
     'nsContentAreaDragDrop.cpp',
-    'nsContentIterator.cpp',
     'nsContentList.cpp',
     'nsContentPermissionHelper.cpp',
     'nsContentPolicy.cpp',
     'nsContentSink.cpp',
     'nsContentTypeParser.cpp',
     'nsCopySupport.cpp',
     'nsDataDocumentContentPolicy.cpp',
     'nsDocumentEncoder.cpp',
--- a/dom/base/nsContentCID.h
+++ b/dom/base/nsContentCID.h
@@ -25,37 +25,16 @@
 
 #define NS_NAMESPACEMANAGER_CID                      \
   { /* d9783472-8fe9-11d2-9d3c-0060088f9ff7 */       \
     0xd9783472, 0x8fe9, 0x11d2, {                    \
       0x9d, 0x3c, 0x00, 0x60, 0x08, 0x8f, 0x9f, 0xf7 \
     }                                                \
   }
 
-#define NS_CONTENTITERATOR_CID                       \
-  { /* {a6cf90e3-15b3-11d2-932e-00805f8add32}*/      \
-    0xa6cf90e3, 0x15b3, 0x11d2, {                    \
-      0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 \
-    }                                                \
-  }
-
-#define NS_PRECONTENTITERATOR_CID                    \
-  { /* {80D7E247-D4B8-45d7-BB59-6F1DD56F384C} */     \
-    0x80d7e247, 0xd4b8, 0x45d7, {                    \
-      0xbb, 0x59, 0x6f, 0x1d, 0xd5, 0x6f, 0x38, 0x4c \
-    }                                                \
-  }
-
-#define NS_SUBTREEITERATOR_CID                       \
-  { /* {a6cf90e5-15b3-11d2-932e-00805f8add32}*/      \
-    0xa6cf90e5, 0x15b3, 0x11d2, {                    \
-      0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 \
-    }                                                \
-  }
-
 // {09F689E0-B4DA-11d2-A68B-00104BDE6048}
 #define NS_EVENTLISTENERMANAGER_CID                 \
   {                                                 \
     0x9f689e0, 0xb4da, 0x11d2, {                    \
       0xa6, 0x8b, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 \
     }                                               \
   }
 
--- a/dom/base/nsContentList.cpp
+++ b/dom/base/nsContentList.cpp
@@ -8,16 +8,17 @@
  * nsBaseContentList is a basic list of content nodes; nsContentList
  * is a commonly used NodeList implementation (used for
  * getElementsByTagName, some properties on HTMLDocument/Document, etc).
  */
 
 #include "nsContentList.h"
 #include "nsIContent.h"
 #include "mozilla/dom/Document.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/dom/Element.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsContentUtils.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/HTMLCollectionBinding.h"
 #include "mozilla/dom/NodeListBinding.h"
 #include "mozilla/Likely.h"
@@ -25,17 +26,16 @@
 #include "jsfriendapi.h"
 #include <algorithm>
 #include "mozilla/dom/NodeInfoInlines.h"
 #include "mozilla/MruCache.h"
 
 #include "PLDHashTable.h"
 
 #ifdef DEBUG_CONTENT_LIST
-#include "nsIContentIterator.h"
 #define ASSERT_IN_SYNC AssertInSync()
 #else
 #define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -885,43 +885,42 @@ void nsContentList::AssertInSync() {
   }
 
   // XXX This code will need to change if nsContentLists can ever match
   // elements that are outside of the document element.
   nsIContent* root = mRootNode->IsDocument()
                          ? mRootNode->AsDocument()->GetRootElement()
                          : mRootNode->AsContent();
 
-  nsCOMPtr<nsIContentIterator> iter;
+  PreContentIterator preOrderIter;
   if (mDeep) {
-    iter = NS_NewPreContentIterator();
-    iter->Init(root);
-    iter->First();
+    preOrderIter.Init(root);
+    preOrderIter.First();
   }
 
   uint32_t cnt = 0, index = 0;
   while (true) {
     if (cnt == mElements.Length() && mState == LIST_LAZY) {
       break;
     }
 
     nsIContent* cur =
-        mDeep ? iter->GetCurrentNode() : mRootNode->GetChildAt(index++);
+        mDeep ? preOrderIter.GetCurrentNode() : mRootNode->GetChildAt(index++);
     if (!cur) {
       break;
     }
 
     if (cur->IsElement() && Match(cur->AsElement())) {
       NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur,
                    "Elements is out of sync");
       ++cnt;
     }
 
     if (mDeep) {
-      iter->Next();
+      preOrderIter.Next();
     }
   }
 
   NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
 }
 #endif
 
 //-----------------------------------------------------
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -93,17 +93,16 @@
 #include "FrameLayerBuilder.h"
 #include "nsDisplayList.h"
 #include "nsROCSSPrimitiveValue.h"
 #include "nsIBaseWindow.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "GeckoProfiler.h"
 #include "mozilla/Preferences.h"
-#include "nsIContentIterator.h"
 #include "nsIStyleSheetService.h"
 #include "nsContentPermissionHelper.h"
 #include "nsCSSPseudoElements.h"  // for CSSPseudoElementType
 #include "nsNetUtil.h"
 #include "HTMLImageElement.h"
 #include "HTMLCanvasElement.h"
 #include "mozilla/css/ImageLoader.h"
 #include "mozilla/layers/IAPZCTreeManager.h"  // for layers::ZoomToRectBehavior
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2023,16 +2023,22 @@ nsresult nsFrameLoader::MaybeCreateDocSh
 
     attrs = oa;
   }
 
   if (OwnerIsMozBrowserFrame()) {
     attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
     attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
     mDocShell->SetFrameType(nsIDocShell::FRAME_TYPE_BROWSER);
+  } else {
+    nsCOMPtr<nsIDocShellTreeItem> parentCheck;
+    mDocShell->GetSameTypeParent(getter_AddRefs(parentCheck));
+    if (!!parentCheck) {
+      mDocShell->SetIsFrame();
+    }
   }
 
   // Apply sandbox flags even if our owner is not an iframe, as this copies
   // flags from our owning content's owning document.
   // Note: ApplySandboxFlags should be called after mDocShell->SetFrameType
   // because we need to get the correct presentation URL in ApplySandboxFlags.
   uint32_t sandboxFlags = 0;
   HTMLIFrameElement* iframe = HTMLIFrameElement::FromNode(mOwnerContent);
deleted file mode 100644
--- a/dom/base/nsIContentIterator.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- 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 __nsIContentIterator_h___
-#define __nsIContentIterator_h___
-
-#include "nsISupports.h"
-#include "nsCOMPtr.h"
-#include "mozilla/RangeBoundary.h"
-
-class nsINode;
-class nsRange;
-
-#define NS_ICONTENTITERATOR_IID                      \
-  {                                                  \
-    0x2550078e, 0xae87, 0x4914, {                    \
-      0xb3, 0x04, 0xe4, 0xd1, 0x46, 0x19, 0x3d, 0x5f \
-    }                                                \
-  }
-
-class nsIContentIterator : public nsISupports {
- public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENTITERATOR_IID)
-
-  /* Initializes an iterator for the subtree rooted by the node aRoot
-   */
-  virtual nsresult Init(nsINode* aRoot) = 0;
-
-  /* Initializes an iterator for the subtree defined by the range aRange
-     Subclasses should make sure they implement both of these!
-   */
-  virtual nsresult Init(nsRange* aRange) = 0;
-
-  /* Initializes an iterator for the subtree between
-     aStartContainer/aStartOffset and aEndContainer/aEndOffset
-     Callers should guarantee that the start point and end point are in
-     document order.
-   */
-  virtual nsresult Init(nsINode* aStartContainer, uint32_t aStartOffset,
-                        nsINode* aEndContainer, uint32_t aEndOffset) = 0;
-
-  /* Initializes an iterator for the subtree between aStart and aEnd.
-     Callers should guarantee that the start point and end point are in
-     document order.
-   */
-  virtual nsresult Init(const mozilla::RawRangeBoundary& aStart,
-                        const mozilla::RawRangeBoundary& aEnd) = 0;
-
-  /** First will reset the list.
-   */
-  virtual void First() = 0;
-
-  /** Last will reset the list to the end.
-   */
-  virtual void Last() = 0;
-
-  /** Next will advance the list.
-   */
-  virtual void Next() = 0;
-
-  /** Prev will decrement the list.
-   */
-  virtual void Prev() = 0;
-
-  /** CurrentItem will return the current item, or null if the list is empty
-   *  @return the current node
-   */
-  virtual nsINode* GetCurrentNode() = 0;
-
-  /** return if the collection is at the end. that is the beginning following a
-   * call to Prev and it is the end of the list following a call to next
-   *  @return if the iterator is done.
-   */
-  virtual bool IsDone() = 0;
-
-  /** PositionAt will position the iterator to the supplied node
-   */
-  virtual nsresult PositionAt(nsINode* aCurNode) = 0;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsIContentIterator, NS_ICONTENTITERATOR_IID)
-
-already_AddRefed<nsIContentIterator> NS_NewContentIterator();
-already_AddRefed<nsIContentIterator> NS_NewPreContentIterator();
-already_AddRefed<nsIContentIterator> NS_NewContentSubtreeIterator();
-
-#endif  // __nsIContentIterator_h___
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -55,17 +55,16 @@
 #include "nsFrameSelection.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsAtom.h"
 #include "nsIBaseWindow.h"
 #include "nsICategoryManager.h"
 #include "nsIContentInlines.h"
-#include "nsIContentIterator.h"
 #include "nsIControllers.h"
 #include "mozilla/dom/Document.h"
 #include "nsIDOMEventListener.h"
 #include "nsILinkHandler.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "mozilla/dom/NodeInfoInlines.h"
 #include "nsIPresShell.h"
 #include "nsIScriptError.h"
--- a/dom/base/nsIScriptableContentIterator.idl
+++ b/dom/base/nsIScriptableContentIterator.idl
@@ -5,17 +5,17 @@
 
 #include "nsISupports.idl"
 
 webidl Node;
 webidl Range;
 
 /**
  * nsIScriptableContentIterator is designed to testing concrete classes of
- * nsIContentIterator.
+ * ContentIteratorBase.
  */
 [scriptable, builtinclass, uuid(9f25fb2a-265f-44f9-a122-62bbf443239e)]
 interface nsIScriptableContentIterator : nsISupports
 {
   cenum IteratorType : 8 {
     NOT_INITIALIZED,
     POST_ORDER_ITERATOR,
     PRE_ORDER_ITERATOR,
@@ -25,48 +25,48 @@ interface nsIScriptableContentIterator :
   /**
    * You need to call initWith*() first.  Then, the instance of this interface
    * decides the type of iterator with its aType argument.  You can call
    * initWith*() multiple times, but you need to keep setting same type as
    * previous call.  If you set different type, these method with throw an
    * exception.
    */
 
-  // See nsIContentIterator::Init(nsINode*)
+  // See ContentIteratorBase::Init(nsINode*)
   void initWithRootNode(in nsIScriptableContentIterator_IteratorType aType,
                         in Node aRoot);
 
-  // See nsIContentIterator::Init(nsRange*)
+  // See ContentIteratorBase::Init(nsRange*)
   void initWithRange(in nsIScriptableContentIterator_IteratorType aType,
                      in Range aRange);
 
-  // See nsIContentIterator::Init(nsINode*, uint32_t, nsINode*, uint32_t)
+  // See ContentIteratorBase::Init(nsINode*, uint32_t, nsINode*, uint32_t)
   void initWithPositions(in nsIScriptableContentIterator_IteratorType aType,
                          in Node aStartContainer, in unsigned long aStartOffset,
                          in Node aEndContainer, in unsigned long aEndOffset);
 
-  // See nsIContentIterator::First()
+  // See ContentIteratorBase::First()
   void first();
 
-  // See nsIContentIterator::Last()
+  // See ContentIteratorBase::Last()
   void last();
 
-  // See nsIContentIterator::Next()
+  // See ContentIteratorBase::Next()
   void next();
 
-  // See nsIContentIterator::Prev()
+  // See ContentIteratorBase::Prev()
   void prev();
 
-  // See nsIContentIterator::GetCurrentNode()
+  // See ContentIteratorBase::GetCurrentNode()
   readonly attribute Node currentNode;
 
-  // See nsIContentIterator::IsDone()
+  // See ContentIteratorBase::IsDone()
   readonly attribute bool isDone;
 
-  // See nsIContentIterator::PositionAt(nsINode*)
+  // See ContentIteratorBase::PositionAt(nsINode*)
   void positionAt(in Node aNode);
 };
 
 %{C++
 #define SCRIPTABLE_CONTENT_ITERATOR_CID \
   { 0xf68037ec, 0x2790, 0x44c5, \
     { 0x8e, 0x5f, 0xdf, 0x5d, 0xa5, 0x8b, 0x93, 0xa7 } }
 #define SCRIPTABLE_CONTENT_ITERATOR_CONTRACTID \
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -11,31 +11,32 @@
 #include "nscore.h"
 #include "nsRange.h"
 
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsIContent.h"
 #include "mozilla/dom/Document.h"
 #include "nsError.h"
-#include "nsIContentIterator.h"
 #include "nsINodeList.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
 #include "nsTextFrame.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/dom/CharacterData.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DocumentType.h"
 #include "mozilla/dom/RangeBinding.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/Likely.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsStyleStruct.h"
 #include "nsStyleStructInlines.h"
 #include "nsComputedDOMStyle.h"
 #include "mozilla/dom/InspectorFontFace.h"
 
 using namespace mozilla;
@@ -1486,26 +1487,26 @@ void nsRange::SelectNodeContents(nsINode
 // The Subtree Content Iterator only returns subtrees that are
 // completely within a given range. It doesn't return a CharacterData
 // node that contains either the start or end point of the range.,
 // nor does it return element nodes when nothing in the element is selected.
 // We need an iterator that will also include these start/end points
 // so that our methods/algorithms aren't cluttered with special
 // case code that tries to include these points while iterating.
 //
-// The RangeSubtreeIterator class mimics the nsIContentIterator
+// The RangeSubtreeIterator class mimics the ContentSubtreeIterator
 // methods we need, so should the Content Iterator support the
 // start/end points in the future, we can switchover relatively
 // easy.
 
 class MOZ_STACK_CLASS RangeSubtreeIterator {
  private:
   enum RangeSubtreeIterState { eDone = 0, eUseStart, eUseIterator, eUseEnd };
 
-  nsCOMPtr<nsIContentIterator> mIter;
+  UniquePtr<ContentSubtreeIterator> mSubtreeIter;
   RangeSubtreeIterState mIterState;
 
   nsCOMPtr<nsINode> mStart;
   nsCOMPtr<nsINode> mEnd;
 
  public:
   RangeSubtreeIterator() : mIterState(eDone) {}
   ~RangeSubtreeIterator() {}
@@ -1557,27 +1558,27 @@ nsresult RangeSubtreeIterator::Init(nsRa
     // node. Null out the end pointer so we only visit the
     // node once!
 
     mEnd = nullptr;
   } else {
     // Now create a Content Subtree Iterator to be used
     // for the subtrees between the end points!
 
-    mIter = NS_NewContentSubtreeIterator();
-
-    nsresult res = mIter->Init(aRange);
+    mSubtreeIter = MakeUnique<ContentSubtreeIterator>();
+
+    nsresult res = mSubtreeIter->Init(aRange);
     if (NS_FAILED(res)) return res;
 
-    if (mIter->IsDone()) {
+    if (mSubtreeIter->IsDone()) {
       // The subtree iterator thinks there's nothing
       // to iterate over, so just free it up so we
       // don't accidentally call into it.
 
-      mIter = nullptr;
+      mSubtreeIter = nullptr;
     }
   }
 
   // Initialize the iterator by calling First().
   // Note that we are ignoring the return value on purpose!
 
   First();
 
@@ -1586,86 +1587,86 @@ nsresult RangeSubtreeIterator::Init(nsRa
 
 already_AddRefed<nsINode> RangeSubtreeIterator::GetCurrentNode() {
   nsCOMPtr<nsINode> node;
 
   if (mIterState == eUseStart && mStart) {
     node = mStart;
   } else if (mIterState == eUseEnd && mEnd) {
     node = mEnd;
-  } else if (mIterState == eUseIterator && mIter) {
-    node = mIter->GetCurrentNode();
+  } else if (mIterState == eUseIterator && mSubtreeIter) {
+    node = mSubtreeIter->GetCurrentNode();
   }
 
   return node.forget();
 }
 
 void RangeSubtreeIterator::First() {
   if (mStart)
     mIterState = eUseStart;
-  else if (mIter) {
-    mIter->First();
+  else if (mSubtreeIter) {
+    mSubtreeIter->First();
 
     mIterState = eUseIterator;
   } else if (mEnd)
     mIterState = eUseEnd;
   else
     mIterState = eDone;
 }
 
 void RangeSubtreeIterator::Last() {
   if (mEnd)
     mIterState = eUseEnd;
-  else if (mIter) {
-    mIter->Last();
+  else if (mSubtreeIter) {
+    mSubtreeIter->Last();
 
     mIterState = eUseIterator;
   } else if (mStart)
     mIterState = eUseStart;
   else
     mIterState = eDone;
 }
 
 void RangeSubtreeIterator::Next() {
   if (mIterState == eUseStart) {
-    if (mIter) {
-      mIter->First();
+    if (mSubtreeIter) {
+      mSubtreeIter->First();
 
       mIterState = eUseIterator;
     } else if (mEnd)
       mIterState = eUseEnd;
     else
       mIterState = eDone;
   } else if (mIterState == eUseIterator) {
-    mIter->Next();
-
-    if (mIter->IsDone()) {
+    mSubtreeIter->Next();
+
+    if (mSubtreeIter->IsDone()) {
       if (mEnd)
         mIterState = eUseEnd;
       else
         mIterState = eDone;
     }
   } else
     mIterState = eDone;
 }
 
 void RangeSubtreeIterator::Prev() {
   if (mIterState == eUseEnd) {
-    if (mIter) {
-      mIter->Last();
+    if (mSubtreeIter) {
+      mSubtreeIter->Last();
 
       mIterState = eUseIterator;
     } else if (mStart)
       mIterState = eUseStart;
     else
       mIterState = eDone;
   } else if (mIterState == eUseIterator) {
-    mIter->Prev();
-
-    if (mIter->IsDone()) {
+    mSubtreeIter->Prev();
+
+    if (mSubtreeIter->IsDone()) {
       if (mStart)
         mIterState = eUseStart;
       else
         mIterState = eDone;
     }
   } else
     mIterState = eDone;
 }
@@ -2611,29 +2612,29 @@ void nsRange::ToString(nsAString& aRetur
     }
   }
 
   /* complex case: mStart.Container() != mEnd.Container(), or mStartParent not a
      text node revisit - there are potential optimizations here and also
      tradeoffs.
   */
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  nsresult rv = iter->Init(this);
+  PostContentIterator postOrderIter;
+  nsresult rv = postOrderIter.Init(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aErr.Throw(rv);
     return;
   }
 
   nsString tempString;
 
   // loop through the content iterator, which returns nodes in the range in
   // close tag order, and grab the text from any text node
-  while (!iter->IsDone()) {
-    nsINode* n = iter->GetCurrentNode();
+  for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+    nsINode* n = postOrderIter.GetCurrentNode();
 
 #ifdef DEBUG_range
     // If debug, dump it:
     n->List(stdout);
 #endif /* DEBUG */
     Text* textNode = n->GetAsText();
     if (textNode)  // if it's a text node, get the text
     {
@@ -2646,18 +2647,16 @@ void nsRange::ToString(nsAString& aRetur
                  mEnd.Container()) {  // only include text before end offset
         textNode->SubstringData(0, mEnd.Offset(), tempString, IgnoreErrors());
         aReturn += tempString;
       } else {  // grab the whole kit-n-kaboodle
         textNode->GetData(tempString);
         aReturn += tempString;
       }
     }
-
-    iter->Next();
   }
 
 #ifdef DEBUG_range
   printf("End Range dump: -----------------------\n");
 #endif /* DEBUG */
 }
 
 void nsRange::Detach() {}
@@ -3054,33 +3053,33 @@ static bool ExcludeIfNextToNonSelectable
 void nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges) {
   MOZ_ASSERT(mIsPositioned);
   MOZ_ASSERT(mEnd.Container());
   MOZ_ASSERT(mStart.Container());
 
   nsRange* range = this;
   RefPtr<nsRange> newRange;
   while (range) {
-    nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
-    nsresult rv = iter->Init(range);
+    PreContentIterator preOrderIter;
+    nsresult rv = preOrderIter.Init(range);
     if (NS_FAILED(rv)) {
       return;
     }
 
     bool added = false;
     bool seenSelectable = false;
     // |firstNonSelectableContent| is the first node in a consecutive sequence
     // of non-IsSelectable nodes.  When we find a selectable node after such
     // a sequence we'll end the last nsRange, create a new one and restart
     // the outer loop.
     nsIContent* firstNonSelectableContent = nullptr;
     while (true) {
       ErrorResult err;
-      nsINode* node = iter->GetCurrentNode();
-      iter->Next();
+      nsINode* node = preOrderIter.GetCurrentNode();
+      preOrderIter.Next();
       bool selectable = true;
       nsIContent* content =
           node && node->IsContent() ? node->AsContent() : nullptr;
       if (content) {
         if (firstNonSelectableContent &&
             ExcludeIfNextToNonSelectable(content)) {
           // Ignorable whitespace next to a sequence of non-selectable nodes
           // counts as non-selectable (bug 1216001).
@@ -3096,17 +3095,17 @@ void nsRange::ExcludeNonSelectableNodes(
           }
         }
       }
 
       if (!selectable) {
         if (!firstNonSelectableContent) {
           firstNonSelectableContent = content;
         }
-        if (iter->IsDone() && seenSelectable) {
+        if (preOrderIter.IsDone() && seenSelectable) {
           // The tail end of the initial range is non-selectable - truncate the
           // current range before the first non-selectable node.
           range->SetEndBefore(*firstNonSelectableContent, err);
         }
       } else if (firstNonSelectableContent) {
         if (range == this && !seenSelectable) {
           // This is the initial range and all its nodes until now are
           // non-selectable so just trim them from the start.
@@ -3152,17 +3151,17 @@ void nsRange::ExcludeNonSelectableNodes(
         }
       } else {
         seenSelectable = true;
         if (!added) {
           added = true;
           aOutRanges->AppendElement(range);
         }
       }
-      if (iter->IsDone()) {
+      if (preOrderIter.IsDone()) {
         return;
       }
     }
   }
 }
 
 struct InnerTextAccumulator {
   explicit InnerTextAccumulator(mozilla::dom::DOMString& aValue)
--- a/dom/base/test/test_bug976673.html
+++ b/dom/base/test/test_bug976673.html
@@ -23,17 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <button id="button">set focus</button>
 <iframe id="iframe" src="http://example.org:80/tests/dom/base/test/iframe_bug976673.html"></iframe>
 <script>
 
 SimpleTest.waitForExplicitFinish();
 
 // In e10s mode, ContentCacheInChild tries to retrieve selected text and
 // caret position when IMEContentObserver notifies IME of focus.  At this time,
-// we hit assertion in nsContentIterator.
+// we hit assertion in ContentIterator.
 SimpleTest.expectAssertions(0, 6);
 
 window.addEventListener("mousedown", function (aEvent) { aEvent.preventDefault(); });
 
 function testSetFocus(aEventType, aCallback)
 {
   var description = "Setting focus from " + aEventType + " handler: ";
 
--- a/dom/base/test/test_content_iterator_subtree.html
+++ b/dom/base/test/test_content_iterator_subtree.html
@@ -75,17 +75,17 @@ function getNodeDescription(aNode) {
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function () {
   let iter = createContentIterator();
 
   /**
-   * FYI: nsContentSubtreeIterator does not support initWithRootNode() nor positionAt().
+   * FYI: ContentSubtreeIterator does not support initWithRootNode() nor positionAt().
    */
 
   /**
    * Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
    */
   document.body.innerHTML = "<div></div>";
   let range = document.createRange();
   range.selectNode(document.body.firstChild);
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1,30 +1,31 @@
 /* -*- 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 "ContentEventHandler.h"
+
+#include "mozilla/ContentIterator.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLUnknownElement.h"
 #include "mozilla/dom/Selection.h"
 #include "nsCaret.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsCopySupport.h"
 #include "nsElementTable.h"
 #include "nsFocusManager.h"
 #include "nsFontMetrics.h"
 #include "nsFrameSelection.h"
-#include "nsIContentIterator.h"
 #include "nsIPresShell.h"
 #include "nsIFrame.h"
 #include "nsIObjectFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
 #include "nsQueryObject.h"
 #include "nsRange.h"
 #include "nsTextFragment.h"
@@ -194,17 +195,17 @@ nsresult ContentEventHandler::RawRange::
 // 2. End of range:
 //   2.1. Cases: ]textNode or text]Node or textNode]
 //        When a text node is end of a range, end node is the text node and
 //        end offset is any number between 0 and the length of the text.
 //   2.2. Case: ]<element>
 //        When before an element node (meaning before the open tag of the
 //        element) is end of a range, end node is previous node causing text.
 //        Note that this case shouldn't be handled directly.  If rule 2.1 and
-//        2.3 are handled correctly, the loop with nsContentIterator shouldn't
+//        2.3 are handled correctly, the loop with ContentIterator shouldn't
 //        reach the element node since the loop should've finished already at
 //        handling the last node which caused some text.
 //   2.3. Case: <element>]
 //        When a line break is caused before a non-empty element node and it's
 //        end of a range, end node is the element and end offset is 0.
 //        (i.e., including open tag of the element)
 //   2.4. Cases: <element/>]
 //        When after an empty element node is end of a range, end node is
@@ -725,23 +726,24 @@ nsresult ContentEventHandler::GenerateFl
   if (startNode == endNode && startNode->IsText()) {
     nsIContent* content = startNode->AsContent();
     AppendSubString(aString, content, aRawRange.StartOffset(),
                     aRawRange.EndOffset() - aRawRange.StartOffset());
     ConvertToNativeNewlines(aString);
     return NS_OK;
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
-  nsresult rv = iter->Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
+  PreContentIterator preOrderIter;
+  nsresult rv =
+      preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  for (; !iter->IsDone(); iter->Next()) {
-    nsINode* node = iter->GetCurrentNode();
+  for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
+    nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     if (!node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
 
@@ -885,23 +887,24 @@ nsresult ContentEventHandler::GenerateFl
   nsINode* startNode = aRawRange.GetStartContainer();
   nsINode* endNode = aRawRange.GetEndContainer();
   if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
     return NS_ERROR_FAILURE;
   }
 
   // baseOffset is the flattened offset of each content node.
   int32_t baseOffset = 0;
-  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
-  nsresult rv = iter->Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
+  PreContentIterator preOrderIter;
+  nsresult rv =
+      preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  for (; !iter->IsDone(); iter->Next()) {
-    nsINode* node = iter->GetCurrentNode();
+  for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
+    nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     if (!node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
 
@@ -1010,27 +1013,27 @@ nsresult ContentEventHandler::SetRawRang
   // Special case like <br contenteditable>
   if (!mRootContent->HasChildren()) {
     nsresult rv = aRawRange->CollapseTo(RawRangeBoundary(mRootContent, 0));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
-  nsresult rv = iter->Init(mRootContent);
+  PreContentIterator preOrderIter;
+  nsresult rv = preOrderIter.Init(mRootContent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   uint32_t offset = 0;
   uint32_t endOffset = aOffset + aLength;
   bool startSet = false;
-  for (; !iter->IsDone(); iter->Next()) {
-    nsINode* node = iter->GetCurrentNode();
+  for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
+    nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     // FYI: mRootContent shouldn't cause any text. So, we can skip it simply.
     if (node == mRootContent || !node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
@@ -1442,23 +1445,24 @@ void ContentEventHandler::EnsureNonEmpty
   aRect.height = std::max(1, aRect.height);
   aRect.width = std::max(1, aRect.width);
 }
 
 ContentEventHandler::FrameAndNodeOffset
 ContentEventHandler::GetFirstFrameInRangeForTextRect(
     const RawRange& aRawRange) {
   NodePosition nodePosition;
-  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
-  nsresult rv = iter->Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
+  PreContentIterator preOrderIter;
+  nsresult rv =
+      preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return FrameAndNodeOffset();
   }
-  for (; !iter->IsDone(); iter->Next()) {
-    nsINode* node = iter->GetCurrentNode();
+  for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
+    nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
 
     if (!node->IsContent()) {
       continue;
     }
 
@@ -1490,18 +1494,19 @@ ContentEventHandler::GetFirstFrameInRang
   GetFrameForTextRect(nodePosition.Container(), nodePosition.Offset(), true,
                       &firstFrame);
   return FrameAndNodeOffset(firstFrame, nodePosition.Offset());
 }
 
 ContentEventHandler::FrameAndNodeOffset
 ContentEventHandler::GetLastFrameInRangeForTextRect(const RawRange& aRawRange) {
   NodePosition nodePosition;
-  nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
-  nsresult rv = iter->Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
+  PreContentIterator preOrderIter;
+  nsresult rv =
+      preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return FrameAndNodeOffset();
   }
 
   const RangeBoundary& endPoint = aRawRange.End();
   MOZ_ASSERT(endPoint.IsSet());
   // If the end point is start of a text node or specified by its parent and
   // index, the node shouldn't be included into the range.  For example,
@@ -1528,18 +1533,18 @@ ContentEventHandler::GetLastFrameInRange
     if (endPoint.IsStartOfContainer() &&
         aRawRange.GetStartContainer() != endPoint.Container()) {
       nextNodeOfRangeEnd = endPoint.Container();
     }
   } else if (endPoint.IsSetAndValid()) {
     nextNodeOfRangeEnd = endPoint.GetChildAtOffset();
   }
 
-  for (iter->Last(); !iter->IsDone(); iter->Prev()) {
-    nsINode* node = iter->GetCurrentNode();
+  for (preOrderIter.Last(); !preOrderIter.IsDone(); preOrderIter.Prev()) {
+    nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
 
     if (!node->IsContent() || node == nextNodeOfRangeEnd) {
       continue;
     }
 
@@ -2100,18 +2105,18 @@ nsresult ContentEventHandler::OnQueryTex
   rv = SetRawRangeFromFlatTextOffset(
       &rawRange, aEvent->mInput.mOffset, aEvent->mInput.mLength, lineBreakType,
       true, &aEvent->mReply.mOffset, getter_AddRefs(lastTextContent));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = GenerateFlatTextContent(rawRange, aEvent->mReply.mString, lineBreakType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // used to iterate over all contents and their frames
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  rv = iter->Init(rawRange.Start().AsRaw(), rawRange.End().AsRaw());
+  PostContentIterator postOrderIter;
+  rv = postOrderIter.Init(rawRange.Start().AsRaw(), rawRange.End().AsRaw());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_FAILURE;
   }
 
   // Get the first frame which causes some text after the offset.
   FrameAndNodeOffset firstFrame = GetFirstFrameInRangeForTextRect(rawRange);
 
   // If GetFirstFrameInRangeForTextRect() does not return valid frame, that
@@ -2288,18 +2293,18 @@ nsresult ContentEventHandler::OnQueryTex
     return NS_ERROR_FAILURE;
   }
 
   // iterate over all covered frames
   for (nsIFrame* frame = firstFrame; frame != lastFrame;) {
     frame = frame->GetNextContinuation();
     if (!frame) {
       do {
-        iter->Next();
-        nsINode* node = iter->GetCurrentNode();
+        postOrderIter.Next();
+        nsINode* node = postOrderIter.GetCurrentNode();
         if (!node) {
           break;
         }
         if (!node->IsContent()) {
           continue;
         }
         nsIFrame* primaryFrame = node->AsContent()->GetPrimaryFrame();
         // The node may be hidden by CSS.
@@ -2309,17 +2314,17 @@ nsresult ContentEventHandler::OnQueryTex
         // We should take only text frame's rect and br frame's rect.  We can
         // always use frame rect of text frame and GetLineBreakerRectBefore()
         // can return exactly correct rect only for <br> frame for now.  On the
         // other hand, GetLineBreakRectBefore() returns guessed caret rect for
         // the other frames.  We shouldn't include such odd rect to the result.
         if (primaryFrame->IsTextFrame() || primaryFrame->IsBrFrame()) {
           frame = primaryFrame;
         }
-      } while (!frame && !iter->IsDone());
+      } while (!frame && !postOrderIter.IsDone());
       if (!frame) {
         break;
       }
     }
     if (frame->IsTextFrame()) {
       frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
     } else {
       MOZ_ASSERT(frame->IsBrFrame());
@@ -2648,19 +2653,17 @@ nsresult ContentEventHandler::OnQueryDOM
     return NS_ERROR_INVALID_ARG;
   }
 
   if (aStartPosition == aEndPosition) {
     *aLength = 0;
     return NS_OK;
   }
 
-  // Don't create nsContentIterator instance until it's really necessary since
-  // destroying without initializing causes unexpected NS_ASSERTION() call.
-  nsCOMPtr<nsIContentIterator> iter;
+  PreContentIterator preOrderIter;
 
   // Working with ContentIterator, we may need to adjust the end position for
   // including it forcibly.
   NodePosition endPosition(aEndPosition);
 
   // This may be called for retrieving the text of removed nodes.  Even in this
   // case, the node thinks it's still in the tree because UnbindFromTree() will
   // be called after here.  However, the node was already removed from the
@@ -2674,18 +2677,17 @@ nsresult ContentEventHandler::OnQueryDOM
     MOZ_ASSERT(aStartPosition.Container() == endPosition.Container(),
                "At removing the node, start and end node should be same");
     MOZ_ASSERT(aStartPosition.Offset() == 0,
                "When the node is being removed, the start offset should be 0");
     MOZ_ASSERT(
         static_cast<uint32_t>(endPosition.Offset()) ==
             endPosition.Container()->GetChildCount(),
         "When the node is being removed, the end offset should be child count");
-    iter = NS_NewPreContentIterator();
-    nsresult rv = iter->Init(aStartPosition.Container());
+    nsresult rv = preOrderIter.Init(aStartPosition.Container());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   } else {
     RawRange prevRawRange;
     nsresult rv = prevRawRange.SetStart(aStartPosition.AsRaw());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -2719,45 +2721,44 @@ nsresult ContentEventHandler::OnQueryDOM
     }
 
     if (endPosition.IsSetAndValid()) {
       // Offset is within node's length; set end of range to that offset
       rv = prevRawRange.SetEnd(endPosition.AsRaw());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-      iter = NS_NewPreContentIterator();
-      rv = iter->Init(prevRawRange.Start().AsRaw(), prevRawRange.End().AsRaw());
+      rv = preOrderIter.Init(prevRawRange.Start().AsRaw(),
+                             prevRawRange.End().AsRaw());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else if (endPosition.Container() != aRootContent) {
       // Offset is past node's length; set end of range to end of node
       rv = prevRawRange.SetEndAfter(endPosition.Container());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-      iter = NS_NewPreContentIterator();
-      rv = iter->Init(prevRawRange.Start().AsRaw(), prevRawRange.End().AsRaw());
+      rv = preOrderIter.Init(prevRawRange.Start().AsRaw(),
+                             prevRawRange.End().AsRaw());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
       // Offset is past the root node; set end of range to end of root node
-      iter = NS_NewPreContentIterator();
-      rv = iter->Init(aRootContent);
+      rv = preOrderIter.Init(aRootContent);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
   }
 
   *aLength = 0;
-  for (; !iter->IsDone(); iter->Next()) {
-    nsINode* node = iter->GetCurrentNode();
+  for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
+    nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     if (!node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
 
@@ -2787,17 +2788,17 @@ nsresult ContentEventHandler::OnQueryDOM
     }
   }
   return NS_OK;
 }
 
 nsresult ContentEventHandler::GetStartOffset(const RawRange& aRawRange,
                                              uint32_t* aOffset,
                                              LineBreakType aLineBreakType) {
-  // To match the "no skip start" hack in nsContentIterator::Init, when range
+  // To match the "no skip start" hack in ContentIterator::Init, when range
   // offset is 0 and the range node is not a container, we have to assume the
   // range _includes_ the node, which means the start offset should _not_
   // include the node.
   //
   // For example, for this content: <br>abc, and range (<br>, 0)-("abc", 1), the
   // range includes the linebreak from <br>, so the start offset should _not_
   // include <br>, and the start offset should be 0.
   //
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -1546,17 +1546,17 @@ void IMEContentObserver::FlushMergeableN
   //       another change.
 
   MOZ_LOG(sIMECOLog, LogLevel::Debug,
           ("0x%p IMEContentObserver::FlushMergeableNotifications(), "
            "creating IMENotificationSender...",
            this));
 
   // If contents in selection range is modified, the selection range still
-  // has removed node from the tree.  In such case, nsContentIterator won't
+  // has removed node from the tree.  In such case, ContentIterator won't
   // work well.  Therefore, we shouldn't use AddScriptRunnder() here since
   // it may kick runnable event immediately after DOM tree is changed but
   // the selection range isn't modified yet.
   mQueuedSender = new IMENotificationSender(this);
   mQueuedSender->Dispatch(mDocShell);
   MOZ_LOG(sIMECOLog, LogLevel::Debug,
           ("0x%p IMEContentObserver::FlushMergeableNotifications(), "
            "finished",
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2492,17 +2492,17 @@ void ContentParent::InitInternal(Process
   // This is only implemented (returns a non-empty list) by MacOSX and Linux
   // at present.
   nsTArray<SystemFontListEntry> fontList;
   gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList);
   nsTArray<LookAndFeelInt> lnfCache = LookAndFeel::GetIntCache();
 
   // Content processes have no permission to access profile directory, so we
   // send the file URL instead.
-  StyleSheet* ucs = nsLayoutStylesheetCache::Singleton()->UserContentSheet();
+  StyleSheet* ucs = nsLayoutStylesheetCache::Singleton()->GetUserContentSheet();
   if (ucs) {
     SerializeURI(ucs->GetSheetURI(), xpcomInit.userContentSheetURL());
   } else {
     SerializeURI(nullptr, xpcomInit.userContentSheetURL());
   }
 
   // 1. Build ContentDeviceData first, as it may affect some gfxVars.
   gfxPlatform::GetPlatform()->BuildContentDeviceData(
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -485,23 +485,37 @@ class VideoData : public MediaData {
  protected:
   ~VideoData();
 
   bool mSentToCompositor;
   UniquePtr<Listener> mListener;
   media::TimeUnit mNextKeyFrameTime;
 };
 
+enum class CryptoScheme : uint8_t {
+  None,
+  Cenc,
+  Cbcs,
+};
+
 class CryptoTrack {
  public:
-  CryptoTrack() : mValid(false), mMode(0), mIVSize(0) {}
-  bool mValid;
-  int32_t mMode;
+  CryptoTrack()
+      : mCryptoScheme(CryptoScheme::None),
+        mIVSize(0),
+        mCryptByteBlock(0),
+        mSkipByteBlock(0) {}
+  CryptoScheme mCryptoScheme;
   int32_t mIVSize;
   nsTArray<uint8_t> mKeyId;
+  uint8_t mCryptByteBlock;
+  uint8_t mSkipByteBlock;
+  nsTArray<uint8_t> mConstantIV;
+
+  bool IsEncrypted() const { return mCryptoScheme != CryptoScheme::None; }
 };
 
 class CryptoSample : public CryptoTrack {
  public:
   nsTArray<uint16_t> mPlainSizes;
   nsTArray<uint32_t> mEncryptedSizes;
   nsTArray<uint8_t> mIV;
   nsTArray<nsTArray<uint8_t>> mInitDatas;
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1392,18 +1392,18 @@ void MediaFormatReader::MaybeResolveMeta
   // range.
   mHasStartTime = true;
   UpdateBuffered();
 
   mMetadataPromise.Resolve(std::move(metadata), __func__);
 }
 
 bool MediaFormatReader::IsEncrypted() const {
-  return (HasAudio() && mAudio.GetCurrentInfo()->mCrypto.mValid) ||
-         (HasVideo() && mVideo.GetCurrentInfo()->mCrypto.mValid);
+  return (HasAudio() && mAudio.GetCurrentInfo()->mCrypto.IsEncrypted()) ||
+         (HasVideo() && mVideo.GetCurrentInfo()->mCrypto.IsEncrypted());
 }
 
 void MediaFormatReader::OnDemuxerInitFailed(const MediaResult& aError) {
   mDemuxerInitRequest.Complete();
   mMetadataPromise.Reject(aError, __func__);
 }
 
 void MediaFormatReader::ReadUpdatedMetadata(MediaInfo* aInfo) {
@@ -2001,17 +2001,18 @@ void MediaFormatReader::HandleDemuxedSam
   const RefPtr<TrackInfoSharedPtr> info = sample->mTrackInfo;
 
   if (info && decoder.mLastStreamSourceID != info->GetID()) {
     nsTArray<RefPtr<MediaRawData>> samples;
     if (decoder.mDecoder) {
       bool recyclable =
           StaticPrefs::MediaDecoderRecycleEnabled() &&
           decoder.mDecoder->SupportDecoderRecycling() &&
-          (*info)->mCrypto.mValid == decoder.GetCurrentInfo()->mCrypto.mValid &&
+          (*info)->mCrypto.mCryptoScheme ==
+              decoder.GetCurrentInfo()->mCrypto.mCryptoScheme &&
           (*info)->mMimeType == decoder.GetCurrentInfo()->mMimeType;
       if (!recyclable && decoder.mTimeThreshold.isNothing() &&
           (decoder.mNextStreamSourceID.isNothing() ||
            decoder.mNextStreamSourceID.ref() != info->GetID())) {
         LOG("%s stream id has changed from:%d to:%d, draining decoder.",
             TrackTypeToStr(aTrack), decoder.mLastStreamSourceID, info->GetID());
         decoder.RequestDrain();
         decoder.mNextStreamSourceID = Some(info->GetID());
@@ -2884,20 +2885,19 @@ void MediaFormatReader::SetVideoDecodeTh
     // If IsSeeking() is true, then video seek must have completed already.
     TimeUnit keyframe;
     if (NS_FAILED(mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&keyframe))) {
       return;
     }
 
     // If the key frame is invalid/infinite, it means the target position is
     // closing to end of stream. We don't want to skip any frame at this point.
-    if (!keyframe.IsValid() || keyframe.IsInfinite()) {
-      return;
-    }
-    threshold = mOriginalSeekTarget.GetTime();
+    threshold = keyframe.IsValid() && !keyframe.IsInfinite()
+                    ? mOriginalSeekTarget.GetTime()
+                    : TimeUnit::Invalid();
   } else {
     return;
   }
 
   LOG("Set seek threshold to %" PRId64, threshold.ToMicroseconds());
   mVideo.mDecoder->SetSeekThreshold(threshold);
 }
 
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -538,17 +538,17 @@ class MediaFormatReader final
         return *mInfo;
       }
       return mOriginalInfo.get();
     }
     // Return the current TrackInfo updated as per the decoder output.
     // Typically for audio, the number of channels and/or sampling rate can vary
     // between what was found in the metadata and what the decoder returned.
     const TrackInfo* GetWorkingInfo() const { return mWorkingInfo.get(); }
-    bool IsEncrypted() const { return GetCurrentInfo()->mCrypto.mValid; }
+    bool IsEncrypted() const { return GetCurrentInfo()->mCrypto.IsEncrypted(); }
 
     // Used by the MDSM for logging purposes.
     Atomic<size_t> mSizeOfQueue;
     // Used by the MDSM to determine if video decoding is hardware accelerated.
     // This value is updated after a frame is successfully decoded.
     Atomic<bool> mIsHardwareAccelerated;
     // Sample format monitoring.
     uint32_t mLastStreamSourceID;
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -389,18 +389,18 @@ class MediaInfo {
     }
     // Set dummy values so that HasAudio() will return true;
     // See AudioInfo::IsValid()
     mAudio.mChannels = 2;
     mAudio.mRate = 44100;
   }
 
   bool IsEncrypted() const {
-    return (HasAudio() && mAudio.mCrypto.mValid) ||
-           (HasVideo() && mVideo.mCrypto.mValid);
+    return (HasAudio() && mAudio.mCrypto.IsEncrypted()) ||
+           (HasVideo() && mVideo.mCrypto.IsEncrypted());
   }
 
   bool HasValidMedia() const { return HasVideo() || HasAudio(); }
 
   void AssertValid() const {
     NS_ASSERTION(!HasAudio() || mAudio.mTrackId != TRACK_INVALID,
                  "Audio track ID must be valid");
     NS_ASSERTION(!HasVideo() || mVideo.mTrackId != TRACK_INVALID,
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -2421,16 +2421,23 @@ RefPtr<MediaManager::StreamPromise> Medi
 
   dom::Document* doc = aWindow->GetExtantDoc();
   if (NS_WARN_IF(!doc)) {
     return StreamPromise::CreateAndReject(
         MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError),
         __func__);
   }
 
+  // Disallow access to null principal pages.
+  if (principal->GetIsNullPrincipal()) {
+    return StreamPromise::CreateAndReject(
+        MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
+        __func__);
+  }
+
   // This principal needs to be sent to different threads and so via IPC.
   // For this reason it's better to convert it to PrincipalInfo right now.
   ipc::PrincipalInfo principalInfo;
   rv = PrincipalToPrincipalInfo(principal, &principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return StreamPromise::CreateAndReject(
         MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError),
         __func__);
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -240,30 +240,45 @@ bool ChromiumCDMParent::InitCDMInputBuff
     return false;
   }
 
   Shmem shmem;
   if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
     return false;
   }
   memcpy(shmem.get<uint8_t>(), aSample->Data(), aSample->Size());
-
+  GMPEncryptionScheme encryptionScheme =
+      GMPEncryptionScheme::kGMPEncryptionNone;
+  switch (crypto.mCryptoScheme) {
+    case CryptoScheme::None:
+      break;  // Default to none
+    case CryptoScheme::Cenc:
+      encryptionScheme = GMPEncryptionScheme::kGMPEncryptionCenc;
+      break;
+    case CryptoScheme::Cbcs:
+      encryptionScheme = GMPEncryptionScheme::kGMPEncryptionCbcs;
+      break;
+    default:
+      GMP_LOG(
+          "InitCDMInputBuffer got unexpected encryption scheme with "
+          "value of %" PRIu8 ". Treating as no encryption.",
+          static_cast<uint8_t>(crypto.mCryptoScheme));
+      MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
+      break;
+  }
   aBuffer = gmp::CDMInputBuffer(
       shmem, crypto.mKeyId, crypto.mIV, aSample->mTime.ToMicroseconds(),
       aSample->mDuration.ToMicroseconds(), crypto.mPlainSizes,
-      crypto.mEncryptedSizes,
-      crypto.mValid ? GMPEncryptionScheme::kGMPEncryptionCenc
-                    : GMPEncryptionScheme::kGMPEncryptionNone);
+      crypto.mEncryptedSizes, encryptionScheme);
   MOZ_ASSERT(
       aBuffer.mEncryptionScheme() == GMPEncryptionScheme::kGMPEncryptionNone ||
           aBuffer.mEncryptionScheme() ==
               GMPEncryptionScheme::kGMPEncryptionCenc,
       "aBuffer should use either no encryption or cenc, other kinds are not "
-      "yet "
-      "supported");
+      "yet supported");
   return true;
 }
 
 bool ChromiumCDMParent::SendBufferToCDM(uint32_t aSizeInBytes) {
   GMP_LOG("ChromiumCDMParent::SendBufferToCDM() size=%" PRIu32, aSizeInBytes);
   Shmem shmem;
   if (!AllocShmem(aSizeInBytes, Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
     return false;
--- a/dom/media/gtest/TestMP4Demuxer.cpp
+++ b/dom/media/gtest/TestMP4Demuxer.cpp
@@ -162,18 +162,18 @@ TEST(MP4Demuxer, Seek) {
                          DO_FAIL);
                },
                DO_FAIL);
   });
 }
 
 static nsCString ToCryptoString(const CryptoSample& aCrypto) {
   nsCString res;
-  if (aCrypto.mValid) {
-    res.AppendPrintf("%d %d ", aCrypto.mMode, aCrypto.mIVSize);
+  if (aCrypto.IsEncrypted()) {
+    res.AppendPrintf("%d ", aCrypto.mIVSize);
     for (size_t i = 0; i < aCrypto.mKeyId.Length(); i++) {
       res.AppendPrintf("%02x", aCrypto.mKeyId[i]);
     }
     res.AppendLiteral(" ");
     for (size_t i = 0; i < aCrypto.mIV.Length(); i++) {
       res.AppendPrintf("%02x", aCrypto.mIV[i]);
     }
     EXPECT_EQ(aCrypto.mPlainSizes.Length(), aCrypto.mEncryptedSizes.Length());
@@ -184,135 +184,135 @@ static nsCString ToCryptoString(const Cr
   } else {
     res.AppendLiteral("no crypto");
   }
   return res;
 }
 
 TEST(MP4Demuxer, CENCFragVideo) {
   const char* video[] = {
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000000 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000000 "
       "5,684 5,16980",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000450 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000450 "
       "5,1826",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000004c3 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000004c3 "
       "5,1215",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000050f "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000050f "
       "5,1302",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000561 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000561 "
       "5,939",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000059c "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000059c "
       "5,763",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000005cc "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000005cc "
       "5,672",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000005f6 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000005f6 "
       "5,748",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000625 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000625 "
       "5,1025",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000666 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000666 "
       "5,730",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000694 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000694 "
       "5,897",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000006cd "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000006cd "
       "5,643",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000006f6 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000006f6 "
       "5,556",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000719 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000719 "
       "5,527",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000073a "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000073a "
       "5,606",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000760 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000760 "
       "5,701",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000078c "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000078c "
       "5,531",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007ae "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007ae "
       "5,562",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007d2 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007d2 "
       "5,576",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007f6 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000007f6 "
       "5,514",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000817 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000817 "
       "5,404",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000831 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000831 "
       "5,635",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000859 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000859 "
       "5,433",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000875 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000875 "
       "5,478",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000893 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000893 "
       "5,474",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008b1 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008b1 "
       "5,462",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008ce "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008ce "
       "5,473",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008ec "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000008ec "
       "5,437",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000908 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000908 "
       "5,418",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000923 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000923 "
       "5,475",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000941 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000941 "
       "5,23133",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000ee7 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000ee7 "
       "5,475",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f05 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f05 "
       "5,402",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f1f "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f1f "
       "5,415",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f39 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f39 "
       "5,408",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f53 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f53 "
       "5,442",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f6f "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f6f "
       "5,385",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f88 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f88 "
       "5,368",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f9f "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000f9f "
       "5,354",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fb6 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fb6 "
       "5,400",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fcf "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fcf "
       "5,399",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fe8 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000000fe8 "
       "5,1098",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000102d "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000102d "
       "5,1508",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000108c "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000108c "
       "5,1345",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000010e1 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000010e1 "
       "5,1945",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000115b "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000115b "
       "5,1824",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000011cd "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000011cd "
       "5,2133",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001253 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001253 "
       "5,2486",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000012ef "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000012ef "
       "5,1739",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000135c "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000135c "
       "5,1836",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000013cf "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000013cf "
       "5,2367",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001463 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001463 "
       "5,2571",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001504 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001504 "
       "5,3008",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000015c0 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000015c0 "
       "5,3255",
-      "1 16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000168c "
+      "16 7e571d037e571d037e571d037e571d03 0000000000000000000000000000168c "
       "5,3225",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001756 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001756 "
       "5,3118",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001819 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001819 "
       "5,2407",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000018b0 "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000018b0 "
       "5,2400",
-      "1 16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001946 "
+      "16 7e571d037e571d037e571d037e571d03 00000000000000000000000000001946 "
       "5,2158",
-      "1 16 7e571d037e571d037e571d037e571d03 000000000000000000000000000019cd "
+      "16 7e571d037e571d037e571d037e571d03 000000000000000000000000000019cd "
       "5,2392",
   };
 
   RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
 
   binding->RunTestAndWait([binding, video]() {
     // grab all video samples.
     binding->mVideoTrack =
@@ -329,203 +329,203 @@ TEST(MP4Demuxer, CENCFragVideo) {
                  binding->mTaskQueue->BeginShutdown();
                },
                DO_FAIL);
   });
 }
 
 TEST(MP4Demuxer, CENCFragAudio) {
   const char* audio[] = {
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000000 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000000 "
       "0,281",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000012 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000012 "
       "0,257",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000023 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000023 "
       "0,246",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000033 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000033 "
       "0,257",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000044 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000044 "
       "0,260",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000055 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000055 "
       "0,260",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000066 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000066 "
       "0,272",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000077 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000077 "
       "0,280",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000089 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000089 "
       "0,284",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000009b "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000009b "
       "0,290",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000ae "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000ae "
       "0,278",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000c0 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000c0 "
       "0,268",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000d1 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000d1 "
       "0,307",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000e5 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000e5 "
       "0,290",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000f8 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000000f8 "
       "0,304",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000010b "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000010b "
       "0,316",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000011f "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000011f "
       "0,308",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000133 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000133 "
       "0,301",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000146 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000146 "
       "0,318",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000015a "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000015a "
       "0,311",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000016e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000016e "
       "0,303",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000181 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000181 "
       "0,325",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000196 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000196 "
       "0,334",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001ab "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001ab "
       "0,344",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001c1 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001c1 "
       "0,344",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001d7 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001d7 "
       "0,387",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001f0 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000001f0 "
       "0,396",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000209 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000209 "
       "0,368",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000220 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000220 "
       "0,373",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000238 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000238 "
       "0,425",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000253 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000253 "
       "0,428",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000026e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000026e "
       "0,426",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000289 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000289 "
       "0,427",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002a4 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002a4 "
       "0,424",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002bf "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002bf "
       "0,447",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002db "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002db "
       "0,446",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002f7 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000002f7 "
       "0,442",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000313 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000313 "
       "0,444",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000032f "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000032f "
       "0,374",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000347 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000347 "
       "0,405",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000361 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000361 "
       "0,372",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000379 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000379 "
       "0,395",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000392 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000392 "
       "0,435",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003ae "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003ae "
       "0,426",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003c9 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003c9 "
       "0,430",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003e4 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003e4 "
       "0,390",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003fd "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000003fd "
       "0,335",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000412 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000412 "
       "0,339",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000428 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000428 "
       "0,352",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000043e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000043e "
       "0,364",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000455 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000455 "
       "0,398",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000046e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000046e "
       "0,451",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000048b "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000048b "
       "0,448",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004a7 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004a7 "
       "0,436",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004c3 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004c3 "
       "0,424",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004de "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004de "
       "0,428",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004f9 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000004f9 "
       "0,413",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000513 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000513 "
       "0,430",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000052e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000052e "
       "0,450",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000054b "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000054b "
       "0,386",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000564 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000564 "
       "0,320",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000578 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000578 "
       "0,347",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000058e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000058e "
       "0,382",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005a6 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005a6 "
       "0,437",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005c2 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005c2 "
       "0,387",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005db "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005db "
       "0,340",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005f1 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000005f1 "
       "0,337",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000607 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000607 "
       "0,389",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000620 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000620 "
       "0,428",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000063b "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000063b "
       "0,426",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000656 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000656 "
       "0,446",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000672 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000672 "
       "0,456",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000068f "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000068f "
       "0,468",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006ad "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006ad "
       "0,468",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006cb "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006cb "
       "0,463",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006e8 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000006e8 "
       "0,467",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000706 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000706 "
       "0,460",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000723 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000723 "
       "0,446",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000073f "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000073f "
       "0,453",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000075c "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000075c "
       "0,448",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000778 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000778 "
       "0,446",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000794 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000794 "
       "0,439",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007b0 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007b0 "
       "0,436",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007cc "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007cc "
       "0,441",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007e8 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000007e8 "
       "0,465",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000806 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000806 "
       "0,448",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000822 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000822 "
       "0,448",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000083e "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000083e "
       "0,469",
-      "1 16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000085c "
+      "16 7e571d047e571d047e571d047e571d04 0000000000000000000000000000085c "
       "0,431",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000877 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000877 "
       "0,437",
-      "1 16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000893 "
+      "16 7e571d047e571d047e571d047e571d04 00000000000000000000000000000893 "
       "0,474",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008b1 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008b1 "
       "0,436",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008cd "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008cd "
       "0,433",
-      "1 16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008e9 "
+      "16 7e571d047e571d047e571d047e571d04 000000000000000000000000000008e9 "
       "0,481",
   };
 
   RefPtr<MP4DemuxerBinding> binding = new MP4DemuxerBinding("gizmo-frag.mp4");
 
   binding->RunTestAndWait([binding, audio]() {
     // grab all audio samples.
     binding->mAudioTrack =
--- a/dom/media/hls/HLSDemuxer.cpp
+++ b/dom/media/hls/HLSDemuxer.cpp
@@ -389,23 +389,30 @@ CryptoSample HLSTrackDemuxer::ExtractCry
   if (!aCryptoInfo) {
     return CryptoSample{};
   }
   // Extract Crypto information
   CryptoSample crypto;
   char const* msg = "";
   do {
     HLS_DEBUG("HLSTrackDemuxer", "Sample has Crypto Info");
-    crypto.mValid = true;
+
     int32_t mode = 0;
     if (NS_FAILED(aCryptoInfo->Mode(&mode))) {
       msg = "Error when extracting encryption mode.";
       break;
     }
-    crypto.mMode = mode;
+    // We currently only handle ctr mode.
+    if (mode != java::sdk::MediaCodec::CRYPTO_MODE_AES_CTR) {
+      msg = "Error: unexpected encryption mode.";
+      break;
+    }
+
+    crypto.mCryptoScheme = CryptoScheme::Cenc;
+
     mozilla::jni::ByteArray::LocalRef ivData;
     if (NS_FAILED(aCryptoInfo->Iv(&ivData))) {
       msg = "Error when extracting encryption IV.";
       break;
     }
     // Data in mIV is uint8_t and jbyte is signed char
     auto&& ivArr = ivData->GetElements();
     crypto.mIV.AppendElements(reinterpret_cast<uint8_t*>(&ivArr[0]),
--- a/dom/media/ipc/PRemoteVideoDecoder.ipdl
+++ b/dom/media/ipc/PRemoteVideoDecoder.ipdl
@@ -32,17 +32,17 @@ async protocol PRemoteVideoDecoder
 parent:
   async Init();
 
   async Input(MediaRawDataIPDL data);
 
   async Flush();
   async Drain();
   async Shutdown();
-
+  // To clear the threshold, call with INT64_MIN.
   async SetSeekThreshold(int64_t time);
 
   async __delete__();
 
 child:
   async InitComplete(nsCString decoderDescription,
                      ConversionRequired conversion);
   async InitFailed(nsresult reason);
--- a/dom/media/ipc/PVideoDecoder.ipdl
+++ b/dom/media/ipc/PVideoDecoder.ipdl
@@ -32,17 +32,17 @@ async protocol PVideoDecoder
 parent:
   async Init();
 
   async Input(MediaRawDataIPDL data);
 
   async Flush();
   async Drain();
   async Shutdown();
-
+  // To clear the threshold, call with INT64_MIN.
   async SetSeekThreshold(int64_t time);
 
   async __delete__();
 
 child:
   async InitComplete(nsCString decoderDescription, bool hardware, nsCString hardwareReason, uint32_t conversion);
   async InitFailed(nsresult reason);
 
--- a/dom/media/ipc/RemoteVideoDecoderChild.cpp
+++ b/dom/media/ipc/RemoteVideoDecoderChild.cpp
@@ -284,17 +284,17 @@ bool RemoteVideoDecoderChild::IsHardware
 nsCString RemoteVideoDecoderChild::GetDescriptionName() const {
   AssertOnManagerThread();
   return mDescription;
 }
 
 void RemoteVideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime) {
   AssertOnManagerThread();
   if (mCanSend) {
-    SendSetSeekThreshold(aTime.ToMicroseconds());
+    SendSetSeekThreshold(aTime.IsValid() ? aTime.ToMicroseconds() : INT64_MIN);
   }
 }
 
 MediaDataDecoder::ConversionRequired RemoteVideoDecoderChild::NeedsConversion()
     const {
   AssertOnManagerThread();
   return mConversion;
 }
--- a/dom/media/ipc/RemoteVideoDecoderParent.cpp
+++ b/dom/media/ipc/RemoteVideoDecoderParent.cpp
@@ -199,17 +199,19 @@ mozilla::ipc::IPCResult RemoteVideoDecod
   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));
+  mDecoder->SetSeekThreshold(aTime == INT64_MIN
+                                 ? TimeUnit::Invalid()
+                                 : TimeUnit::FromMicroseconds(aTime));
   return IPC_OK();
 }
 
 void RemoteVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
     mDecoder->Shutdown();
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -299,17 +299,17 @@ bool VideoDecoderChild::IsHardwareAccele
 nsCString VideoDecoderChild::GetDescriptionName() const {
   AssertOnManagerThread();
   return mDescription;
 }
 
 void VideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime) {
   AssertOnManagerThread();
   if (mCanSend) {
-    SendSetSeekThreshold(aTime.ToMicroseconds());
+    SendSetSeekThreshold(aTime.IsValid() ? aTime.ToMicroseconds() : INT64_MIN);
   }
 }
 
 MediaDataDecoder::ConversionRequired VideoDecoderChild::NeedsConversion()
     const {
   AssertOnManagerThread();
   return mConversion;
 }
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -247,17 +247,19 @@ mozilla::ipc::IPCResult VideoDecoderPare
   mDecoder = nullptr;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VideoDecoderParent::RecvSetSeekThreshold(
     const int64_t& aTime) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
-  mDecoder->SetSeekThreshold(TimeUnit::FromMicroseconds(aTime));
+  mDecoder->SetSeekThreshold(aTime == INT64_MIN
+                                 ? TimeUnit::Invalid()
+                                 : TimeUnit::FromMicroseconds(aTime));
   return IPC_OK();
 }
 
 void VideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(OnManagerThread());
   if (mDecoder) {
     mDecoder->Shutdown();
--- a/dom/media/mp4/DecoderData.cpp
+++ b/dom/media/mp4/DecoderData.cpp
@@ -42,24 +42,39 @@ mozilla::Result<mozilla::Ok, nsresult> C
 
 bool MP4AudioInfo::IsValid() const {
   return mChannels > 0 && mRate > 0 &&
          // Accept any mime type here, but if it's aac, validate the profile.
          (!mMimeType.EqualsLiteral("audio/mp4a-latm") || mProfile > 0 ||
           mExtendedProfile > 0);
 }
 
-static void UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig,
-                                     const Mp4parseSinfInfo& aSinf) {
+static MediaResult UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig,
+                                            const Mp4parseSinfInfo& aSinf) {
   if (aSinf.is_encrypted != 0) {
-    aConfig.mCrypto.mValid = true;
-    aConfig.mCrypto.mMode = aSinf.is_encrypted;
+    if (aSinf.scheme_type == MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENC) {
+      aConfig.mCrypto.mCryptoScheme = CryptoScheme::Cenc;
+    } else if (aSinf.scheme_type == MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBCS) {
+      aConfig.mCrypto.mCryptoScheme = CryptoScheme::Cbcs;
+    } else {
+      // Unsupported encryption type;
+      return MediaResult(
+          NS_ERROR_DOM_MEDIA_METADATA_ERR,
+          RESULT_DETAIL(
+              "Unsupported encryption scheme encountered aSinf.scheme_type=%d",
+              static_cast<int>(aSinf.scheme_type)));
+    }
     aConfig.mCrypto.mIVSize = aSinf.iv_size;
     aConfig.mCrypto.mKeyId.AppendElements(aSinf.kid.data, aSinf.kid.length);
+    aConfig.mCrypto.mCryptByteBlock = aSinf.crypt_byte_block;
+    aConfig.mCrypto.mSkipByteBlock = aSinf.skip_byte_block;
+    aConfig.mCrypto.mConstantIV.AppendElements(aSinf.constant_iv.data,
+                                               aSinf.constant_iv.length);
   }
+  return NS_OK;
 }
 
 MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* track,
                                  const Mp4parseTrackAudioInfo* audio) {
   MOZ_DIAGNOSTIC_ASSERT(audio->sample_info_count > 0,
                         "Must have at least one audio sample info");
   if (audio->sample_info_count == 0) {
     return MediaResult(
@@ -82,17 +97,19 @@ MediaResult MP4AudioInfo::Update(const M
     if (audio->sample_info[i].protected_data.is_encrypted) {
       if (hasCrypto) {
         // Multiple crypto entries found. We don't handle this.
         return MediaResult(
             NS_ERROR_DOM_MEDIA_METADATA_ERR,
             RESULT_DETAIL(
                 "Multiple crypto info encountered while updating audio track"));
       }
-      UpdateTrackProtectedInfo(*this, audio->sample_info[i].protected_data);
+      auto rv =
+          UpdateTrackProtectedInfo(*this, audio->sample_info[i].protected_data);
+      NS_ENSURE_SUCCESS(rv, rv);
       hasCrypto = true;
     }
   }
 
   // We assume that the members of the first sample info are representative of
   // the entire track. This code will need to be updated should this assumption
   // ever not hold. E.g. if we need to handle different codecs in a single
   // track, or if we have different numbers or channels in a single track.
@@ -166,17 +183,19 @@ MediaResult MP4VideoInfo::Update(const M
     if (video->sample_info[i].protected_data.is_encrypted) {
       if (hasCrypto) {
         // Multiple crypto entries found. We don't handle this.
         return MediaResult(
             NS_ERROR_DOM_MEDIA_METADATA_ERR,
             RESULT_DETAIL(
                 "Multiple crypto info encountered while updating video track"));
       }
-      UpdateTrackProtectedInfo(*this, video->sample_info[i].protected_data);
+      auto rv =
+          UpdateTrackProtectedInfo(*this, video->sample_info[i].protected_data);
+      NS_ENSURE_SUCCESS(rv, rv);
       hasCrypto = true;
     }
   }
 
   // We assume that the members of the first sample info are representative of
   // the entire track. This code will need to be updated should this assumption
   // ever not hold. E.g. if we need to handle different codecs in a single
   // track, or if we have different numbers or channels in a single track.
--- a/dom/media/mp4/Index.cpp
+++ b/dom/media/mp4/Index.cpp
@@ -106,93 +106,168 @@ already_AddRefed<MediaRawData> SampleIte
 
   size_t bytesRead;
   if (!mIndex->mSource->ReadAt(sample->mOffset, writer->Data(), sample->Size(),
                                &bytesRead) ||
       bytesRead != sample->Size()) {
     return nullptr;
   }
 
-  if (mCurrentSample == 0 && mIndex->mMoofParser) {
-    const nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
-    MOZ_ASSERT(mCurrentMoof < moofs.Length());
-    const Moof* currentMoof = &moofs[mCurrentMoof];
-    if (!currentMoof->mPsshes.IsEmpty()) {
-      // This Moof contained crypto init data. Report that. We only report
-      // the init data on the Moof's first sample, to avoid reporting it more
-      // than once per Moof.
-      writer->mCrypto.mValid = true;
-      writer->mCrypto.mInitDatas.AppendElements(currentMoof->mPsshes);
+  MoofParser* moofParser = mIndex->mMoofParser.get();
+  if (!moofParser) {
+    // File is not fragmented, we can't have crypto, just early return.
+    Next();
+    return sample.forget();
+  }
+
+  SampleDescriptionEntry* sampleDescriptionEntry = GetSampleDescriptionEntry();
+  if (!sampleDescriptionEntry) {
+    // For the file to be valid the tfhd must reference a sample description
+    // entry.
+    return nullptr;
+  }
+
+  // Match scheme type box to enum representation.
+  CryptoScheme cryptoScheme = CryptoScheme::None;
+  // If a fragment references a sample description without crypto info them we
+  // treat it as unencrypted, even if other fragments may be encrypted.
+  if (sampleDescriptionEntry->mIsEncryptedEntry) {
+    if (!moofParser->mSinf.IsValid()) {
+      MOZ_ASSERT_UNREACHABLE(
+          "Sample description entry reports sample is encrypted, but no "
+          "sinf was parsed!");
+      return nullptr;
+    }
+    if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cenc")) {
+      cryptoScheme = CryptoScheme::Cenc;
+      writer->mCrypto.mCryptoScheme = CryptoScheme::Cenc;
       writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cenc");
+    } else if (moofParser->mSinf.mDefaultEncryptionType == AtomType("cbcs")) {
+      cryptoScheme = CryptoScheme::Cbcs;
+      writer->mCrypto.mCryptoScheme = CryptoScheme::Cbcs;
+      writer->mCrypto.mInitDataType = NS_LITERAL_STRING("cbcs");
+    } else {
+      MOZ_ASSERT_UNREACHABLE(
+          "Sample description entry reports sample is encrypted, but no "
+          "scheme, or an unsupported shceme is in use!");
+      return nullptr;
     }
   }
 
-  if (!s->mCencRange.IsEmpty()) {
-    MoofParser* parser = mIndex->mMoofParser.get();
-
-    if (!parser || !parser->mSinf.IsValid()) {
-      return nullptr;
+  if (mCurrentSample == 0) {
+    const nsTArray<Moof>& moofs = moofParser->Moofs();
+    const Moof* currentMoof = &moofs[mCurrentMoof];
+    if (!currentMoof->mPsshes.IsEmpty()) {
+      MOZ_ASSERT(sampleDescriptionEntry->mIsEncryptedEntry,
+                 "Unencrypted fragments should not contain pssh boxes");
+      MOZ_ASSERT(cryptoScheme != CryptoScheme::None);
+      // This Moof contained crypto init data. Report that. We only report
+      // the init data on the Moof's first sample, to avoid reporting it more
+      // than once per Moof.
+      writer->mCrypto.mInitDatas.AppendElements(currentMoof->mPsshes);
     }
-
-    uint8_t ivSize = parser->mSinf.mDefaultIVSize;
+  }
 
-    // The size comes from an 8 bit field
-    AutoTArray<uint8_t, 256> cenc;
-    cenc.SetLength(s->mCencRange.Length());
-    if (!mIndex->mSource->ReadAt(s->mCencRange.mStart, cenc.Elements(),
-                                 cenc.Length(), &bytesRead) ||
-        bytesRead != cenc.Length()) {
-      return nullptr;
-    }
-    BufferReader reader(cenc);
-    writer->mCrypto.mValid = true;
+  if (sampleDescriptionEntry->mIsEncryptedEntry) {
+    writer->mCrypto.mCryptoScheme = cryptoScheme;
 
+    MOZ_ASSERT(writer->mCrypto.mKeyId.IsEmpty(),
+               "Sample should not already have a key ID");
+    MOZ_ASSERT(writer->mCrypto.mConstantIV.IsEmpty(),
+               "Sample should not already have a constant IV");
     CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry();
     if (sampleInfo) {
       // Use sample group information if present, this supersedes track level
       // information.
       writer->mCrypto.mKeyId.AppendElements(sampleInfo->mKeyId);
-      ivSize = sampleInfo->mIVSize;
+      writer->mCrypto.mIVSize = sampleInfo->mIVSize;
+      writer->mCrypto.mCryptByteBlock = sampleInfo->mCryptByteBlock;
+      writer->mCrypto.mSkipByteBlock = sampleInfo->mSkipByteBlock;
+      writer->mCrypto.mConstantIV.AppendElements(sampleInfo->mConsantIV);
+    } else {
+      // Use the crypto info from track metadata
+      writer->mCrypto.mKeyId.AppendElements(moofParser->mSinf.mDefaultKeyID,
+                                            16);
+      writer->mCrypto.mIVSize = moofParser->mSinf.mDefaultIVSize;
+      writer->mCrypto.mCryptByteBlock =
+          moofParser->mSinf.mDefaultCryptByteBlock;
+      writer->mCrypto.mSkipByteBlock = moofParser->mSinf.mDefaultSkipByteBlock;
+      writer->mCrypto.mConstantIV.AppendElements(
+          moofParser->mSinf.mDefaultConstantIV);
     }
 
-    writer->mCrypto.mIVSize = ivSize;
-
-    if (!reader.ReadArray(writer->mCrypto.mIV, ivSize)) {
-      return nullptr;
-    }
-
-    auto res = reader.ReadU16();
-    if (res.isOk() && res.unwrap() > 0) {
-      uint16_t count = res.unwrap();
-
-      if (reader.Remaining() < count * 6) {
+    MOZ_ASSERT((writer->mCrypto.mIVSize == 0 &&
+                !writer->mCrypto.mConstantIV.IsEmpty()) ||
+                   !s->mCencRange.IsEmpty(),
+               "Crypto information should contain either a constant IV, or "
+               "have auxiliary information that will contain an IV");
+    // Parse auxiliary information if present
+    if (!s->mCencRange.IsEmpty()) {
+      // The size comes from an 8 bit field
+      AutoTArray<uint8_t, 256> cencAuxInfo;
+      cencAuxInfo.SetLength(s->mCencRange.Length());
+      if (!mIndex->mSource->ReadAt(s->mCencRange.mStart, cencAuxInfo.Elements(),
+                                   cencAuxInfo.Length(), &bytesRead) ||
+          bytesRead != cencAuxInfo.Length()) {
+        return nullptr;
+      }
+      BufferReader reader(cencAuxInfo);
+      if (!reader.ReadArray(writer->mCrypto.mIV, writer->mCrypto.mIVSize)) {
         return nullptr;
       }
 
-      for (size_t i = 0; i < count; i++) {
-        auto res_16 = reader.ReadU16();
-        auto res_32 = reader.ReadU32();
-        if (res_16.isErr() || res_32.isErr()) {
+      // Parse the auxiliary information for subsample information
+      auto res = reader.ReadU16();
+      if (res.isOk() && res.unwrap() > 0) {
+        uint16_t count = res.unwrap();
+
+        if (reader.Remaining() < count * 6) {
           return nullptr;
         }
-        writer->mCrypto.mPlainSizes.AppendElement(res_16.unwrap());
-        writer->mCrypto.mEncryptedSizes.AppendElement(res_32.unwrap());
+
+        for (size_t i = 0; i < count; i++) {
+          auto res_16 = reader.ReadU16();
+          auto res_32 = reader.ReadU32();
+          if (res_16.isErr() || res_32.isErr()) {
+            return nullptr;
+          }
+          writer->mCrypto.mPlainSizes.AppendElement(res_16.unwrap());
+          writer->mCrypto.mEncryptedSizes.AppendElement(res_32.unwrap());
+        }
+      } else {
+        // No subsample information means the entire sample is encrypted.
+        writer->mCrypto.mPlainSizes.AppendElement(0);
+        writer->mCrypto.mEncryptedSizes.AppendElement(sample->Size());
       }
-    } else {
-      // No subsample information means the entire sample is encrypted.
-      writer->mCrypto.mPlainSizes.AppendElement(0);
-      writer->mCrypto.mEncryptedSizes.AppendElement(sample->Size());
     }
   }
 
   Next();
 
   return sample.forget();
 }
 
+SampleDescriptionEntry* SampleIterator::GetSampleDescriptionEntry() {
+  nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
+  Moof& currentMoof = moofs[mCurrentMoof];
+  uint32_t sampleDescriptionIndex =
+      currentMoof.mTfhd.mDefaultSampleDescriptionIndex;
+  // Mp4 indices start at 1, shift down 1 so we index our array correctly.
+  sampleDescriptionIndex--;
+  FallibleTArray<SampleDescriptionEntry>& sampleDescriptions =
+      mIndex->mMoofParser->mSampleDescriptions;
+  if (sampleDescriptionIndex >= sampleDescriptions.Length()) {
+    MOZ_ASSERT_UNREACHABLE(
+        "Should always be able to find the appropriate sample description! "
+        "Malformed mp4?");
+    return nullptr;
+  }
+  return &sampleDescriptions[sampleDescriptionIndex];
+}
+
 CencSampleEncryptionInfoEntry* SampleIterator::GetSampleEncryptionEntry() {
   nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
   Moof* currentMoof = &moofs[mCurrentMoof];
   SampleToGroupEntry* sampleToGroupEntry = nullptr;
 
   // Default to using the sample to group entries for the fragment, otherwise
   // fall back to the sample to group entries for the track.
   FallibleTArray<SampleToGroupEntry>* sampleToGroupEntries =
--- a/dom/media/mp4/Index.h
+++ b/dom/media/mp4/Index.h
@@ -31,16 +31,19 @@ class SampleIterator {
   ~SampleIterator();
   already_AddRefed<mozilla::MediaRawData> GetNext();
   void Seek(Microseconds aTime);
   Microseconds GetNextKeyframeTime();
 
  private:
   Sample* Get();
 
+  // Gets the sample description entry for the current moof, or nullptr if
+  // called without a valid current moof.
+  SampleDescriptionEntry* GetSampleDescriptionEntry();
   CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry();
 
   void Next();
   RefPtr<Index> mIndex;
   friend class Index;
   size_t mCurrentMoof;
   size_t mCurrentSample;
 };
--- a/dom/media/mp4/MP4Demuxer.cpp
+++ b/dom/media/mp4/MP4Demuxer.cpp
@@ -417,17 +417,17 @@ RefPtr<MP4TrackDemuxer::SeekPromise> MP4
 
 already_AddRefed<MediaRawData> MP4TrackDemuxer::GetNextSample() {
   RefPtr<MediaRawData> sample = mIterator->GetNext();
   if (!sample) {
     return nullptr;
   }
   if (mInfo->GetAsVideoInfo()) {
     sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData;
-    if (mIsH264 && !sample->mCrypto.mValid) {
+    if (mIsH264 && !sample->mCrypto.IsEncrypted()) {
       H264::FrameType type = H264::GetFrameType(sample);
       switch (type) {
         case H264::FrameType::I_FRAME:
           MOZ_FALLTHROUGH;
         case H264::FrameType::OTHER: {
           bool keyframe = type == H264::FrameType::I_FRAME;
           if (sample->mKeyframe != keyframe) {
             NS_WARNING(nsPrintfCString("Frame incorrectly marked as %skeyframe "
@@ -453,27 +453,16 @@ already_AddRefed<MediaRawData> MP4TrackD
           // So we keep the invalid frame, relying on the H264 decoder to
           // handle the error later.
           // TODO: make demuxer errors non-fatal.
           break;
       }
     }
   }
 
-  if (sample->mCrypto.mValid) {
-    UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
-    writer->mCrypto.mMode = mInfo->mCrypto.mMode;
-
-    // Only use the default key parsed from the moov if we haven't already got
-    // one from the sample group description.
-    if (writer->mCrypto.mKeyId.Length() == 0) {
-      writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
-      writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
-    }
-  }
   return sample.forget();
 }
 
 RefPtr<MP4TrackDemuxer::SamplesPromise> MP4TrackDemuxer::GetSamples(
     int32_t aNumSamples) {
   EnsureUpToDateIndex();
   RefPtr<SamplesHolder> samples = new SamplesHolder;
   if (!aNumSamples) {
--- a/dom/media/mp4/MP4Metadata.cpp
+++ b/dom/media/mp4/MP4Metadata.cpp
@@ -346,17 +346,17 @@ MP4Metadata::ResultAndTrackInfo MP4Metad
                 ("mp4parse_get_track_audio_info returned error %d", rv));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL("Cannot parse %s track #%zu",
                                           TrackTypeToStr(aType), aTrackNumber)),
                 nullptr};
       }
       auto track = mozilla::MakeUnique<MP4AudioInfo>();
       MediaResult updateStatus = track->Update(&info, &audio);
-      if (updateStatus != NS_OK) {
+      if (NS_FAILED(updateStatus)) {
         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
                 ("Updating audio track failed with %s",
                  updateStatus.Message().get()));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL(
                                 "Failed to update %s track #%zu with error: %s",
                                 TrackTypeToStr(aType), aTrackNumber,
                                 updateStatus.Message().get())),
@@ -373,17 +373,17 @@ MP4Metadata::ResultAndTrackInfo MP4Metad
                 ("mp4parse_get_track_video_info returned error %d", rv));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL("Cannot parse %s track #%zu",
                                           TrackTypeToStr(aType), aTrackNumber)),
                 nullptr};
       }
       auto track = mozilla::MakeUnique<MP4VideoInfo>();
       MediaResult updateStatus = track->Update(&info, &video);
-      if (updateStatus != NS_OK) {
+      if (NS_FAILED(updateStatus)) {
         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
                 ("Updating video track failed with %s",
                  updateStatus.Message().get()));
         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
                             RESULT_DETAIL(
                                 "Failed to update %s track #%zu with error: %s",
                                 TrackTypeToStr(aType), aTrackNumber,
                                 updateStatus.Message().get())),
--- a/dom/media/mp4/MoofParser.cpp
+++ b/dom/media/mp4/MoofParser.cpp
@@ -418,17 +418,17 @@ Moof::Moof(Box& aBox, Trex& aTrex, Mvhd&
       for (auto& sample : mIndex) {
         sample.mDecodeTime = dtsOffset + int64_t(compositionDuration * adjust);
         compositionDuration += sample.mCompositionRange.Length();
       }
       mTimeRange = MP4Interval<Microseconds>(
           ctsOrder[0]->mCompositionRange.start,
           ctsOrder.LastElement()->mCompositionRange.end);
     }
-    ProcessCenc();
+    ProcessCencAuxInfo(aSinf.mDefaultEncryptionType);
   }
 }
 
 bool Moof::GetAuxInfo(AtomType aType,
                       FallibleTArray<MediaByteRange>* aByteRanges) {
   aByteRanges->Clear();
 
   Saiz* saiz = nullptr;
@@ -487,19 +487,19 @@ bool Moof::GetAuxInfo(AtomType aType,
       }
     }
     return true;
   }
 
   return false;
 }
 
-bool Moof::ProcessCenc() {
+bool Moof::ProcessCencAuxInfo(AtomType aScheme) {
   FallibleTArray<MediaByteRange> cencRanges;
-  if (!GetAuxInfo(AtomType("cenc"), &cencRanges) ||
+  if (!GetAuxInfo(aScheme, &cencRanges) ||
       cencRanges.Length() != mIndex.Length()) {
     return false;
   }
   for (int i = 0; i < cencRanges.Length(); i++) {
     mIndex[i].mCencRange = cencRanges[i];
   }
   return true;
 }
@@ -1063,37 +1063,56 @@ Result<Ok, nsresult> Sgpd::Parse(Box& aB
   }
   return Ok();
 }
 
 Result<Ok, nsresult> CencSampleEncryptionInfoEntry::Init(BoxReader& aReader) {
   // Skip a reserved byte.
   MOZ_TRY(aReader->ReadU8());
 
-  uint8_t possiblePatternInfo;
-  MOZ_TRY_VAR(possiblePatternInfo, aReader->ReadU8());
-  uint8_t flag;
-  MOZ_TRY_VAR(flag, aReader->ReadU8());
+  uint8_t pattern;
+  MOZ_TRY_VAR(pattern, aReader->ReadU8());
+  mCryptByteBlock = pattern >> 4;
+  mSkipByteBlock = pattern & 0x0f;
+
+  uint8_t isEncrypted;
+  MOZ_TRY_VAR(isEncrypted, aReader->ReadU8());
+  mIsEncrypted = isEncrypted != 0;
 
   MOZ_TRY_VAR(mIVSize, aReader->ReadU8());
 
   // Read the key id.
-  uint8_t key;
+  if (!mKeyId.SetLength(kKeyIdSize, fallible)) {
+    LOG(CencSampleEncryptionInfoEntry, "OOM");
+    return Err(NS_ERROR_FAILURE);
+  }
   for (uint32_t i = 0; i < kKeyIdSize; ++i) {
-    MOZ_TRY_VAR(key, aReader->ReadU8());
-    mKeyId.AppendElement(key);
+    MOZ_TRY_VAR(mKeyId.ElementAt(i), aReader->ReadU8());
   }
 
-  mIsEncrypted = flag != 0;
-
   if (mIsEncrypted) {
     if (mIVSize != 8 && mIVSize != 16) {
       return Err(NS_ERROR_FAILURE);
     }
   } else if (mIVSize != 0) {
-    return Err(NS_ERROR_FAILURE);
+    // Protected content with 0 sized IV indicates a constant IV is present.
+    // This is used for the cbcs scheme.
+    uint8_t constantIVSize;
+    MOZ_TRY_VAR(constantIVSize, aReader->ReadU8());
+    if (constantIVSize != 8 && constantIVSize != 16) {
+      LOG(CencSampleEncryptionInfoEntry, "Unexpected constantIVSize: %" PRIu8,
+          constantIVSize);
+      return Err(NS_ERROR_FAILURE);
+    }
+    if (!mConsantIV.SetLength(constantIVSize, mozilla::fallible)) {
+      LOG(CencSampleEncryptionInfoEntry, "OOM");
+      return Err(NS_ERROR_FAILURE);
+    }
+    for (uint32_t i = 0; i < constantIVSize; ++i) {
+      MOZ_TRY_VAR(mConsantIV.ElementAt(i), aReader->ReadU8());
+    }
   }
 
   return Ok();
 }
 
 #undef LOG
 }  // namespace mozilla
--- a/dom/media/mp4/MoofParser.h
+++ b/dom/media/mp4/MoofParser.h
@@ -180,25 +180,32 @@ class Sbgp final : public Atom  // Sampl
   AtomType mGroupingType;
   uint32_t mGroupingTypeParam;
   FallibleTArray<SampleToGroupEntry> mEntries;
 
  protected:
   Result<Ok, nsresult> Parse(Box& aBox);
 };
 
+// Stores information form CencSampleEncryptionInformationGroupEntry (seig).
+// Cenc here refers to the common encryption standard, rather than the specific
+// cenc scheme from that standard. This structure is used for all encryption
+// schemes. I.e. it is used for both cenc and cbcs, not just cenc.
 struct CencSampleEncryptionInfoEntry final {
  public:
   CencSampleEncryptionInfoEntry() {}
 
   Result<Ok, nsresult> Init(BoxReader& aReader);
 
   bool mIsEncrypted = false;
   uint8_t mIVSize = 0;
   nsTArray<uint8_t> mKeyId;
+  uint8_t mCryptByteBlock = 0;
+  uint8_t mSkipByteBlock = 0;
+  nsTArray<uint8_t> mConsantIV;
 };
 
 class Sgpd final : public Atom  // SampleGroupDescription box.
 {
  public:
   explicit Sgpd(Box& aBox);
 
   AtomType mGroupingType;
@@ -241,17 +248,23 @@ class Moof final : public Atom {
  private:
   // aDecodeTime is updated to the end of the parsed TRAF on return.
   void ParseTraf(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts,
                  Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio);
   // aDecodeTime is updated to the end of the parsed TRUN on return.
   Result<Ok, nsresult> ParseTrun(Box& aBox, Mvhd& aMvhd, Mdhd& aMdhd,
                                  Edts& aEdts, uint64_t* aDecodeTime,
                                  bool aIsAudio);
-  bool ProcessCenc();
+  // Process the sample auxiliary information used by common encryption.
+  // aScheme is used to select the appropriate auxiliary information and should
+  // be set based on the encryption scheme used by the track being processed.
+  // Note, the term cenc here refers to the standard, not the specific scheme
+  // from that standard. I.e. this function is used to handle up auxiliary
+  // information from the cenc and cbcs schemes.
+  bool ProcessCencAuxInfo(AtomType aScheme);
   uint64_t mMaxRoundingError;
 };
 
 DDLoggedTypeDeclName(MoofParser);
 
 class MoofParser : public DecoderDoctorLifeLogger<MoofParser> {
  public:
   MoofParser(ByteStream* aSource, uint32_t aTrackId, bool aIsAudio)
--- a/dom/media/mp4/SinfParser.cpp
+++ b/dom/media/mp4/SinfParser.cpp
@@ -50,18 +50,46 @@ Result<Ok, nsresult> SinfParser::ParseSc
 
 Result<Ok, nsresult> SinfParser::ParseTenc(Box& aBox) {
   BoxReader reader(aBox);
 
   if (reader->Remaining() < 24) {
     return Err(NS_ERROR_FAILURE);
   }
 
-  MOZ_TRY(reader->ReadU32());  // flags -- ignore
+  uint32_t flags;
+  MOZ_TRY_VAR(flags, reader->ReadU32());
+  uint8_t version = flags >> 24;
 
-  uint32_t isEncrypted;
-  MOZ_TRY_VAR(isEncrypted, reader->ReadU24());
+  // Skip reserved byte
+  MOZ_TRY(reader->ReadU8());
+  if (version >= 1) {
+    uint8_t pattern;
+    MOZ_TRY_VAR(pattern, reader->ReadU8());
+    mSinf.mDefaultCryptByteBlock = pattern >> 4;
+    mSinf.mDefaultSkipByteBlock = pattern & 0x0f;
+  } else {
+    // Reserved if version is less than 1
+    MOZ_TRY(reader->ReadU8());
+    mSinf.mDefaultCryptByteBlock = 0;
+    mSinf.mDefaultSkipByteBlock = 0;
+  }
+
+  uint8_t isEncrypted;
+  MOZ_TRY_VAR(isEncrypted, reader->ReadU8());
   MOZ_TRY_VAR(mSinf.mDefaultIVSize, reader->ReadU8());
   memcpy(mSinf.mDefaultKeyID, reader->Read(16), 16);
+
+  if (isEncrypted && mSinf.mDefaultIVSize == 0) {
+    uint8_t defaultConstantIVSize;
+    MOZ_TRY_VAR(defaultConstantIVSize, reader->ReadU8());
+    if (!mSinf.mDefaultConstantIV.SetLength(defaultConstantIVSize,
+                                            mozilla::fallible)) {
+      return Err(NS_ERROR_FAILURE);
+    }
+    for (uint8_t i = 0; i < defaultConstantIVSize; i++) {
+      MOZ_TRY_VAR(mSinf.mDefaultConstantIV.ElementAt(i), reader->ReadU8());
+    }
+  }
   return Ok();
 }
 
 }  // namespace mozilla
--- a/dom/media/mp4/SinfParser.h
+++ b/dom/media/mp4/SinfParser.h
@@ -10,26 +10,35 @@
 #include "AtomType.h"
 
 namespace mozilla {
 
 class Box;
 
 class Sinf : public Atom {
  public:
-  Sinf() : mDefaultIVSize(0), mDefaultEncryptionType() {}
+  Sinf()
+      : mDefaultIVSize(0),
+        mDefaultEncryptionType(),
+        mDefaultCryptByteBlock(0),
+        mDefaultSkipByteBlock(0) {}
   explicit Sinf(Box& aBox);
 
-  virtual bool IsValid() override {
-    return !!mDefaultIVSize && !!mDefaultEncryptionType;
+  bool IsValid() override {
+    return !!mDefaultEncryptionType &&  // Should have an encryption scheme
+           (mDefaultIVSize > 0 ||       // and either a default IV size
+            mDefaultConstantIV.Length() > 0);  // or a constant IV.
   }
 
   uint8_t mDefaultIVSize;
   AtomType mDefaultEncryptionType;
   uint8_t mDefaultKeyID[16];
+  uint8_t mDefaultCryptByteBlock;
+  uint8_t mDefaultSkipByteBlock;
+  nsTArray<uint8_t> mDefaultConstantIV;
 };
 
 class SinfParser {
  public:
   explicit SinfParser(Box& aBox);
 
   Sinf& GetSinf() { return mSinf; }
 
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -192,17 +192,17 @@ void PDMFactory::EnsureInit() const {
 already_AddRefed<MediaDataDecoder> PDMFactory::CreateDecoder(
     const CreateDecoderParams& aParams) {
   if (aParams.mUseNullDecoder.mUse) {
     MOZ_ASSERT(mNullPDM);
     return CreateDecoderWithPDM(mNullPDM, aParams);
   }
 
   const TrackInfo& config = aParams.mConfig;
-  bool isEncrypted = mEMEPDM && config.mCrypto.mValid;
+  bool isEncrypted = mEMEPDM && config.mCrypto.IsEncrypted();
 
   if (isEncrypted) {
     return CreateDecoderWithPDM(mEMEPDM, aParams);
   }
 
   DecoderDoctorDiagnostics* diagnostics = aParams.mDiagnostics;
   if (diagnostics) {
     // If libraries failed to load, the following loop over mCurrentPDMs
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -314,17 +314,18 @@ class MediaDataDecoder : public DecoderD
   }
 
   // Return the name of the MediaDataDecoder, only used for decoding.
   // May be accessed in a non thread-safe fashion.
   virtual nsCString GetDescriptionName() const = 0;
 
   // Set a hint of seek target time to decoder. Decoder will drop any decoded
   // data which pts is smaller than this value. This threshold needs to be clear
-  // after reset decoder.
+  // after reset decoder. To clear it explicitly, call this method with
+  // TimeUnit::Invalid().
   // Decoder may not honor this value. However, it'd be better that
   // video decoder implements this API to improve seek performance.
   // Note: it should be called before Input() or after Flush().
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {}
 
   // When playing adaptive playback, recreating an Android video decoder will
   // cause the transition not smooth during resolution change.
   // Reuse the decoder if the decoder support recycling.
--- a/dom/media/platforms/agnostic/bytestreams/Adts.cpp
+++ b/dom/media/platforms/agnostic/bytestreams/Adts.cpp
@@ -48,17 +48,17 @@ bool Adts::ConvertSample(uint16_t aChann
   header[5] = ((newSize & 7) << 5) + 0x1f;
   header[6] = 0xfc;
 
   UniquePtr<MediaRawDataWriter> writer(aSample->CreateWriter());
   if (!writer->Prepend(&header[0], ArrayLength(header))) {
     return false;
   }
 
-  if (aSample->mCrypto.mValid) {
+  if (aSample->mCrypto.IsEncrypted()) {
     if (aSample->mCrypto.mPlainSizes.Length() == 0) {
       writer->mCrypto.mPlainSizes.AppendElement(kADTSHeaderSize);
       writer->mCrypto.mEncryptedSizes.AppendElement(aSample->Size() -
                                                     kADTSHeaderSize);
     } else {
       writer->mCrypto.mPlainSizes[0] += kADTSHeaderSize;
     }
   }
@@ -77,17 +77,17 @@ bool Adts::RevertSample(MediaRawData* aS
       // Not ADTS.
       return false;
     }
   }
 
   UniquePtr<MediaRawDataWriter> writer(aSample->CreateWriter());
   writer->PopFront(kADTSHeaderSize);
 
-  if (aSample->mCrypto.mValid) {
+  if (aSample->mCrypto.IsEncrypted()) {
     if (aSample->mCrypto.mPlainSizes.Length() > 0 &&
         writer->mCrypto.mPlainSizes[0] >= kADTSHeaderSize) {
       writer->mCrypto.mPlainSizes[0] -= kADTSHeaderSize;
     }
   }
 
   return true;
 }
--- a/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp
+++ b/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp
@@ -65,17 +65,17 @@ Result<Ok, nsresult> AnnexB::ConvertSamp
     if (!samplewriter->Prepend(annexB->Elements(), annexB->Length())) {
       return Err(NS_ERROR_OUT_OF_MEMORY);
     }
 
     // Prepending the NAL with SPS/PPS will mess up the encryption subsample
     // offsets. So we need to account for the extra bytes by increasing
     // the length of the first clear data subsample. Otherwise decryption
     // will fail.
-    if (aSample->mCrypto.mValid) {
+    if (aSample->mCrypto.IsEncrypted()) {
       if (aSample->mCrypto.mPlainSizes.Length() == 0) {
         samplewriter->mCrypto.mPlainSizes.AppendElement(annexB->Length());
         samplewriter->mCrypto.mEncryptedSizes.AppendElement(
             samplewriter->Size() - annexB->Length());
       } else {
         samplewriter->mCrypto.mPlainSizes[0] += annexB->Length();
       }
     }
--- a/dom/media/platforms/agnostic/bytestreams/H264.cpp
+++ b/dom/media/platforms/agnostic/bytestreams/H264.cpp
@@ -799,17 +799,17 @@ static int32_t ConditionDimension(float 
   // PPS content
   nsTArray<uint8_t> pps;
   ByteWriter ppsw(pps);
   int numPps = 0;
 
   int nalLenSize = ((*aSample->mExtraData)[4] & 3) + 1;
 
   size_t sampleSize = aSample->Size();
-  if (aSample->mCrypto.mValid) {
+  if (aSample->mCrypto.IsEncrypted()) {
     // The content is encrypted, we can only parse the non-encrypted data.
     MOZ_ASSERT(aSample->mCrypto.mPlainSizes.Length() > 0);
     if (aSample->mCrypto.mPlainSizes.Length() == 0 ||
         aSample->mCrypto.mPlainSizes[0] > sampleSize) {
       // This is invalid content.
       return nullptr;
     }
     sampleSize = aSample->mCrypto.mPlainSizes[0];
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -371,17 +371,17 @@ static already_AddRefed<MediaDataDecoder
   }
   RefPtr<MediaDataDecoderProxy> decoder(
       new EMEMediaDataDecoderProxy(thread.forget(), aProxy, aParams));
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder> EMEDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
-  MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+  MOZ_ASSERT(aParams.mConfig.mCrypto.IsEncrypted());
 
   if (StaticPrefs::MediaEmeVideoBlank()) {
     EME_LOG("EMEDecoderModule::CreateVideoDecoder() creating a blank decoder.");
     RefPtr<PlatformDecoderModule> m(CreateBlankDecoderModule());
     return m->CreateVideoDecoder(aParams);
   }
 
   if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
@@ -402,17 +402,17 @@ already_AddRefed<MediaDataDecoder> EMEDe
   RefPtr<MediaDataDecoder> emeDecoder(
       new EMEDecryptor(decoder, mProxy, aParams.mTaskQueue, aParams.mType,
                        aParams.mOnWaitingForKeyEvent));
   return emeDecoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder> EMEDecoderModule::CreateAudioDecoder(
     const CreateDecoderParams& aParams) {
-  MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+  MOZ_ASSERT(aParams.mConfig.mCrypto.IsEncrypted());
 
   // We don't support using the GMP to decode audio.
   MOZ_ASSERT(!SupportsMimeType(aParams.mConfig.mMimeType, nullptr));
   MOZ_ASSERT(mPDM);
 
   if (StaticPrefs::MediaEmeAudioBlank()) {
     EME_LOG("EMEDecoderModule::CreateAudioDecoder() creating a blank decoder.");
     RefPtr<PlatformDecoderModule> m(CreateBlankDecoderModule());
--- a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -20,17 +20,17 @@ SamplesWaitingForKey::SamplesWaitingForK
       mProxy(aProxy),
       mType(aType),
       mOnWaitingForKeyEvent(aOnWaitingForKey) {}
 
 SamplesWaitingForKey::~SamplesWaitingForKey() { Flush(); }
 
 RefPtr<SamplesWaitingForKey::WaitForKeyPromise>
 SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample) {
-  if (!aSample || !aSample->mCrypto.mValid || !mProxy) {
+  if (!aSample || !aSample->mCrypto.IsEncrypted() || !mProxy) {
     return WaitForKeyPromise::CreateAndResolve(aSample, __func__);
   }
   auto caps = mProxy->Capabilites().Lock();
   const auto& keyid = aSample->mCrypto.mKeyId;
   if (caps->IsKeyUsable(keyid)) {
     return WaitForKeyPromise::CreateAndResolve(aSample, __func__);
   }
   SampleEntry entry;
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -27,43 +27,50 @@
 using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::java;
 using namespace mozilla::java::sdk;
 using media::TimeUnit;
 
 namespace mozilla {
 
-class RemoteVideoDecoder : public RemoteDataDecoder {
+// Hold a reference to the output buffer until we're ready to release it back to
+// the Java codec (for rendering or not).
+class RenderOrReleaseOutput {
  public:
-  // Hold an output buffer and render it to the surface when the frame is sent
-  // to compositor, or release it if not presented.
-  class RenderOrReleaseOutput : public VideoData::Listener {
-   public:
-    RenderOrReleaseOutput(java::CodecProxy::Param aCodec,
-                          java::Sample::Param aSample)
-        : mCodec(aCodec), mSample(aSample) {}
+  RenderOrReleaseOutput(CodecProxy::Param aCodec, Sample::Param aSample)
+      : mCodec(aCodec), mSample(aSample) {}
+
+  virtual ~RenderOrReleaseOutput() { ReleaseOutput(false); }
 
-    ~RenderOrReleaseOutput() { ReleaseOutput(false); }
-
-    void OnSentToCompositor() override {
-      ReleaseOutput(true);
+ protected:
+  void ReleaseOutput(bool aToRender) {
+    if (mCodec && mSample) {
+      mCodec->ReleaseOutput(mSample, aToRender);
       mCodec = nullptr;
       mSample = nullptr;
     }
+  }
 
-   private:
-    void ReleaseOutput(bool aToRender) {
-      if (mCodec && mSample) {
-        mCodec->ReleaseOutput(mSample, aToRender);
-      }
-    }
+ private:
+  CodecProxy::GlobalRef mCodec;
+  Sample::GlobalRef mSample;
+};
 
-    java::CodecProxy::GlobalRef mCodec;
-    java::Sample::GlobalRef mSample;
+class RemoteVideoDecoder : public RemoteDataDecoder {
+ public:
+  // Render the output to the surface when the frame is sent
+  // to compositor, or release it if not presented.
+  class CompositeListener : private RenderOrReleaseOutput,
+                            public VideoData::Listener {
+   public:
+    CompositeListener(CodecProxy::Param aCodec, Sample::Param aSample)
+        : RenderOrReleaseOutput(aCodec, aSample) {}
+
+    void OnSentToCompositor() override { ReleaseOutput(true); }
   };
 
   class InputInfo {
    public:
     InputInfo() {}
 
     InputInfo(const int64_t aDurationUs, const gfx::IntSize& aImageSize,
               const gfx::IntSize& aDisplaySize)
@@ -81,66 +88,18 @@ class RemoteVideoDecoder : public Remote
     explicit CallbacksSupport(RemoteVideoDecoder* aDecoder)
         : mDecoder(aDecoder) {}
 
     void HandleInput(int64_t aTimestamp, bool aProcessed) override {
       mDecoder->UpdateInputStatus(aTimestamp, aProcessed);
     }
 
     void HandleOutput(Sample::Param aSample) override {
-      UniquePtr<VideoData::Listener> releaseSample(
-          new RenderOrReleaseOutput(mDecoder->mJavaDecoder, aSample));
-
-      BufferInfo::LocalRef info = aSample->Info();
-
-      int32_t flags;
-      bool ok = NS_SUCCEEDED(info->Flags(&flags));
-
-      int32_t offset;
-      ok &= NS_SUCCEEDED(info->Offset(&offset));
-
-      int32_t size;
-      ok &= NS_SUCCEEDED(info->Size(&size));
-
-      int64_t presentationTimeUs;
-      ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
-
-      if (!ok) {
-        HandleError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                                RESULT_DETAIL("VideoCallBack::HandleOutput")));
-        return;
-      }
-
-      InputInfo inputInfo;
-      ok = mDecoder->mInputInfos.Find(presentationTimeUs, inputInfo);
-      bool isEOS = !!(flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM);
-      if (!ok && !isEOS) {
-        // Ignore output with no corresponding input.
-        return;
-      }
-
-      if (ok && (size > 0 || presentationTimeUs >= 0)) {
-        RefPtr<layers::Image> img = new SurfaceTextureImage(
-            mDecoder->mSurfaceHandle, inputInfo.mImageSize,
-            false /* NOT continuous */, gl::OriginPos::BottomLeft);
-
-        RefPtr<VideoData> v = VideoData::CreateFromImage(
-            inputInfo.mDisplaySize, offset,
-            TimeUnit::FromMicroseconds(presentationTimeUs),
-            TimeUnit::FromMicroseconds(inputInfo.mDurationUs), img,
-            !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME),
-            TimeUnit::FromMicroseconds(presentationTimeUs));
-
-        v->SetListener(std::move(releaseSample));
-        mDecoder->UpdateOutputStatus(std::move(v));
-      }
-
-      if (isEOS) {
-        mDecoder->DrainComplete();
-      }
+      // aSample will be implicitly converted into a GlobalRef.
+      mDecoder->ProcessOutput(std::move(aSample));
     }
 
     void HandleError(const MediaResult& aError) override {
       mDecoder->Error(aError);
     }
 
     friend class RemoteDataDecoder;
 
@@ -187,24 +146,22 @@ class RemoteVideoDecoder : public Remote
     mIsCodecSupportAdaptivePlayback =
         mJavaDecoder->IsAdaptivePlaybackSupported();
     mIsHardwareAccelerated = mJavaDecoder->IsHardwareAccelerated();
     return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
   }
 
   RefPtr<MediaDataDecoder::FlushPromise> Flush() override {
     RefPtr<RemoteVideoDecoder> self = this;
-    return RemoteDataDecoder::Flush()->Then(
-        mTaskQueue, __func__,
-        [self](const FlushPromise::ResolveOrRejectValue& aValue) {
-          self->mInputInfos.Clear();
-          self->mSeekTarget.reset();
-          self->mLatestOutputTime.reset();
-          return FlushPromise::CreateAndResolveOrReject(aValue, __func__);
-        });
+    return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+      mInputInfos.Clear();
+      mSeekTarget.reset();
+      mLatestOutputTime.reset();
+      return RemoteDataDecoder::ProcessFlush();
+    });
   }
 
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override {
     const VideoInfo* config =
         aSample->mTrackInfo ? aSample->mTrackInfo->GetAsVideoInfo() : &mConfig;
     MOZ_ASSERT(config);
 
@@ -216,18 +173,23 @@ class RemoteVideoDecoder : public Remote
 
   bool SupportDecoderRecycling() const override {
     return mIsCodecSupportAdaptivePlayback;
   }
 
   void SetSeekThreshold(const TimeUnit& aTime) override {
     RefPtr<RemoteVideoDecoder> self = this;
     nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
-        "RemoteVideoDecoder::SetSeekThreshold",
-        [self, aTime]() { self->mSeekTarget = Some(aTime); });
+        "RemoteVideoDecoder::SetSeekThreshold", [self, aTime]() {
+          if (aTime.IsValid()) {
+            self->mSeekTarget = Some(aTime);
+          } else {
+            self->mSeekTarget.reset();
+          }
+        });
     nsresult rv = mTaskQueue->Dispatch(runnable.forget());
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
   }
 
   bool IsUsefulData(const RefPtr<MediaData>& aSample) override {
     AssertOnTaskQueue();
 
@@ -249,38 +211,105 @@ class RemoteVideoDecoder : public Remote
     return mIsHardwareAccelerated;
   }
 
   ConversionRequired NeedsConversion() const override {
     return ConversionRequired::kNeedAnnexB;
   }
 
  private:
+  // Param and LocalRef are only valid for the duration of a JNI method call.
+  // Use GlobalRef as the parameter type to keep the Java object referenced
+  // until running.
+  void ProcessOutput(Sample::GlobalRef&& aSample) {
+    if (!mTaskQueue->IsCurrentThreadIn()) {
+      nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<Sample::GlobalRef&&>(
+          "RemoteVideoDecoder::ProcessOutput", this,
+          &RemoteVideoDecoder::ProcessOutput, std::move(aSample)));
+      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+      Unused << rv;
+      return;
+    }
+
+    AssertOnTaskQueue();
+
+    UniquePtr<VideoData::Listener> releaseSample(
+        new CompositeListener(mJavaDecoder, aSample));
+
+    BufferInfo::LocalRef info = aSample->Info();
+    MOZ_ASSERT(info);
+
+    int32_t flags;
+    bool ok = NS_SUCCEEDED(info->Flags(&flags));
+
+    int32_t offset;
+    ok &= NS_SUCCEEDED(info->Offset(&offset));
+
+    int32_t size;
+    ok &= NS_SUCCEEDED(info->Size(&size));
+
+    int64_t presentationTimeUs;
+    ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
+
+    if (!ok) {
+      Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                        RESULT_DETAIL("VideoCallBack::HandleOutput")));
+      return;
+    }
+
+    InputInfo inputInfo;
+    ok = mInputInfos.Find(presentationTimeUs, inputInfo);
+    bool isEOS = !!(flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM);
+    if (!ok && !isEOS) {
+      // Ignore output with no corresponding input.
+      return;
+    }
+
+    if (ok && (size > 0 || presentationTimeUs >= 0)) {
+      RefPtr<layers::Image> img = new SurfaceTextureImage(
+          mSurfaceHandle, inputInfo.mImageSize, false /* NOT continuous */,
+          gl::OriginPos::BottomLeft);
+
+      RefPtr<VideoData> v = VideoData::CreateFromImage(
+          inputInfo.mDisplaySize, offset,
+          TimeUnit::FromMicroseconds(presentationTimeUs),
+          TimeUnit::FromMicroseconds(inputInfo.mDurationUs), img,
+          !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME),
+          TimeUnit::FromMicroseconds(presentationTimeUs));
+
+      v->SetListener(std::move(releaseSample));
+      RemoteDataDecoder::UpdateOutputStatus(std::move(v));
+    }
+
+    if (isEOS) {
+      DrainComplete();
+    }
+  }
+
   const VideoInfo mConfig;
   GeckoSurface::GlobalRef mSurface;
   AndroidSurfaceTextureHandle mSurfaceHandle;
   // Only accessed on reader's task queue.
   bool mIsCodecSupportAdaptivePlayback = false;
   // Can be accessed on any thread, but only written on during init.
   bool mIsHardwareAccelerated = false;
-  // Accessed on mTaskQueue, reader's TaskQueue and Java callback tread.
-  // SimpleMap however is thread-safe, so it's okay to do so.
+  // Accessed on mTaskQueue and reader's TaskQueue. SimpleMap however is
+  // thread-safe, so it's okay to do so.
   SimpleMap<InputInfo> mInputInfos;
   // Only accessed on the TaskQueue.
   Maybe<TimeUnit> mSeekTarget;
   Maybe<TimeUnit> mLatestOutputTime;
 };
 
 class RemoteAudioDecoder : public RemoteDataDecoder {
  public:
   RemoteAudioDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat,
                      const nsString& aDrmStubId, TaskQueue* aTaskQueue)
       : RemoteDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType,
-                          aFormat, aDrmStubId, aTaskQueue),
-        mConfig(aConfig) {
+                          aFormat, aDrmStubId, aTaskQueue) {
     JNIEnv* const env = jni::GetEnvForThread();
 
     bool formatHasCSD = false;
     NS_ENSURE_SUCCESS_VOID(
         aFormat->ContainsKey(NS_LITERAL_STRING("csd-0"), &formatHasCSD));
 
     if (!formatHasCSD && aConfig.mCodecSpecificConfig->Length() >= 2) {
       jni::ByteBuffer::LocalRef buffer(env);
@@ -315,92 +344,133 @@ class RemoteAudioDecoder : public Remote
     explicit CallbacksSupport(RemoteAudioDecoder* aDecoder)
         : mDecoder(aDecoder) {}
 
     void HandleInput(int64_t aTimestamp, bool aProcessed) override {
       mDecoder->UpdateInputStatus(aTimestamp, aProcessed);
     }
 
     void HandleOutput(Sample::Param aSample) override {
-      BufferInfo::LocalRef info = aSample->Info();
-
-      int32_t flags;
-      bool ok = NS_SUCCEEDED(info->Flags(&flags));
-
-      int32_t offset;
-      ok &= NS_SUCCEEDED(info->Offset(&offset));
+      // aSample will be implicitly converted into a GlobalRef.
+      mDecoder->ProcessOutput(std::move(aSample));
+    }
 
-      int64_t presentationTimeUs;
-      ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
-
-      int32_t size;
-      ok &= NS_SUCCEEDED(info->Size(&size));
-
-      if (!ok) {
-        HandleError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                                RESULT_DETAIL("AudioCallBack::HandleOutput")));
+    void HandleOutputFormatChanged(MediaFormat::Param aFormat) override {
+      int32_t outputChannels = 0;
+      aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &outputChannels);
+      AudioConfig::ChannelLayout layout(outputChannels);
+      if (!layout.IsValid()) {
+        mDecoder->Error(MediaResult(
+            NS_ERROR_DOM_MEDIA_FATAL_ERR,
+            RESULT_DETAIL("Invalid channel layout:%d", outputChannels)));
         return;
       }
 
-      if (size > 0) {
-#ifdef MOZ_SAMPLE_TYPE_S16
-        const int32_t numSamples = size / 2;
-#else
-#error We only support 16-bit integer PCM
-#endif
-
-        const int32_t numFrames = numSamples / mOutputChannels;
-        AlignedAudioBuffer audio(numSamples);
-        if (!audio) {
-          mDecoder->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
-          return;
-        }
-
-        jni::ByteBuffer::LocalRef dest =
-            jni::ByteBuffer::New(audio.get(), size);
-        aSample->WriteToByteBuffer(dest);
+      int32_t sampleRate = 0;
+      aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &sampleRate);
+      LOG("Audio output format changed: channels:%d sample rate:%d",
+          outputChannels, sampleRate);
 
-        RefPtr<AudioData> data = new AudioData(
-            0, TimeUnit::FromMicroseconds(presentationTimeUs),
-            FramesToTimeUnit(numFrames, mOutputSampleRate), numFrames,
-            std::move(audio), mOutputChannels, mOutputSampleRate);
-
-        mDecoder->UpdateOutputStatus(std::move(data));
-      }
-
-      if ((flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) != 0) {
-        mDecoder->DrainComplete();
-      }
-    }
-
-    void HandleOutputFormatChanged(MediaFormat::Param aFormat) override {
-      aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &mOutputChannels);
-      AudioConfig::ChannelLayout layout(mOutputChannels);
-      if (!layout.IsValid()) {
-        mDecoder->Error(MediaResult(
-            NS_ERROR_DOM_MEDIA_FATAL_ERR,
-            RESULT_DETAIL("Invalid channel layout:%d", mOutputChannels)));
-        return;
-      }
-      aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &mOutputSampleRate);
-      LOG("Audio output format changed: channels:%d sample rate:%d",
-          mOutputChannels, mOutputSampleRate);
+      mDecoder->ProcessOutputFormatChange(outputChannels, sampleRate);
     }
 
     void HandleError(const MediaResult& aError) override {
       mDecoder->Error(aError);
     }
 
    private:
     RemoteAudioDecoder* mDecoder;
-    int32_t mOutputChannels;
-    int32_t mOutputSampleRate;
   };
 
-  const AudioInfo mConfig;
+  // Param and LocalRef are only valid for the duration of a JNI method call.
+  // Use GlobalRef as the parameter type to keep the Java object referenced
+  // until running.
+  void ProcessOutput(Sample::GlobalRef&& aSample) {
+    if (!mTaskQueue->IsCurrentThreadIn()) {
+      nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<Sample::GlobalRef&&>(
+          "RemoteAudioDecoder::ProcessOutput", this,
+          &RemoteAudioDecoder::ProcessOutput, std::move(aSample)));
+      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+      Unused << rv;
+      return;
+    }
+
+    AssertOnTaskQueue();
+
+    RenderOrReleaseOutput autoRelease(mJavaDecoder, aSample);
+
+    BufferInfo::LocalRef info = aSample->Info();
+    MOZ_ASSERT(info);
+
+    int32_t flags;
+    bool ok = NS_SUCCEEDED(info->Flags(&flags));
+
+    int32_t offset;
+    ok &= NS_SUCCEEDED(info->Offset(&offset));
+
+    int64_t presentationTimeUs;
+    ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
+
+    int32_t size;
+    ok &= NS_SUCCEEDED(info->Size(&size));
+
+    if (!ok) {
+      Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
+      return;
+    }
+
+    if (size > 0) {
+#ifdef MOZ_SAMPLE_TYPE_S16
+      const int32_t numSamples = size / 2;
+#else
+#error We only support 16-bit integer PCM
+#endif
+
+      const int32_t numFrames = numSamples / mOutputChannels;
+      AlignedAudioBuffer audio(numSamples);
+      if (!audio) {
+        Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+        return;
+      }
+
+      jni::ByteBuffer::LocalRef dest = jni::ByteBuffer::New(audio.get(), size);
+      aSample->WriteToByteBuffer(dest);
+
+      RefPtr<AudioData> data = new AudioData(
+          0, TimeUnit::FromMicroseconds(presentationTimeUs),
+          FramesToTimeUnit(numFrames, mOutputSampleRate), numFrames,
+          std::move(audio), mOutputChannels, mOutputSampleRate);
+
+      UpdateOutputStatus(std::move(data));
+    }
+
+    if ((flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) != 0) {
+      DrainComplete();
+    }
+  }
+
+  void ProcessOutputFormatChange(int32_t aChannels, int32_t aSampleRate) {
+    if (!mTaskQueue->IsCurrentThreadIn()) {
+      nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<int32_t, int32_t>(
+          "RemoteAudioDecoder::ProcessOutputFormatChange", this,
+          &RemoteAudioDecoder::ProcessOutputFormatChange, aChannels,
+          aSampleRate));
+      MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+      Unused << rv;
+      return;
+    }
+
+    AssertOnTaskQueue();
+
+    mOutputChannels = aChannels;
+    mOutputSampleRate = aSampleRate;
+  }
+
+  int32_t mOutputChannels;
+  int32_t mOutputSampleRate;
 };
 
 already_AddRefed<MediaDataDecoder> RemoteDataDecoder::CreateAudioDecoder(
     const CreateDecoderParams& aParams, const nsString& aDrmStubId,
     CDMProxy* aProxy) {
   const AudioInfo& config = aParams.AudioConfig();
   MediaFormat::LocalRef format;
   NS_ENSURE_SUCCESS(
@@ -443,69 +513,74 @@ RemoteDataDecoder::RemoteDataDecoder(Med
       mMimeType(aMimeType),
       mFormat(aFormat),
       mDrmStubId(aDrmStubId),
       mTaskQueue(aTaskQueue),
       mNumPendingInputs(0) {}
 
 RefPtr<MediaDataDecoder::FlushPromise> RemoteDataDecoder::Flush() {
   RefPtr<RemoteDataDecoder> self = this;
-  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
-    mDecodedData = DecodedData();
-    mNumPendingInputs = 0;
-    mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-    mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-    mDrainStatus = DrainStatus::DRAINED;
-    mJavaDecoder->Flush();
-    return FlushPromise::CreateAndResolve(true, __func__);
-  });
+  return InvokeAsync(mTaskQueue, this, __func__,
+                     &RemoteDataDecoder::ProcessFlush);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> RemoteDataDecoder::ProcessFlush() {
+  AssertOnTaskQueue();
+
+  mDecodedData = DecodedData();
+  UpdatePendingInputStatus(PendingOp::CLEAR);
+  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  SetState(State::DRAINED);
+  mJavaDecoder->Flush();
+  return FlushPromise::CreateAndResolve(true, __func__);
 }
 
 RefPtr<MediaDataDecoder::DecodePromise> RemoteDataDecoder::Drain() {
   RefPtr<RemoteDataDecoder> self = this;
   return InvokeAsync(mTaskQueue, __func__, [self, this]() {
-    if (mShutdown) {
+    if (GetState() == State::SHUTDOWN) {
       return DecodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                             __func__);
     }
     RefPtr<DecodePromise> p = mDrainPromise.Ensure(__func__);
-    if (mDrainStatus == DrainStatus::DRAINED) {
+    if (GetState() == State::DRAINED) {
       // There's no operation to perform other than returning any already
       // decoded data.
       ReturnDecodedData();
       return p;
     }
 
-    if (mDrainStatus == DrainStatus::DRAINING) {
+    if (GetState() == State::DRAINING) {
       // Draining operation already pending, let it complete its course.
       return p;
     }
 
     BufferInfo::LocalRef bufferInfo;
     nsresult rv = BufferInfo::New(&bufferInfo);
     if (NS_FAILED(rv)) {
       return DecodePromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
     }
-    mDrainStatus = DrainStatus::DRAINING;
+    SetState(State::DRAINING);
     bufferInfo->Set(0, 0, -1, MediaCodec::BUFFER_FLAG_END_OF_STREAM);
     mJavaDecoder->Input(nullptr, bufferInfo, nullptr);
     return p;
   });
 }
 
 RefPtr<ShutdownPromise> RemoteDataDecoder::Shutdown() {
   LOG("");
   RefPtr<RemoteDataDecoder> self = this;
   return InvokeAsync(mTaskQueue, this, __func__,
                      &RemoteDataDecoder::ProcessShutdown);
 }
 
 RefPtr<ShutdownPromise> RemoteDataDecoder::ProcessShutdown() {
   AssertOnTaskQueue();
-  mShutdown = true;
+  SetState(State::SHUTDOWN);
   if (mJavaDecoder) {
     mJavaDecoder->Release();
     mJavaDecoder = nullptr;
   }
 
   if (mJavaCallbacks) {
     JavaCallbacksSupport::GetNative(mJavaCallbacks)->Cancel();
     JavaCallbacksSupport::DisposeNative(mJavaCallbacks);
@@ -516,17 +591,17 @@ RefPtr<ShutdownPromise> RemoteDataDecode
 
   return ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
 static CryptoInfo::LocalRef GetCryptoInfoFromSample(
     const MediaRawData* aSample) {
   auto& cryptoObj = aSample->mCrypto;
 
-  if (!cryptoObj.mValid) {
+  if (!cryptoObj.IsEncrypted()) {
     return nullptr;
   }
 
   CryptoInfo::LocalRef cryptoInfo;
   nsresult rv = CryptoInfo::New(&cryptoInfo);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   uint32_t numSubSamples = std::min<uint32_t>(
@@ -588,116 +663,120 @@ RefPtr<MediaDataDecoder::DecodePromise> 
     BufferInfo::LocalRef bufferInfo;
     nsresult rv = BufferInfo::New(&bufferInfo);
     if (NS_FAILED(rv)) {
       return DecodePromise::CreateAndReject(
           MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
     }
     bufferInfo->Set(0, sample->Size(), sample->mTime.ToMicroseconds(), 0);
 
-    self->mDrainStatus = DrainStatus::DRAINABLE;
+    self->SetState(State::DRAINABLE);
     return self->mJavaDecoder->Input(bytes, bufferInfo,
                                      GetCryptoInfoFromSample(sample))
                ? self->mDecodePromise.Ensure(__func__)
                : DecodePromise::CreateAndReject(
                      MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
   });
 }
 
+void RemoteDataDecoder::UpdatePendingInputStatus(PendingOp aOp) {
+  AssertOnTaskQueue();
+  switch (aOp) {
+    case PendingOp::INCREASE:
+      mNumPendingInputs++;
+      break;
+    case PendingOp::DECREASE:
+      mNumPendingInputs--;
+      break;
+    case PendingOp::CLEAR:
+      mNumPendingInputs = 0;
+      break;
+  }
+}
+
 void RemoteDataDecoder::UpdateInputStatus(int64_t aTimestamp, bool aProcessed) {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<int64_t, bool>(
         "RemoteDataDecoder::UpdateInputStatus", this,
         &RemoteDataDecoder::UpdateInputStatus, aTimestamp, aProcessed));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
     return;
   }
   AssertOnTaskQueue();
-  if (mShutdown) {
+  if (GetState() == State::SHUTDOWN) {
     return;
   }
 
   if (!aProcessed) {
-    mNumPendingInputs++;
-  } else if (mNumPendingInputs > 0) {
-    mNumPendingInputs--;
+    UpdatePendingInputStatus(PendingOp::INCREASE);
+  } else if (HasPendingInputs()) {
+    UpdatePendingInputStatus(PendingOp::DECREASE);
   }
 
-  if (mNumPendingInputs ==
-          0 ||  // Input has been processed, request the next one.
+  if (!HasPendingInputs() ||  // Input has been processed, request the next one.
       !mDecodedData.IsEmpty()) {  // Previous output arrived before Decode().
     ReturnDecodedData();
   }
 }
 
 void RemoteDataDecoder::UpdateOutputStatus(RefPtr<MediaData>&& aSample) {
-  if (!mTaskQueue->IsCurrentThreadIn()) {
-    nsresult rv =
-        mTaskQueue->Dispatch(NewRunnableMethod<const RefPtr<MediaData>>(
-            "RemoteDataDecoder::UpdateOutputStatus", this,
-            &RemoteDataDecoder::UpdateOutputStatus, std::move(aSample)));
-    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-    Unused << rv;
-    return;
-  }
   AssertOnTaskQueue();
-  if (mShutdown) {
+  if (GetState() == State::SHUTDOWN) {
     return;
   }
   if (IsUsefulData(aSample)) {
     mDecodedData.AppendElement(std::move(aSample));
   }
   ReturnDecodedData();
 }
 
 void RemoteDataDecoder::ReturnDecodedData() {
   AssertOnTaskQueue();
-  MOZ_ASSERT(!mShutdown);
+  MOZ_ASSERT(GetState() != State::SHUTDOWN);
 
   // We only want to clear mDecodedData when we have resolved the promises.
   if (!mDecodePromise.IsEmpty()) {
     mDecodePromise.Resolve(std::move(mDecodedData), __func__);
     mDecodedData = DecodedData();
   } else if (!mDrainPromise.IsEmpty() &&
-             (!mDecodedData.IsEmpty() ||
-              mDrainStatus == DrainStatus::DRAINED)) {
+             (!mDecodedData.IsEmpty() || GetState() == State::DRAINED)) {
     mDrainPromise.Resolve(std::move(mDecodedData), __func__);
     mDecodedData = DecodedData();
   }
 }
 
 void RemoteDataDecoder::DrainComplete() {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsresult rv = mTaskQueue->Dispatch(
         NewRunnableMethod("RemoteDataDecoder::DrainComplete", this,
                           &RemoteDataDecoder::DrainComplete));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
     return;
   }
   AssertOnTaskQueue();
-  if (mShutdown) {
+  if (GetState() == State::SHUTDOWN) {
     return;
   }
-  mDrainStatus = DrainStatus::DRAINED;
+  SetState(State::DRAINED);
   ReturnDecodedData();
   // Make decoder accept input again.
   mJavaDecoder->Flush();
 }
 
 void RemoteDataDecoder::Error(const MediaResult& aError) {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod<MediaResult>(
         "RemoteDataDecoder::Error", this, &RemoteDataDecoder::Error, aError));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
     return;
   }
   AssertOnTaskQueue();
-  if (mShutdown) {
+  if (GetState() == State::SHUTDOWN) {
     return;
   }
   mDecodePromise.RejectIfExists(aError, __func__);
   mDrainPromise.RejectIfExists(aError, __func__);
 }
 
 }  // namespace mozilla
--- a/dom/media/platforms/android/RemoteDataDecoder.h
+++ b/dom/media/platforms/android/RemoteDataDecoder.h
@@ -37,46 +37,60 @@ class RemoteDataDecoder : public MediaDa
 
  protected:
   virtual ~RemoteDataDecoder() {}
   RemoteDataDecoder(MediaData::Type aType, const nsACString& aMimeType,
                     java::sdk::MediaFormat::Param aFormat,
                     const nsString& aDrmStubId, TaskQueue* aTaskQueue);
 
   // Methods only called on mTaskQueue.
+  RefPtr<FlushPromise> ProcessFlush();
   RefPtr<ShutdownPromise> ProcessShutdown();
   void UpdateInputStatus(int64_t aTimestamp, bool aProcessed);
   void UpdateOutputStatus(RefPtr<MediaData>&& aSample);
   void ReturnDecodedData();
   void DrainComplete();
   void Error(const MediaResult& aError);
   void AssertOnTaskQueue() { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); }
 
+  enum class State { DRAINED, DRAINABLE, DRAINING, SHUTDOWN };
+  void SetState(State aState) {
+    AssertOnTaskQueue();
+    mState = aState;
+  }
+  State GetState() {
+    AssertOnTaskQueue();
+    return mState;
+  }
+
   // Whether the sample will be used.
   virtual bool IsUsefulData(const RefPtr<MediaData>& aSample) { return true; }
 
   MediaData::Type mType;
 
   nsAutoCString mMimeType;
   java::sdk::MediaFormat::GlobalRef mFormat;
 
   java::CodecProxy::GlobalRef mJavaDecoder;
   java::CodecProxy::NativeCallbacks::GlobalRef mJavaCallbacks;
   nsString mDrmStubId;
 
   RefPtr<TaskQueue> mTaskQueue;
-  // Only ever accessed on mTaskqueue.
-  bool mShutdown = false;
+
+ private:
+  enum class PendingOp { INCREASE, DECREASE, CLEAR };
+  void UpdatePendingInputStatus(PendingOp aOp);
+  size_t HasPendingInputs() {
+    AssertOnTaskQueue();
+    return mNumPendingInputs > 0;
+  }
+
+  // The following members must only be accessed on mTaskqueue.
   MozPromiseHolder<DecodePromise> mDecodePromise;
   MozPromiseHolder<DecodePromise> mDrainPromise;
-  enum class DrainStatus {
-    DRAINED,
-    DRAINABLE,
-    DRAINING,
-  };
-  DrainStatus mDrainStatus = DrainStatus::DRAINED;
   DecodedData mDecodedData;
+  State mState = State::DRAINED;
   size_t mNumPendingInputs;
 };
 
 }  // namespace mozilla
 
 #endif
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -241,17 +241,21 @@ RefPtr<MediaDataDecoder::DecodePromise> 
 AppleVTDecoder::AppleFrameRef* AppleVTDecoder::CreateAppleFrameRef(
     const MediaRawData* aSample) {
   MOZ_ASSERT(aSample);
   return new AppleFrameRef(*aSample);
 }
 
 void AppleVTDecoder::SetSeekThreshold(const media::TimeUnit& aTime) {
   LOG("SetSeekThreshold %lld", aTime.ToMicroseconds());
-  mSeekTargetThreshold = Some(aTime);
+  if (aTime.IsValid()) {
+    mSeekTargetThreshold = Some(aTime);
+  } else {
+    mSeekTargetThreshold.reset();
+  }
 }
 
 //
 // Implementation details.
 //
 
 // Callback passed to the VideoToolbox decoder for returning data.
 // This needs to be static because the API takes a C-style pair of
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.h
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.h
@@ -53,17 +53,21 @@ class MFTManager {
     return false;
   }
 
   virtual TrackInfo::TrackType GetType() = 0;
 
   virtual nsCString GetDescriptionName() const = 0;
 
   virtual void SetSeekThreshold(const media::TimeUnit& aTime) {
-    mSeekTargetThreshold = Some(aTime);
+    if (aTime.IsValid()) {
+      mSeekTargetThreshold = Some(aTime);
+    } else {
+      mSeekTargetThreshold.reset();
+    }
   }
 
   virtual MediaDataDecoder::ConversionRequired NeedsConversion() const {
     return MediaDataDecoder::ConversionRequired::kNeedNone;
   }
 
  protected:
   // IMFTransform wrapper that performs the decoding.
--- a/dom/media/platforms/wrappers/MediaChangeMonitor.cpp
+++ b/dom/media/platforms/wrappers/MediaChangeMonitor.cpp
@@ -141,17 +141,17 @@ class VPXChangeMonitor : public MediaCha
                                                   : 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) {
+    if (aSample->mCrypto.IsEncrypted()) {
       return NS_OK;
     }
     // For both VP8 and VP9, we only look for resolution changes
     // on keyframes. Other resolution changes are invalid.
     if (!aSample->mKeyframe) {
       return NS_OK;
     }
 
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -4,16 +4,18 @@ subsuite = media
 support-files =
   head.js
   dataChannel.js
   mediaStreamPlayback.js
   network.js
   nonTrickleIce.js
   pc.js
   templates.js
+  test_enumerateDevices_iframe.html
+  test_getUserMedia_permission_iframe.html
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
   sdpUtils.js
   addTurnsSelfsignedCert.js
   parser_rtp.js
   peerconnection_audio_forced_sample_rate.js
   !/dom/canvas/test/captureStream_common.js
@@ -39,18 +41,16 @@ skip-if = toolkit == 'android'  # Bug 11
 [test_dataChannel_basicVideo.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_bug1013809.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_dataOnlyBufferedAmountLow.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_noOffer.html]
 [test_enumerateDevices.html]
-[test_enumerateDevices_iframe.html]
-skip-if = true # needed by test_enumerateDevices.html on builders
 [test_enumerateDevices_navigation.html]
 [test_ondevicechange.html]
 skip-if = os == 'android' || verify
 [test_getUserMedia_active_autoplay.html]
 [test_getUserMedia_audioCapture.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), android(Bug 1264333)
 [test_getUserMedia_addTrackRemoveTrack.html]
 skip-if = android_version == '18' || os == 'linux' # android(Bug 1189784, timeouts on 4.3 emulator), linux bug 1377450
@@ -92,16 +92,17 @@ skip-if = android_version == '18' # andr
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_mediaElementCapture_video.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_mediaStreamClone.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_mediaStreamConstructors.html]
 [test_getUserMedia_mediaStreamTrackClone.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
+[test_getUserMedia_permission.html]
 [test_getUserMedia_playAudioTwice.html]
 [test_getUserMedia_playVideoAudioTwice.html]
 [test_getUserMedia_playVideoTwice.html]
 [test_getUserMedia_scarySources.html]
 skip-if = toolkit == 'android' # no screenshare or windowshare on android
 [test_getUserMedia_spinEventLoop.html]
 [test_getUserMedia_trackCloneCleanup.html]
 [test_getUserMedia_trackEnded.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_permission.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Test getUserMedia in iframes", bug: "1371741" });
+/**
+  Tests covering enumerateDevices API and deviceId constraint. Exercise code.
+*/
+
+// Call gUM in iframe.
+async function iframeGum(dict, iframe = document.createElement("iframe")) {
+  Object.assign(iframe, dict);
+  if (dict.src) {
+    info(`<iframe src="${dict.src}" sandbox="${dict.sandbox}">`);
+  } else {
+    info(`<iframe srcdoc sandbox="${dict.sandbox}">`);
+  }
+  document.documentElement.appendChild(iframe);
+
+  const once = (t, msg) => new Promise(r => t.addEventListener(msg, r, { once: true }));
+  const haveMessage = once(window, "message");
+  await new Promise(resolve => iframe.onload = resolve);
+  return (await haveMessage).data;
+};
+
+runTest(async () => {
+  const path = "/tests/dom/media/tests/mochitest/test_getUserMedia_permission_iframe.html";
+
+  async function sourceFn() {
+    try {
+      const gUM = c => navigator.mediaDevices.getUserMedia(c);
+      let message;
+      let stream;
+      try {
+        stream = await gUM({ video: true });
+        message = 'success';
+      } catch(e) {
+        message = e.name;
+      }
+      parent.postMessage(message, 'http://mochi.test:8888');
+
+      if (message == "success") {
+        stream.getTracks().forEach(track => track.stop());
+      }
+    } catch (e) {
+      setTimeout(() => { throw e; });
+    }
+  }
+
+  const source = `<html\><script\>(${sourceFn.toString()})()</script\></html\>`;
+
+  // Test gUM in sandboxed vs. regular iframe.
+
+  for (const origin of ["http://mochi.test:8888", "https://example.com", "http://test1.mochi.test:8888"]) {
+    const src = origin + path;
+    is(await iframeGum({ src, sandbox: "allow-scripts" }),
+       "NotAllowedError", "gUM fails in sandboxed iframe " + origin);
+    is(await iframeGum({ src, sandbox: "allow-scripts allow-same-origin " + origin }),
+       "success", "gUM works in regular iframe");
+  }
+
+  // Test gUM in sandboxed vs regular srcdoc iframe
+
+  const iframeSrcdoc = document.createElement("iframe");
+  iframeSrcdoc.srcdoc = source;
+  is(await iframeGum({ sandbox: "allow-scripts" }, iframeSrcdoc),
+     "NotAllowedError", "gUM fails in sandboxed srcdoc iframe");
+  is(await iframeGum({ sandbox: "allow-scripts allow-same-origin" }, iframeSrcdoc),
+     "success", "gUM works in regular srcdoc iframe");
+
+  // Test gUM in sandboxed vs regular blob iframe
+
+  const blob = new Blob([source], {type : "text/html"});
+	let src = URL.createObjectURL(blob);
+  is(await iframeGum({ src, sandbox: "allow-scripts" }),
+     "NotAllowedError", "gUM fails in sandboxed blob iframe");
+  is(await iframeGum({ src, sandbox: "allow-scripts allow-same-origin"}),
+     "success", "gUM works in regular blob iframe");
+  URL.revokeObjectURL(src);
+
+  // data iframes always have null-principals
+
+  src = `data:text/html;base64,${btoa(source)}`;
+  is(await iframeGum({ src, sandbox: "allow-scripts" }),
+     "NotAllowedError", "gUM fails in sandboxed data iframe");
+  is(await iframeGum({ src, sandbox: "allow-scripts allow-same-origin"}),
+     "NotAllowedError", "gUM fails in regular data iframe");
+});
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_permission_iframe.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<pre id="test">
+<script type="application/javascript">
+/**
+  Runs inside iframe in test_getUserMedia_permission.html.
+*/
+
+const gUM = c => navigator.mediaDevices.getUserMedia(c);
+
+(async () => {
+  let message;
+  let stream;
+  try {
+    stream = await gUM({ video: true });
+    message = "success";
+  } catch(e) {
+    message = e.name;
+  }
+  parent.postMessage(message, "http://mochi.test:8888");
+
+  if (message == "success") {
+    stream.getTracks().forEach(track => track.stop());
+  }
+})().catch(e => setTimeout(() => { throw e; }));
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/AudioParamDescriptorMap.h
@@ -0,0 +1,24 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AudioParamDescriptor_h
+#define mozilla_dom_AudioParamDescriptor_h
+
+#include "mozilla/dom/AudioParamDescriptorBinding.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+// Note: we call this "map" to match the spec, but we store audio param
+// descriptors in an array, because we need ordered access, and don't need the
+// map functionalities.
+typedef nsTArray<AudioParamDescriptor> AudioParamDescriptorMap;
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_AudioParamDescriptor_h
--- a/dom/media/webaudio/AudioWorkletGlobalScope.cpp
+++ b/dom/media/webaudio/AudioWorkletGlobalScope.cpp
@@ -5,16 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioWorkletGlobalScope.h"
 
 #include "AudioWorkletImpl.h"
 #include "jsapi.h"
 #include "mozilla/dom/AudioWorkletGlobalScopeBinding.h"
 #include "mozilla/dom/WorkletPrincipal.h"
+#include "mozilla/dom/AudioParamDescriptorBinding.h"
+#include "nsPrintfCString.h"
+#include "nsTHashtable.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope,
                                    mNameToProcessorMap);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioWorkletGlobalScope)
@@ -173,24 +176,106 @@ void AudioWorkletGlobalScope::RegisterPr
     return;
   }
 
   /**
    * 11. Queue a task to the control thread to add the key-value pair
    *     (name - descriptors) to the node name to parameter descriptor
    *     map of the associated BaseAudioContext.
    */
+  AudioParamDescriptorMap map = DescriptorsFromJS(aCx, descriptors, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
   // TODO: we don't have a proper mechanism to communicate with the
   // control thread currently. See
   // https://bugzilla.mozilla.org/show_bug.cgi?id=1473467#c3
   // and https://bugzilla.mozilla.org/show_bug.cgi?id=1492014
 }
 
 WorkletImpl* AudioWorkletGlobalScope::Impl() const { return mImpl; }
 
 uint64_t AudioWorkletGlobalScope::CurrentFrame() const { return mCurrentFrame; }
 
 double AudioWorkletGlobalScope::CurrentTime() const { return mCurrentTime; }
 
 float AudioWorkletGlobalScope::SampleRate() const { return mSampleRate; }
 
+AudioParamDescriptorMap AudioWorkletGlobalScope::DescriptorsFromJS(
+    JSContext* aCx, const JS::Rooted<JS::Value>& aDescriptors,
+    ErrorResult& aRv) {
+  // We already checked if aDescriptors is an array or undefined in step 8 of
+  // registerProcessor, so we should be confident aDescriptors if valid here
+  if (aDescriptors.isUndefined()) {
+    return AudioParamDescriptorMap();
+  }
+  MOZ_ASSERT(aDescriptors.isObject());
+
+  AudioParamDescriptorMap res;
+  // To check for duplicates
+  nsTHashtable<nsStringHashKey> namesSet;
+
+  JS::Rooted<JSObject*> aDescriptorsArray(aCx, &aDescriptors.toObject());
+  uint32_t length = 0;
+  if (!JS_GetArrayLength(aCx, aDescriptorsArray, &length)) {
+    aRv.NoteJSContextException(aCx);
+    return AudioParamDescriptorMap();
+  }
+
+  for (uint32_t i = 0; i < length; ++i) {
+    JS::Rooted<JS::Value> descriptorElement(aCx);
+    if (!JS_GetElement(aCx, aDescriptorsArray, i, &descriptorElement)) {
+      aRv.NoteJSContextException(aCx);
+      return AudioParamDescriptorMap();
+    }
+
+    AudioParamDescriptor descriptor;
+    nsPrintfCString sourceDescription("Element %u in parameterDescriptors", i);
+    if (!descriptor.Init(aCx, descriptorElement, sourceDescription.get())) {
+      aRv.NoteJSContextException(aCx);
+      return AudioParamDescriptorMap();
+    }
+
+    if (namesSet.Contains(descriptor.mName)) {
+      aRv.ThrowDOMException(
+          NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+          NS_LITERAL_CSTRING("Duplicated name \"") +
+              NS_ConvertUTF16toUTF8(descriptor.mName) +
+              NS_LITERAL_CSTRING("\" in parameterDescriptors."));
+      return AudioParamDescriptorMap();
+    }
+
+    if (descriptor.mMinValue > descriptor.mMaxValue) {
+      aRv.ThrowDOMException(
+          NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+          NS_LITERAL_CSTRING("In parameterDescriptors, ") +
+              NS_ConvertUTF16toUTF8(descriptor.mName) +
+              NS_LITERAL_CSTRING(" minValue should be smaller than maxValue."));
+      return AudioParamDescriptorMap();
+    }
+
+    if (descriptor.mDefaultValue < descriptor.mMinValue ||
+        descriptor.mDefaultValue > descriptor.mMaxValue) {
+      aRv.ThrowDOMException(
+          NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+          NS_LITERAL_CSTRING("In parameterDescriptors, ") +
+              NS_ConvertUTF16toUTF8(descriptor.mName) +
+              NS_LITERAL_CSTRING(" defaultValue is out of the range defined by "
+                                 "minValue and maxValue."));
+      return AudioParamDescriptorMap();
+    }
+
+    if (!res.AppendElement(descriptor)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return AudioParamDescriptorMap();
+    }
+
+    if (!namesSet.PutEntry(descriptor.mName, fallible)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return AudioParamDescriptorMap();
+    }
+  }
+
+  return res;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/webaudio/AudioWorkletGlobalScope.h
+++ b/dom/media/webaudio/AudioWorkletGlobalScope.h
@@ -2,16 +2,17 @@
 /* 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 mozilla_dom_AudioWorkletGlobalScope_h
 #define mozilla_dom_AudioWorkletGlobalScope_h
 
+#include "mozilla/dom/AudioParamDescriptorMap.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/WorkletGlobalScope.h"
 #include "nsRefPtrHashtable.h"
 
 namespace mozilla {
 
 class AudioWorkletImpl;
 
@@ -37,16 +38,23 @@ class AudioWorkletGlobalScope final : pu
 
   double CurrentTime() const;
 
   float SampleRate() const;
 
  private:
   ~AudioWorkletGlobalScope() = default;
 
+  // Returns an AudioParamDescriptorMap filled with AudioParamDescriptor
+  // objects, extracted from JS. Returns an empty map in case of error and set
+  // aRv accordingly.
+  AudioParamDescriptorMap DescriptorsFromJS(
+      JSContext* aCx, const JS::Rooted<JS::Value>& aDescriptors,
+      ErrorResult& aRv);
+
   const RefPtr<AudioWorkletImpl> mImpl;
 
   uint64_t mCurrentFrame;
   double mCurrentTime;
   float mSampleRate;
 
   typedef nsRefPtrHashtable<nsStringHashKey, VoidFunction>
       NodeNameToProcessorDefinitionMap;
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -42,16 +42,17 @@ EXPORTS.mozilla.dom += [
     'AnalyserNode.h',
     'AudioBuffer.h',
     'AudioBufferSourceNode.h',
     'AudioContext.h',
     'AudioDestinationNode.h',
     'AudioListener.h',
     'AudioNode.h',
     'AudioParam.h',
+    'AudioParamDescriptorMap.h',
     'AudioParamMap.h',
     'AudioProcessingEvent.h',
     'AudioScheduledSourceNode.h',
     'AudioWorkletGlobalScope.h',
     'AudioWorkletNode.h',
     'AudioWorkletProcessor.h',
     'BiquadFilterNode.h',
     'ChannelMergerNode.h',
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -356,17 +356,19 @@ nsresult WebMDemuxer::ReadMetadata() {
           break;
       }
       uint64_t duration = 0;
       r = nestegg_duration(context, &duration);
       if (!r) {
         mInfo.mVideo.mDuration = TimeUnit::FromNanoseconds(duration);
       }
       mInfo.mVideo.mCrypto = GetTrackCrypto(TrackInfo::kVideoTrack, track);
-      if (mInfo.mVideo.mCrypto.mValid) {
+      if (mInfo.mVideo.mCrypto.IsEncrypted()) {
+        MOZ_ASSERT(mInfo.mVideo.mCrypto.mCryptoScheme == CryptoScheme::Cenc,
+                   "WebM should only use cenc scheme");
         mCrypto.AddInitData(NS_LITERAL_STRING("webm"),
                             mInfo.mVideo.mCrypto.mKeyId);
       }
     } else if (type == NESTEGG_TRACK_AUDIO && !mHasAudio) {
       nestegg_audio_params params;
       r = nestegg_track_audio_params(context, track, &params);
       if (r == -1) {
         return NS_ERROR_FAILURE;
@@ -421,17 +423,19 @@ nsresult WebMDemuxer::ReadMetadata() {
                                                           headerLens[0]);
       }
       uint64_t duration = 0;
       r = nestegg_duration(context, &duration);
       if (!r) {
         mInfo.mAudio.mDuration = TimeUnit::FromNanoseconds(duration);
       }
       mInfo.mAudio.mCrypto = GetTrackCrypto(TrackInfo::kAudioTrack, track);
-      if (mInfo.mAudio.mCrypto.mValid) {
+      if (mInfo.mAudio.mCrypto.IsEncrypted()) {
+        MOZ_ASSERT(mInfo.mAudio.mCrypto.mCryptoScheme == CryptoScheme::Cenc,
+                   "WebM should only use cenc scheme");
         mCrypto.AddInitData(NS_LITERAL_STRING("webm"),
                             mInfo.mAudio.mCrypto.mKeyId);
       }
     }
   }
   return NS_OK;
 }
 
@@ -503,18 +507,18 @@ CryptoTrack WebMDemuxer::GetTrackCrypto(
 
   uint32_t i;
   nsTArray<uint8_t> initData;
   for (i = 0; i < contentEncKeyIdLength; i++) {
     initData.AppendElement(contentEncKeyId[i]);
   }
 
   if (!initData.IsEmpty()) {
-    crypto.mValid = true;
-    // crypto.mMode is not used for WebMs
+    // Webm only uses a cenc style scheme.
+    crypto.mCryptoScheme = CryptoScheme::Cenc;
     crypto.mIVSize = WEBM_IV_SIZE;
     crypto.mKeyId = std::move(initData);
   }
 
   return crypto;
 }
 
 nsresult WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType,
@@ -700,17 +704,17 @@ nsresult WebMDemuxer::GetNextPacket(Trac
     }
 
     if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED ||
         packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) {
       UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
       unsigned char const* iv;
       size_t ivLength;
       nestegg_packet_iv(holder->Packet(), &iv, &ivLength);
-      writer->mCrypto.mValid = true;
+      writer->mCrypto.mCryptoScheme = CryptoScheme::Cenc;
       writer->mCrypto.mIVSize = ivLength;
       if (ivLength == 0) {
         // Frame is not encrypted. This shouldn't happen as it means the
         // encryption bit is set on a frame with no IV, but we gracefully
         // handle incase.
         MOZ_ASSERT_UNREACHABLE(
             "Unencrypted packets should not have the encryption bit set!");
         WEBM_DEBUG("Unencrypted packet with encryption bit set");
@@ -1160,19 +1164,18 @@ void WebMTrackDemuxer::Reset() {
     SetNextKeyFrameTime();
   } else {
     mNextKeyframeTime.reset();
   }
 }
 
 void WebMTrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples) {
   for (const auto& sample : aSamples) {
-    if (sample->mCrypto.mValid) {
+    if (sample->mCrypto.IsEncrypted()) {
       UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter());
-      writer->mCrypto.mMode = mInfo->mCrypto.mMode;
       writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
       writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
     }
   }
   if (mNextKeyframeTime.isNothing() ||
       aSamples.LastElement()->mTime >= mNextKeyframeTime.value()) {
     SetNextKeyFrameTime();
   }
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AudioParamDescriptor.webidl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://webaudio.github.io/web-audio-api/#dictdef-audioparamdescriptor
+ *
+ * Copyright © 2018 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+dictionary AudioParamDescriptor {
+    required DOMString name;
+    float defaultValue = 0;
+    // FIXME: we use 3.402823466e38 instead of 3.4028235e38 to workaround a bug
+    // in Visual Studio compiler (see bug 1501709 for more information).
+    // We should put back 3.4028235e38 once MSVC support is dropped (i.e. once
+    // bug 1512504 is fixed)
+    float minValue = -3.402823466e38;
+    float maxValue = 3.402823466e38;
+    // AutomationRate for AudioWorklet is not needed until bug 1504984 is
+    // implemented
+    // AutomationRate automationRate = "a-rate";
+};
+
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -373,16 +373,17 @@ WEBIDL_FILES = [
     'Attr.webidl',
     'AudioBuffer.webidl',
     'AudioBufferSourceNode.webidl',
     'AudioContext.webidl',
     'AudioDestinationNode.webidl',
     'AudioListener.webidl',
     'AudioNode.webidl',
     'AudioParam.webidl',
+    'AudioParamDescriptor.webidl',
     'AudioParamMap.webidl',
     'AudioProcessingEvent.webidl',
     'AudioScheduledSourceNode.webidl',
     'AudioTrack.webidl',
     'AudioTrackList.webidl',
     'AudioWorklet.webidl',
     'AudioWorkletGlobalScope.webidl',
     'AudioWorkletNode.webidl',
--- a/dom/worklet/tests/test_audioWorkletGlobalScopeRegisterProcessor.html
+++ b/dom/worklet/tests/test_audioWorkletGlobalScopeRegisterProcessor.html
@@ -15,16 +15,25 @@ function configureTest() {
   var expected_errors = [
     "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not a constructor.",
     "NotSupportedError: Argument 1 of AudioWorkletGlobalScope.registerProcessor should not be an empty string.",
     "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not an object.",
     "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.process is not callable.",
     "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.process is not callable.",
     "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor constructor.parameterDescriptors is neither an array nor undefined.",
     "NotSupportedError: Argument 1 of AudioWorkletGlobalScope.registerProcessor is invalid: a class with the same name is already registered.",
+    "TypeError: Missing required 'name' member of AudioParamDescriptor.",
+    "TypeError: 'defaultValue' member of AudioParamDescriptor is not a finite floating-point value.",
+    "TypeError: 'minValue' member of AudioParamDescriptor is not a finite floating-point value.",
+    "TypeError: 'maxValue' member of AudioParamDescriptor is not a finite floating-point value.",
+    "NotSupportedError: Duplicated name \"test\" in parameterDescriptors.",
+    "TypeError: Element 0 in parameterDescriptors can't be converted to a dictionary.",
+    "NotSupportedError: In parameterDescriptors, test defaultValue is out of the range defined by minValue and maxValue.",
+    "NotSupportedError: In parameterDescriptors, test defaultValue is out of the range defined by minValue and maxValue.",
+    "NotSupportedError: In parameterDescriptors, test minValue should be smaller than maxValue.",
     ];
 
   var expected_errors_i = 0;
 
   function consoleListener() {
     SpecialPowers.addObserver(this, "console-api-log-event");
   }
 
--- a/dom/worklet/tests/worklet_test_audioWorkletGlobalScopeRegisterProcessor.js
+++ b/dom/worklet/tests/worklet_test_audioWorkletGlobalScopeRegisterProcessor.js
@@ -36,16 +36,154 @@ class GoodDescriptorsWorkletProcessor ex
 class DummyProcessWorkletProcessor extends AudioWorkletProcessor {
   constructor() { super(); }
 
   process() {
     // Do nothing, output silence
   }
 }
 
+class DescriptorsNoNameWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      defaultValue: 0.707
+    }];
+  }
+}
+
+class DescriptorsDefaultValueNotNumberWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: "test",
+      defaultValue: "test"
+    }];
+  }
+}
+
+class DescriptorsMinValueNotNumberWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: "test",
+      minValue: "test"
+    }];
+  }
+}
+
+class DescriptorsMaxValueNotNumberWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: "test",
+      maxValue: "test"
+    }];
+  }
+}
+
+class DescriptorsDuplicatedNameWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: "test",
+    }, {
+      name: "test",
+    }];
+  }
+}
+
+class DescriptorsNotDictWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [42];
+  }
+}
+
+class DescriptorsOutOfRangeMinWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: 'test',
+      defaultValue: 0,
+      minValue: 1,
+      maxValue: 2,
+    }];
+  }
+}
+
+class DescriptorsOutOfRangeMaxWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: 'test',
+      defaultValue: 3,
+      minValue: 1,
+      maxValue: 2,
+    }];
+  }
+}
+
+class DescriptorsBadRangeMaxWorkletProcessor extends AudioWorkletProcessor {
+  constructor() { super(); }
+
+  process() {
+    // Do nothing, output silence
+  }
+
+  static get parameterDescriptors() {
+    return [{
+      name: 'test',
+      defaultValue: 1.5,
+      minValue: 2,
+      maxValue: 1,
+    }];
+  }
+}
+
 // Test not a constructor
 // "TypeError: Argument 2 of AudioWorkletGlobalScope.registerProcessor is not a constructor."
 try {
   registerProcessor("sure!", () => {});
 } catch (e) {
   console.log(e)
 }
 
@@ -99,8 +237,80 @@ registerProcessor("dummy-worklet-process
 
 // Test class adding class with the same name twice
 // "NotSupportedError: Operation is not supported: Argument 1 of AudioWorkletGlobalScope.registerProcessor is invalid: a class with the same name is already registered."
 try {
   registerProcessor("dummy-worklet-processor", DummyProcessWorkletProcessor);
 } catch (e) {
   console.log(e)
 }
+
+// "name" is a mandatory field in descriptors
+// "TypeError: Missing required 'name' member of AudioParamDescriptor."
+try {
+  registerProcessor("descriptors-no-name-worklet-processor", DescriptorsNoNameWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// "defaultValue" should be a number
+// "TypeError: 'defaultValue' member of AudioParamDescriptor is not a finite floating-point value."
+try {
+  registerProcessor("descriptors-default-value-not-number-worklet-processor", DescriptorsDefaultValueNotNumberWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// "min" should be a number
+// "TypeError: 'minValue' member of AudioParamDescriptor is not a finite floating-point value."
+try {
+  registerProcessor("descriptors-min-value-not-number-worklet-processor", DescriptorsMinValueNotNumberWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// "max" should be a number
+// "TypeError: 'maxValue' member of AudioParamDescriptor is not a finite floating-point value."
+try {
+  registerProcessor("descriptors-max-value-not-number-worklet-processor", DescriptorsMaxValueNotNumberWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// Duplicated values are not allowed for "name"
+// "NotSupportedError: Duplicated name \"test\" in parameterDescriptors"
+try {
+  registerProcessor("descriptors-duplicated-name-worklet-processor", DescriptorsDuplicatedNameWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// Descriptors' elements should be dictionnary
+// "TypeError: Element 0 in parameterDescriptors can't be converted to a dictionary.",
+try {
+  registerProcessor("descriptors-not-dict-worklet-processor", DescriptorsNotDictWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// defaultValue value should be in range [minValue, maxValue]. defaultValue < minValue is not allowed
+// "NotSupportedError: In parameterDescriptors, test defaultValue is out of the range defined by minValue and maxValue.",
+try {
+  registerProcessor("descriptors-out-of-range-min-worklet-processor", DescriptorsOutOfRangeMinWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// defaultValue value should be in range [minValue, maxValue]. defaultValue > maxValue is not allowed
+// "NotSupportedError: In parameterDescriptors, test defaultValue is out of the range defined by minValue and maxValue.",
+try {
+  registerProcessor("descriptors-out-of-range-max-worklet-processor", DescriptorsOutOfRangeMaxWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
+
+// We should have minValue < maxValue to define a valid range
+// "NotSupportedError: In parameterDescriptors, test minValue should be smaller than maxValue.",
+try {
+  registerProcessor("descriptors-bad-range-max-worklet-processor", DescriptorsBadRangeMaxWorkletProcessor);
+} catch (e) {
+  console.log(e)
+}
--- a/editor/libeditor/DeleteRangeTransaction.cpp
+++ b/editor/libeditor/DeleteRangeTransaction.cpp
@@ -3,25 +3,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DeleteRangeTransaction.h"
 
 #include "DeleteNodeTransaction.h"
 #include "DeleteTextTransaction.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/dom/Selection.h"
 #include "mozilla/EditorBase.h"
-#include "mozilla/dom/Selection.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/RangeBoundary.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsINode.h"
 #include "nsAString.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 DeleteRangeTransaction::DeleteRangeTransaction(EditorBase& aEditorBase,
@@ -209,36 +209,35 @@ nsresult DeleteRangeTransaction::CreateT
 }
 
 nsresult DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween(
     nsRange* aRangeToDelete) {
   if (NS_WARN_IF(!mEditorBase)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
-
-  nsresult rv = iter->Init(aRangeToDelete);
+  ContentSubtreeIterator subtreeIter;
+  nsresult rv = subtreeIter.Init(aRangeToDelete);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  while (!iter->IsDone()) {
-    nsCOMPtr<nsINode> node = iter->GetCurrentNode();
+  while (!subtreeIter.IsDone()) {
+    nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       return NS_ERROR_NULL_POINTER;
     }
 
     RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
         DeleteNodeTransaction::MaybeCreate(*mEditorBase, *node);
     // XXX This is odd handling.  Even if some nodes in the range are not
     //     editable, editor should append transactions because they could
     //     at undoing/redoing.  Additionally, if the transaction needs to
     //     delete/restore all nodes, it should at undoing/redoing.
     if (NS_WARN_IF(!deleteNodeTransaction)) {
       return NS_ERROR_FAILURE;
     }
     AppendChild(deleteNodeTransaction);
 
-    iter->Next();
+    subtreeIter.Next();
   }
   return NS_OK;
 }
 
 }  // namespace mozilla
--- a/editor/libeditor/EditorUtils.cpp
+++ b/editor/libeditor/EditorUtils.cpp
@@ -1,81 +1,77 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/EditorUtils.h"
 
+#include "mozilla/ContentIterator.h"
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/dom/Selection.h"
 #include "nsComponentManagerUtils.h"
 #include "nsError.h"
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsIDocShell.h"
 #include "mozilla/dom/Document.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsINode.h"
 
 class nsISupports;
 class nsRange;
 
 namespace mozilla {
 
 using namespace dom;
 
 /******************************************************************************
  * some helper classes for iterating the dom tree
  *****************************************************************************/
 
-DOMIterator::DOMIterator(
-    nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) {
+DOMIterator::DOMIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+    : mIter(&mPostOrderIter) {
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-  mIter = NS_NewContentIterator();
   DebugOnly<nsresult> rv = mIter->Init(&aNode);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 nsresult DOMIterator::Init(nsRange& aRange) {
-  mIter = NS_NewContentIterator();
   return mIter->Init(&aRange);
 }
 
-DOMIterator::DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) {
+DOMIterator::DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+    : mIter(&mPostOrderIter) {
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-DOMIterator::~DOMIterator() {}
-
 void DOMIterator::AppendList(
     const BoolDomIterFunctor& functor,
     nsTArray<OwningNonNull<nsINode>>& arrayOfNodes) const {
   // Iterate through dom and build list
   for (; !mIter->IsDone(); mIter->Next()) {
     nsCOMPtr<nsINode> node = mIter->GetCurrentNode();
 
     if (functor(node)) {
       arrayOfNodes.AppendElement(*node);
     }
   }
 }
 
 DOMSubtreeIterator::DOMSubtreeIterator(
     MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
-    : DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) {}
+    : DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) {
+  mIter = &mSubtreeIter;
+}
 
 nsresult DOMSubtreeIterator::Init(nsRange& aRange) {
-  mIter = NS_NewContentSubtreeIterator();
   return mIter->Init(&aRange);
 }
 
-DOMSubtreeIterator::~DOMSubtreeIterator() {}
-
 /******************************************************************************
  * some general purpose editor utils
  *****************************************************************************/
 
 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
                                  EditorRawDOMPoint* aOutPoint /* = nullptr */) {
   if (aOutPoint) {
     aOutPoint->Clear();
--- a/editor/libeditor/EditorUtils.h
+++ b/editor/libeditor/EditorUtils.h
@@ -1,28 +1,28 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_EditorUtils_h
 #define mozilla_EditorUtils_h
 
+#include "mozilla/ContentIterator.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/EditAction.h"
 #include "mozilla/EditorBase.h"
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/GuardObjects.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsIEditor.h"
 #include "nscore.h"
 
 class nsAtom;
-class nsIContentIterator;
 class nsISimpleEnumerator;
 class nsITransferable;
 class nsRange;
 
 namespace mozilla {
 template <class T>
 class OwningNonNull;
 
@@ -445,35 +445,41 @@ class BoolDomIterFunctor {
   virtual bool operator()(nsINode* aNode) const = 0;
 };
 
 class MOZ_RAII DOMIterator {
  public:
   explicit DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
 
   explicit DOMIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-  virtual ~DOMIterator();
+  virtual ~DOMIterator() = default;
 
   nsresult Init(nsRange& aRange);
 
   void AppendList(
       const BoolDomIterFunctor& functor,
       nsTArray<mozilla::OwningNonNull<nsINode>>& arrayOfNodes) const;
 
  protected:
-  nsCOMPtr<nsIContentIterator> mIter;
+  ContentIteratorBase* mIter;
+  PostContentIterator mPostOrderIter;
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 class MOZ_RAII DOMSubtreeIterator final : public DOMIterator {
  public:
   explicit DOMSubtreeIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
-  virtual ~DOMSubtreeIterator();
+  virtual ~DOMSubtreeIterator() = default;
 
   nsresult Init(nsRange& aRange);
+
+ private:
+  ContentSubtreeIterator mSubtreeIter;
+  explicit DOMSubtreeIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM) =
+      delete;
 };
 
 class TrivialFunctor final : public BoolDomIterFunctor {
  public:
   // Used to build list of all nodes iterator covers
   virtual bool operator()(nsINode* aNode) const override { return true; }
 };
 
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -7,16 +7,17 @@
 #include "HTMLEditRules.h"
 
 #include <stdlib.h>
 
 #include "HTMLEditUtils.h"
 #include "TextEditUtils.h"
 #include "WSRunObject.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/CSSEditUtils.h"
 #include "mozilla/EditAction.h"
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Move.h"
 #include "mozilla/Preferences.h"
@@ -34,17 +35,16 @@
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsAtom.h"
 #include "nsHTMLDocument.h"
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsID.h"
 #include "nsIFrame.h"
 #include "nsIHTMLAbsPosEditor.h"
 #include "nsINode.h"
 #include "nsLiteralString.h"
 #include "nsRange.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
@@ -9432,30 +9432,28 @@ nsresult HTMLEditRules::RemoveEmptyNodes
   // the skiplist as being empty (without even doing the IsEmptyNode check) on
   // the theory that if they weren't empty, we would have encountered a
   // non-empty child earlier and thus put this parent node on the skiplist.
   //
   // Unfortunately I can't use that strategy here, because the range may
   // include some children of a node while excluding others.  Thus I could find
   // all the _examined_ children empty, but still not have an empty parent.
 
-  // need an iterator
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-
-  nsresult rv = iter->Init(mDocChangeRange);
+  PostContentIterator postOrderIter;
+  nsresult rv = postOrderIter.Init(mDocChangeRange);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsTArray<OwningNonNull<nsINode>> arrayOfEmptyNodes, arrayOfEmptyCites,
       skipList;
 
   // Check for empty nodes
-  while (!iter->IsDone()) {
-    OwningNonNull<nsINode> node = *iter->GetCurrentNode();
+  for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+    OwningNonNull<nsINode> node = *postOrderIter.GetCurrentNode();
 
     nsCOMPtr<nsINode> parent = node->GetParentNode();
 
     size_t idx = skipList.IndexOf(node);
     if (idx != skipList.NoIndex) {
       // This node is on our skip list.  Skip processing for this node, and
       // replace its value in the skip list with the value of its parent
       if (parent) {
@@ -9510,18 +9508,16 @@ nsresult HTMLEditRules::RemoveEmptyNodes
         }
       }
 
       if (!isEmptyNode && parent) {
         // put parent on skip list
         skipList.AppendElement(*parent);
       }
     }
-
-    iter->Next();
   }
 
   // now delete the empty nodes
   for (OwningNonNull<nsINode>& delNode : arrayOfEmptyNodes) {
     if (HTMLEditorRef().IsModifiableNode(delNode)) {
       rv = HTMLEditorRef().DeleteNodeWithTransaction(*delNode);
       if (NS_WARN_IF(!CanHandleEditAction())) {
         return NS_ERROR_EDITOR_DESTROYED;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/HTMLEditor.h"
 
 #include "mozilla/ComposerCommandsUpdater.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/EditAction.h"
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/mozInlineSpellChecker.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TextEvents.h"
 
@@ -29,17 +30,16 @@
 #include "mozilla/dom/DocumentInlines.h"
 #include "nsISelectionController.h"
 #include "nsILinkHandler.h"
 #include "nsIInlineSpellChecker.h"
 
 #include "mozilla/css/Loader.h"
 
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsIMutableArray.h"
 #include "nsContentUtils.h"
 #include "nsIDocumentEncoder.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsFocusManager.h"
 #include "nsPIDOMWindow.h"
@@ -1119,44 +1119,44 @@ nsresult HTMLEditor::TabInTable(bool inI
   // find enclosing table
   RefPtr<Element> table = GetEnclosingTable(cellElement);
   if (NS_WARN_IF(!table)) {
     return NS_OK;
   }
 
   // advance to next cell
   // first create an iterator over the table
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  nsresult rv = iter->Init(table);
+  PostContentIterator postOrderIter;
+  nsresult rv = postOrderIter.Init(table);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  // position iter at block
-  rv = iter->PositionAt(cellElement);
+  // position postOrderIter at block
+  rv = postOrderIter.PositionAt(cellElement);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsINode> node;
   do {
     if (inIsShift) {
-      iter->Prev();
+      postOrderIter.Prev();
     } else {
-      iter->Next();
+      postOrderIter.Next();
     }
 
-    node = iter->GetCurrentNode();
+    node = postOrderIter.GetCurrentNode();
 
     if (node && HTMLEditUtils::IsTableCell(node) &&
         GetEnclosingTable(node) == table) {
       CollapseSelectionToDeepestNonTableFirstChild(node);
       *outHandled = true;
       return NS_OK;
     }
-  } while (!iter->IsDone());
+  } while (!postOrderIter.IsDone());
 
   if (!(*outHandled) && !inIsShift) {
     // If we haven't handled it yet, then we must have run off the end of the
     // table.  Insert a new row.
     // XXX We should investigate whether this behavior is supported by other
     //     browsers later.
     AutoEditActionDataSetter editActionData(*this,
                                             EditAction::eInsertTableRowElement);
@@ -2781,21 +2781,22 @@ already_AddRefed<Element> HTMLEditor::Ge
       }
     }
   }
 
   if (SelectionRefPtr()->IsCollapsed()) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  iter->Init(firstRange);
+  PostContentIterator postOrderIter;
+  postOrderIter.Init(firstRange);
 
   RefPtr<Element> lastElementInRange;
-  for (nsINode* lastNodeInRange = nullptr; !iter->IsDone(); iter->Next()) {
+  for (nsINode* lastNodeInRange = nullptr; !postOrderIter.IsDone();
+       postOrderIter.Next()) {
     if (lastElementInRange) {
       // When any node follows an element node, not only one element is
       // selected so that return nullptr.
       return nullptr;
     }
 
     // This loop ignored any non-element nodes before first element node.
     // Its purpose must be that this method allow to this case as selecting
@@ -2803,17 +2804,17 @@ already_AddRefed<Element> HTMLEditor::Ge
     // - <p>abc <b>d[ef</b>}</p>
     // because children of an element node is listed up before the element.
     // However, this case must not be expected by the initial developer:
     // - <p>a[bc <b>def</b>}</p>
     // When we meet non-parent and non-next-sibling node of previous node,
     // it means that the range across element boundary (open tag in HTML
     // source).  So, in this case, we should not say only the following
     // element is selected.
-    nsINode* currentNode = iter->GetCurrentNode();
+    nsINode* currentNode = postOrderIter.GetCurrentNode();
     MOZ_ASSERT(currentNode);
     if (lastNodeInRange && lastNodeInRange->GetParentNode() != currentNode &&
         lastNodeInRange->GetNextSibling() != currentNode) {
       return nullptr;
     }
 
     lastNodeInRange = currentNode;
 
@@ -3056,34 +3057,33 @@ HTMLEditor::GetLinkedObjects(nsIArray** 
   NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER);
 
   nsresult rv;
   nsCOMPtr<nsIMutableArray> nodes = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
   RefPtr<Document> doc = GetDocument();
   NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
 
-  iter->Init(doc->GetRootElement());
+  PostContentIterator postOrderIter;
+  postOrderIter.Init(doc->GetRootElement());
 
   // loop through the content iterator for each content node
-  while (!iter->IsDone()) {
-    nsCOMPtr<nsINode> node = iter->GetCurrentNode();
+  for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+    nsCOMPtr<nsINode> node = postOrderIter.GetCurrentNode();
     if (node) {
       // Let nsURIRefObject make the hard decisions:
       nsCOMPtr<nsIURIRefObject> refObject;
       rv = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node);
       if (NS_SUCCEEDED(rv)) {
         nodes->AppendElement(refObject);
       }
     }
-    iter->Next();
   }
 
   nodes.forget(aNodeList);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::AddOverrideStyleSheet(const nsAString& aURL) {
@@ -3773,28 +3773,24 @@ nsresult HTMLEditor::CollapseAdjacentTex
   NS_ENSURE_TRUE(aInRange, NS_ERROR_NULL_POINTER);
   AutoTransactionsConserveSelection dontChangeMySelection(*this);
   nsTArray<nsCOMPtr<nsINode>> textNodes;
   // we can't actually do anything during iteration, so store the text nodes in
   // an array don't bother ref counting them because we know we can hold them
   // for the lifetime of this method
 
   // build a list of editable text nodes
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
-
-  iter->Init(aInRange);
-
-  while (!iter->IsDone()) {
-    nsINode* node = iter->GetCurrentNode();
+  ContentSubtreeIterator subtreeIter;
+  subtreeIter.Init(aInRange);
+  for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+    nsINode* node = subtreeIter.GetCurrentNode();
     if (node->NodeType() == nsINode::TEXT_NODE &&
         IsEditable(node->AsContent())) {
       textNodes.AppendElement(node);
     }
-
-    iter->Next();
   }
 
   // now that I have a list of text nodes, collapse adjacent text nodes
   // NOTE: assumption that JoinNodes keeps the righthand node
   while (textNodes.Length() > 1) {
     // we assume a textNodes entry can't be nullptr
     nsINode* leftTextNode = textNodes[0];
     nsINode* rightTextNode = textNodes[1];
@@ -4435,29 +4431,28 @@ nsresult HTMLEditor::SetCSSBackgroundCol
         // starting textnode and an ending textnode which are only partially
         // contained by the range.
 
         // Let's handle the nodes reported by the iterator.  These nodes are
         // entirely contained in the selection range.  We build up a list of
         // them (since doing operations on the document during iteration would
         // perturb the iterator).
 
-        OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
-
         nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
         nsCOMPtr<nsINode> node;
 
         // Iterate range and build up array
-        rv = iter->Init(range);
+        ContentSubtreeIterator subtreeIter;
+        rv = subtreeIter.Init(range);
         // Init returns an error if no nodes in range.  This can easily happen
         // with the subtree iterator if the selection doesn't contain any
         // *whole* nodes.
         if (NS_SUCCEEDED(rv)) {
-          for (; !iter->IsDone(); iter->Next()) {
-            node = iter->GetCurrentNode();
+          for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+            node = subtreeIter.GetCurrentNode();
             NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
 
             if (IsEditable(node)) {
               arrayOfNodes.AppendElement(*node);
             }
           }
         }
         // First check the start parent of the range to see if it needs to be
--- a/editor/libeditor/HTMLStyleEditor.cpp
+++ b/editor/libeditor/HTMLStyleEditor.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/HTMLEditor.h"
 
 #include "HTMLEditUtils.h"
 #include "TextEditUtils.h"
 #include "TypeInState.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/EditAction.h"
 #include "mozilla/EditorUtils.h"
 #include "mozilla/SelectionState.h"
 #include "mozilla/TextEditRules.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/mozalloc.h"
 #include "nsAString.h"
@@ -21,17 +22,16 @@
 #include "nsCOMPtr.h"
 #include "nsCaseTreatment.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsAtom.h"
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsNameSpaceManager.h"
 #include "nsINode.h"
 #include "nsISupportsImpl.h"
 #include "nsLiteralString.h"
 #include "nsRange.h"
 #include "nsReadableUtils.h"
 #include "nsString.h"
 #include "nsStringFwd.h"
@@ -170,28 +170,27 @@ nsresult HTMLEditor::SetInlinePropertyIn
       // starting textnode and an ending textnode which are only partially
       // contained by the range.
 
       // Let's handle the nodes reported by the iterator.  These nodes are
       // entirely contained in the selection range.  We build up a list of them
       // (since doing operations on the document during iteration would perturb
       // the iterator).
 
-      OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
-
       nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
 
       // Iterate range and build up array
-      rv = iter->Init(range);
+      ContentSubtreeIterator subtreeIter;
+      rv = subtreeIter.Init(range);
       // Init returns an error if there are no nodes in range.  This can easily
       // happen with the subtree iterator if the selection doesn't contain any
       // *whole* nodes.
       if (NS_SUCCEEDED(rv)) {
-        for (; !iter->IsDone(); iter->Next()) {
-          OwningNonNull<nsINode> node = *iter->GetCurrentNode();
+        for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+          OwningNonNull<nsINode> node = *subtreeIter.GetCurrentNode();
 
           if (node->IsContent() && IsEditable(node)) {
             arrayOfNodes.AppendElement(*node->AsContent());
           }
         }
       }
       // First check the start parent of the range to see if it needs to be
       // separately handled (it does if it's a text node, due to how the
@@ -1036,28 +1035,32 @@ nsresult HTMLEditor::GetInlinePropertyBa
 
       isSet = IsTextPropertySetByContent(collapsedNode, &aProperty, aAttribute,
                                          aValue, outValue);
       *aFirst = *aAny = *aAll = isSet;
       return NS_OK;
     }
 
     // Non-collapsed selection
-    nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
 
     nsAutoString firstValue, theValue;
 
     nsCOMPtr<nsINode> endNode = range->GetEndContainer();
     int32_t endOffset = range->EndOffset();
 
-    for (iter->Init(range); !iter->IsDone(); iter->Next()) {
-      if (!iter->GetCurrentNode()->IsContent()) {
+    PostContentIterator postOrderIter;
+    DebugOnly<nsresult> rvIgnored = postOrderIter.Init(range);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "Failed to initialize post-order content iterator");
+    for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+      if (!postOrderIter.GetCurrentNode()->IsContent()) {
         continue;
       }
-      nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
+      nsCOMPtr<nsIContent> content =
+          postOrderIter.GetCurrentNode()->AsContent();
 
       if (content->IsHTMLElement(nsGkAtoms::body)) {
         break;
       }
 
       // just ignore any non-editable nodes
       if (content->GetAsText() &&
           (!IsEditable(content) || IsEmptyTextNode(*content))) {
@@ -1336,23 +1339,26 @@ nsresult HTMLEditor::RemoveInlinePropert
               SetInlinePropertyOnTextNode(
                   *startNode->GetAsText(), range->StartOffset(),
                   range->EndOffset(), *aProperty, aAttribute, value);
             }
           }
         }
       } else {
         // Not the easy case.  Range not contained in single text node.
-        nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
 
         nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
 
         // Iterate range and build up array
-        for (iter->Init(range); !iter->IsDone(); iter->Next()) {
-          nsCOMPtr<nsINode> node = iter->GetCurrentNode();
+        ContentSubtreeIterator subtreeIter;
+        DebugOnly<nsresult> rvIgnored = subtreeIter.Init(range);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "Failed to initialize subtree iterator");
+        for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+          nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
           if (NS_WARN_IF(!node)) {
             return NS_ERROR_FAILURE;
           }
           if (IsEditable(node) && node->IsContent()) {
             arrayOfNodes.AppendElement(*node->AsContent());
           }
         }
 
@@ -1483,27 +1489,27 @@ nsresult HTMLEditor::RelativeFontChange(
       // starting textnode and an ending textnode which are only partially
       // contained by the range.
 
       // Let's handle the nodes reported by the iterator.  These nodes are
       // entirely contained in the selection range.  We build up a list of them
       // (since doing operations on the document during iteration would perturb
       // the iterator).
 
-      OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
-
       // Iterate range and build up array
-      rv = iter->Init(range);
+      ContentSubtreeIterator subtreeIter;
+      rv = subtreeIter.Init(range);
       if (NS_SUCCEEDED(rv)) {
         nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
-        for (; !iter->IsDone(); iter->Next()) {
-          if (NS_WARN_IF(!iter->GetCurrentNode()->IsContent())) {
+        for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+          if (NS_WARN_IF(!subtreeIter.GetCurrentNode()->IsContent())) {
             return NS_ERROR_FAILURE;
           }
-          OwningNonNull<nsIContent> node = *iter->GetCurrentNode()->AsContent();
+          OwningNonNull<nsIContent> node =
+              *subtreeIter.GetCurrentNode()->AsContent();
 
           if (IsEditable(node)) {
             arrayOfNodes.AppendElement(node);
           }
         }
 
         // Now that we have the list, do the font size change on each node
         for (auto& node : arrayOfNodes) {
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -6,16 +6,17 @@
 #include "mozilla/TextEditor.h"
 
 #include "EditAggregateTransaction.h"
 #include "HTMLEditRules.h"
 #include "InternetCiter.h"
 #include "TextEditUtils.h"
 #include "gfxFontUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/ContentIterator.h"
 #include "mozilla/EditAction.h"
 #include "mozilla/EditorDOMPoint.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEditRules.h"
 #include "mozilla/TextComposition.h"
@@ -34,17 +35,16 @@
 #include "nsCopySupport.h"
 #include "nsDebug.h"
 #include "nsDependentSubstring.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIAbsorbingTransaction.h"
 #include "nsIClipboard.h"
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsIDocumentEncoder.h"
 #include "nsINode.h"
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "nsISupportsPrimitives.h"
 #include "nsITransferable.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsNameSpaceManager.h"
@@ -1450,22 +1450,21 @@ TextEditor::GetTextLength(int32_t* aCoun
     return NS_OK;
   }
 
   Element* rootElement = GetRoot();
   if (NS_WARN_IF(!rootElement)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-
   uint32_t totalLength = 0;
-  iter->Init(rootElement);
-  for (; !iter->IsDone(); iter->Next()) {
-    nsCOMPtr<nsINode> currentNode = iter->GetCurrentNode();
+  PostContentIterator postOrderIter;
+  postOrderIter.Init(rootElement);
+  for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+    nsCOMPtr<nsINode> currentNode = postOrderIter.GetCurrentNode();
     if (IsTextNode(currentNode) && IsEditable(currentNode)) {
       totalLength += currentNode->Length();
     }
   }
 
   *aCount = totalLength;
   return NS_OK;
 }
rename from editor/spellchecker/nsFilteredContentIterator.cpp
rename to editor/spellchecker/FilteredContentIterator.cpp
--- a/editor/spellchecker/nsFilteredContentIterator.cpp
+++ b/editor/spellchecker/FilteredContentIterator.cpp
@@ -1,196 +1,176 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "FilteredContentIterator.h"
+
+#include "mozilla/ContentIterator.h"
+#include "mozilla/Move.h"
 #include "mozilla/mozalloc.h"
-#include "mozilla/Move.h"
+
 #include "nsComponentManagerUtils.h"
 #include "nsComposeTxtSrvFilter.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
-#include "nsFilteredContentIterator.h"
 #include "nsAtom.h"
 #include "nsIContent.h"
-#include "nsIContentIterator.h"
 #include "nsINode.h"
 #include "nsISupportsBase.h"
 #include "nsISupportsUtils.h"
 #include "nsRange.h"
 
-using namespace mozilla;
+namespace mozilla {
 
-//------------------------------------------------------------
-nsFilteredContentIterator::nsFilteredContentIterator(
+FilteredContentIterator::FilteredContentIterator(
     UniquePtr<nsComposeTxtSrvFilter> aFilter)
-    : mIterator(NS_NewContentIterator()),
-      mPreIterator(NS_NewPreContentIterator()),
+    : mCurrentIterator(nullptr),
       mFilter(std::move(aFilter)),
       mDidSkip(false),
       mIsOutOfRange(false),
       mDirection(eDirNotSet) {}
 
-//------------------------------------------------------------
-nsFilteredContentIterator::~nsFilteredContentIterator() {}
+FilteredContentIterator::~FilteredContentIterator() {}
 
-//------------------------------------------------------------
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFilteredContentIterator)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFilteredContentIterator)
+NS_IMPL_CYCLE_COLLECTION(FilteredContentIterator, mPostIterator, mPreIterator,
+                         mRange)
 
-NS_INTERFACE_MAP_BEGIN(nsFilteredContentIterator)
-  NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentIterator)
-  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsFilteredContentIterator)
-NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FilteredContentIterator, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FilteredContentIterator, Release)
 
-NS_IMPL_CYCLE_COLLECTION(nsFilteredContentIterator, mCurrentIterator, mIterator,
-                         mPreIterator, mRange)
-
-//------------------------------------------------------------
-nsresult nsFilteredContentIterator::Init(nsINode* aRoot) {
+nsresult FilteredContentIterator::Init(nsINode* aRoot) {
   NS_ENSURE_ARG_POINTER(aRoot);
-  NS_ENSURE_TRUE(mPreIterator, NS_ERROR_FAILURE);
-  NS_ENSURE_TRUE(mIterator, NS_ERROR_FAILURE);
   mIsOutOfRange = false;
   mDirection = eForward;
-  mCurrentIterator = mPreIterator;
+  mCurrentIterator = &mPreIterator;
 
   mRange = new nsRange(aRoot);
   mRange->SelectNode(*aRoot, IgnoreErrors());
 
-  nsresult rv = mPreIterator->Init(mRange);
+  nsresult rv = mPreIterator.Init(mRange);
   NS_ENSURE_SUCCESS(rv, rv);
-  return mIterator->Init(mRange);
+  return mPostIterator.Init(mRange);
 }
 
-//------------------------------------------------------------
-nsresult nsFilteredContentIterator::Init(nsRange* aRange) {
+nsresult FilteredContentIterator::Init(nsRange* aRange) {
   if (NS_WARN_IF(!aRange)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (NS_WARN_IF(!aRange->IsPositioned())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mRange = aRange->CloneRange();
 
   return InitWithRange();
 }
 
-//------------------------------------------------------------
-nsresult nsFilteredContentIterator::Init(nsINode* aStartContainer,
-                                         uint32_t aStartOffset,
-                                         nsINode* aEndContainer,
-                                         uint32_t aEndOffset) {
+nsresult FilteredContentIterator::Init(nsINode* aStartContainer,
+                                       uint32_t aStartOffset,
+                                       nsINode* aEndContainer,
+                                       uint32_t aEndOffset) {
   return Init(RawRangeBoundary(aStartContainer, aStartOffset),
               RawRangeBoundary(aEndContainer, aEndOffset));
 }
 
-nsresult nsFilteredContentIterator::Init(const RawRangeBoundary& aStart,
-                                         const RawRangeBoundary& aEnd) {
+nsresult FilteredContentIterator::Init(const RawRangeBoundary& aStart,
+                                       const RawRangeBoundary& aEnd) {
   RefPtr<nsRange> range;
   nsresult rv = nsRange::CreateRange(aStart, aEnd, getter_AddRefs(range));
   if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!range) ||
       NS_WARN_IF(!range->IsPositioned())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   MOZ_ASSERT(range->StartRef() == aStart);
   MOZ_ASSERT(range->EndRef() == aEnd);
 
   mRange = std::move(range);
 
   return InitWithRange();
 }
 
-nsresult nsFilteredContentIterator::InitWithRange() {
+nsresult FilteredContentIterator::InitWithRange() {
   MOZ_ASSERT(mRange);
   MOZ_ASSERT(mRange->IsPositioned());
 
-  if (NS_WARN_IF(!mPreIterator) || NS_WARN_IF(!mIterator)) {
-    return NS_ERROR_FAILURE;
-  }
-
   mIsOutOfRange = false;
   mDirection = eForward;
-  mCurrentIterator = mPreIterator;
+  mCurrentIterator = &mPreIterator;
 
-  nsresult rv = mPreIterator->Init(mRange);
+  nsresult rv = mPreIterator.Init(mRange);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  return mIterator->Init(mRange);
+  return mPostIterator.Init(mRange);
 }
 
-//------------------------------------------------------------
-nsresult nsFilteredContentIterator::SwitchDirections(bool aChangeToForward) {
+nsresult FilteredContentIterator::SwitchDirections(bool aChangeToForward) {
   nsINode* node = mCurrentIterator->GetCurrentNode();
 
   if (aChangeToForward) {
-    mCurrentIterator = mPreIterator;
+    mCurrentIterator = &mPreIterator;
     mDirection = eForward;
   } else {
-    mCurrentIterator = mIterator;
+    mCurrentIterator = &mPostIterator;
     mDirection = eBackward;
   }
 
   if (node) {
     nsresult rv = mCurrentIterator->PositionAt(node);
     if (NS_FAILED(rv)) {
       mIsOutOfRange = true;
       return rv;
     }
   }
   return NS_OK;
 }
 
-//------------------------------------------------------------
-void nsFilteredContentIterator::First() {
+void FilteredContentIterator::First() {
   if (!mCurrentIterator) {
     NS_ERROR("Missing iterator!");
 
     return;
   }
 
   // If we are switching directions then
   // we need to switch how we process the nodes
   if (mDirection != eForward) {
-    mCurrentIterator = mPreIterator;
+    mCurrentIterator = &mPreIterator;
     mDirection = eForward;
     mIsOutOfRange = false;
   }
 
   mCurrentIterator->First();
 
   if (mCurrentIterator->IsDone()) {
     return;
   }
 
   nsINode* currentNode = mCurrentIterator->GetCurrentNode();
 
   bool didCross;
   CheckAdvNode(currentNode, didCross, eForward);
 }
 
-//------------------------------------------------------------
-void nsFilteredContentIterator::Last() {
+void FilteredContentIterator::Last() {
   if (!mCurrentIterator) {
     NS_ERROR("Missing iterator!");
 
     return;
   }
 
   // If we are switching directions then
   // we need to switch how we process the nodes
   if (mDirection != eBackward) {
-    mCurrentIterator = mIterator;
+    mCurrentIterator = &mPostIterator;
     mDirection = eBackward;
     mIsOutOfRange = false;
   }
 
   mCurrentIterator->Last();
 
   if (mCurrentIterator->IsDone()) {
     return;
@@ -255,21 +235,20 @@ static bool ContentIsInTraversalRange(ns
   NS_ENSURE_TRUE(aNextContent && aRange, false);
 
   return ContentIsInTraversalRange(
       aNextContent, aIsPreMode, aRange->GetStartContainer(),
       static_cast<int32_t>(aRange->StartOffset()), aRange->GetEndContainer(),
       static_cast<int32_t>(aRange->EndOffset()));
 }
 
-//------------------------------------------------------------
 // Helper function to advance to the next or previous node
-nsresult nsFilteredContentIterator::AdvanceNode(nsINode* aNode,
-                                                nsINode*& aNewNode,
-                                                eDirectionType aDir) {
+nsresult FilteredContentIterator::AdvanceNode(nsINode* aNode,
+                                              nsINode*& aNewNode,
+                                              eDirectionType aDir) {
   nsCOMPtr<nsIContent> nextNode;
   if (aDir == eForward) {
     nextNode = aNode->GetNextSibling();
   } else {
     nextNode = aNode->GetPreviousSibling();
   }
 
   if (nextNode) {
@@ -303,20 +282,19 @@ nsresult nsFilteredContentIterator::Adva