Merge inbound to mozilla-central. a=merge
authorNoemi Erli <nerli@mozilla.com>
Fri, 11 Jan 2019 23:54:33 +0200
changeset 453520 c1894cbb4e7150a3a4a16e5d0702e038e7bd466f
parent 453517 0ce024c9151142de96f1009cd2e5a7678281c443 (diff)
parent 453519 27845cbdcac6f18002fd0d46021ba963487c6659 (current diff)
child 453521 3aec75953c2888e37b511f0fdba10c9a3b26a272
child 453557 610a3472661a63f437f584c302739175aaa77db0
child 453615 1ddef6cf6c7ee86191872cac0782b9f6dc01c923
push id35358
push usernerli@mozilla.com
push dateFri, 11 Jan 2019 21:54:52 +0000
treeherdermozilla-central@c1894cbb4e71 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
c1894cbb4e71 / 66.0a1 / 20190111215452 / files
nightly linux64
c1894cbb4e71 / 66.0a1 / 20190111215452 / files
nightly mac
c1894cbb4e71 / 66.0a1 / 20190111215452 / files
nightly win32
c1894cbb4e71 / 66.0a1 / 20190111215452 / files
nightly win64
c1894cbb4e71 / 66.0a1 / 20190111215452 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/Scalars.yaml
--- 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/mediasink/AudioSink.cpp
+++ b/dom/media/mediasink/AudioSink.cpp
@@ -182,19 +182,17 @@ nsresult AudioSink::InitializeAudioStrea
   if (NS_FAILED(rv)) {
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
     return rv;
   }
 
   // Set playback params before calling Start() so they can take effect
   // as soon as the 1st DataCallback of the AudioStream fires.
-  if (aParams.mVolume) {
-    mAudioStream->SetVolume(*aParams.mVolume);
-  }
+  mAudioStream->SetVolume(aParams.mVolume);
   mAudioStream->SetPlaybackRate(aParams.mPlaybackRate);
   mAudioStream->SetPreservesPitch(aParams.mPreservesPitch);
   return mAudioStream->Start();
 }
 
 TimeUnit AudioSink::GetEndTime() const {
   int64_t written;
   {
--- a/dom/media/mediasink/AudioSinkWrapper.cpp
+++ b/dom/media/mediasink/AudioSinkWrapper.cpp
@@ -24,19 +24,17 @@ void AudioSinkWrapper::Shutdown() {
 const MediaSink::PlaybackParams& AudioSinkWrapper::GetPlaybackParams() const {
   AssertOwnerThread();
   return mParams;
 }
 
 void AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams) {
   AssertOwnerThread();
   if (mAudioSink) {
-    if (aParams.mVolume) {
-      mAudioSink->SetVolume(*aParams.mVolume);
-    }
+    mAudioSink->SetVolume(aParams.mVolume);
     mAudioSink->SetPlaybackRate(aParams.mPlaybackRate);
     mAudioSink->SetPreservesPitch(aParams.mPreservesPitch);
   }
   mParams = aParams;
 }
 
 RefPtr<MediaSink::EndedPromise> AudioSinkWrapper::OnEnded(TrackType aType) {
   AssertOwnerThread();
@@ -93,17 +91,17 @@ TimeUnit AudioSinkWrapper::GetPosition(T
 
 bool AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const {
   AssertOwnerThread();
   return mAudioSink ? mAudioSink->HasUnplayedFrames() : false;
 }
 
 void AudioSinkWrapper::SetVolume(double aVolume) {
   AssertOwnerThread();
-  mParams.mVolume = Some(aVolume);
+  mParams.mVolume = aVolume;
   if (mAudioSink) {
     mAudioSink->SetVolume(aVolume);
   }
 }
 
 void AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate) {
   AssertOwnerThread();
   if (!mAudioEnded) {
@@ -193,20 +191,16 @@ void AudioSinkWrapper::Stop() {
   AssertOwnerThread();
   MOZ_ASSERT(mIsStarted, "playback not started.");
 
   mIsStarted = false;
   mAudioEnded = true;
 
   if (mAudioSink) {
     mAudioSinkEndedPromise.DisconnectIfExists();
-    // Reset volume to signal that it should
-    // not be updated, in case the volume
-    // has been changed outside MediaElement.
-    mParams.mVolume.reset();
     mAudioSink->Shutdown();
     mAudioSink = nullptr;
     mEndedPromise = nullptr;
   }
 }
 
 bool AudioSinkWrapper::IsStarted() const {
   AssertOwnerThread();
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -474,17 +474,17 @@ void DecodedStream::SetPlaying(bool aPla
     return;
   }
 
   mPlaying = aPlaying;
 }
 
 void DecodedStream::SetVolume(double aVolume) {
   AssertOwnerThread();
-  mParams.mVolume = Some(aVolume);
+  mParams.mVolume = aVolume;
 }
 
 void DecodedStream::SetPlaybackRate(double aPlaybackRate) {
   AssertOwnerThread();
   mParams.mPlaybackRate = aPlaybackRate;
 }
 
 void DecodedStream::SetPreservesPitch(bool aPreservesPitch) {
@@ -725,18 +725,17 @@ void DecodedStream::SendData() {
   AssertOwnerThread();
   MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
 
   // Not yet created on the main thread. MDSM will try again later.
   if (!mData) {
     return;
   }
 
-  MOZ_ASSERT(mParams.mVolume.isSome(), "Volume should exist at that point");
-  SendAudio(mParams.mVolume.value(), mSameOrigin, mPrincipalHandle);
+  SendAudio(mParams.mVolume, mSameOrigin, mPrincipalHandle);
   SendVideo(mSameOrigin, mPrincipalHandle);
 }
 
 TimeUnit DecodedStream::GetEndTime(TrackType aType) const {
   AssertOwnerThread();
   if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
     auto t = mStartTime.ref() +
              FramesToTimeUnit(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
--- a/dom/media/mediasink/MediaSink.h
+++ b/dom/media/mediasink/MediaSink.h
@@ -34,18 +34,18 @@ class TimeStamp;
  */
 class MediaSink {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSink);
   typedef mozilla::TrackInfo::TrackType TrackType;
 
   struct PlaybackParams {
     PlaybackParams()
-        : mVolume(Some(1.0)), mPlaybackRate(1.0), mPreservesPitch(true) {}
-    Maybe<double> mVolume;
+        : mVolume(1.0), mPlaybackRate(1.0), mPreservesPitch(true) {}
+    double mVolume;
     double mPlaybackRate;
     bool mPreservesPitch;
     RefPtr<AudioDeviceInfo> mSink;
   };
 
   // Return the playback parameters of this sink.
   // Can be called in any state.
   virtual const PlaybackParams& GetPlaybackParams() const = 0;
--- 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()))