Merge autoland to mozilla-central. a=merge
authorNorisz Fay <nfay@mozilla.com>
Thu, 21 Oct 2021 12:35:33 +0300
changeset 596543 f12b7ea34395623b0480b78451e5a0fb6eeb7707
parent 596462 54672b7d3f45ce1c72f9fb48c0f6d8594a3c6bdb (current diff)
parent 596542 d822837fdcba33ff70def737ef49e7ccfec0861d (diff)
child 596555 632c998505e6520ffd5b67688207c8b648c5db61
push id38900
push usernfay@mozilla.com
push dateThu, 21 Oct 2021 09:36:31 +0000
treeherdermozilla-central@f12b7ea34395 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone95.0a1
first release with
nightly linux32
f12b7ea34395 / 95.0a1 / 20211021093631 / files
nightly linux64
f12b7ea34395 / 95.0a1 / 20211021093631 / files
nightly mac
f12b7ea34395 / 95.0a1 / 20211021093631 / files
nightly win32
f12b7ea34395 / 95.0a1 / 20211021093631 / files
nightly win64
f12b7ea34395 / 95.0a1 / 20211021093631 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
layout/base/nsPresContext.cpp
--- a/accessible/generic/LocalAccessible.cpp
+++ b/accessible/generic/LocalAccessible.cpp
@@ -641,22 +641,50 @@ nsRect LocalAccessible::ParentRelativeBo
             if (context->GetHitRegionRect(mContent->AsElement(), bounds)) {
               return bounds;
             }
           }
         }
       }
     }
 
-    if (mParent) {
-      boundingFrame = mParent->GetFrame();
+    // We need to find a frame to make our bounds relative to. We'll store this
+    // in `boundingFrame`. Ultimately, we'll create screen-relative coordinates
+    // by summing the x, y offsets of our ancestors' bounds in
+    // RemoteAccessibleBase::Bounds(), so it is important that our bounding
+    // frame have a corresponding accessible.
+    if (IsDoc() &&
+        nsCoreUtils::IsTopLevelContentDocInProcess(AsDoc()->DocumentNode())) {
+      // Tab documents and OOP iframe docs won't have ancestor accessibles with
+      // frames. We'll use their presshell root frame instead.
+      // XXX bug 1736635: Should DocAccessibles return their presShell frame on
+      // GetFrame()?
+      boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
+    }
+
+    // Iterate through ancestors to find one with a frame.
+    LocalAccessible* parent = mParent;
+    while (parent && !boundingFrame) {
+      if (parent->IsDoc()) {
+        // If we find a doc accessible, use its presshell's root frame
+        // (since doc accessibles themselves don't have frames).
+        boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
+        break;
+      }
+
+      if ((boundingFrame = parent->GetFrame())) {
+        // Otherwise, if the parent has a frame, use that
+        break;
+      }
+
+      parent = parent->LocalParent();
     }
 
     if (!boundingFrame) {
-      // if we can't get the bounding frame, use the pres shell root
+      MOZ_ASSERT_UNREACHABLE("No ancestor with frame?");
       boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
     }
 
     nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
         frame, boundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
 
     if (unionRect.IsEmpty()) {
       // If we end up with a 0x0 rect from above (or one with negative
--- a/accessible/tests/browser/bounds/browser.ini
+++ b/accessible/tests/browser/bounds/browser.ini
@@ -9,8 +9,9 @@ support-files =
 [browser_test_resolution.js]
 skip-if = e10s && os == 'win' # bug 1372296
 [browser_test_zoom.js]
 skip-if = webrender # Bug 1734271
 [browser_test_zoom_text.js]
 https_first_disabled = true
 skip-if = e10s && os == 'win' # bug 1372296
 [browser_zero_area.js]
+[browser_test_display_contents.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_display_contents.js
@@ -0,0 +1,48 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function testContentBounds(browser, acc) {
+  let [
+    expectedX,
+    expectedY,
+    expectedWidth,
+    expectedHeight,
+  ] = await getContentBoundsForDOMElm(browser, getAccessibleDOMNodeID(acc));
+
+  let contentDPR = await getContentDPR(browser);
+  let [x, y, width, height] = getBounds(acc, contentDPR);
+  let prettyAccName = prettyName(acc);
+  is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
+  is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
+  is(width, expectedWidth, "Wrong width of " + prettyAccName);
+  ok(height >= expectedHeight, "Wrong height of " + prettyAccName);
+}
+
+async function runTests(browser, accDoc) {
+  let p = findAccessibleChildByID(accDoc, "div");
+  let p2 = findAccessibleChildByID(accDoc, "p");
+
+  await testContentBounds(browser, p);
+  await testContentBounds(browser, p2);
+}
+
+/**
+ * Test accessible bounds for accs with display:contents
+ */
+addAccessibleTask(
+  `
+  <div id="div">before
+    <ul id="ul" style="display: contents;">
+      <li id="li" style="display: contents;">
+        <p id="p">item</p>
+      </li>
+    </ul>
+  </div>`,
+  runTests,
+  { iframe: true, remoteIframe: true }
+);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -676,16 +676,50 @@ pref("browser.tabs.tooltipsShowPidAndAct
 pref("security.allow_eval_with_system_principal", false);
 pref("security.allow_eval_in_parent_process", false);
 
 pref("security.allow_parent_unrestricted_js_loads", false);
 
 // Unload tabs when available memory is running low
 pref("browser.tabs.unloadOnLowMemory", true);
 
+#if defined(XP_MACOSX)
+  // During low memory periods, poll with this frequency (milliseconds)
+  // until memory is no longer low. Changes to the pref take effect immediately.
+  // Browser restart not required. Chosen to be consistent with the windows
+  // implementation, but otherwise the 10s value is arbitrary.
+  pref("browser.lowMemoryPollingIntervalMS", 10000);
+
+  // Pref to control the reponse taken on macOS when the OS is under memory
+  // pressure. Changes to the pref take effect immediately. Browser restart not
+  // required. The pref value is a bitmask:
+  // 0x0: No response (other than recording for telemetry, crash reporting)
+  // 0x1: Use the tab unloading feature to reduce memory use. Requires that
+  //      the above "browser.tabs.unloadOnLowMemory" pref be set to true for tab
+  //      unloading to occur.
+  // 0x2: Issue the internal "memory-pressure" notification to reduce memory use
+  // 0x3: Both 0x1 and 0x2.
+  #if defined(NIGHTLY_BUILD)
+  pref("browser.lowMemoryResponseMask", 3);
+  #else
+  pref("browser.lowMemoryResponseMask", 0);
+  #endif
+
+  // Controls which macOS memory-pressure level triggers the browser low memory
+  // response. Changes to the pref take effect immediately. Browser restart not
+  // required. By default, use the "critical" level as that occurs after "warn"
+  // and we only want to trigger the low memory reponse when necessary.
+  // The macOS system memory-pressure level is either none, "warn", or
+  // "critical". The OS notifies the browser when the level changes. A false
+  // value for the pref indicates the low memory response should occur when
+  // reaching the "critical" level. A true value indicates the response should
+  // occur when reaching the "warn" level.
+  pref("browser.lowMemoryResponseOnWarn", false);
+#endif
+
 pref("browser.ctrlTab.sortByRecentlyUsed", false);
 
 // By default, do not export HTML at shutdown.
 // If true, at shutdown the bookmarks in your menu and toolbar will
 // be exported as HTML to the bookmarks.html file.
 pref("browser.bookmarks.autoExportHTML",          false);
 
 // The maximum number of daily bookmark backups to
--- a/browser/base/content/browser-fullScreenAndPointerLock.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -370,27 +370,22 @@ var FullScreen = {
       Cu.reportError("Tried to shift the toolbar by a non-numeric distance.");
       return;
     }
 
     // shiftSize is sent from Cocoa widget code as a very precise double. We
     // don't need that kind of precision in our CSS.
     shiftSize = shiftSize.toFixed(2);
     let toolbox = document.getElementById("navigator-toolbox");
-    let browserEl = document.getElementById("browser");
     if (shiftSize > 0) {
       toolbox.style.setProperty("transform", `translateY(${shiftSize}px)`);
       toolbox.style.setProperty("z-index", "2");
-      toolbox.style.setProperty("position", "relative");
-      browserEl.style.setProperty("position", "relative");
     } else {
       toolbox.style.removeProperty("transform");
       toolbox.style.removeProperty("z-index");
-      toolbox.style.removeProperty("position");
-      browserEl.style.removeProperty("position");
     }
   },
 
   handleEvent(event) {
     switch (event.type) {
       case "willenterfullscreen":
         this.willToggle(true);
         break;
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -231,17 +231,17 @@ panelview[mainview] > .panel-header {
 @supports -moz-bool-pref("browser.tabs.hideThrobber") {
   .tab-throbber {
     display: none !important;
   }
 }
 %endif
 
 #tabbrowser-tabs[positionpinnedtabs] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[pinned] {
-  position: fixed !important;
+  position: absolute !important;
   display: block;
 }
 
 #tabbrowser-tabs[movingtab] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[selected],
 #tabbrowser-tabs[movingtab] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[multiselected] {
   position: relative;
   z-index: 2;
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -4098,16 +4098,17 @@ BrowserGlue.prototype = {
     win.document.l10n.setAttributes(
       message,
       "restore-session-startup-suggestion-message"
     );
 
     const buttons = [
       {
         "l10n-id": "restore-session-startup-suggestion-button",
+        primary: true,
         callback: () => {
           win.PanelUI.selectAndMarkItem([
             "appMenu-history-button",
             "appMenu-restoreSession",
           ]);
         },
       },
     ];
--- a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
+++ b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
@@ -292,16 +292,20 @@
 
     .title {
       visibility: hidden;
     }
   }
 }
 
 .edit-topsites-wrapper {
+  .top-site-inner > .top-site-button > .tile {
+    border: 1px solid var(--newtab-border-color);
+  }
+
   .modal {
     box-shadow: $shadow-secondary;
     left: 0;
     margin: 0 auto;
     max-height: calc(100% - 40px);
     position: fixed;
     right: 0;
     top: 40px;
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -755,16 +755,19 @@ main.has-snippet {
 }
 .top-site-outer.dragged .tile *, .top-site-outer.dragged .tile::before {
   display: none;
 }
 .top-site-outer.dragged .title {
   visibility: hidden;
 }
 
+.edit-topsites-wrapper .top-site-inner > .top-site-button > .tile {
+  border: 1px solid var(--newtab-border-color);
+}
 .edit-topsites-wrapper .modal {
   box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
   left: 0;
   margin: 0 auto;
   max-height: calc(100% - 40px);
   position: fixed;
   right: 0;
   top: 40px;
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -759,16 +759,19 @@ main.has-snippet {
 }
 .top-site-outer.dragged .tile *, .top-site-outer.dragged .tile::before {
   display: none;
 }
 .top-site-outer.dragged .title {
   visibility: hidden;
 }
 
+.edit-topsites-wrapper .top-site-inner > .top-site-button > .tile {
+  border: 1px solid var(--newtab-border-color);
+}
 .edit-topsites-wrapper .modal {
   box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
   left: 0;
   margin: 0 auto;
   max-height: calc(100% - 40px);
   position: fixed;
   right: 0;
   top: 40px;
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -755,16 +755,19 @@ main.has-snippet {
 }
 .top-site-outer.dragged .tile *, .top-site-outer.dragged .tile::before {
   display: none;
 }
 .top-site-outer.dragged .title {
   visibility: hidden;
 }
 
+.edit-topsites-wrapper .top-site-inner > .top-site-button > .tile {
+  border: 1px solid var(--newtab-border-color);
+}
 .edit-topsites-wrapper .modal {
   box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
   left: 0;
   margin: 0 auto;
   max-height: calc(100% - 40px);
   position: fixed;
   right: 0;
   top: 40px;
--- a/browser/components/search/BrowserSearchTelemetry.jsm
+++ b/browser/components/search/BrowserSearchTelemetry.jsm
@@ -29,16 +29,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
 // browser.search.adclicks.*
 const KNOWN_SEARCH_SOURCES = new Map([
   ["abouthome", "about_home"],
   ["contextmenu", "contextmenu"],
   ["newtab", "about_newtab"],
   ["searchbar", "searchbar"],
   ["system", "system"],
   ["urlbar", "urlbar"],
+  ["urlbar-handoff", "urlbar_handoff"],
   ["urlbar-searchmode", "urlbar_searchmode"],
   ["webextension", "webextension"],
 ]);
 
 /**
  * This class handles saving search telemetry related to the url bar,
  * search bar and other areas as per the sources above.
  */
@@ -199,16 +200,17 @@ class BrowserSearchTelemetryHandler {
         histogram.add(countIdSource);
       }
 
       // Dispatch the search signal to other handlers.
       switch (source) {
         case "urlbar":
         case "searchbar":
         case "urlbar-searchmode":
+        case "urlbar-handoff":
           this._handleSearchAndUrlbar(browser, engine, source, details);
           break;
         case "abouthome":
         case "newtab":
           this._recordSearch(browser, engine, details.url, source, "enter");
           break;
         default:
           this._recordSearch(browser, engine, details.url, source);
--- a/browser/components/search/docs/telemetry.rst
+++ b/browser/components/search/docs/telemetry.rst
@@ -46,27 +46,29 @@ SEARCH_COUNTS - SAP usage
       keywords refer to bookmarks. We expect no results for this SAP in Firefox
       83+, since urlbar-searchmode replaces it.
     - ``abouthome``
     - ``contextmenu``
     - ``newtab``
     - ``searchbar``
     - ``system``
     - ``urlbar`` Except aliases and search mode.
+    - ``urlbar_handoff`` Used when searching from about:newtab.
     - ``urlbar-searchmode`` Used when the Urlbar is in search mode.
     - ``webextension``
 
 browser.engagement.navigation.*
   These keyed scalars track search through different SAPs, for example the
   urlbar is tracked by ``browser.engagement.navigation.urlbar``.
   It counts loads triggered in a subsession from the specified SAP, broken down
   by the originating action.
   Possible SAPs are:
 
     - ``urlbar``  Except search mode.
+    - ``urlbar_handoff`` Used when searching from about:newtab.
     - ``urlbar_searchmode``  Used when the Urlbar is in search mode.
     - ``searchbar``
     - ``about_home``
     - ``about_newtab``
     - ``contextmenu``
     - ``webextension``
     - ``system`` Indicates a search from the command line.
 
@@ -110,16 +112,17 @@ browser.search.content.*
   These keyed scalar track counts of SERP page loads. The key format is
   ``<provider>:[tagged|tagged-follow-on|organic]:[<code>|none]``.
 
   These will eventually replace the SEARCH_COUNTS - SERP results.
 
   They are broken down by the originating SAP where known:
 
   - ``urlbar``  Except search mode.
+  - ``urlbar_handoff`` Used when searching from about:newtab.
   - ``urlbar_searchmode``  Used when the Urlbar is in search mode.
   - ``searchbar``
   - ``about_home``
   - ``about_newtab``
   - ``contextmenu``
   - ``webextension``
   - ``system`` Indicates a search from the command line.
   - ``tabhistory`` Indicates a search was counted as a result of the user loading it from the tab history.
--- a/browser/components/search/test/browser/browser_search_telemetry_sources.js
+++ b/browser/components/search/test/browser/browser_search_telemetry_sources.js
@@ -67,17 +67,25 @@ async function waitForIdle() {
 
 SearchTestUtils.init(this);
 UrlbarTestUtils.init(this);
 
 add_task(async function setup() {
   SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO);
   await waitForIdle();
   await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.suggest.searches", true]],
+    set: [
+      ["browser.urlbar.suggest.searches", true],
+      [
+        "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+        true,
+      ],
+      // Ensure to add search suggestion telemetry as search_suggestion not search_formhistory.
+      ["browser.urlbar.maxHistoricalSearchSuggestions", 0],
+    ],
   });
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
   Services.prefs.setBoolPref("browser.search.log", true);
 
   let currentEngineName = (await Services.search.getDefault()).name;
 
@@ -185,16 +193,60 @@ add_task(async function test_source_urlb
       return tab;
     },
     async () => {
       BrowserTestUtils.removeTab(tab);
     }
   );
 });
 
+add_task(async function test_source_urlbar_handoff() {
+  let tab;
+  await track_ad_click(
+    "urlbar-handoff",
+    "urlbar_handoff",
+    async () => {
+      tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+      BrowserTestUtils.loadURI(tab.linkedBrowser, "about:newtab");
+      await BrowserTestUtils.browserStopped(tab.linkedBrowser, "about:newtab");
+
+      info("Focus on search input in newtab content");
+      await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
+        const searchInput = content.document.querySelector(".fake-editable");
+        searchInput.click();
+      });
+
+      info("Get suggestions");
+      for (const c of "searchSuggestion".split("")) {
+        EventUtils.synthesizeKey(c);
+        /* eslint-disable mozilla/no-arbitrary-setTimeout */
+        await new Promise(r => setTimeout(r, 50));
+      }
+      await TestUtils.waitForCondition(async () => {
+        const index = await getFirstSuggestionIndex();
+        return index >= 0;
+      }, "Wait until suggestions are ready");
+
+      let idx = await getFirstSuggestionIndex();
+      Assert.greaterOrEqual(idx, 0, "there should be a first suggestion");
+      const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+      while (idx--) {
+        EventUtils.sendKey("down");
+      }
+      EventUtils.sendKey("return");
+      await onLoaded;
+
+      return tab;
+    },
+    async () => {
+      BrowserTestUtils.removeTab(tab);
+    }
+  );
+});
+
 add_task(async function test_source_searchbar() {
   let tab;
   await track_ad_click(
     "searchbar",
     "searchbar",
     async () => {
       tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -4203,16 +4203,23 @@ var SessionStoreInternal = {
       );
     }
     // Because newClosedTabsData are put in first, we need to
     // copy also the _lastClosedTabGroupCount.
     this._windows[
       aWindow.__SSi
     ]._lastClosedTabGroupCount = newLastClosedTabGroupCount;
 
+    if (!this._isWindowLoaded(aWindow)) {
+      // from now on, the data will come from the actual window
+      delete this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)];
+      WINDOW_RESTORE_IDS.delete(aWindow);
+      delete this._windows[aWindow.__SSi]._restoring;
+    }
+
     // Restore tabs, if any.
     if (winData.tabs.length) {
       this.restoreTabs(aWindow, tabs, winData.tabs, selectTab);
     }
 
     // set smoothScroll back to the original value
     arrowScrollbox.smoothScroll = smoothScroll;
 
@@ -4397,23 +4404,16 @@ var SessionStoreInternal = {
    * @param aSelectTab
    *        Index of the tab to select. This is a 1-based index where "1"
    *        indicates the first tab should be selected, and "0" indicates that
    *        the currently selected tab will not be changed.
    */
   restoreTabs(aWindow, aTabs, aTabData, aSelectTab) {
     var tabbrowser = aWindow.gBrowser;
 
-    if (!this._isWindowLoaded(aWindow)) {
-      // from now on, the data will come from the actual window
-      delete this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)];
-      WINDOW_RESTORE_IDS.delete(aWindow);
-      delete this._windows[aWindow.__SSi]._restoring;
-    }
-
     let numTabsToRestore = aTabs.length;
     let numTabsInWindow = tabbrowser.tabs.length;
     let tabsDataArray = this._windows[aWindow.__SSi].tabs;
 
     // Update the window state in case we shut down without being notified.
     // Individual tab states will be taken care of by restoreTab() below.
     if (numTabsInWindow == numTabsToRestore) {
       // Remove all previous tab data.
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -96,16 +96,17 @@ https_first_disabled = true
 https_first_disabled = true
 skip-if =
   !e10s || !crashreporter || verify
 [browser_formdata_max_size.js]
 [browser_multiple_select_after_load.js]
 [browser_pinned_tabs.js]
 skip-if = debug || ccov # Bug 1625525
 [browser_restore_srcdoc.js]
+[browser_restore_tabless_window.js]
 [browser_unrestored_crashedTabs.js]
 skip-if =
   !e10s || !crashreporter
 [browser_revive_crashed_bg_tabs.js]
 https_first_disabled = true
 skip-if =
   !e10s || !crashreporter
 [browser_dying_cache.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_restore_tabless_window.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * It's possible for us to restore windows without tabs,
+ * when on Windows/Linux the user closes the last tab as
+ * a means of closing the last window. That last tab
+ * would appear as a closed tab in session state for the
+ * window, with no open tabs (so the state would be resumed
+ * as showing the homepage). See bug 490136 for context.
+ * This test checks that in this case, the resulting window
+ * is functional and indeed has the required previously
+ * closed tabs available.
+ */
+add_task(async function test_restoring_tabless_window() {
+  let newWin = await BrowserTestUtils.openNewBrowserWindow();
+  let newState = {
+    windows: [
+      {
+        tabs: [],
+        _closedTabs: [
+          {
+            state: { entries: [{ url: "about:" }] },
+            title: "About:",
+          },
+        ],
+      },
+    ],
+  };
+
+  await setWindowState(newWin, newState, true);
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    newWin.gBrowser,
+    "https://example.com"
+  );
+  await TabStateFlusher.flush(tab.linkedBrowser);
+  let receivedState = SessionStore.getWindowState(newWin);
+  let { tabs, selected } = receivedState.windows[0];
+  is(tabs.length, 2, "Should have two tabs");
+  is(selected, 2, "Should have selected the new tab");
+  ok(
+    tabs[1]?.entries.some(e => e.url == "https://example.com/"),
+    "Should have found the new URL"
+  );
+
+  let closedTabData = SessionStore.getClosedTabData(newWin);
+  is(closedTabData.length, 1, "Should have found 1 closed tab");
+  is(
+    closedTabData[0]?.state.entries[0].url,
+    "about:",
+    "Should have found the right URL for the closed tab"
+  );
+  await BrowserTestUtils.closeWindow(newWin);
+});
--- a/browser/components/tabunloader/content/aboutUnloads.js
+++ b/browser/components/tabunloader/content/aboutUnloads.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { TabUnloader } = ChromeUtils.import(
   "resource:///modules/TabUnloader.jsm"
 );
 
 async function refreshData() {
-  const sortedTabs = await TabUnloader.getSortedTabs();
+  const sortedTabs = await TabUnloader.getSortedTabs(null);
   const tabTable = document.querySelector(".tab-table > tbody");
   const getHost = uri => {
     try {
       return uri?.host;
     } catch (e) {
       return uri?.spec;
     }
   };
@@ -109,17 +109,17 @@ async function refreshData() {
   tabTable.appendChild(fragmentRows);
   updateTimestamp();
 }
 
 async function onLoad() {
   document
     .getElementById("button-unload")
     .addEventListener("click", async () => {
-      await TabUnloader.unloadLeastRecentlyUsedTab();
+      await TabUnloader.unloadLeastRecentlyUsedTab(null);
       await refreshData();
     });
   await refreshData();
 }
 
 try {
   document.addEventListener("DOMContentLoaded", onLoad, { once: true });
 } catch (ex) {
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -679,16 +679,17 @@ class UrlbarInput {
    *
    * @param {string} searchString
    * @param {nsISearchEngine} [searchEngine]
    *   Optional. If included and the right prefs are set, we will enter search
    *   mode when handing `searchString` from the fake input to the Urlbar.
    *
    */
   handoff(searchString, searchEngine) {
+    this._isHandoffSession = true;
     if (UrlbarPrefs.get("shouldHandOffToSearchMode") && searchEngine) {
       this.search(searchString, {
         searchEngine,
         searchModeEntry: "handoff",
       });
     } else {
       this.search(searchString);
     }
@@ -2227,25 +2228,32 @@ class UrlbarInput {
    * @param {boolean} searchActionDetails.isFormHistory
    *   True if this query was initiated from a form history result.
    * @param {string} searchActionDetails.url
    *   The url this query was triggered with.
    */
   _recordSearch(engine, event, searchActionDetails = {}) {
     const isOneOff = this.view.oneOffSearchButtons.eventTargetIsAOneOff(event);
 
-    BrowserSearchTelemetry.recordSearch(
-      this.window.gBrowser.selectedBrowser,
-      engine,
+    let source = "urlbar";
+    if (this._isHandoffSession) {
+      source = "urlbar-handoff";
+    } else if (this.searchMode && !isOneOff) {
       // Without checking !isOneOff, we might record the string
       // oneoff_urlbar-searchmode in the SEARCH_COUNTS probe (in addition to
       // oneoff_urlbar and oneoff_searchbar). The extra information is not
       // necessary; the intent is the same regardless of whether the user is
       // in search mode when they do a key-modified click/enter on a one-off.
-      this.searchMode && !isOneOff ? "urlbar-searchmode" : "urlbar",
+      source = "urlbar-searchmode";
+    }
+
+    BrowserSearchTelemetry.recordSearch(
+      this.window.gBrowser.selectedBrowser,
+      engine,
+      source,
       { ...searchActionDetails, isOneOff }
     );
   }
 
   /**
    * Shortens the given value, usually by removing http:// and trailing slashes.
    *
    * @param {string} val
@@ -2728,16 +2736,18 @@ class UrlbarInput {
       this.searchMode?.entry != "oneoff"
     ) {
       this.controller.engagementEvent.discard();
     }
   }
 
   _on_blur(event) {
     this.focusedViaMousedown = false;
+    this._isHandoffSession = false;
+
     // We cannot count every blur events after a missed engagement as abandoment
     // because the user may have clicked on some view element that executes
     // a command causing a focus change. For example opening preferences from
     // the oneoff settings button.
     // For now we detect that case by discarding the event on command, but we
     // may want to figure out a more robust way to detect abandonment.
     this.controller.engagementEvent.record(event, {
       searchString: this._lastSearchString,
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -323,16 +323,18 @@ support-files =
 [browser_urlbar_event_telemetry_noEvent.js]
 [browser_urlbar_selection.js]
 skip-if = (os == 'mac') # bug 1570474
 [browser_urlbar_telemetry_dynamic.js]
 support-files =
   urlbarTelemetryUrlbarDynamic.css
 [browser_urlbar_telemetry_extension.js]
 tags = search-telemetry
+[browser_urlbar_telemetry_handoff.js]
+tags = search-telemetry
 [browser_urlbar_telemetry_places.js]
 https_first_disabled = true
 tags = search-telemetry
 [browser_urlbar_telemetry_quicksuggest.js]
 tags = search-telemetry
 [browser_urlbar_telemetry_remotetab.js]
 tags = search-telemetry
 [browser_urlbar_telemetry_searchmode.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_handoff.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SearchSERPTelemetry } = ChromeUtils.import(
+  "resource:///modules/SearchSERPTelemetry.jsm"
+);
+
+const TEST_PROVIDER_INFO = [
+  {
+    telemetryId: "example",
+    searchPageRegexp: /^https:\/\/example.com\/browser\/browser\/components\/search\/test\/browser\/searchTelemetry(?:Ad)?.html/,
+    queryParamName: "s",
+    codeParamName: "abc",
+    codePrefixes: ["ff"],
+    followOnParamNames: ["a"],
+    extraAdServersRegexps: [/^https:\/\/example\.com\/ad2?/],
+  },
+];
+
+function getPageUrl(useAdPage = false) {
+  let page = useAdPage ? "searchTelemetryAd.html" : "searchTelemetry.html";
+  return `https://example.com/browser/browser/components/search/test/browser/${page}`;
+}
+
+// sharedData messages are only passed to the child on idle. Therefore
+// we wait for a few idles to try and ensure the messages have been able
+// to be passed across and handled.
+async function waitForIdle() {
+  for (let i = 0; i < 10; i++) {
+    await new Promise(resolve => Services.tm.idleDispatchToMainThread(resolve));
+  }
+}
+
+add_task(async function setup() {
+  SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO);
+  await waitForIdle();
+
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [
+        "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+        true,
+      ],
+    ],
+  });
+
+  await SearchTestUtils.installSearchExtension({
+    search_url: getPageUrl(true),
+    search_url_get_params: "s={searchTerms}&abc=ff",
+    suggest_url:
+      "https://example.com/browser/browser/components/search/test/browser/searchSuggestionEngine.sjs",
+    suggest_url_get_params: "query={searchTerms}",
+  });
+  const testEngine = Services.search.getEngineByName("Example");
+  const originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(testEngine);
+
+  const oldCanRecord = Services.telemetry.canRecordExtended;
+  Services.telemetry.canRecordExtended = true;
+  Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+  registerCleanupFunction(async function() {
+    await Services.search.setDefault(originalEngine);
+    await PlacesUtils.history.clear();
+
+    Services.telemetry.canRecordExtended = oldCanRecord;
+    Services.telemetry.setEventRecordingEnabled("navigation", false);
+
+    SearchSERPTelemetry.overrideSearchTelemetryForTests();
+
+    Services.telemetry.clearScalars();
+    Services.telemetry.clearEvents();
+  });
+});
+
+add_task(async function test_search() {
+  Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
+
+  const histogram = TelemetryTestUtils.getAndClearKeyedHistogram(
+    "SEARCH_COUNTS"
+  );
+
+  info("Load about:newtab in new window");
+  const newtab = "about:newtab";
+  const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  BrowserTestUtils.loadURI(tab.linkedBrowser, newtab);
+  await BrowserTestUtils.browserStopped(tab.linkedBrowser, newtab);
+
+  info("Focus on search input in newtab content");
+  await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
+    const searchInput = content.document.querySelector(".fake-editable");
+    searchInput.click();
+  });
+
+  info("Search and wait the result");
+  const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  EventUtils.synthesizeKey("q");
+  EventUtils.synthesizeKey("VK_RETURN");
+  await onLoaded;
+
+  info("Check the telemetries");
+  await assertScalars([
+    ["browser.engagement.navigation.urlbar_handoff", "search_enter", 1],
+  ]);
+  await assertHistogram(histogram, [
+    ["example.in-content:sap:ff", 1],
+    ["other-Example.urlbar-handoff", 1],
+  ]);
+  TelemetryTestUtils.assertEvents(
+    [
+      [
+        "navigation",
+        "search",
+        "urlbar_handoff",
+        "enter",
+        { engine: "other-Example" },
+      ],
+    ],
+    { category: "navigation", method: "search" }
+  );
+
+  BrowserTestUtils.removeTab(tab);
+});
+
+async function assertHistogram(histogram, expectedResults) {
+  await TestUtils.waitForCondition(() => {
+    const snapshot = histogram.snapshot();
+    return expectedResults.every(([key]) => key in snapshot);
+  }, "Wait until the histogram has expected keys");
+
+  for (const [key, value] of expectedResults) {
+    TelemetryTestUtils.assertKeyedHistogramSum(histogram, key, value);
+  }
+}
+
+async function assertScalars(expectedResults) {
+  await TestUtils.waitForCondition(() => {
+    const scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+    return expectedResults.every(([scalarName]) => scalarName in scalars);
+  }, "Wait until the scalars have expected keyed scalars");
+
+  const scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+  for (const [scalarName, key, value] of expectedResults) {
+    TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, key, value);
+  }
+}
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -508,16 +508,17 @@
   ${AddAssociationIfNoneExist} ".pdf" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".oga" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".ogg" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".ogv" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".pdf" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".webm" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".svg" "FirefoxHTML$5"
   ${AddAssociationIfNoneExist} ".webp"  "FirefoxHTML$5"
+  ${AddAssociationIfNoneExist} ".avif" "FirefoxHTML$5"
 
   ; An empty string is used for the 5th param because FirefoxHTML is not a
   ; protocol handler
   ${AddDisabledDDEHandlerValues} "FirefoxHTML$5" "$2" "$8,1" \
                                  "${AppRegName} HTML Document" ""
 
   ${AddDisabledDDEHandlerValues} "FirefoxURL$5" "$2" "$8,1" "${AppRegName} URL" \
                                  "true"
@@ -599,16 +600,17 @@
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".htm"   "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".html"  "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".pdf"   "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".shtml" "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xht"   "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xhtml" "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".svg"   "FirefoxHTML$2"
   WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".webp"  "FirefoxHTML$2"
+  WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".avif"  "FirefoxHTML$2"
 
   WriteRegStr ${RegKey} "$0\Capabilities\StartMenu" "StartMenuInternet" "$1"
 
   ; In the past, we supported ftp.  Since we don't delete and re-create the
   ; entire key, we need to remove any existing registration.
   DeleteRegValue ${RegKey} "$0\Capabilities\URLAssociations" "ftp"
 
   WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "http"   "FirefoxURL$2"
@@ -668,16 +670,17 @@
     ${WriteApplicationsSupportedType} ${RegKey} ".pjpeg"
     ${WriteApplicationsSupportedType} ${RegKey} ".pjp"
     ${WriteApplicationsSupportedType} ${RegKey} ".png"
     ${WriteApplicationsSupportedType} ${RegKey} ".rdf"
     ${WriteApplicationsSupportedType} ${RegKey} ".shtml"
     ${WriteApplicationsSupportedType} ${RegKey} ".svg"
     ${WriteApplicationsSupportedType} ${RegKey} ".webm"
     ${WriteApplicationsSupportedType} ${RegKey} ".webp"
+    ${WriteApplicationsSupportedType} ${RegKey} ".avif"
     ${WriteApplicationsSupportedType} ${RegKey} ".xht"
     ${WriteApplicationsSupportedType} ${RegKey} ".xhtml"
     ${WriteApplicationsSupportedType} ${RegKey} ".xml"
   ${EndIf}
 !macroend
 !define SetStartMenuInternet "!insertmacro SetStartMenuInternet"
 
 ; Add registry keys to support the Firefox 32 bit to 64 bit migration. These
@@ -1617,16 +1620,18 @@ Function SetAsDefaultAppUserHKCU
     AppAssocReg::SetAppAsDefault "$R9" ".htm" "file"
     Pop $0
     AppAssocReg::SetAppAsDefault "$R9" ".html" "file"
     Pop $0
     AppAssocReg::SetAppAsDefault "$R9" ".shtml" "file"
     Pop $0
     AppAssocReg::SetAppAsDefault "$R9" ".webp" "file"
     Pop $0
+    AppAssocReg::SetAppAsDefault "$R9" ".avif" "file"
+    Pop $0
     AppAssocReg::SetAppAsDefault "$R9" ".xht" "file"
     Pop $0
     AppAssocReg::SetAppAsDefault "$R9" ".xhtml" "file"
     Pop $0
     AppAssocReg::SetAppAsDefault "$R9" "http" "protocol"
     Pop $0
     AppAssocReg::SetAppAsDefault "$R9" "https" "protocol"
     Pop $0
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -491,16 +491,17 @@ Section "Uninstall"
   ${un.RegCleanFileHandler}  ".xhtml" "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".oga"   "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".ogg"   "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".ogv"   "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".pdf"   "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".webm"  "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".svg"   "FirefoxHTML-$AppUserModelID"
   ${un.RegCleanFileHandler}  ".webp"  "FirefoxHTML-$AppUserModelID"
+  ${un.RegCleanFileHandler}  ".avif"  "FirefoxHTML-$AppUserModelID"
 
   SetShellVarContext all  ; Set SHCTX to HKLM
   ${un.GetSecondInstallPath} "Software\Mozilla" $R9
   ${If} $R9 == "false"
     SetShellVarContext current  ; Set SHCTX to HKCU
     ${un.GetSecondInstallPath} "Software\Mozilla" $R9
   ${EndIf}
 
--- a/browser/modules/TabUnloader.jsm
+++ b/browser/modules/TabUnloader.jsm
@@ -24,16 +24,20 @@ ChromeUtils.defineModuleGetter(
 // If there are only this many or fewer tabs open, just sort by weight, and close
 // the lowest tab. Otherwise, do a more intensive compuation that determines the
 // tabs to close based on memory and process use.
 const MIN_TABS_COUNT = 10;
 
 // Weight for non-discardable tabs.
 const NEVER_DISCARD = 100000;
 
+// Default minimum inactive duration.  Tabs that were accessed in the last
+// period of this duration are not unloaded.
+const kMinInactiveDurationInMs = 600000; // ten minutes
+
 let criteriaTypes = [
   ["isNonDiscardable", NEVER_DISCARD],
   ["isLoading", 8],
   ["usingPictureInPicture", NEVER_DISCARD],
   ["playingMedia", NEVER_DISCARD],
   ["usingWebRTC", NEVER_DISCARD],
   ["isPinned", 2],
 ];
@@ -77,16 +81,20 @@ let DefaultTabUnloaderMethods = {
   usingWebRTC(tab, weight) {
     return webrtcUI.browserHasStreams(tab.linkedBrowser) ? weight : 0;
   },
 
   getMinTabCount() {
     return MIN_TABS_COUNT;
   },
 
+  getNow() {
+    return Date.now();
+  },
+
   *iterateTabs() {
     for (let win of Services.wm.getEnumerator("navigator:browser")) {
       for (let tab of win.gBrowser.tabs) {
         yield { tab, gBrowser: win.gBrowser };
       }
     }
   },
 
@@ -150,17 +158,17 @@ var TabUnloader = {
   isDiscardable(tab) {
     if (!("weight" in tab)) {
       return false;
     }
     return tab.weight < NEVER_DISCARD;
   },
 
   // This method is exposed on nsITabUnloader
-  async unloadTabAsync() {
+  async unloadTabAsync(minInactiveDuration = kMinInactiveDurationInMs) {
     const watcher = Cc["@mozilla.org/xpcom/memory-watcher;1"].getService(
       Ci.nsIAvailableMemoryWatcherBase
     );
 
     if (!Services.prefs.getBoolPref("browser.tabs.unloadOnLowMemory", true)) {
       watcher.onUnloadAttemptCompleted(Cr.NS_ERROR_NOT_AVAILABLE);
       return;
     }
@@ -169,28 +177,34 @@ var TabUnloader = {
       // Don't post multiple unloading requests.  The situation may be solved
       // when the active unloading task is completed.
       Services.console.logStringMessage("Unloading a tab is in progress.");
       watcher.onUnloadAttemptCompleted(Cr.NS_ERROR_ABORT);
       return;
     }
 
     this._isUnloading = true;
-    const isTabUnloaded = await this.unloadLeastRecentlyUsedTab();
+    const isTabUnloaded = await this.unloadLeastRecentlyUsedTab(
+      minInactiveDuration
+    );
     this._isUnloading = false;
 
     watcher.onUnloadAttemptCompleted(
       isTabUnloaded ? Cr.NS_OK : Cr.NS_ERROR_NOT_AVAILABLE
     );
   },
 
   /**
    * Get a list of tabs that can be discarded. This list includes all tabs in
    * all windows and is sorted based on a weighting described below.
    *
+   * @param minInactiveDuration If this value is a number, tabs that were accessed
+   *        in the last |minInactiveDuration| msec are not unloaded even if they
+   *        are least-recently-used.
+   *
    * @param tabMethods an helper object with methods called by this algorithm.
    *
    * The algorithm used is:
    *   1. Sort all of the tabs by a base weight. Tabs with a higher weight, such as
    *      those that are pinned or playing audio, will appear at the end. When two
    *      tabs have the same weight, sort by the order in which they were last.
    *      recently accessed Tabs that have a weight of NEVER_DISCARD are included in
    *       the list, but will not be discarded.
@@ -205,21 +219,34 @@ var TabUnloader = {
    *      tab uses.
    *   5. Combine these weights to produce a final tab discard order, and discard the
    *      first tab. If this fails, then discard the next tab in the list until no more
    *      non-discardable tabs are found.
    *
    * The tabMethods are used so that unit tests can use false tab objects and
    * override their behaviour.
    */
-  async getSortedTabs(tabMethods = DefaultTabUnloaderMethods) {
+  async getSortedTabs(
+    minInactiveDuration = kMinInactiveDurationInMs,
+    tabMethods = DefaultTabUnloaderMethods
+  ) {
     let tabs = [];
 
+    const now = tabMethods.getNow();
+
     let lowestWeight = 1000;
     for (let tab of tabMethods.iterateTabs()) {
+      if (
+        typeof minInactiveDuration == "number" &&
+        now - tab.tab.lastAccessed < minInactiveDuration
+      ) {
+        // Skip "fresh" tabs, which were accessed within the specified duration.
+        continue;
+      }
+
       let weight = determineTabBaseWeight(tab, tabMethods);
 
       // Don't add tabs that have a weight of -1.
       if (weight != -1) {
         tab.weight = weight;
         tabs.push(tab);
         if (weight < lowestWeight) {
           lowestWeight = weight;
@@ -274,18 +301,20 @@ var TabUnloader = {
 
     return tabs;
   },
 
   /**
    * Select and discard one tab.
    * @returns true if a tab was unloaded, otherwise false.
    */
-  async unloadLeastRecentlyUsedTab() {
-    let sortedTabs = await this.getSortedTabs();
+  async unloadLeastRecentlyUsedTab(
+    minInactiveDuration = kMinInactiveDurationInMs
+  ) {
+    const sortedTabs = await this.getSortedTabs(minInactiveDuration);
 
     for (let tabInfo of sortedTabs) {
       if (!this.isDiscardable(tabInfo)) {
         // Since |sortedTabs| is sorted, once we see an undiscardable tab
         // no need to continue the loop.
         return false;
       }
 
--- a/browser/modules/test/browser/browser_TabUnloader.js
+++ b/browser/modules/test/browser/browser_TabUnloader.js
@@ -81,17 +81,17 @@ async function addWebRTCTab(win = window
 }
 
 async function pressure() {
   let tabDiscarded = BrowserTestUtils.waitForEvent(
     document,
     "TabBrowserDiscarded",
     true
   );
-  TabUnloader.unloadTabAsync();
+  TabUnloader.unloadTabAsync(null);
   return tabDiscarded;
 }
 
 function pressureAndObserve(aExpectedTopic) {
   const promise = new Promise(resolve => {
     const observer = {
       QueryInterface: ChromeUtils.generateQI([
         "nsIObserver",
@@ -101,22 +101,22 @@ function pressureAndObserve(aExpectedTop
         if (aTopicInner == aExpectedTopic) {
           Services.obs.removeObserver(observer, aTopicInner);
           resolve(aData);
         }
       },
     };
     Services.obs.addObserver(observer, aExpectedTopic);
   });
-  TabUnloader.unloadTabAsync();
+  TabUnloader.unloadTabAsync(null);
   return promise;
 }
 
 async function compareTabOrder(expectedOrder) {
-  let tabInfo = await TabUnloader.getSortedTabs();
+  let tabInfo = await TabUnloader.getSortedTabs(null);
 
   is(
     tabInfo.length,
     expectedOrder.length,
     "right number of tabs in discard sort list"
   );
   for (let idx = 0; idx < expectedOrder.length; idx++) {
     is(tabInfo[idx].tab, expectedOrder[idx], "index " + idx + " is incorrect");
@@ -209,17 +209,17 @@ add_task(async function test() {
 
   await pressure();
   ok(!pinnedTab.linkedPanel, "unloaded a pinned tab");
   await compareTabOrder([soundTab, tab0, pinnedSoundTab]);
 
   ok(pinnedSoundTab.soundPlaying, "tab is still playing sound");
 
   // There are no unloadable tabs.
-  TabUnloader.unloadTabAsync();
+  TabUnloader.unloadTabAsync(null);
   ok(soundTab.linkedPanel, "a tab playing sound is never unloaded");
 
   const histogram = TelemetryTestUtils.getAndClearHistogram(
     "TAB_UNLOAD_TO_RELOAD"
   );
 
   // It's possible that we're already in the memory-pressure state
   // and we may receive the "ongoing" message.
--- a/browser/modules/test/unit/test_TabUnloader.js
+++ b/browser/modules/test/unit/test_TabUnloader.js
@@ -36,16 +36,20 @@ let TestTabUnloaderMethods = {
     return /\bwebrtc\b/.test(tab.keywords) ? weight : 0;
   },
 
   getMinTabCount() {
     // Use a low number for testing.
     return 3;
   },
 
+  getNow() {
+    return 100;
+  },
+
   *iterateProcesses(tab) {
     for (let process of tab.process.split(",")) {
       yield Number(process);
     }
   },
 
   async calculateMemoryUsage(processMap, tabs) {
     let memory = tabs[0].memory;
@@ -129,16 +133,23 @@ let unloadTests = [
       "3",
       "4 media",
       "5 media pinned",
       "6 selected",
     ],
     result: "2,0,3,5,1,4",
   },
   {
+    // Since TestTabUnloaderMethods.getNow() returns 100 and the test
+    // passes minInactiveDuration = 0 to TabUnloader.getSortedTabs(),
+    // tab 200 and 300 are excluded from the result.
+    tabs: ["300", "10", "50", "100", "200"],
+    result: "1,2,3",
+  },
+  {
     tabs: ["1", "2", "3", "4", "5", "6"],
     process: ["1", "2", "1", "1", "1", "1"],
     result: "1,0,2,3,4,5",
   },
   {
     tabs: ["1", "2 selected", "3", "4", "5", "6"],
     process: ["1", "2", "1", "1", "1", "1"],
     result: "0,2,3,4,5,1",
@@ -402,17 +413,20 @@ add_task(async function doTests() {
           gBrowser: globalBrowser,
         };
         yield tab;
       }
     }
     TestTabUnloaderMethods.iterateTabs = iterateTabs;
 
     let expectedOrder = "";
-    let sortedTabs = await TabUnloader.getSortedTabs(TestTabUnloaderMethods);
+    const sortedTabs = await TabUnloader.getSortedTabs(
+      0,
+      TestTabUnloaderMethods
+    );
     for (let tab of sortedTabs) {
       if (expectedOrder) {
         expectedOrder += ",";
       }
       expectedOrder += tab.tab.originalIndex;
     }
 
     Assert.equal(expectedOrder, test.result);
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -26,18 +26,25 @@
   --chrome-content-separator-color: rgba(0,0,0,.3);
 }
 
 :root[lwt-popup-brighttext] {
   --panel-separator-color: rgba(249,249,250,.1);
   --arrowpanel-field-background: rgba(12,12,13,.3);
 }
 
+#browser {
+  /* #browser and #navigator-toolbox must have relative positions so that the
+     latter can slide over the former in fullscreen mode. */
+  position: relative;
+}
+
 #navigator-toolbox {
   appearance: none;
+  position: relative;
 }
 
 #navigator-toolbox:not(:-moz-lwtheme) {
   background-color: window;
 }
 
 @media not (prefers-contrast) {
   #navigator-toolbox:not(:-moz-lwtheme) {
--- a/browser/themes/osx/customizableui/panelUI.css
+++ b/browser/themes/osx/customizableui/panelUI.css
@@ -25,17 +25,18 @@ panel[type="arrow"][side="right"] {
 }
 
 #downloadsPanel,
 #widget-overflow,
 #appMenu-popup,
 #customizationui-widget-panel,
 #identity-popup,
 #permission-popup,
-#protections-popup {
+#protections-popup,
+#appMenu-notification-popup {
   margin-top: -1px; /* Overrides value from panelUI.inc.css */
 }
 
 #BMB_bookmarksPopup {
   margin-top: -4px; /* Overrides value from panelUI.inc.css */
 }
 
 .panel-subview-body {
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -50,16 +50,17 @@
   --autocomplete-popup-separator-color: color-mix(in srgb, currentColor 14%, transparent);
 
   --identity-box-margin-inline: 4px;
   --urlbar-box-bgcolor: var(--button-bgcolor);
   --urlbar-box-focus-bgcolor: var(--button-bgcolor);
   --urlbar-box-hover-bgcolor: var(--button-hover-bgcolor);
   --urlbar-box-active-bgcolor: var(--button-active-bgcolor);
   --urlbar-box-text-color: inherit;
+  --urlbar-box-hover-text-color: var(--urlbar-box-text-color);
   --urlbar-min-height: 32px;
   --urlbar-icon-fill-opacity: 0.9;
   --urlbar-icon-padding: 6px; /* (32px - 2px border - 2px padding - 16px icon) / 2 */
   /* This should be used for icons and chiclets inside the input field. It makes
      the gap around them more uniform when they are close to the field edges */
   --urlbar-icon-border-radius: calc(var(--toolbarbutton-border-radius) - 1px);
   --urlbar-popup-url-color: -moz-nativehyperlinktext;
 
@@ -69,16 +70,20 @@
   --lwt-brighttext-url-color: #00ddff;
 }
 
 @media (prefers-contrast) {
   :root {
     --autocomplete-popup-separator-color: color-mix(in srgb, currentColor 86%, transparent);
     --urlbar-icon-fill-opacity: 1;
     --checkbox-checked-border-color: var(--checkbox-checked-bgcolor);
+
+    --urlbar-box-hover-bgcolor: SelectedItem;
+    --urlbar-box-active-bgcolor: SelectedItem;
+    --urlbar-box-hover-text-color: SelectedItemText;
   }
 }
 
 :root[uidensity=compact] {
   --urlbar-min-height: 26px;
   --urlbar-icon-padding: 3px; /* (26px - 2px border - 2px padding - 16px icon) / 2 */
 }
 
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -457,25 +457,27 @@ description#identity-popup-content-verif
   border-radius: 2px;
   padding: 0 var(--horizontal-padding);
 }
 
 .protections-popup-category:-moz-focusring,
 .protections-popup-category:hover,
 .protections-popup-footer-button:-moz-focusring,
 .protections-popup-footer-button:hover,
+#protections-popup-not-blocking-section-why:hover,
 #protections-popup-show-report-stack:hover > .protections-popup-footer-button {
-  background-color: var(--arrowpanel-dimmed);
+  background-color: var(--panel-item-hover-bgcolor);
   outline: none;
 }
 
 .protections-popup-category:hover:active,
 .protections-popup-footer-button:hover:active,
+#protections-popup-not-blocking-section-why:hover:active,
 #protections-popup-show-report-stack:hover:active > .protections-popup-footer-button {
-  background-color: var(--arrowpanel-dimmed-further);
+  background-color: var(--panel-item-active-bgcolor);
 }
 
 /* This subview could get filled with a lot of trackers, set a maximum size
  * and allow it to scroll vertically.*/
 #protections-popup-socialblockView,
 #protections-popup-cookiesView,
 #protections-popup-trackersView {
   max-height: calc(600px + var(--height-offset));
@@ -584,21 +586,24 @@ description#identity-popup-content-verif
   margin: 0;
 }
 
 #protections-popup-not-blocking-section-why {
   margin: 0;
 }
 
 #protections-popup-not-blocking-section-why:hover {
-  background-color: var(--arrowpanel-dimmed);
-  outline: 4px solid var(--arrowpanel-dimmed);
+  outline: 4px solid var(--panel-item-hover-bgcolor);
   text-decoration: none;
 }
 
+#protections-popup-not-blocking-section-why:hover:active {
+  outline-color: var(--panel-item-active-bgcolor);
+}
+
 .protections-popup-category.notFound {
   color: var(--panel-description-color);
   fill: var(--panel-description-color);
 }
 
 .protections-popup-category.notFound:hover {
   background: none;
 }
@@ -1000,28 +1005,16 @@ description#identity-popup-content-verif
   padding: var(--arrowpanel-menuitem-padding);
   margin: var(--arrowpanel-menuitem-margin);
 }
 
 #protections-popup-footer-protection-type-label {
   margin-inline-end: 0;
 }
 
-.protections-popup-category:hover,
-.protections-popup-footer-button:hover,
-#protections-popup-show-report-stack:hover > .protections-popup-footer-button {
-  background-color: var(--panel-item-hover-bgcolor);
-}
-
-.protections-popup-category:hover:active,
-.protections-popup-footer-button:hover:active,
-#protections-popup-show-report-stack:hover:active > .protections-popup-footer-button {
-  background-color: var(--panel-item-active-bgcolor);
-}
-
 .protections-popup-category:focus-visible,
 .protections-popup-footer-button:focus-visible {
   box-shadow: var(--panelview-toolbarbutton-focus-box-shadow);
   background: none;
 }
 
 #blocked-popup-indicator-item,
 #geo-access-indicator-item {
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -159,17 +159,18 @@ panelmultiview[transitioning] > .panel-v
 }
 
 #downloadsPanel,
 #widget-overflow,
 #appMenu-popup,
 #customizationui-widget-panel,
 #identity-popup,
 #permission-popup,
-#protections-popup {
+#protections-popup,
+#appMenu-notification-popup {
   margin-top: -4px;
 }
 
 #BMB_bookmarksPopup {
   margin-top: -8px;
 }
 
 .panel-subview-body {
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -69,36 +69,38 @@
 #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button,
 #urlbar-label-box {
   background-color: var(--urlbar-box-bgcolor);
   color: var(--urlbar-box-text-color);
   padding-inline: 8px;
   border-radius: var(--urlbar-icon-border-radius);
 }
 
-#urlbar[focused="true"] #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button,
-#urlbar[focused="true"] #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button,
-#urlbar[focused="true"] #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button,
+#urlbar[focused="true"] #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button:not(:hover, [open=true]),
+#urlbar[focused="true"] #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button:not(:hover, [open=true]),
+#urlbar[focused="true"] #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button:not(:hover, [open=true]),
 #urlbar[focused="true"] #urlbar-label-box {
   background-color: var(--urlbar-box-focus-bgcolor);
 }
 
 #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button:hover:not([open]),
 #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button:hover:not([open]),
 #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button:hover:not([open]) {
   background-color: var(--urlbar-box-hover-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
 #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button:hover:active,
 #identity-box[pageproxystate="valid"].notSecureText > .identity-box-button[open=true],
 #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button:hover:active,
 #identity-box[pageproxystate="valid"].chromeUI > .identity-box-button[open=true],
 #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button:hover:active,
 #identity-box[pageproxystate="valid"].extensionPage > .identity-box-button[open=true] {
   background-color: var(--urlbar-box-active-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
 #urlbar[searchmode]:not([focused="true"]) > #urlbar-input-container > #urlbar-search-mode-indicator,
 #urlbar[searchmode]:not([focused="true"]) > #urlbar-input-container > #urlbar-label-box {
   pointer-events: none;
 }
 
 #urlbar[searchmode]:not([focused="true"]) > #urlbar-input-container > #urlbar-search-mode-indicator > #urlbar-search-mode-indicator-close {
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -309,30 +309,25 @@
   height: 16px;
   -moz-context-properties: fill, fill-opacity;
   fill-opacity: 0.6;
   fill: currentColor;
   border-radius: var(--urlbar-icon-border-radius);
 }
 
 #urlbar-search-mode-indicator-close:hover {
-  background-color: hsla(0,0%,70%,.2);
+  background-color: var(--urlbar-box-hover-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 #urlbar-search-mode-indicator-close:hover:active {
-  background-color: hsla(0,0%,70%,.3);
+  background-color: var(--urlbar-box-active-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
-/* Use system colors for low/high contrast mode */
 @media (prefers-contrast) {
-  #urlbar-search-mode-indicator {
-    background-color: SelectedItem;
-    outline-color: SelectedItem;
-    color: SelectedItemText;
-  }
-
   #urlbar-search-mode-indicator-close {
     fill-opacity: 1.0;
   }
 }
 
 #urlbar-search-mode-indicator-title {
   padding-inline: 0 3px;
 }
@@ -401,24 +396,26 @@
 :root[uidensity=compact] #urlbar-go-button,
 :root[uidensity=compact] .search-go-button {
   margin-inline: 1px;
 }
 
 .urlbar-page-action:not([disabled]):hover,
 #urlbar-go-button:hover,
 .search-go-button:hover {
-  background-color: hsla(0,0%,70%,.2);
+  background-color: var(--urlbar-box-hover-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
 .urlbar-page-action:not([disabled])[open],
 .urlbar-page-action:not([disabled]):hover:active,
 #urlbar-go-button:hover:active,
 .search-go-button:hover:active {
-  background-color: hsla(0,0%,70%,.3);
+  background-color: var(--urlbar-box-active-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
 .urlbar-page-action:-moz-focusring {
   outline: var(--toolbarbutton-focus-outline);
   outline-offset: -2px;
 }
 
 #urlbar-go-button,
@@ -589,26 +586,28 @@
   padding: 3px 7px;
   border-radius: var(--urlbar-icon-border-radius);
   background-color: var(--urlbar-box-bgcolor);
   color: var(--urlbar-box-text-color);
   margin-block: calc((var(--urlbar-min-height) - 20px) / 2 - 1px /* border */ - var(--urlbar-container-padding));
   overflow: hidden;
 }
 
-#urlbar[focused="true"] #urlbar-zoom-button {
+#urlbar[focused="true"] #urlbar-zoom-button:not(:hover) {
   background-color: var(--urlbar-box-focus-bgcolor);
 }
 
 #urlbar-zoom-button:hover {
   background-color: var(--urlbar-box-hover-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
 #urlbar-zoom-button:hover:active {
   background-color: var(--urlbar-box-active-bgcolor);
+  color: var(--urlbar-box-hover-text-color);
 }
 
 @keyframes urlbar-zoom-reset-pulse {
   0% {
     transform: scale(0);
   }
   75% {
     transform: scale(1.5);
--- a/build/docs/visualstudio.rst
+++ b/build/docs/visualstudio.rst
@@ -1,34 +1,23 @@
 .. _build_visualstudio:
 
 ======================
 Visual Studio Projects
 ======================
 
-The build system contains alpha support for generating Visual Studio
-project files to aid with development.
+The build system automatically generates Visual Studio project files to aid
+with development, as part of a normal ``mach build`` from the command line.
 
-To generate Visual Studio project files, you'll need to have a configured tree::
-
-   mach configure
-
-(If you have built recently, your tree is already configured.)
+You can find the solution file at ``$OBJDIR/msvs/mozilla.sln``.
 
-Then, simply generate the Visual Studio build backend::
-
-   mach build-backend -b VisualStudio
+If you want to generate the project files before/without doing a full build,
+running ``./mach configure && ./mach build-backend -b VisualStudio`` will do
+so.
 
-If all goes well, the path to the generated Solution (``.sln``) file should be
-printed. You should be able to open that solution with Visual Studio 2010 or
-newer.
-
-Currently, output is hard-coded to the Visual Studio 2010 format. If you open
-the solution in a newer Visual Studio release, you will be prompted to upgrade
-projects. Simply click through the wizard to do that.
 
 Structure of Solution
 =====================
 
 The Visual Studio solution consists of hundreds of projects spanning thousands
 of files. To help with organization, the solution is divided into the following
 trees/folders:
 
@@ -54,30 +43,18 @@ Libraries
    ``dom_base`` project.
 
    These projects don't do anything when built. If you build a project here,
    the *binaries* build target project is built.
 
 Updating Project Files
 ======================
 
-As you pull and update the source tree, your Visual Studio files may fall out
-of sync with the build configuration. The tree should still build fine from
-within Visual Studio. But source files may be missing and IntelliSense may not
-have the proper build configuration.
-
-To account for this, you'll want to periodically regenerate the Visual Studio
-project files. You can do this within Visual Studio by building the
-``Build Targets :: visual-studio`` project or by running
-``mach build-backend -b VisualStudio`` from the command line.
-
-Currently, regeneration rewrites the original project files. **If you've made
-any customizations to the solution or projects, they will likely get
-overwritten.** We would like to improve this user experience in the
-future.
+Either re-running ``./mach build`` or ``./mach build-backend -b VisualStudio``
+will update the Visual Studio files after the tree changes.
 
 Moving Project Files Around
 ===========================
 
 The produced Visual Studio solution and project files should be portable.
 If you want to move them to a non-default directory, they should continue
 to work from wherever they are. If they don't, please file a bug.
 
--- a/config/makefiles/rust.mk
+++ b/config/makefiles/rust.mk
@@ -90,25 +90,32 @@ rustflags_neon += -C target_feature=+neo
 endif
 endif
 
 rustflags_sancov =
 ifdef LIBFUZZER
 ifndef MOZ_TSAN
 ifndef FUZZING_JS_FUZZILLI
 # These options should match what is implicitly enabled for `clang -fsanitize=fuzzer`
-#   here: https://github.com/llvm/llvm-project/blob/release/8.x/clang/lib/Driver/SanitizerArgs.cpp#L354
+#   here: https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/Driver/SanitizerArgs.cpp#L422
 #
 #  -sanitizer-coverage-inline-8bit-counters      Increments 8-bit counter for every edge.
 #  -sanitizer-coverage-level=4                   Enable coverage for all blocks, critical edges, and indirect calls.
 #  -sanitizer-coverage-trace-compares            Tracing of CMP and similar instructions.
 #  -sanitizer-coverage-pc-table                  Create a static PC table.
 #
 # In TSan builds, we must not pass any of these, because sanitizer coverage is incompatible with TSan.
-rustflags_sancov += -Cpasses=sancov -Cllvm-args=-sanitizer-coverage-inline-8bit-counters -Cllvm-args=-sanitizer-coverage-level=4 -Cllvm-args=-sanitizer-coverage-trace-compares -Cllvm-args=-sanitizer-coverage-pc-table
+#
+# sancov legacy pass was removed in rustc 1.57 and replaced by sancov-module
+ifeq (1,$(words $(filter 1.53.% 1.54.% 1.55.% 1.56.%,$(RUSTC_VERSION))))
+rustflags_sancov += -Cpasses=sancov
+else
+rustflags_sancov += -Cpasses=sancov-module
+endif
+rustflags_sancov += -Cllvm-args=-sanitizer-coverage-inline-8bit-counters -Cllvm-args=-sanitizer-coverage-level=4 -Cllvm-args=-sanitizer-coverage-trace-compares -Cllvm-args=-sanitizer-coverage-pc-table
 endif
 endif
 endif
 
 rustflags_override = $(MOZ_RUST_DEFAULT_FLAGS) $(rustflags_neon)
 
 ifdef DEVELOPER_OPTIONS
 # By default the Rust compiler will perform a limited kind of ThinLTO on each
--- a/devtools/client/webconsole/test/browser/browser_webconsole_cors_errors.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_cors_errors.js
@@ -38,41 +38,46 @@ add_task(async function() {
   makeFaultyCorsCall("CORSDisabled");
   message = await onCorsMessage;
   await checkCorsMessage(message, "CORSDisabled");
   await pushPref("content.cors.disable", false);
 
   info("Test CORSPreflightDidNotSucceed");
   onCorsMessage = waitForMessage(
     hud,
-    `CORS preflight response did not succeed`
+    `(Reason: CORS preflight response did not succeed). Status code: `
   );
   makeFaultyCorsCall("CORSPreflightDidNotSucceed");
   message = await onCorsMessage;
   await checkCorsMessage(message, "CORSPreflightDidNotSucceed");
 
   info("Test CORS did not succeed");
-  onCorsMessage = waitForMessage(hud, "Reason: CORS request did not succeed");
+  onCorsMessage = waitForMessage(
+    hud,
+    "(Reason: CORS request did not succeed). Status code: "
+  );
   makeFaultyCorsCall("CORSDidNotSucceed");
   message = await onCorsMessage;
   await checkCorsMessage(message, "CORSDidNotSucceed");
 
   info("Test CORSExternalRedirectNotAllowed");
   onCorsMessage = waitForMessage(
     hud,
     "Reason: CORS request external redirect not allowed"
   );
   makeFaultyCorsCall("CORSExternalRedirectNotAllowed");
   message = await onCorsMessage;
   await checkCorsMessage(message, "CORSExternalRedirectNotAllowed");
 
   info("Test CORSMissingAllowOrigin");
   onCorsMessage = waitForMessage(
     hud,
-    `Reason: CORS header ${quote("Access-Control-Allow-Origin")} missing`
+    `(Reason: CORS header ${quote(
+      "Access-Control-Allow-Origin"
+    )} missing). Status code: `
   );
   makeFaultyCorsCall("CORSMissingAllowOrigin");
   message = await onCorsMessage;
   await checkCorsMessage(message, "CORSMissingAllowOrigin");
 
   info("Test CORSMultipleAllowOriginNotAllowed");
   onCorsMessage = waitForMessage(
     hud,
--- a/devtools/server/actors/errordocs.js
+++ b/devtools/server/actors/errordocs.js
@@ -140,27 +140,27 @@ const ErrorCategories = {
 };
 
 const baseCorsErrorUrl =
   "https://developer.mozilla.org/docs/Web/HTTP/CORS/Errors/";
 const corsParams =
   "?utm_source=devtools&utm_medium=firefox-cors-errors&utm_campaign=default";
 const CorsErrorDocs = {
   CORSDisabled: "CORSDisabled",
-  CORSDidNotSucceed: "CORSDidNotSucceed",
+  CORSDidNotSucceed2: "CORSDidNotSucceed",
   CORSOriginHeaderNotAdded: "CORSOriginHeaderNotAdded",
   CORSExternalRedirectNotAllowed: "CORSExternalRedirectNotAllowed",
   CORSRequestNotHttp: "CORSRequestNotHttp",
-  CORSMissingAllowOrigin: "CORSMissingAllowOrigin",
+  CORSMissingAllowOrigin2: "CORSMissingAllowOrigin",
   CORSMultipleAllowOriginNotAllowed: "CORSMultipleAllowOriginNotAllowed",
   CORSAllowOriginNotMatchingOrigin: "CORSAllowOriginNotMatchingOrigin",
   CORSNotSupportingCredentials: "CORSNotSupportingCredentials",
   CORSMethodNotFound: "CORSMethodNotFound",
   CORSMissingAllowCredentials: "CORSMissingAllowCredentials",
-  CORSPreflightDidNotSucceed2: "CORSPreflightDidNotSucceed",
+  CORSPreflightDidNotSucceed3: "CORSPreflightDidNotSucceed",
   CORSInvalidAllowMethod: "CORSInvalidAllowMethod",
   CORSInvalidAllowHeader: "CORSInvalidAllowHeader",
   CORSMissingAllowHeaderFromPreflight2: "CORSMissingAllowHeaderFromPreflight",
 };
 
 const baseStorageAccessPolicyErrorUrl =
   "https://developer.mozilla.org/docs/Mozilla/Firefox/Privacy/Storage_access_policy/Errors/";
 const storageAccessPolicyParams =
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -251,16 +251,17 @@
 #include "nsAttrValue.h"
 #include "nsAttrValueInlines.h"
 #include "nsBaseHashtable.h"
 #include "nsBidiUtils.h"
 #include "nsCRT.h"
 #include "nsCSSPropertyID.h"
 #include "nsCSSProps.h"
 #include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
 #include "nsCanvasFrame.h"
 #include "nsCaseTreatment.h"
 #include "nsCharsetSource.h"
 #include "nsCommandManager.h"
 #include "nsCommandParams.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentList.h"
@@ -391,16 +392,17 @@
 #include "nsQueryObject.h"
 #include "nsRange.h"
 #include "nsRect.h"
 #include "nsRefreshDriver.h"
 #include "nsSandboxFlags.h"
 #include "nsSerializationHelper.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStringFlags.h"
+#include "nsStyleUtil.h"
 #include "nsStringIterator.h"
 #include "nsStyleSheetService.h"
 #include "nsStyleStruct.h"
 #include "nsTextNode.h"
 #include "nsUnicharUtils.h"
 #include "nsWrapperCache.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsXPCOMCID.h"
@@ -14447,16 +14449,98 @@ Element* Document::TopLayerPop(FunctionR
       // The top element of the stack is now an in-doc element. Return here.
       break;
     }
   }
 
   return removedElement;
 }
 
+void Document::GetWireframe(bool aIncludeNodes,
+                            Nullable<Wireframe>& aWireframe) {
+  using FrameForPointOptions = nsLayoutUtils::FrameForPointOptions;
+  using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+  FlushPendingNotifications(FlushType::Layout);
+
+  PresShell* shell = GetPresShell();
+  if (!shell) {
+    return;
+  }
+
+  nsPresContext* pc = shell->GetPresContext();
+  if (!pc) {
+    return;
+  }
+
+  auto& wireframe = aWireframe.SetValue();
+  nsStyleUtil::GetSerializedColorValue(shell->GetCanvasBackground(),
+                                       wireframe.mCanvasBackground.Construct());
+
+  FrameForPointOptions options;
+  options.mBits += FrameForPointOption::IgnoreCrossDoc;
+  options.mBits += FrameForPointOption::IgnorePaintSuppression;
+  options.mBits += FrameForPointOption::OnlyVisible;
+
+  AutoTArray<nsIFrame*, 32> frames;
+  const RelativeTo relativeTo{shell->GetRootFrame(),
+                              mozilla::ViewportType::Layout};
+  nsLayoutUtils::GetFramesForArea(relativeTo, pc->GetVisibleArea(), frames,
+                                  options);
+
+  // TODO(emilio): We could rewrite hit testing to return nsDisplayItem*s or
+  // something perhaps, but seems hard / like it'd involve at least some extra
+  // copying around, since they don't outlive GetFramesForArea.
+  auto& rects = wireframe.mRects.Construct();
+  if (!rects.SetCapacity(frames.Length(), fallible)) {
+    return;
+  }
+  for (nsIFrame* frame : frames) {
+    // Can't really fail because SetCapacity succeeded.
+    auto& taggedRect = *rects.AppendElement(fallible);
+    const auto r =
+        CSSRect::FromAppUnits(nsLayoutUtils::TransformFrameRectToAncestor(
+            frame, frame->GetRectRelativeToSelf(), relativeTo));
+    if (aIncludeNodes) {
+      if (nsIContent* c = frame->GetContent()) {
+        taggedRect.mNode.Construct(c);
+      }
+    }
+    taggedRect.mRect.Construct(MakeRefPtr<DOMRectReadOnly>(
+        ToSupports(this), r.x, r.y, r.width, r.height));
+    taggedRect.mType.Construct() = [&] {
+      if (frame->IsTextFrame()) {
+        nsStyleUtil::GetSerializedColorValue(
+            frame->StyleText()->mWebkitTextFillColor.CalcColor(frame),
+            taggedRect.mColor.Construct());
+        return WireframeRectType::Text;
+      }
+      if (frame->IsImageFrame() || frame->IsSVGOuterSVGFrame()) {
+        return WireframeRectType::Image;
+      }
+      if (frame->IsThemed()) {
+        return WireframeRectType::Background;
+      }
+      bool drawImage = false;
+      bool drawColor = false;
+      const nscolor color = nsCSSRendering::DetermineBackgroundColor(
+          pc, frame->Style(), frame, drawImage, drawColor);
+      if (drawImage &&
+          !frame->StyleBackground()->mImage.BottomLayer().mImage.IsNone()) {
+        return WireframeRectType::Image;
+      }
+      if (drawColor) {
+        nsStyleUtil::GetSerializedColorValue(color,
+                                             taggedRect.mColor.Construct());
+        return WireframeRectType::Background;
+      }
+      return WireframeRectType::Unknown;
+    }();
+  }
+}
+
 Element* Document::GetTopLayerTop() {
   if (mTopLayer.IsEmpty()) {
     return nullptr;
   }
   uint32_t last = mTopLayer.Length() - 1;
   nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[last]));
   NS_ASSERTION(element, "Should have a top layer element!");
   NS_ASSERTION(element->IsInComposedDoc(),
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3397,16 +3397,19 @@ class Document : public nsINode,
   void MozSetImageElement(const nsAString& aImageElementId, Element* aElement);
   nsIURI* GetDocumentURIObject() const;
   // Not const because all the fullscreen goop is not const
   const char* GetFullscreenError(CallerType);
   bool FullscreenEnabled(CallerType aCallerType) {
     return !GetFullscreenError(aCallerType);
   }
 
+  MOZ_CAN_RUN_SCRIPT void GetWireframe(bool aIncludeNodes,
+                                       Nullable<Wireframe>&);
+
   Element* GetTopLayerTop();
   // Return the fullscreen element in the top layer
   Element* GetUnretargetedFullScreenElement() const;
   bool Fullscreen() const { return !!GetUnretargetedFullScreenElement(); }
   already_AddRefed<Promise> ExitFullscreen(ErrorResult&);
   void ExitPointerLock() { PointerLockManager::Unlock(this); }
   void GetFgColor(nsAString& aFgColor);
   void SetFgColor(const nsAString& aFgColor);
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Implementation of mozilla::dom::Selection
  */
 
 #include "mozilla/dom/Selection.h"
+#include "mozilla/intl/Bidi.h"
 
 #include "mozilla/AccessibleCaretEventHub.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoCopyListener.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ContentIterator.h"
@@ -380,17 +381,19 @@ bool Selection::IsEditorSelection() cons
 Nullable<int16_t> Selection::GetCaretBidiLevel(
     mozilla::ErrorResult& aRv) const {
   MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
 
   if (!mFrameSelection) {
     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
     return Nullable<int16_t>();
   }
-  nsBidiLevel caretBidiLevel = mFrameSelection->GetCaretBidiLevel();
+  mozilla::intl::Bidi::EmbeddingLevel caretBidiLevel =
+      static_cast<mozilla::intl::Bidi::EmbeddingLevel>(
+          mFrameSelection->GetCaretBidiLevel());
   return (caretBidiLevel & BIDI_LEVEL_UNDEFINED)
              ? Nullable<int16_t>()
              : Nullable<int16_t>(caretBidiLevel);
 }
 
 void Selection::SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel,
                                   mozilla::ErrorResult& aRv) {
   MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
@@ -398,17 +401,17 @@ void Selection::SetCaretBidiLevel(const 
   if (!mFrameSelection) {
     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
     return;
   }
   if (aCaretBidiLevel.IsNull()) {
     mFrameSelection->UndefineCaretBidiLevel();
   } else {
     mFrameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
-        aCaretBidiLevel.Value());
+        mozilla::intl::Bidi::EmbeddingLevel(aCaretBidiLevel.Value()));
   }
 }
 
 /**
  * Test whether the supplied range points to a single table element.
  * Result is one of the TableSelectionMode constants. "None" means
  * a table element isn't selected.
  */
@@ -1352,17 +1355,18 @@ nsIFrame* Selection::GetPrimaryOrCaretFr
 
   if (!mFrameSelection) {
     return nullptr;
   }
 
   CaretAssociationHint hint = mFrameSelection->GetHint();
 
   if (aVisual) {
-    nsBidiLevel caretBidiLevel = mFrameSelection->GetCaretBidiLevel();
+    mozilla::intl::Bidi::EmbeddingLevel caretBidiLevel =
+        mFrameSelection->GetCaretBidiLevel();
 
     return nsCaret::GetCaretFrameForNodeOffset(
         mFrameSelection, aContent, aOffset, hint, caretBidiLevel,
         /* aReturnUnadjustedFrame = */ nullptr, aOffsetUsed);
   }
 
   return nsFrameSelection::GetFrameForNodeOffset(aContent, aOffset, hint,
                                                  aOffsetUsed);
@@ -3293,19 +3297,20 @@ void Selection::Modify(const nsAString& 
     }
     uint32_t focusOffset = FocusOffset();
     CollapseInLimiter(focusNode, focusOffset);
   }
 
   // If the paragraph direction of the focused frame is right-to-left,
   // we may have to swap the direction of movement.
   if (nsIFrame* frame = GetPrimaryFrameForFocusNode(visual)) {
-    nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(frame);
-
-    if (paraDir == NSBIDI_RTL && visual) {
+    mozilla::intl::Bidi::Direction paraDir =
+        nsBidiPresUtils::ParagraphDirection(frame);
+
+    if (paraDir == mozilla::intl::Bidi::Direction::RTL && visual) {
       if (amount == eSelectBeginLine) {
         amount = eSelectEndLine;
         forward = !forward;
       } else if (amount == eSelectEndLine) {
         amount = eSelectBeginLine;
         forward = !forward;
       }
     }
@@ -3469,36 +3474,38 @@ void Selection::SetStartAndEndInternal(I
 nsresult Selection::SelectionLanguageChange(bool aLangRTL) {
   if (!mFrameSelection) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 
   // if the direction of the language hasn't changed, nothing to do
-  nsBidiLevel kbdBidiLevel = aLangRTL ? NSBIDI_RTL : NSBIDI_LTR;
+  mozilla::intl::Bidi::EmbeddingLevel kbdBidiLevel =
+      aLangRTL ? mozilla::intl::Bidi::EmbeddingLevel::RTL()
+               : mozilla::intl::Bidi::EmbeddingLevel::LTR();
   if (kbdBidiLevel == frameSelection->mKbdBidiLevel) {
     return NS_OK;
   }
 
   frameSelection->mKbdBidiLevel = kbdBidiLevel;
 
   nsIFrame* focusFrame = GetPrimaryFrameForFocusNode(false);
   if (!focusFrame) {
     return NS_ERROR_FAILURE;
   }
 
   auto [frameStart, frameEnd] = focusFrame->GetOffsets();
   RefPtr<nsPresContext> context = GetPresContext();
-  nsBidiLevel levelBefore, levelAfter;
+  mozilla::intl::Bidi::EmbeddingLevel levelBefore, levelAfter;
   if (!context) {
     return NS_ERROR_FAILURE;
   }
 
-  nsBidiLevel level = focusFrame->GetEmbeddingLevel();
+  mozilla::intl::Bidi::EmbeddingLevel level = focusFrame->GetEmbeddingLevel();
   int32_t focusOffset = static_cast<int32_t>(FocusOffset());
   if ((focusOffset != frameStart) && (focusOffset != frameEnd))
     // the cursor is not at a frame boundary, so the level of both the
     // characters (logically) before and after the cursor is equal to the frame
     // level
     levelBefore = levelAfter = level;
   else {
     // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find
@@ -3506,36 +3513,40 @@ nsresult Selection::SelectionLanguageCha
     nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
     nsPrevNextBidiLevels levels =
         frameSelection->GetPrevNextBidiLevels(focusContent, focusOffset, false);
 
     levelBefore = levels.mLevelBefore;
     levelAfter = levels.mLevelAfter;
   }
 
-  if (IS_SAME_DIRECTION(levelBefore, levelAfter)) {
+  if (levelBefore.IsSameDirection(levelAfter)) {
     // if cursor is between two characters with the same orientation, changing
     // the keyboard language must toggle the cursor level between the level of
     // the character with the lowest level (if the new language corresponds to
     // the orientation of that character) and this level plus 1 (if the new
     // language corresponds to the opposite orientation)
-    if ((level != levelBefore) && (level != levelAfter))
+    if ((level != levelBefore) && (level != levelAfter)) {
       level = std::min(levelBefore, levelAfter);
-    if (IS_SAME_DIRECTION(level, kbdBidiLevel))
+    }
+    if (level.IsSameDirection(kbdBidiLevel)) {
       frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level);
-    else
-      frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(level + 1);
+    } else {
+      frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
+          mozilla::intl::Bidi::EmbeddingLevel(level + 1));
+    }
   } else {
     // if cursor is between characters with opposite orientations, changing the
     // keyboard language must change the cursor level to that of the adjacent
     // character with the orientation corresponding to the new language.
-    if (IS_SAME_DIRECTION(levelBefore, kbdBidiLevel))
+    if (levelBefore.IsSameDirection(kbdBidiLevel)) {
       frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelBefore);
-    else
+    } else {
       frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(levelAfter);
+    }
   }
 
   // The caret might have moved, so invalidate the desired position
   // for future usages of up-arrow or down-arrow
   frameSelection->InvalidateDesiredCaretPos();
 
   return NS_OK;
 }
--- a/dom/base/test/test_warning_for_blocked_cross_site_request.html
+++ b/dom/base/test/test_warning_for_blocked_cross_site_request.html
@@ -33,28 +33,28 @@ var tests = {
   xhr : {
     uri_test : "http://invalid",
     result : null,
     category: "CORSAllowOriginNotMatchingOrigin"
   },
   font : {
     uri_test : "font_bad",
     result : null,
-    category: "CORSMissingAllowOrigin",
+    category: "CORSMissingAllowOrigin2",
   },
   shape_outside : {
     uri_test : "bad_shape_outside",
     result : null,
-    category: "CORSMissingAllowOrigin",
+    category: "CORSMissingAllowOrigin2",
     ignore_windowID: true,
   },
   mask_image : {
     uri_test : "bad_mask_image",
     result : null,
-    category: "CORSMissingAllowOrigin",
+    category: "CORSMissingAllowOrigin2",
     ignore_windowID: true,
   },
 }
 
 function testsComplete() {
   for (var testName in tests) {
     var test = tests[testName];
     if (test.result == null) {
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -7,16 +7,17 @@
 
 #include "mozilla/gfx/Helpers.h"
 #include "nsXULElement.h"
 
 #include "nsMathUtils.h"
 
 #include "nsContentUtils.h"
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/PresShellInlines.h"
 #include "mozilla/SVGImageContext.h"
 #include "mozilla/SVGObserverUtils.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/GeneratePlaceholderCanvasData.h"
 #include "nsPresContext.h"
@@ -3498,21 +3499,21 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
     if (mMissingFonts) {
       mMissingFonts->Flush();
     }
   }
 
   using ContextState = CanvasRenderingContext2D::ContextState;
 
   virtual void SetText(const char16_t* aText, int32_t aLength,
-                       nsBidiDirection aDirection) override {
+                       mozilla::intl::Bidi::Direction aDirection) override {
     mFontgrp->UpdateUserFonts();  // ensure user font generation is current
     // adjust flags for current direction run
     gfx::ShapedTextFlags flags = mTextRunFlags;
-    if (aDirection == NSBIDI_RTL) {
+    if (aDirection == mozilla::intl::Bidi::Direction::RTL) {
       flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
     } else {
       flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL;
     }
     mTextRun = mFontgrp->MakeTextRun(
         aText, aLength, mDrawTarget, mAppUnitsPerDevPixel, flags,
         nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts,
         mMissingFonts.get());
@@ -3868,17 +3869,19 @@ TextMetrics* CanvasRenderingContext2D::D
                                     aOp == TextDrawOperation::MEASURE;
   processor.mFontgrp = currentFontStyle;
 
   nscoord totalWidthCoord;
 
   // calls bidi algo twice since it needs the full text width and the
   // bounding boxes before rendering anything
   aError = nsBidiPresUtils::ProcessText(
-      textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR,
+      textToDraw.get(), textToDraw.Length(),
+      isRTL ? mozilla::intl::Bidi::EmbeddingLevel::RTL()
+            : mozilla::intl::Bidi::EmbeddingLevel::LTR(),
       presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_MEASURE,
       nullptr, 0, &totalWidthCoord, &mBidiEngine);
   if (aError.Failed()) {
     return nullptr;
   }
 
   float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel;
 
@@ -4009,17 +4012,19 @@ TextMetrics* CanvasRenderingContext2D::D
 
   // save the previous bounding box
   gfxRect boundingBox = processor.mBoundingBox;
 
   // don't ever need to measure the bounding box twice
   processor.mDoMeasureBoundingBox = false;
 
   aError = nsBidiPresUtils::ProcessText(
-      textToDraw.get(), textToDraw.Length(), isRTL ? NSBIDI_RTL : NSBIDI_LTR,
+      textToDraw.get(), textToDraw.Length(),
+      isRTL ? mozilla::intl::Bidi::EmbeddingLevel::RTL()
+            : mozilla::intl::Bidi::EmbeddingLevel::LTR(),
       presShell->GetPresContext(), processor, nsBidiPresUtils::MODE_DRAW,
       nullptr, 0, nullptr, &mBidiEngine);
 
   if (aError.Failed()) {
     return nullptr;
   }
 
   mTarget->SetTransform(oldTransform);
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -4,28 +4,29 @@
 
 #ifndef CanvasRenderingContext2D_h
 #define CanvasRenderingContext2D_h
 
 #include <vector>
 #include "mozilla/dom/BasicRenderingContext2D.h"
 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/SurfaceFromElementResult.h"
 #include "mozilla/UniquePtr.h"
 #include "FilterDescription.h"
 #include "gfx2DGlue.h"
 #include "nsICanvasRenderingContextInternal.h"
-#include "nsBidi.h"
 #include "nsColor.h"
+#include "nsIFrame.h"
 
 class gfxFontGroup;
 class nsGlobalWindowInner;
 class nsXULElement;
 
 namespace mozilla {
 class ErrorResult;
 class PresShell;
@@ -792,17 +793,17 @@ class CanvasRenderingContext2D final : p
     RefPtr<Element> mElement;
     // Path of the hit region in the 2d context coordinate space (not user
     // space)
     RefPtr<gfx::Path> mPath;
   };
 
   nsTArray<RegionInfo> mHitRegionsOptions;
 
-  nsBidi mBidiEngine;
+  mozilla::intl::Bidi mBidiEngine;
 
   /**
    * Returns true if a shadow should be drawn along with a
    * drawing operation.
    */
   bool NeedToDrawShadow() {
     const ContextState& state = CurrentState();
 
--- a/dom/chrome-webidl/NetDashboard.webidl
+++ b/dom/chrome-webidl/NetDashboard.webidl
@@ -62,16 +62,17 @@ dictionary WebSocketDict {
 
 dictionary DnsCacheEntry {
   DOMString hostname = "";
   sequence<DOMString> hostaddr;
   DOMString family = "";
   double expiration = 0;
   boolean trr = false;
   DOMString originAttributesSuffix = "";
+  DOMString flags = "";
 };
 
 [GenerateConversionToJS]
 dictionary DNSCacheDict {
   sequence<DnsCacheEntry> entries;
 };
 
 [GenerateConversionToJS]
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -5,27 +5,27 @@
 # Mixed Content Blocker
 # LOCALIZATION NOTE: "%1$S" is the URI of the blocked mixed content resource
 BlockMixedDisplayContent = Blocked loading mixed display content “%1$S”
 BlockMixedActiveContent = Blocked loading mixed active content “%1$S”
 
 # CORS
 # LOCALIZATION NOTE: Do not translate "Access-Control-Allow-Origin", Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers
 CORSDisabled=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS disabled).
-CORSDidNotSucceed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request did not succeed).
+CORSDidNotSucceed2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request did not succeed). Status code: %2$S.
 CORSOriginHeaderNotAdded=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Origin’ cannot be added).
 CORSExternalRedirectNotAllowed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request external redirect not allowed).
 CORSRequestNotHttp=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request not http).
-CORSMissingAllowOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
+CORSMissingAllowOrigin2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: %2$S.
 CORSMultipleAllowOriginNotAllowed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed).
 CORSAllowOriginNotMatchingOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘%2$S’).
 CORSNotSupportingCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘%1$S’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).
 CORSMethodNotFound=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’).
 CORSMissingAllowCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
-CORSPreflightDidNotSucceed2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight response did not succeed).
+CORSPreflightDidNotSucceed3=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight response did not succeed). Status code: %2$S.
 CORSInvalidAllowMethod=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Methods’).
 CORSInvalidAllowHeader=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Headers’).
 CORSMissingAllowHeaderFromPreflight2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: header ‘%2$S’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).
 
 # LOCALIZATION NOTE: Do not translate "Strict-Transport-Security", "HSTS", "max-age" or "includeSubDomains"
 STSUnknownError=Strict-Transport-Security: An unknown error occurred processing the header specified by the site.
 STSUntrustworthyConnection=Strict-Transport-Security: The connection to the site is untrustworthy, so the specified header was ignored.
 STSCouldNotParseHeader=Strict-Transport-Security: The site specified a header that could not be parsed successfully.
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -458,17 +458,17 @@ class VideoData : public MediaData {
 
   // Creates a new VideoData containing a deep copy of aBuffer. May use
   // aContainer to allocate an Image to hold the copied data.
   static already_AddRefed<VideoData> CreateAndCopyData(
       const VideoInfo& aInfo, ImageContainer* aContainer, int64_t aOffset,
       const media::TimeUnit& aTime, const media::TimeUnit& aDuration,
       const YCbCrBuffer& aBuffer, bool aKeyframe,
       const media::TimeUnit& aTimecode, const IntRect& aPicture,
-      layers::KnowsCompositor* aAllocator = nullptr);
+      layers::KnowsCompositor* aAllocator);
 
   static already_AddRefed<VideoData> CreateAndCopyData(
       const VideoInfo& aInfo, ImageContainer* aContainer, int64_t aOffset,
       const media::TimeUnit& aTime, const media::TimeUnit& aDuration,
       const YCbCrBuffer& aBuffer, const YCbCrBuffer::Plane& aAlphaPlane,
       bool aKeyframe, const media::TimeUnit& aTimecode,
       const IntRect& aPicture);
 
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -965,17 +965,17 @@ already_AddRefed<VideoData> ChromiumCDMP
   b.mYUVColorSpace =
       DefaultColorSpace({aFrame.mImageWidth(), aFrame.mImageHeight()});
 
   gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
   RefPtr<VideoData> v = VideoData::CreateAndCopyData(
       mVideoInfo, mImageContainer, mLastStreamOffset,
       media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()),
       media::TimeUnit::FromMicroseconds(aFrame.mDuration()), b, false,
-      media::TimeUnit::FromMicroseconds(-1), pictureRegion);
+      media::TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor);
 
   return v.forget();
 }
 
 ipc::IPCResult ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus) {
   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecodeFailed(this=%p status=%" PRIu32
                 ")",
@@ -1032,17 +1032,18 @@ void ChromiumCDMParent::ActorDestroy(Act
   if (mAbnormalShutdown && callback) {
     callback->Terminated();
   }
   MaybeDisconnect(mAbnormalShutdown);
 }
 
 RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMParent::InitializeVideoDecoder(
     const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
-    RefPtr<layers::ImageContainer> aImageContainer) {
+    RefPtr<layers::ImageContainer> aImageContainer,
+    RefPtr<layers::KnowsCompositor> aKnowsCompositor) {
   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
   if (mIsShutdown) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
         MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                     RESULT_DETAIL("ChromiumCDMParent is shutdown")),
         __func__);
   }
 
@@ -1079,16 +1080,17 @@ RefPtr<MediaDataDecoder::InitPromise> Ch
   mMaxRefFrames = (aConfig.mCodec() == cdm::VideoCodec::kCodecH264)
                       ? H264::HasSPS(aInfo.mExtraData)
                             ? H264::ComputeMaxRefFrames(aInfo.mExtraData)
                             : 16
                       : 0;
 
   mVideoDecoderInitialized = true;
   mImageContainer = aImageContainer;
+  mKnowsCompositor = aKnowsCompositor;
   mVideoInfo = aInfo;
   mVideoFrameBufferSize = bufferSize;
 
   return mInitVideoDecoderPromise.Ensure(__func__);
 }
 
 ipc::IPCResult ChromiumCDMParent::RecvOnDecoderInitDone(
     const uint32_t& aStatus) {
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -83,17 +83,18 @@ class ChromiumCDMParent final : public P
                           const nsCString& aMinHdcpVersion);
 
   RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample);
 
   // TODO: Add functions for clients to send data to CDM, and
   // a Close() function.
   RefPtr<MediaDataDecoder::InitPromise> InitializeVideoDecoder(
       const gmp::CDMVideoDecoderConfig& aConfig, const VideoInfo& aInfo,
-      RefPtr<layers::ImageContainer> aImageContainer);
+      RefPtr<layers::ImageContainer> aImageContainer,
+      RefPtr<layers::KnowsCompositor> aKnowsCompositor);
 
   RefPtr<MediaDataDecoder::DecodePromise> DecryptAndDecodeFrame(
       MediaRawData* aSample);
 
   RefPtr<MediaDataDecoder::FlushPromise> FlushVideoDecoder();
 
   RefPtr<MediaDataDecoder::DecodePromise> Drain();
 
@@ -176,16 +177,17 @@ class ChromiumCDMParent final : public P
   nsTArray<RefPtr<DecryptJob>> mDecrypts;
 
   MozPromiseHolder<InitPromise> mInitPromise;
 
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
 
   RefPtr<layers::ImageContainer> mImageContainer;
+  RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   VideoInfo mVideoInfo;
   uint64_t mLastStreamOffset = 0;
 
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
 
   size_t mVideoFrameBufferSize = 0;
 
   // Count of the number of shmems in the set used to return decoded video
--- a/dom/media/platforms/agnostic/AOMDecoder.cpp
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -206,20 +206,20 @@ RefPtr<MediaDataDecoder::DecodePromise> 
       default:
         b.mYUVColorSpace = DefaultColorSpace({img->d_w, img->d_h});
         break;
     }
     b.mColorRange = img->range == AOM_CR_FULL_RANGE ? ColorRange::FULL
                                                     : ColorRange::LIMITED;
 
     RefPtr<VideoData> v;
-    v = VideoData::CreateAndCopyData(mInfo, mImageContainer, aSample->mOffset,
-                                     aSample->mTime, aSample->mDuration, b,
-                                     aSample->mKeyframe, aSample->mTimecode,
-                                     mInfo.ScaledImageRect(img->d_w, img->d_h));
+    v = VideoData::CreateAndCopyData(
+        mInfo, mImageContainer, aSample->mOffset, aSample->mTime,
+        aSample->mDuration, b, aSample->mKeyframe, aSample->mTimecode,
+        mInfo.ScaledImageRect(img->d_w, img->d_h), nullptr);
 
     if (!v) {
       LOG("Image allocation error source %ux%u display %ux%u picture %ux%u",
           img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
           mInfo.mImage.width, mInfo.mImage.height);
       return DecodePromise::CreateAndReject(
           MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
     }
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -64,19 +64,20 @@ already_AddRefed<MediaData> BlankVideoDa
   buffer.mPlanes[2].mData = frame.get();
   buffer.mPlanes[2].mStride = (mFrameWidth + 1) / 2;
   buffer.mPlanes[2].mHeight = (mFrameHeight + 1) / 2;
   buffer.mPlanes[2].mWidth = (mFrameWidth + 1) / 2;
   buffer.mPlanes[2].mSkip = 0;
 
   buffer.mYUVColorSpace = gfx::YUVColorSpace::BT601;
 
-  return VideoData::CreateAndCopyData(
-      mInfo, mImageContainer, aSample->mOffset, aSample->mTime,
-      aSample->mDuration, buffer, aSample->mKeyframe, aSample->mTime, mPicture);
+  return VideoData::CreateAndCopyData(mInfo, mImageContainer, aSample->mOffset,
+                                      aSample->mTime, aSample->mDuration,
+                                      buffer, aSample->mKeyframe,
+                                      aSample->mTime, mPicture, nullptr);
 }
 
 BlankAudioDataCreator::BlankAudioDataCreator(uint32_t aChannelCount,
                                              uint32_t aSampleRate)
     : mFrameSum(0), mChannelCount(aChannelCount), mSampleRate(aSampleRate) {}
 
 already_AddRefed<MediaData> BlankAudioDataCreator::Create(
     MediaRawData* aSample) {
--- a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp
@@ -15,17 +15,18 @@
 namespace mozilla {
 
 ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder(
     const GMPVideoDecoderParams& aParams, CDMProxy* aCDMProxy)
     : mCDMParent(aCDMProxy->AsChromiumCDMProxy()->GetCDMParent()),
       mConfig(aParams.mConfig),
       mCrashHelper(aParams.mCrashHelper),
       mGMPThread(GetGMPThread()),
-      mImageContainer(aParams.mImageContainer) {}
+      mImageContainer(aParams.mImageContainer),
+      mKnowsCompositor(aParams.mKnowsCompositor) {}
 
 ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder() = default;
 
 static uint32_t ToCDMH264Profile(uint8_t aProfile) {
   switch (aProfile) {
     case 66:
       return cdm::VideoCodecProfile::kH264ProfileBaseline;
     case 77:
@@ -84,20 +85,22 @@ RefPtr<MediaDataDecoder::InitPromise> Ch
     default:
       MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
       break;
   }
 
   RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
   VideoInfo info = mConfig;
   RefPtr<layers::ImageContainer> imageContainer = mImageContainer;
-  return InvokeAsync(
-      mGMPThread, __func__, [cdm, config, info, imageContainer]() {
-        return cdm->InitializeVideoDecoder(config, info, imageContainer);
-      });
+  RefPtr<layers::KnowsCompositor> knowsCompositor = mKnowsCompositor;
+  return InvokeAsync(mGMPThread, __func__,
+                     [cdm, config, info, imageContainer, knowsCompositor]() {
+                       return cdm->InitializeVideoDecoder(
+                           config, info, imageContainer, knowsCompositor);
+                     });
 }
 
 nsCString ChromiumCDMVideoDecoder::GetDescriptionName() const {
   return "chromium cdm video decoder"_ns;
 }
 
 MediaDataDecoder::ConversionRequired ChromiumCDMVideoDecoder::NeedsConversion()
     const {
--- a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h
+++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ChromiumCDMVideoDecoder_h_
 #define ChromiumCDMVideoDecoder_h_
 
 #include "ChromiumCDMParent.h"
 #include "PlatformDecoderModule.h"
+#include "mozilla/layers/KnowsCompositor.h"
 
 namespace mozilla {
 
 class CDMProxy;
 struct GMPVideoDecoderParams;
 
 DDLoggedTypeDeclNameAndBase(ChromiumCDMVideoDecoder, MediaDataDecoder);
 
@@ -35,15 +36,16 @@ class ChromiumCDMVideoDecoder
  private:
   ~ChromiumCDMVideoDecoder();
 
   RefPtr<gmp::ChromiumCDMParent> mCDMParent;
   const VideoInfo mConfig;
   RefPtr<GMPCrashHelper> mCrashHelper;
   nsCOMPtr<nsISerialEventTarget> mGMPThread;
   RefPtr<layers::ImageContainer> mImageContainer;
+  RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   MozPromiseHolder<InitPromise> mInitPromise;
   bool mConvertToAnnexB = false;
 };
 
 }  // namespace mozilla
 
 #endif  // ChromiumCDMVideoDecoder_h_
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -29,17 +29,18 @@ static bool IsOnGMPThread() {
   MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread);
   return gmpThread->IsOnCurrentThread();
 }
 #endif
 
 GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
     : mConfig(aParams.VideoConfig()),
       mImageContainer(aParams.mImageContainer),
-      mCrashHelper(aParams.mCrashHelper) {}
+      mCrashHelper(aParams.mCrashHelper),
+      mKnowsCompositor(aParams.mKnowsCompositor) {}
 
 void GMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
   GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
 
   MOZ_ASSERT(IsOnGMPThread());
 
   VideoData::YCbCrBuffer b;
   for (int i = 0; i < kGMPNumOfPlanes; ++i) {
@@ -59,17 +60,17 @@ void GMPVideoDecoder::Decoded(GMPVideoi4
       DefaultColorSpace({decodedFrame->Width(), decodedFrame->Height()});
 
   gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(),
                              decodedFrame->Height());
   RefPtr<VideoData> v = VideoData::CreateAndCopyData(
       mConfig, mImageContainer, mLastStreamOffset,
       media::TimeUnit::FromMicroseconds(decodedFrame->Timestamp()),
       media::TimeUnit::FromMicroseconds(decodedFrame->Duration()), b, false,
-      media::TimeUnit::FromMicroseconds(-1), pictureRegion);
+      media::TimeUnit::FromMicroseconds(-1), pictureRegion, mKnowsCompositor);
   RefPtr<GMPVideoDecoder> self = this;
   if (v) {
     mDecodedData.AppendElement(std::move(v));
   } else {
     mDecodedData.Clear();
     mDecodePromise.RejectIfExists(
         MediaResult(NS_ERROR_OUT_OF_MEMORY,
                     RESULT_DETAIL("CallBack::CreateAndCopyData")),
@@ -118,17 +119,18 @@ void GMPVideoDecoder::Terminated() {
 }
 
 GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
     : mConfig(aParams.mConfig),
       mGMP(nullptr),
       mHost(nullptr),
       mConvertNALUnitLengths(false),
       mCrashHelper(aParams.mCrashHelper),
-      mImageContainer(aParams.mImageContainer) {}
+      mImageContainer(aParams.mImageContainer),
+      mKnowsCompositor(aParams.mKnowsCompositor) {}
 
 void GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) {
   if (MP4Decoder::IsH264(mConfig.mMimeType)) {
     aTags.AppendElement("h264"_ns);
   } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
     aTags.AppendElement("vp8"_ns);
   } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
     aTags.AppendElement("vp9"_ns);
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/layers/KnowsCompositor.h"
 #if !defined(GMPVideoDecoder_h_)
 #  define GMPVideoDecoder_h_
 
 #  include "GMPVideoDecoderProxy.h"
 #  include "ImageContainer.h"
 #  include "MediaDataDecoderProxy.h"
 #  include "MediaInfo.h"
 #  include "PlatformDecoderModule.h"
@@ -17,16 +18,17 @@
 namespace mozilla {
 
 struct MOZ_STACK_CLASS GMPVideoDecoderParams {
   explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams);
 
   const VideoInfo& mConfig;
   layers::ImageContainer* mImageContainer;
   GMPCrashHelper* mCrashHelper;
+  layers::KnowsCompositor* mKnowsCompositor;
 };
 
 DDLoggedTypeDeclNameAndBase(GMPVideoDecoder, MediaDataDecoder);
 
 class GMPVideoDecoder : public MediaDataDecoder,
                         public GMPVideoDecoderCallbackProxy,
                         public DecoderDoctorLifeLogger<GMPVideoDecoder> {
  public:
@@ -82,16 +84,17 @@ class GMPVideoDecoder : public MediaData
   GMPVideoDecoderProxy* mGMP;
   GMPVideoHost* mHost;
   bool mConvertNALUnitLengths;
   MozPromiseHolder<InitPromise> mInitPromise;
   RefPtr<GMPCrashHelper> mCrashHelper;
 
   int64_t mLastStreamOffset = 0;
   RefPtr<layers::ImageContainer> mImageContainer;
+  RefPtr<layers::KnowsCompositor> mKnowsCompositor;
 
   MozPromiseHolder<DecodePromise> mDecodePromise;
   MozPromiseHolder<DecodePromise> mDrainPromise;
   MozPromiseHolder<FlushPromise> mFlushPromise;
   DecodedData mDecodedData;
   bool mConvertToAnnexB = false;
 };
 
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -947,17 +947,17 @@ already_AddRefed<VideoData> MediaDataHel
 
   RefPtr<VideoData> data = VideoData::CreateAndCopyData(
       info, mImageContainer,
       0,                                     // Filled later by caller.
       media::TimeUnit::Zero(),               // Filled later by caller.
       media::TimeUnit::FromMicroseconds(1),  // We don't know the duration.
       b,
       0,  // Filled later by caller.
-      media::TimeUnit::FromMicroseconds(-1), info.ImageRect());
+      media::TimeUnit::FromMicroseconds(-1), info.ImageRect(), nullptr);
 
   MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug,
           ("YUV420 VideoData: disp width %d, height %d, pic width %d, height "
            "%d, time %lld",
            info.mDisplay.width, info.mDisplay.height, info.mImage.width,
            info.mImage.height, aBufferData->mBuffer->nTimeStamp));
 
   return data.forget();
--- a/dom/vr/XRFrame.cpp
+++ b/dom/vr/XRFrame.cpp
@@ -43,18 +43,22 @@ already_AddRefed<XRViewerPose> XRFrame::
 
   if (aReferenceSpace.GetSession() != mSession) {
     aRv.ThrowInvalidStateError(
         "The XRReferenceSpace passed to GetViewerPose must belong to the "
         "XRSession that GetViewerPose is called on.");
     return nullptr;
   }
 
-  // TODO (Bug 1616390) - Validate that poses may be reported:
-  // https://immersive-web.github.io/webxr/#poses-may-be-reported
+  if (!mSession->CanReportPoses()) {
+    aRv.ThrowSecurityError(
+        "The visibilityState of the XRSpace's XRSession "
+        "that is passed to GetViewerPose must be 'visible'.");
+    return nullptr;
+  }
 
   // TODO (Bug 1616393) - Check if poses must be limited:
   // https://immersive-web.github.io/webxr/#poses-must-be-limited
 
   bool emulatedPosition = aReferenceSpace.IsPositionEmulated();
 
   XRRenderState* renderState = mSession->GetActiveRenderState();
   float depthNear = (float)renderState->DepthNear();
@@ -140,21 +144,20 @@ already_AddRefed<XRPose> XRFrame::GetPos
 
   if (aSpace.GetSession() != mSession || aBaseSpace.GetSession() != mSession) {
     aRv.ThrowInvalidStateError(
         "The XRSpace passed to GetPose must belong to the "
         "XRSession that GetPose is called on.");
     return nullptr;
   }
 
-  // TODO (Bug 1616390) - Validate that poses may be reported:
-  // https://immersive-web.github.io/webxr/#poses-may-be-reported
-  if (aSpace.GetSession()->VisibilityState() != XRVisibilityState::Visible) {
-    aRv.ThrowInvalidStateError(
-        "An XRSpace ’s visibilityState in not 'visible'.");
+  if (!mSession->CanReportPoses()) {
+    aRv.ThrowSecurityError(
+        "The visibilityState of the XRSpace's XRSession "
+        "that is passed to GetPose must be 'visible'.");
     return nullptr;
   }
 
   // TODO (Bug 1616393) - Check if poses must be limited:
   // https://immersive-web.github.io/webxr/#poses-must-be-limited
 
   const bool emulatedPosition = aSpace.IsPositionEmulated();
   gfx::Matrix4x4Double base;
--- a/dom/vr/XRSession.cpp
+++ b/dom/vr/XRSession.cpp
@@ -170,21 +170,29 @@ already_AddRefed<Promise> XRSession::End
   return promise.forget();
 }
 
 bool XRSession::IsImmersive() const {
   // Only immersive sessions have a VRDisplayClient
   return mDisplayClient != nullptr;
 }
 
-XRVisibilityState XRSession::VisibilityState() {
+XRVisibilityState XRSession::VisibilityState() const {
   return XRVisibilityState::Visible;
   // TODO (Bug 1609771): Implement changing visibility state
 }
 
+// https://immersive-web.github.io/webxr/#poses-may-be-reported
+// Given that an XRSession cannot be requested without explicit consent
+// by the user, the only necessary check is whether the XRSession's
+// visiblityState is 'visible'.
+bool XRSession::CanReportPoses() const {
+  return VisibilityState() == XRVisibilityState::Visible;
+}
+
 // https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate
 void XRSession::UpdateRenderState(const XRRenderStateInit& aNewState,
                                   ErrorResult& aRv) {
   if (mEnded) {
     aRv.ThrowInvalidStateError(
         "UpdateRenderState can not be called on an XRSession that has ended.");
     return;
   }
--- a/dom/vr/XRSession.h
+++ b/dom/vr/XRSession.h
@@ -59,17 +59,17 @@ class XRSession final : public DOMEventT
       gfx::VRDisplayClient* aClient, uint32_t aPresentationGroup,
       const nsTArray<XRReferenceSpaceType>& aEnabledReferenceSpaceTypes);
 
   // WebIDL Boilerplate
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL Attributes
-  XRVisibilityState VisibilityState();
+  XRVisibilityState VisibilityState() const;
   XRRenderState* RenderState();
   XRInputSourceArray* InputSources();
 
   // WebIDL Methods
   void UpdateRenderState(const XRRenderStateInit& aNewState, ErrorResult& aRv);
   already_AddRefed<Promise> RequestReferenceSpace(
       const XRReferenceSpaceType& aReferenceSpaceType, ErrorResult& aRv);
   int32_t RequestAnimationFrame(XRFrameRequestCallback& aCallback,
@@ -93,16 +93,17 @@ class XRSession final : public DOMEventT
   XRRenderState* GetActiveRenderState() const;
   bool IsEnded() const;
   bool IsImmersive() const;
   MOZ_CAN_RUN_SCRIPT
   void StartFrame();
   void ExitPresent();
   RefPtr<XRViewerPose> PooledViewerPose(const gfx::Matrix4x4Double& aTransform,
                                         bool aEmulatedPosition);
+  bool CanReportPoses() const;
 
   // nsARefreshObserver
   MOZ_CAN_RUN_SCRIPT
   void WillRefresh(mozilla::TimeStamp aTime) override;
 
  protected:
   virtual ~XRSession();
   void LastRelease() override;
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -711,8 +711,30 @@ partial interface Document {
   void setNotifyFormOrPasswordRemoved(boolean aShouldNotify);
 };
 
 // Extension to allow chrome code to detect initial about:blank documents.
 partial interface Document {
   [ChromeOnly]
   readonly attribute boolean isInitialDocument;
 };
+
+// Extension to allow chrome code to get some wireframe-like structure.
+enum WireframeRectType {
+  "image",
+  "background",
+  "text",
+  "unknown",
+};
+dictionary WireframeTaggedRect {
+  DOMRectReadOnly rect;
+  DOMString? color; /* Only relevant for "background" rects */
+  WireframeRectType type;
+  Node? node;
+};
+dictionary Wireframe {
+  DOMString canvasBackground;
+  sequence<WireframeTaggedRect> rects;
+};
+partial interface Document {
+  [ChromeOnly]
+  Wireframe? getWireframe(optional boolean aIncludeNodes = false);
+};
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1573,21 +1573,20 @@ nsXULPrototypeScript::nsXULPrototypeScri
     : nsXULPrototypeNode(eType_Script),
       mLineNo(aLineNo),
       mSrcLoading(false),
       mOutOfLine(true),
       mSrcLoadWaiters(nullptr),
       mStencil(nullptr) {}
 
 static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx,
-                             const JS::ReadOnlyCompileOptions& aOptions,
                              JS::Stencil* aStencil) {
   JS::TranscodeBuffer buffer;
   JS::TranscodeResult code;
-  code = JS::EncodeStencil(aCx, aOptions, aStencil, buffer);
+  code = JS::EncodeStencil(aCx, aStencil, buffer);
 
   if (code != JS::TranscodeResult::Ok) {
     if (code == JS::TranscodeResult::Throw) {
       JS_ClearPendingException(aCx);
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     MOZ_ASSERT(IsTranscodeFailureResult(code));
@@ -1684,20 +1683,17 @@ nsresult nsXULPrototypeScript::Serialize
   rv = aStream->Write32(mLineNo);
   if (NS_FAILED(rv)) return rv;
   rv = aStream->Write32(0);  // See bug 1418294.
   if (NS_FAILED(rv)) return rv;
 
   JSContext* cx = jsapi.cx();
   MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
 
-  JS::CompileOptions options(cx);
-  FillCompileOptions(options);
-
-  return WriteStencil(aStream, cx, options, mStencil);
+  return WriteStencil(aStream, cx, mStencil);
 }
 
 nsresult nsXULPrototypeScript::SerializeOutOfLine(
     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) {
   if (!mSrcURI->SchemeIs("chrome"))
     // Don't cache scripts that don't come from chrome uris.
     return NS_ERROR_NOT_IMPLEMENTED;
 
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -5,32 +5,33 @@
 
 #include "EditorBase.h"
 
 #include "mozilla/DebugOnly.h"  // for DebugOnly
 
 #include <stdio.h>   // for nullptr, stdout
 #include <string.h>  // for strcmp
 
-#include "ChangeAttributeTransaction.h"       // for ChangeAttributeTransaction
-#include "CompositionTransaction.h"           // for CompositionTransaction
-#include "CreateElementTransaction.h"         // for CreateElementTransaction
-#include "DeleteNodeTransaction.h"            // for DeleteNodeTransaction
-#include "DeleteRangeTransaction.h"           // for DeleteRangeTransaction
-#include "DeleteTextTransaction.h"            // for DeleteTextTransaction
-#include "EditAggregateTransaction.h"         // for EditAggregateTransaction
-#include "EditTransactionBase.h"              // for EditTransactionBase
-#include "EditorEventListener.h"              // for EditorEventListener
-#include "gfxFontUtils.h"                     // for gfxFontUtils
-#include "HTMLEditUtils.h"                    // for HTMLEditUtils
-#include "InsertNodeTransaction.h"            // for InsertNodeTransaction
-#include "InsertTextTransaction.h"            // for InsertTextTransaction
-#include "JoinNodeTransaction.h"              // for JoinNodeTransaction
-#include "PlaceholderTransaction.h"           // for PlaceholderTransaction
-#include "SplitNodeTransaction.h"             // for SplitNodeTransaction
+#include "ChangeAttributeTransaction.h"  // for ChangeAttributeTransaction
+#include "CompositionTransaction.h"      // for CompositionTransaction
+#include "CreateElementTransaction.h"    // for CreateElementTransaction
+#include "DeleteNodeTransaction.h"       // for DeleteNodeTransaction
+#include "DeleteRangeTransaction.h"      // for DeleteRangeTransaction
+#include "DeleteTextTransaction.h"       // for DeleteTextTransaction
+#include "EditAggregateTransaction.h"    // for EditAggregateTransaction
+#include "EditTransactionBase.h"         // for EditTransactionBase
+#include "EditorEventListener.h"         // for EditorEventListener
+#include "gfxFontUtils.h"                // for gfxFontUtils
+#include "HTMLEditUtils.h"               // for HTMLEditUtils
+#include "InsertNodeTransaction.h"       // for InsertNodeTransaction
+#include "InsertTextTransaction.h"       // for InsertTextTransaction
+#include "JoinNodeTransaction.h"         // for JoinNodeTransaction
+#include "PlaceholderTransaction.h"      // for PlaceholderTransaction
+#include "SplitNodeTransaction.h"        // for SplitNodeTransaction
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/BasePrincipal.h"            // for BasePrincipal
 #include "mozilla/CheckedInt.h"               // for CheckedInt
 #include "mozilla/ComposerCommandsUpdater.h"  // for ComposerCommandsUpdater
 #include "mozilla/ContentEvents.h"            // for InternalClipboardEvent
 #include "mozilla/CSSEditUtils.h"             // for CSSEditUtils
 #include "mozilla/EditAction.h"               // for EditSubAction
 #include "mozilla/EditorDOMPoint.h"           // for EditorDOMPoint
 #include "mozilla/EditorSpellCheck.h"         // for EditorSpellCheck
@@ -5760,22 +5761,23 @@ EditorBase::AutoCaretBidiLevelManager::A
   if (NS_WARN_IF(!frameSelection)) {
     mFailed = true;
     return;
   }
 
   nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels(
       aPointAtCaret.GetContainerAsContent(), aPointAtCaret.Offset(), true);
 
-  nsBidiLevel levelBefore = levels.mLevelBefore;
-  nsBidiLevel levelAfter = levels.mLevelAfter;
-
-  nsBidiLevel currentCaretLevel = frameSelection->GetCaretBidiLevel();
-
-  nsBidiLevel levelOfDeletion;
+  mozilla::intl::Bidi::EmbeddingLevel levelBefore = levels.mLevelBefore;
+  mozilla::intl::Bidi::EmbeddingLevel levelAfter = levels.mLevelAfter;
+
+  mozilla::intl::Bidi::EmbeddingLevel currentCaretLevel =
+      frameSelection->GetCaretBidiLevel();
+
+  mozilla::intl::Bidi::EmbeddingLevel levelOfDeletion;
   levelOfDeletion = (nsIEditor::eNext == aDirectionAndAmount ||
                      nsIEditor::eNextWord == aDirectionAndAmount)
                         ? levelAfter
                         : levelBefore;
 
   if (currentCaretLevel == levelOfDeletion) {
     return;  // Perform the deletion
   }
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -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/. */
 
 #ifndef mozilla_EditorBase_h
 #define mozilla_EditorBase_h
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/Assertions.h"          // for MOZ_ASSERT, etc.
 #include "mozilla/EditAction.h"          // for EditAction and EditSubAction
 #include "mozilla/EditorDOMPoint.h"      // for EditorDOMPoint
 #include "mozilla/EventForwards.h"       // for InputEventTargetRanges
 #include "mozilla/Maybe.h"               // for Maybe
 #include "mozilla/OwningNonNull.h"       // for OwningNonNull
 #include "mozilla/TypeInState.h"         // for PropItem, StyleCache
 #include "mozilla/RangeBoundary.h"       // for RawRangeBoundary, RangeBoundary
@@ -23,17 +24,16 @@
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/Text.h"
 #include "nsAtom.h"    // for nsAtom, nsStaticAtom
 #include "nsCOMPtr.h"  // for already_AddRefed, nsCOMPtr
 #include "nsCycleCollectionParticipant.h"
 #include "nsGkAtoms.h"
 #include "nsIContentInlines.h"       // for nsINode::IsEditable()
 #include "nsIEditor.h"               // for nsIEditor, etc.
-#include "nsIFrame.h"                // for nsBidiLevel
 #include "nsISelectionController.h"  // for nsISelectionController constants
 #include "nsISelectionListener.h"    // for nsISelectionListener
 #include "nsISupportsImpl.h"         // for EditorBase::Release, etc.
 #include "nsIWeakReferenceUtils.h"   // for nsWeakPtr
 #include "nsLiteralString.h"         // for NS_LITERAL_STRING
 #include "nsPIDOMWindow.h"           // for nsPIDOMWindowInner, etc.
 #include "nsString.h"                // for nsCString
 #include "nsTArray.h"                // for nsTArray and nsAutoTArray
@@ -1980,17 +1980,17 @@ class EditorBase : public nsIEditor,
 
     /**
      * MaybeUpdateCaretBidiLevel() may update caret bidi level and schedule to
      * paint it if they are necessary.
      */
     void MaybeUpdateCaretBidiLevel(const EditorBase& aEditorBase) const;
 
    private:
-    Maybe<nsBidiLevel> mNewCaretBidiLevel;
+    Maybe<mozilla::intl::Bidi::EmbeddingLevel> mNewCaretBidiLevel;
     bool mFailed = false;
     bool mCanceled = false;
   };
 
   /**
    * UndefineCaretBidiLevel() resets bidi level of the caret.
    */
   void UndefineCaretBidiLevel() const;
--- a/gfx/layers/NativeLayerCA.h
+++ b/gfx/layers/NativeLayerCA.h
@@ -8,16 +8,17 @@
 
 #include <IOSurface/IOSurface.h>
 
 #include <deque>
 #include <unordered_map>
 #include <ostream>
 
 #include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
 
 #include "mozilla/gfx/MacIOSurface.h"
 #include "mozilla/layers/NativeLayer.h"
 #include "CFTypeRefPtr.h"
 #include "nsRegion.h"
 #include "nsISupportsImpl.h"
 
 #ifdef __OBJC__
@@ -114,37 +115,42 @@ class NativeLayerRootCA : public NativeL
 
   void SetBackingScale(float aBackingScale);
   float BackingScale();
 
   already_AddRefed<NativeLayer> CreateLayerForExternalTexture(
       bool aIsOpaque) override;
 
   void SetWindowIsFullscreen(bool aFullscreen);
+  void NoteMouseMove();
+  void NoteMouseMoveAtTime(const TimeStamp& aTime);
 
  protected:
   explicit NativeLayerRootCA(CALayer* aLayer);
   ~NativeLayerRootCA() override;
 
   struct Representation {
     explicit Representation(CALayer* aRootCALayer);
     ~Representation();
     void Commit(WhichRepresentation aRepresentation,
                 const nsTArray<RefPtr<NativeLayerCA>>& aSublayers,
-                bool aWindowIsFullscreen);
+                bool aWindowIsFullscreen, bool aMouseMovedRecently);
     CALayer* FindVideoLayerToIsolate(
         WhichRepresentation aRepresentation,
         const nsTArray<RefPtr<NativeLayerCA>>& aSublayers);
     CALayer* mRootCALayer = nullptr;  // strong
-    bool mMutated = false;
+    bool mMutatedLayerStructure = false;
+    bool mMutatedMouseMovedRecently = false;
   };
 
   template <typename F>
   void ForAllRepresentations(F aFn);
 
+  void UpdateMouseMovedRecently();
+
   Mutex mMutex;  // protects all other fields
   Representation mOnscreenRepresentation;
   Representation mOffscreenRepresentation;
   NativeLayerRootSnapshotterCA* mWeakSnapshotter = nullptr;
   nsTArray<RefPtr<NativeLayerCA>> mSublayers;  // in z-order
   float mBackingScale = 1.0f;
   bool mMutated = false;
 
@@ -158,16 +164,22 @@ class NativeLayerRootCA : public NativeL
   // Set to false when CommitToScreen() completes successfully. When true,
   // indicates that CommitToScreen() needs to be called at the next available
   // opportunity.
   bool mCommitPending = false;
 
   // Updated by the layer's view's window to match the fullscreen state
   // of that window.
   bool mWindowIsFullscreen = false;
+
+  // Updated by the layer's view's window call to NoteMouseMoveAtTime().
+  TimeStamp mLastMouseMoveTime;
+
+  // Has the mouse recently moved?
+  bool mMouseMovedRecently = false;
 };
 
 class RenderSourceNLRS;
 
 class NativeLayerRootSnapshotterCA final : public NativeLayerRootSnapshotter {
  public:
   static UniquePtr<NativeLayerRootSnapshotterCA> Create(
       NativeLayerRootCA* aLayerRoot, CALayer* aRootCALayer);
--- a/gfx/layers/NativeLayerCA.mm
+++ b/gfx/layers/NativeLayerCA.mm
@@ -129,17 +129,19 @@ static CALayer* MakeOffscreenRootCALayer
   layer.masksToBounds = YES;
   layer.geometryFlipped = YES;
   return layer;
 }
 
 NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer)
     : mMutex("NativeLayerRootCA"),
       mOnscreenRepresentation(aLayer),
-      mOffscreenRepresentation(MakeOffscreenRootCALayer()) {}
+      mOffscreenRepresentation(MakeOffscreenRootCALayer()) {
+  NoteMouseMove();
+}
 
 NativeLayerRootCA::~NativeLayerRootCA() {
   MOZ_RELEASE_ASSERT(mSublayers.IsEmpty(),
                      "Please clear all layers before destroying the layer root.");
 }
 
 already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(
     const IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) {
@@ -157,27 +159,27 @@ void NativeLayerRootCA::AppendLayer(Nati
   MutexAutoLock lock(mMutex);
 
   RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
   MOZ_RELEASE_ASSERT(layerCA);
 
   mSublayers.AppendElement(layerCA);
   layerCA->SetBackingScale(mBackingScale);
   layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
-  ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
+  ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; });
 }
 
 void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) {
   MutexAutoLock lock(mMutex);
 
   RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
   MOZ_RELEASE_ASSERT(layerCA);
 
   mSublayers.RemoveElement(layerCA);
-  ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
+  ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; });
 }
 
 void NativeLayerRootCA::SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) {
   MutexAutoLock lock(mMutex);
 
   // Ideally, we'd just be able to do mSublayers = std::move(aLayers).
   // However, aLayers has a different type: it carries NativeLayer objects, whereas mSublayers
   // carries NativeLayerCA objects, so we have to downcast all the elements first. There's one other
@@ -190,17 +192,17 @@ void NativeLayerRootCA::SetLayers(const 
     MOZ_RELEASE_ASSERT(layerCA);
     layerCA->SetBackingScale(mBackingScale);
     layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
     layersCA.AppendElement(std::move(layerCA));
   }
 
   if (layersCA != mSublayers) {
     mSublayers = std::move(layersCA);
-    ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
+    ForAllRepresentations([&](Representation& r) { r.mMutatedLayerStructure = true; });
   }
 }
 
 void NativeLayerRootCA::SetBackingScale(float aBackingScale) {
   MutexAutoLock lock(mMutex);
 
   mBackingScale = aBackingScale;
   for (auto layer : mSublayers) {
@@ -233,17 +235,19 @@ bool NativeLayerRootCA::CommitToScreen()
   {
     MutexAutoLock lock(mMutex);
 
     if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) {
       mCommitPending = true;
       return false;
     }
 
-    mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers, mWindowIsFullscreen);
+    UpdateMouseMovedRecently();
+    mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers, mWindowIsFullscreen,
+                                   mMouseMovedRecently);
 
     mCommitPending = false;
   }
 
   if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) {
     static uint32_t sFrameID = 0;
     uint32_t frameID = sFrameID++;
 
@@ -281,73 +285,77 @@ void NativeLayerRootCA::OnNativeLayerRoo
     NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) {
   MutexAutoLock lock(mMutex);
   MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter);
   mWeakSnapshotter = nullptr;
 }
 
 void NativeLayerRootCA::CommitOffscreen() {
   MutexAutoLock lock(mMutex);
-  mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers, mWindowIsFullscreen);
+  mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers, mWindowIsFullscreen,
+                                  false);
 }
 
 template <typename F>
 void NativeLayerRootCA::ForAllRepresentations(F aFn) {
   aFn(mOnscreenRepresentation);
   aFn(mOffscreenRepresentation);
 }
 
 NativeLayerRootCA::Representation::Representation(CALayer* aRootCALayer)
     : mRootCALayer([aRootCALayer retain]) {}
 
 NativeLayerRootCA::Representation::~Representation() {
-  if (mMutated) {
+  if (mMutatedLayerStructure) {
     // Clear the root layer's sublayers. At this point the window is usually closed, so this
     // transaction does not cause any screen updates.
     AutoCATransaction transaction;
     mRootCALayer.sublayers = @[];
   }
 
   [mRootCALayer release];
 }
 
 void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentation,
                                                const nsTArray<RefPtr<NativeLayerCA>>& aSublayers,
-                                               bool aWindowIsFullscreen) {
-  if (!mMutated &&
+                                               bool aWindowIsFullscreen, bool aMouseMovedRecently) {
+  bool mightIsolate = (aRepresentation == WhichRepresentation::ONSCREEN &&
+                       StaticPrefs::gfx_core_animation_specialize_video());
+  bool mustRebuild = (mightIsolate && mMutatedMouseMovedRecently);
+
+  if (!mMutatedLayerStructure && !mustRebuild &&
       std::none_of(aSublayers.begin(), aSublayers.end(), [=](const RefPtr<NativeLayerCA>& layer) {
         return layer->HasUpdate(aRepresentation);
       })) {
     // No updates, skip creating the CATransaction altogether.
     return;
   }
 
   AutoCATransaction transaction;
 
   // Call ApplyChanges on our sublayers first, and then update the root layer's
   // list of sublayers. The order is important because we need layer->UnderlyingCALayer()
   // to be non-null, and the underlying CALayer gets lazily initialized in ApplyChanges().
   for (auto layer : aSublayers) {
     layer->ApplyChanges(aRepresentation);
   }
 
-  if (mMutated) {
+  if (mMutatedLayerStructure || mustRebuild) {
     NSMutableArray<CALayer*>* sublayers = [NSMutableArray arrayWithCapacity:aSublayers.Length()];
     for (auto layer : aSublayers) {
       [sublayers addObject:layer->UnderlyingCALayer(aRepresentation)];
     }
     mRootCALayer.sublayers = sublayers;
 
     // Now that we've set these layer relationships, we check to see if we should break
     // them and isolate a single video layer. It's important to do this *after* the
     // sublayers have been set, because we need the relationships there to do the
     // bounds checking of layer spaces against each other.
     // Bug 1731821 should eliminate this entire if block.
-    if (aWindowIsFullscreen && aRepresentation == WhichRepresentation::ONSCREEN &&
-        StaticPrefs::gfx_core_animation_specialize_video()) {
+    if (mightIsolate && aWindowIsFullscreen && !aMouseMovedRecently) {
       CALayer* isolatedLayer = FindVideoLayerToIsolate(aRepresentation, aSublayers);
       if (isolatedLayer) {
         // Create a full coverage black layer behind the isolated layer.
         CGFloat rootWidth = mRootCALayer.bounds.size.width;
         CGFloat rootHeight = mRootCALayer.bounds.size.height;
 
         // Reaching the low-power mode requires that there is a single black layer
         // covering the entire window behind the video layer. Create that layer.
@@ -355,19 +363,20 @@ void NativeLayerRootCA::Representation::
         blackLayer.position = NSZeroPoint;
         blackLayer.anchorPoint = NSZeroPoint;
         blackLayer.bounds = CGRectMake(0, 0, rootWidth, rootHeight);
         blackLayer.backgroundColor = [[NSColor blackColor] CGColor];
 
         mRootCALayer.sublayers = @[ blackLayer, isolatedLayer ];
       }
     }
+  }
 
-    mMutated = false;
-  }
+  mMutatedLayerStructure = false;
+  mMutatedMouseMovedRecently = false;
 }
 
 CALayer* NativeLayerRootCA::Representation::FindVideoLayerToIsolate(
     WhichRepresentation aRepresentation, const nsTArray<RefPtr<NativeLayerCA>>& aSublayers) {
   // Run a heuristic to determine if any one of aSublayers is a video layer that should be
   // isolated. These layers are ordered back-to-front. This function will return a candidate
   // CALayer if all of the following are true:
   // 1) The candidate layer is the topmost layer, and is a video layer.
@@ -475,21 +484,42 @@ void NativeLayerRootCA::DumpLayerTreeToF
 
 void NativeLayerRootCA::SetWindowIsFullscreen(bool aFullscreen) {
   if (mWindowIsFullscreen != aFullscreen) {
     mWindowIsFullscreen = aFullscreen;
 
     for (auto layer : mSublayers) {
       layer->SetRootWindowIsFullscreen(mWindowIsFullscreen);
     }
+
+    // Treat this as a mouse move, for purposes of resetting our timer.
+    NoteMouseMove();
+
     PrepareForCommit();
     CommitToScreen();
   }
 }
 
+void NativeLayerRootCA::NoteMouseMove() { mLastMouseMoveTime = TimeStamp::NowLoRes(); }
+
+void NativeLayerRootCA::NoteMouseMoveAtTime(const TimeStamp& aTime) { mLastMouseMoveTime = aTime; }
+
+void NativeLayerRootCA::UpdateMouseMovedRecently() {
+  static const double SECONDS_TO_WAIT = 2.0;
+
+  bool newMouseMovedRecently =
+      ((TimeStamp::NowLoRes() - mLastMouseMoveTime).ToSeconds() < SECONDS_TO_WAIT);
+
+  if (newMouseMovedRecently != mMouseMovedRecently) {
+    mMouseMovedRecently = newMouseMovedRecently;
+
+    ForAllRepresentations([&](Representation& r) { r.mMutatedMouseMovedRecently = true; });
+  }
+}
+
 NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(NativeLayerRootCA* aLayerRoot,
                                                            RefPtr<GLContext>&& aGL,
                                                            CALayer* aRootCALayer)
     : mLayerRoot(aLayerRoot), mGL(aGL) {
   AutoCATransaction transaction;
   mRenderer = [[CARenderer rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext()
                                           options:nil] retain];
   mRenderer.layer = aRootCALayer;
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -592,17 +592,16 @@ struct DIGroup {
             *mKey, ViewAs<ImagePixel>(mVisibleRect,
                                       PixelCastJustification::LayerIsImage));
         mLastVisibleRect = mVisibleRect;
         PushImage(aBuilder, itemBounds);
       }
       return;
     }
 
-    gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
     std::vector<RefPtr<ScaledFont>> fonts;
     bool validFonts = true;
     RefPtr<WebRenderDrawEventRecorder> recorder =
         MakeAndAddRef<WebRenderDrawEventRecorder>(
             [&](MemStream& aStream,
                 std::vector<RefPtr<ScaledFont>>& aScaledFonts) {
               size_t count = aScaledFonts.size();
               aStream.write((const char*)&count, sizeof(count));
@@ -615,18 +614,18 @@ struct DIGroup {
                   break;
                 }
                 BlobFont font = {key.value(), scaled};
                 aStream.write((const char*)&font, sizeof(font));
               }
               fonts = std::move(aScaledFonts);
             });
 
-    RefPtr<gfx::DrawTarget> dummyDt = gfx::Factory::CreateDrawTarget(
-        gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
+    RefPtr<gfx::DrawTarget> dummyDt =
+        gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
 
     RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(
         recorder, dummyDt, mLayerBounds.ToUnknownRect());
     // Setup the gfxContext
     RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
     context->SetMatrix(
         Matrix::Scaling(mScale.width, mScale.height)
             .PostTranslate(mResidualOffset.x, mResidualOffset.y));
@@ -1087,25 +1086,24 @@ static bool IsItemProbablyActive(
   switch (aItem->GetType()) {
     case DisplayItemType::TYPE_TRANSFORM: {
       nsDisplayTransform* transformItem =
           static_cast<nsDisplayTransform*>(aItem);
       const Matrix4x4Flagged& t = transformItem->GetTransform();
       Matrix t2d;
       bool is2D = t.Is2D(&t2d);
       GP("active: %d\n", transformItem->MayBeAnimated(aDisplayListBuilder));
-      return transformItem->MayBeAnimated(aDisplayListBuilder, false) ||
-             !is2D ||
+      return transformItem->MayBeAnimated(aDisplayListBuilder) || !is2D ||
              HasActiveChildren(*transformItem->GetChildren(), aBuilder,
                                aResources, aSc, aManager, aDisplayListBuilder);
     }
     case DisplayItemType::TYPE_OPACITY: {
       nsDisplayOpacity* opacityItem = static_cast<nsDisplayOpacity*>(aItem);
       bool active = opacityItem->NeedsActiveLayer(aDisplayListBuilder,
-                                                  opacityItem->Frame(), false);
+                                                  opacityItem->Frame());
       GP("active: %d\n", active);
       return active ||
              HasActiveChildren(*opacityItem->GetChildren(), aBuilder,
                                aResources, aSc, aManager, aDisplayListBuilder);
     }
     case DisplayItemType::TYPE_FOREIGN_OBJECT: {
       return true;
     }
--- a/image/SourceSurfaceBlobImage.cpp
+++ b/image/SourceSurfaceBlobImage.cpp
@@ -164,18 +164,18 @@ Maybe<BlobImageKeyData> SourceSurfaceBlo
               }
               BlobFont font = {key.value(), scaled};
               aStream.write((const char*)&font, sizeof(font));
             }
 
             fonts = std::move(aScaledFonts);
           });
 
-  RefPtr<DrawTarget> dummyDt = Factory::CreateDrawTarget(
-      BackendType::SKIA, IntSize(1, 1), SurfaceFormat::OS_RGBA);
+  RefPtr<DrawTarget> dummyDt =
+      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   RefPtr<DrawTarget> dt =
       Factory::CreateRecordingDrawTarget(recorder, dummyDt, imageRectOrigin);
 
   if (!dt || !dt->IsValid()) {
     return Nothing();
   }
 
   bool contextPaint = mSVGContext && mSVGContext->GetContextPaint();
new file mode 100644
--- /dev/null
+++ b/intl/components/gtest/TestBidi.cpp
@@ -0,0 +1,278 @@
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/intl/Bidi.h"
+#include "mozilla/Span.h"
+namespace mozilla::intl {
+
+struct VisualRun {
+  Span<const char16_t> string;
+  Bidi::Direction direction;
+};
+
+/**
+ * An iterator for visual runs in a paragraph. See Bug 1736597 for integrating
+ * this into the public API.
+ */
+class MOZ_STACK_CLASS VisualRunIter {
+ public:
+  VisualRunIter(Bidi& aBidi, Span<const char16_t> aParagraph,
+                Bidi::EmbeddingLevel aLevel)
+      : mBidi(aBidi), mParagraph(aParagraph) {
+    // Crash in case of errors by calling unwrap. If this were a real API, this
+    // would be a TryCreate call.
+    mBidi.SetParagraph(aParagraph, aLevel).unwrap();
+    mRunCount = mBidi.CountRuns().unwrap();
+  }
+
+  Maybe<VisualRun> Next() {
+    if (mRunIndex >= mRunCount) {
+      return Nothing();
+    }
+
+    int32_t stringIndex = -1;
+    int32_t stringLength = -1;
+
+    Bidi::Direction direction =
+        mBidi.GetVisualRun(mRunIndex, &stringIndex, &stringLength);
+
+    Span<const char16_t> string(mParagraph.Elements() + stringIndex,
+                                stringLength);
+    mRunIndex++;
+    return Some(VisualRun{string, direction});
+  }
+
+ private:
+  Bidi& mBidi;
+  Span<const char16_t> mParagraph = Span<const char16_t>();
+  int32_t mRunIndex = 0;
+  int32_t mRunCount = 0;
+};
+
+struct LogicalRun {
+  Span<const char16_t> string;
+  Bidi::EmbeddingLevel embeddingLevel;
+};
+
+/**
+ * An iterator for logical runs in a paragraph. See Bug 1736597 for integrating
+ * this into the public API.
+ */
+class MOZ_STACK_CLASS LogicalRunIter {
+ public:
+  LogicalRunIter(Bidi& aBidi, Span<const char16_t> aParagraph,
+                 Bidi::EmbeddingLevel aLevel)
+      : mBidi(aBidi), mParagraph(aParagraph) {
+    // Crash in case of errors by calling unwrap. If this were a real API, this
+    // would be a TryCreate call.
+    mBidi.SetParagraph(aParagraph, aLevel).unwrap();
+    mBidi.CountRuns().unwrap();
+  }
+
+  Maybe<LogicalRun> Next() {
+    if (mRunIndex >= static_cast<int32_t>(mParagraph.Length())) {
+      return Nothing();
+    }
+
+    int32_t logicalLimit;
+
+    Bidi::EmbeddingLevel embeddingLevel;
+    mBidi.GetLogicalRun(mRunIndex, &logicalLimit, &embeddingLevel);
+
+    Span<const char16_t> string(mParagraph.Elements() + mRunIndex,
+                                logicalLimit - mRunIndex);
+
+    mRunIndex = logicalLimit;
+    return Some(LogicalRun{string, embeddingLevel});
+  }
+
+ private:
+  Bidi& mBidi;
+  Span<const char16_t> mParagraph = Span<const char16_t>();
+  int32_t mRunIndex = 0;
+};
+
+TEST(IntlBidi, SimpleLTR)
+{
+  Bidi bidi{};
+  LogicalRunIter logicalRunIter(bidi, MakeStringSpan(u"this is a paragraph"),
+                                Bidi::EmbeddingLevel::DefaultLTR());
+  ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0);
+  ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::LTR);
+
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"this is a paragraph"));
+    ASSERT_EQ(logicalRun->embeddingLevel, 0);
+    ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::LTR);
+  }
+
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isNothing());
+  }
+}
+
+TEST(IntlBidi, SimpleRTL)
+{
+  Bidi bidi{};
+  LogicalRunIter logicalRunIter(bidi, MakeStringSpan(u"فايرفوكس رائع"),
+                                Bidi::EmbeddingLevel::DefaultLTR());
+  ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 1);
+  ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::RTL);
+
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"فايرفوكس رائع"));
+    ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::RTL);
+    ASSERT_EQ(logicalRun->embeddingLevel, 1);
+  }
+
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isNothing());
+  }
+}
+
+TEST(IntlBidi, MultiLevel)
+{
+  Bidi bidi{};
+  LogicalRunIter logicalRunIter(
+      bidi, MakeStringSpan(u"Firefox is awesome: رائع Firefox"),
+      Bidi::EmbeddingLevel::DefaultLTR());
+  ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0);
+  ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::Mixed);
+
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"Firefox is awesome: "));
+    ASSERT_EQ(logicalRun->embeddingLevel, 0);
+  }
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"رائع"));
+    ASSERT_EQ(logicalRun->embeddingLevel, 1);
+  }
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u" Firefox"));
+    ASSERT_EQ(logicalRun->embeddingLevel, 0);
+  }
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isNothing());
+  }
+}
+
+TEST(IntlBidi, RtlOverride)
+{
+  Bidi bidi{};
+  // Set the paragraph using the RTL embedding mark U+202B, and the LTR
+  // embedding mark U+202A to increase the embedding level. This mark switches
+  // the weakly directional character "_". This demonstrates that embedding
+  // levels can be computed.
+  LogicalRunIter logicalRunIter(
+      bidi, MakeStringSpan(u"ltr\u202b___رائع___\u202a___ltr__"),
+      Bidi::EmbeddingLevel::DefaultLTR());
+  ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0);
+  ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::Mixed);
+
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"ltr"));
+    ASSERT_EQ(logicalRun->embeddingLevel, 0);
+    ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::LTR);
+  }
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"\u202b___رائع___"));
+    ASSERT_EQ(logicalRun->embeddingLevel, 1);
+    ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::RTL);
+  }
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isSome());
+    ASSERT_EQ(logicalRun->string, MakeStringSpan(u"\u202a___ltr__"));
+    ASSERT_EQ(logicalRun->embeddingLevel, 2);
+    ASSERT_EQ(logicalRun->embeddingLevel.Direction(), Bidi::Direction::LTR);
+  }
+  {
+    auto logicalRun = logicalRunIter.Next();
+    ASSERT_TRUE(logicalRun.isNothing());
+  }
+}
+
+TEST(IntlBidi, VisualRuns)
+{
+  Bidi bidi{};
+
+  VisualRunIter visualRunIter(
+      bidi,
+      MakeStringSpan(
+          u"first visual run التشغيل البصري الثاني third visual run"),
+      Bidi::EmbeddingLevel::DefaultLTR());
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isSome());
+    ASSERT_EQ(run->string, MakeStringSpan(u"first visual run "));
+    ASSERT_EQ(run->direction, Bidi::Direction::LTR);
+  }
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isSome());
+    ASSERT_EQ(run->string, MakeStringSpan(u"التشغيل البصري الثاني"));
+    ASSERT_EQ(run->direction, Bidi::Direction::RTL);
+  }
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isSome());
+    ASSERT_EQ(run->string, MakeStringSpan(u" third visual run"));
+    ASSERT_EQ(run->direction, Bidi::Direction::LTR);
+  }
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isNothing());
+  }
+}
+
+TEST(IntlBidi, VisualRunsWithEmbeds)
+{
+  // Compare this test to the logical order test.
+  Bidi bidi{};
+  VisualRunIter visualRunIter(
+      bidi, MakeStringSpan(u"ltr\u202b___رائع___\u202a___ltr___"),
+      Bidi::EmbeddingLevel::DefaultLTR());
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isSome());
+    ASSERT_EQ(run->string, MakeStringSpan(u"ltr"));
+    ASSERT_EQ(run->direction, Bidi::Direction::LTR);
+  }
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isSome());
+    ASSERT_EQ(run->string, MakeStringSpan(u"\u202a___ltr___"));
+    ASSERT_EQ(run->direction, Bidi::Direction::LTR);
+  }
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isSome());
+    ASSERT_EQ(run->string, MakeStringSpan(u"\u202b___رائع___"));
+    ASSERT_EQ(run->direction, Bidi::Direction::RTL);
+  }
+  {
+    Maybe<VisualRun> run = visualRunIter.Next();
+    ASSERT_TRUE(run.isNothing());
+  }
+}
+
+}  // namespace mozilla::intl
new file mode 100644
--- /dev/null
+++ b/intl/components/gtest/TestDateIntervalFormat.cpp
@@ -0,0 +1,193 @@
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/intl/DateIntervalFormat.h"
+#include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePart.h"
+#include "mozilla/Span.h"
+
+#include "unicode/uformattedvalue.h"
+
+#include "TestBuffer.h"
+
+namespace mozilla::intl {
+
+const double DATE201901030000GMT = 1546473600000.0;
+const double DATE201901050000GMT = 1546646400000.0;
+
+TEST(IntlDateIntervalFormat, TryFormatDateTime)
+{
+  UniquePtr<DateIntervalFormat> dif =
+      DateIntervalFormat::TryCreate(MakeStringSpan("en-US"),
+                                    MakeStringSpan(u"MMddHHmm"),
+                                    MakeStringSpan(u"GMT"))
+          .unwrap();
+
+  AutoFormattedDateInterval formatted;
+
+  // Pass two same Date time, 'equal' should be true.
+  bool equal;
+  auto result = dif->TryFormatDateTime(DATE201901030000GMT, DATE201901030000GMT,
+                                       formatted, &equal);
+  ASSERT_TRUE(result.isOk());
+  ASSERT_TRUE(equal);
+
+  auto spanResult = formatted.ToSpan();
+  ASSERT_TRUE(spanResult.isOk());
+
+  ASSERT_EQ(spanResult.unwrap(), MakeStringSpan(u"01/03, 00:00"));
+
+  result = dif->TryFormatDateTime(DATE201901030000GMT, DATE201901050000GMT,
+                                  formatted, &equal);
+  ASSERT_TRUE(result.isOk());
+  ASSERT_FALSE(equal);
+
+  spanResult = formatted.ToSpan();
+  ASSERT_TRUE(spanResult.isOk());
+  ASSERT_EQ(spanResult.unwrap(),
+            MakeStringSpan(u"01/03, 00:00 – 01/05, 00:00"));
+}
+
+TEST(IntlDateIntervalFormat, TryFormatCalendar)
+{
+  auto dateTimePatternGenerator =
+      DateTimePatternGenerator::TryCreate("en").unwrap();
+
+  UniquePtr<DateTimeFormat> dtFormat =
+      DateTimeFormat::TryCreateFromSkeleton(
+          MakeStringSpan("en-US"), MakeStringSpan(u"yMMddHHmm"),
+          dateTimePatternGenerator.get(), Nothing(),
+          Some(MakeStringSpan(u"GMT")))
+          .unwrap();
+
+  UniquePtr<DateIntervalFormat> dif =
+      DateIntervalFormat::TryCreate(MakeStringSpan("en-US"),
+                                    MakeStringSpan(u"MMddHHmm"),
+                                    MakeStringSpan(u"GMT"))
+          .unwrap();
+
+  AutoFormattedDateInterval formatted;
+
+  // Two Calendar objects with the same date time.
+  auto sameCal = dtFormat->CloneCalendar(DATE201901030000GMT);
+  ASSERT_TRUE(sameCal.isOk());
+
+  auto cal = sameCal.unwrap();
+  bool equal;
+  auto result = dif->TryFormatCalendar(*cal, *cal, formatted, &equal);
+  ASSERT_TRUE(result.isOk());
+  ASSERT_TRUE(equal);
+
+  auto spanResult = formatted.ToSpan();
+  ASSERT_TRUE(spanResult.isOk());
+  ASSERT_EQ(spanResult.unwrap(), MakeStringSpan(u"01/03, 00:00"));
+
+  auto startCal = dtFormat->CloneCalendar(DATE201901030000GMT);
+  ASSERT_TRUE(startCal.isOk());
+  auto endCal = dtFormat->CloneCalendar(DATE201901050000GMT);
+  ASSERT_TRUE(endCal.isOk());
+
+  result = dif->TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(),
+                                  formatted, &equal);
+  ASSERT_TRUE(result.isOk());
+  ASSERT_FALSE(equal);
+
+  spanResult = formatted.ToSpan();
+  ASSERT_TRUE(spanResult.isOk());
+  ASSERT_EQ(spanResult.unwrap(),
+            MakeStringSpan(u"01/03, 00:00 – 01/05, 00:00"));
+}
+
+TEST(IntlDateIntervalFormat, TryFormattedToParts)
+{
+  UniquePtr<DateIntervalFormat> dif =
+      DateIntervalFormat::TryCreate(MakeStringSpan("en-US"),
+                                    MakeStringSpan(u"MMddHHmm"),
+                                    MakeStringSpan(u"GMT"))
+          .unwrap();
+
+  AutoFormattedDateInterval formatted;
+  bool equal;
+  auto result = dif->TryFormatDateTime(DATE201901030000GMT, DATE201901050000GMT,
+                                       formatted, &equal);
+  ASSERT_TRUE(result.isOk());
+  ASSERT_FALSE(equal);
+
+  Span<const char16_t> formattedSpan = formatted.ToSpan().unwrap();
+  ASSERT_EQ(formattedSpan, MakeStringSpan(u"01/03, 00:00 – 01/05, 00:00"));
+
+  mozilla::intl::DateTimePartVector parts;
+  result = dif->TryFormattedToParts(formatted, parts);
+  ASSERT_TRUE(result.isOk());
+
+  auto getSubSpan = [formattedSpan, &parts](size_t index) {
+    size_t start = index == 0 ? 0 : parts[index - 1].mEndIndex;
+    size_t end = parts[index].mEndIndex;
+    return formattedSpan.FromTo(start, end);
+  };
+
+  ASSERT_EQ(parts[0].mType, DateTimePartType::Month);
+  ASSERT_EQ(getSubSpan(0), MakeStringSpan(u"01"));
+  ASSERT_EQ(parts[0].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[1].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(1), MakeStringSpan(u"/"));
+  ASSERT_EQ(parts[1].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[2].mType, DateTimePartType::Day);
+  ASSERT_EQ(getSubSpan(2), MakeStringSpan(u"03"));
+  ASSERT_EQ(parts[2].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[3].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(3), MakeStringSpan(u", "));
+  ASSERT_EQ(parts[3].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[4].mType, DateTimePartType::Hour);
+  ASSERT_EQ(getSubSpan(4), MakeStringSpan(u"00"));
+  ASSERT_EQ(parts[4].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[5].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(5), MakeStringSpan(u":"));
+  ASSERT_EQ(parts[5].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[6].mType, DateTimePartType::Minute);
+  ASSERT_EQ(getSubSpan(6), MakeStringSpan(u"00"));
+  ASSERT_EQ(parts[6].mSource, DateTimePartSource::StartRange);
+
+  ASSERT_EQ(parts[7].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(7), MakeStringSpan(u" – "));
+  ASSERT_EQ(parts[7].mSource, DateTimePartSource::Shared);
+
+  ASSERT_EQ(parts[8].mType, DateTimePartType::Month);
+  ASSERT_EQ(getSubSpan(8), MakeStringSpan(u"01"));
+  ASSERT_EQ(parts[8].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts[9].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(9), MakeStringSpan(u"/"));
+  ASSERT_EQ(parts[9].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts[10].mType, DateTimePartType::Day);
+  ASSERT_EQ(getSubSpan(10), MakeStringSpan(u"05"));
+  ASSERT_EQ(parts[10].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts[11].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(11), MakeStringSpan(u", "));
+  ASSERT_EQ(parts[11].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts[12].mType, DateTimePartType::Hour);
+  ASSERT_EQ(getSubSpan(12), MakeStringSpan(u"00"));
+  ASSERT_EQ(parts[12].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts[13].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubSpan(13), MakeStringSpan(u":"));
+  ASSERT_EQ(parts[13].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts[14].mType, DateTimePartType::Minute);
+  ASSERT_EQ(getSubSpan(14), MakeStringSpan(u"00"));
+  ASSERT_EQ(parts[14].mSource, DateTimePartSource::EndRange);
+
+  ASSERT_EQ(parts.length(), 15u);
+}
+}  // namespace mozilla::intl
--- a/intl/components/gtest/TestDateTimeFormat.cpp
+++ b/intl/components/gtest/TestDateTimeFormat.cpp
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "gtest/gtest.h"
 
 #include "mozilla/intl/Calendar.h"
 #include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePart.h"
 #include "mozilla/intl/DateTimePatternGenerator.h"
 #include "mozilla/Span.h"
 #include "TestBuffer.h"
 
 #include <string_view>
 
 namespace mozilla::intl {
 
@@ -532,9 +533,64 @@ TEST(IntlDateTimeFormat, GetAvailableLoc
   }
 
   // Each locale should be found exactly once.
   ASSERT_EQ(english, 1);
   ASSERT_EQ(german, 1);
   ASSERT_EQ(chinese, 1);
 }
 
+TEST(IntlDateTimeFormat, TryFormatToParts)
+{
+  auto dateTimePatternGenerator =
+      DateTimePatternGenerator::TryCreate("en").unwrap();
+
+  UniquePtr<DateTimeFormat> dtFormat =
+      DateTimeFormat::TryCreateFromSkeleton(
+          MakeStringSpan("en-US"), MakeStringSpan(u"yMMddHHmm"),
+          dateTimePatternGenerator.get(), Nothing(),
+          Some(MakeStringSpan(u"GMT")))
+          .unwrap();
+
+  TestBuffer<char16_t> buffer;
+  mozilla::intl::DateTimePartVector parts;
+  auto result = dtFormat->TryFormatToParts(DATE, buffer, parts);
+  ASSERT_TRUE(result.isOk());
+
+  std::u16string_view strView = buffer.get_string_view();
+  ASSERT_EQ(strView, u"09/23/2002, 17:07");
+
+  auto getSubStringView = [strView, &parts](size_t index) {
+    size_t pos = index == 0 ? 0 : parts[index - 1].mEndIndex;
+    size_t count = parts[index].mEndIndex - pos;
+    return strView.substr(pos, count);
+  };
+
+  ASSERT_EQ(parts[0].mType, DateTimePartType::Month);
+  ASSERT_EQ(getSubStringView(0), u"09");
+
+  ASSERT_EQ(parts[1].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubStringView(1), u"/");
+
+  ASSERT_EQ(parts[2].mType, DateTimePartType::Day);
+  ASSERT_EQ(getSubStringView(2), u"23");
+
+  ASSERT_EQ(parts[3].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubStringView(3), u"/");
+
+  ASSERT_EQ(parts[4].mType, DateTimePartType::Year);
+  ASSERT_EQ(getSubStringView(4), u"2002");
+
+  ASSERT_EQ(parts[5].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubStringView(5), u", ");
+
+  ASSERT_EQ(parts[6].mType, DateTimePartType::Hour);
+  ASSERT_EQ(getSubStringView(6), u"17");
+
+  ASSERT_EQ(parts[7].mType, DateTimePartType::Literal);
+  ASSERT_EQ(getSubStringView(7), u":");
+
+  ASSERT_EQ(parts[8].mType, DateTimePartType::Minute);
+  ASSERT_EQ(getSubStringView(8), u"07");
+
+  ASSERT_EQ(parts.length(), 9u);
+}
 }  // namespace mozilla::intl
--- a/intl/components/gtest/moz.build
+++ b/intl/components/gtest/moz.build
@@ -1,18 +1,20 @@
 # -*- 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/.
 
 UNIFIED_SOURCES += [
+    "TestBidi.cpp",
     "TestCalendar.cpp",
     "TestCollator.cpp",
     "TestCurrency.cpp",
+    "TestDateIntervalFormat.cpp",
     "TestDateTimeFormat.cpp",
     "TestListFormat.cpp",
     "TestLocale.cpp",
     "TestLocaleCanonicalizer.cpp",
     "TestMeasureUnit.cpp",
     "TestNumberFormat.cpp",
     "TestNumberingSystem.cpp",
     "TestPluralRules.cpp",
--- a/intl/components/moz.build
+++ b/intl/components/moz.build
@@ -1,18 +1,21 @@
 # -*- 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/.
 EXPORTS.mozilla.intl = [
+    "src/Bidi.h",
     "src/Calendar.h",
     "src/Collator.h",
     "src/Currency.h",
+    "src/DateIntervalFormat.h",
     "src/DateTimeFormat.h",
+    "src/DateTimePart.h",
     "src/DateTimePatternGenerator.h",
     "src/ICU4CGlue.h",
     "src/ICU4CLibrary.h",
     "src/ICUError.h",
     "src/ListFormat.h",
     "src/Locale.h",
     "src/LocaleCanonicalizer.h",
     "src/MeasureUnit.h",
@@ -22,20 +25,23 @@ EXPORTS.mozilla.intl = [
     "src/NumberRangeFormat.h",
     "src/PluralRules.h",
     "src/RelativeTimeFormat.h",
     "src/String.h",
     "src/TimeZone.h",
 ]
 
 UNIFIED_SOURCES += [
+    "src/Bidi.cpp",
     "src/Calendar.cpp",
     "src/Collator.cpp",
     "src/Currency.cpp",
+    "src/DateIntervalFormat.cpp",
     "src/DateTimeFormat.cpp",
+    "src/DateTimeFormatUtils.cpp",
     "src/DateTimePatternGenerator.cpp",
     "src/ICU4CGlue.cpp",
     "src/ICU4CLibrary.cpp",
     "src/ListFormat.cpp",
     "src/Locale.cpp",
     "src/LocaleCanonicalizer.cpp",
     "src/LocaleGenerated.cpp",
     "src/MeasureUnit.cpp",
new file mode 100644
--- /dev/null
+++ b/intl/components/src/Bidi.cpp
@@ -0,0 +1,163 @@
+/* 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/intl/Bidi.h"
+#include "mozilla/Casting.h"
+#include "mozilla/intl/ICU4CGlue.h"
+
+#include "unicode/ubidi.h"
+
+namespace mozilla::intl {
+
+Bidi::Bidi() { mBidi = ubidi_open(); }
+Bidi::~Bidi() { ubidi_close(mBidi.GetMut()); }
+
+ICUResult Bidi::SetParagraph(Span<const char16_t> aParagraph,
+                             Bidi::EmbeddingLevel aLevel) {
+  // Do not allow any reordering of the runs, as this can change the
+  // performance characteristics of working with runs. In the default mode,
+  // the levels can be iterated over directly, rather than relying on computing
+  // logical runs on the fly. This can have negative performance characteristics
+  // compared to iterating over the levels.
+  //
+  // In the UBIDI_REORDER_RUNS_ONLY the levels are encoded with additional
+  // information which can be safely ignored in this Bidi implementation.
+  // Note that this check is here since setting the mode must be done before
+  // calls to setting the paragraph.
+  MOZ_ASSERT(ubidi_getReorderingMode(mBidi.GetMut()) == UBIDI_REORDER_DEFAULT);
+
+  UErrorCode status = U_ZERO_ERROR;
+  ubidi_setPara(mBidi.GetMut(), aParagraph.Elements(),
+                AssertedCast<int32_t>(aParagraph.Length()), aLevel, nullptr,
+                &status);
+
+  mLevels = nullptr;
+
+  return ToICUResult(status);
+}
+
+Bidi::ParagraphDirection Bidi::GetParagraphDirection() const {
+  switch (ubidi_getDirection(mBidi.GetConst())) {
+    case UBIDI_LTR:
+      return Bidi::ParagraphDirection::LTR;
+    case UBIDI_RTL:
+      return Bidi::ParagraphDirection::RTL;
+    case UBIDI_MIXED:
+      return Bidi::ParagraphDirection::Mixed;
+    case UBIDI_NEUTRAL:
+      // This is only used in `ubidi_getBaseDirection` which is unused in this
+      // API.
+      MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value.");
+  };
+  return Bidi::ParagraphDirection::Mixed;
+}
+
+/* static */
+void Bidi::ReorderVisual(const EmbeddingLevel* aLevels, int32_t aLength,
+                         int32_t* aIndexMap) {
+  ubidi_reorderVisual(reinterpret_cast<const uint8_t*>(aLevels), aLength,
+                      aIndexMap);
+}
+
+static Bidi::Direction ToBidiDirection(UBiDiDirection aDirection) {
+  switch (aDirection) {
+    case UBIDI_LTR:
+      return Bidi::Direction::LTR;
+    case UBIDI_RTL:
+      return Bidi::Direction::RTL;
+    case UBIDI_MIXED:
+    case UBIDI_NEUTRAL:
+      MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value.");
+  }
+  return Bidi::Direction::LTR;
+}
+
+Result<int32_t, ICUError> Bidi::CountRuns() {
+  UErrorCode status = U_ZERO_ERROR;
+  int32_t runCount = ubidi_countRuns(mBidi.GetMut(), &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  mLength = ubidi_getProcessedLength(mBidi.GetConst());
+  mLevels = mLength > 0 ? reinterpret_cast<const Bidi::EmbeddingLevel*>(
+                              ubidi_getLevels(mBidi.GetMut(), &status))
+                        : nullptr;
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return runCount;
+}
+
+void Bidi::GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimitOut,
+                         Bidi::EmbeddingLevel* aLevelOut) {
+  MOZ_ASSERT(mLevels, "CountRuns hasn't been run?");
+  MOZ_RELEASE_ASSERT(aLogicalStart < mLength, "Out of bound");
+  EmbeddingLevel level = mLevels[aLogicalStart];
+  int32_t limit;
+  for (limit = aLogicalStart + 1; limit < mLength; limit++) {
+    if (mLevels[limit] != level) {
+      break;
+    }
+  }
+  *aLogicalLimitOut = limit;
+  *aLevelOut = level;
+}
+
+bool Bidi::EmbeddingLevel::IsDefaultLTR() const {
+  return mValue == UBIDI_DEFAULT_LTR;
+};
+
+bool Bidi::EmbeddingLevel::IsDefaultRTL() const {
+  return mValue == UBIDI_DEFAULT_RTL;
+};
+
+bool Bidi::EmbeddingLevel::IsRTL() const {
+  // If the least significant bit is 1, then the embedding level
+  // is right-to-left.
+  // If the least significant bit is 0, then the embedding level
+  // is left-to-right.
+  return (mValue & 0x1) == 1;
+};
+
+bool Bidi::EmbeddingLevel::IsLTR() const { return !IsRTL(); };
+
+bool Bidi::EmbeddingLevel::IsSameDirection(EmbeddingLevel aOther) const {
+  return (((mValue ^ aOther) & 1) == 0);
+}
+
+Bidi::EmbeddingLevel Bidi::EmbeddingLevel::LTR() {
+  return Bidi::EmbeddingLevel(0);
+};
+
+Bidi::EmbeddingLevel Bidi::EmbeddingLevel::RTL() {
+  return Bidi::EmbeddingLevel(1);
+};
+
+Bidi::EmbeddingLevel Bidi::EmbeddingLevel::DefaultLTR() {
+  return Bidi::EmbeddingLevel(UBIDI_DEFAULT_LTR);
+};
+
+Bidi::EmbeddingLevel Bidi::EmbeddingLevel::DefaultRTL() {
+  return Bidi::EmbeddingLevel(UBIDI_DEFAULT_RTL);
+};
+
+Bidi::Direction Bidi::EmbeddingLevel::Direction() {
+  return IsRTL() ? Direction::RTL : Direction::LTR;
+};
+
+uint8_t Bidi::EmbeddingLevel::Value() const { return mValue; }
+
+Bidi::EmbeddingLevel Bidi::GetParagraphEmbeddingLevel() const {
+  return Bidi::EmbeddingLevel(ubidi_getParaLevel(mBidi.GetConst()));
+}
+
+Bidi::Direction Bidi::GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart,
+                                   int32_t* aLength) {
+  return ToBidiDirection(
+      ubidi_getVisualRun(mBidi.GetMut(), aRunIndex, aLogicalStart, aLength));
+}
+
+}  // namespace mozilla::intl
new file mode 100644
--- /dev/null
+++ b/intl/components/src/Bidi.h
@@ -0,0 +1,241 @@
+/* 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 intl_components_Bidi_h_
+#define intl_components_Bidi_h_
+
+#include "mozilla/intl/ICU4CGlue.h"
+
+struct UBiDi;
+
+namespace mozilla::intl {
+
+/**
+ * This component is a Mozilla-focused API for working with bidirectional (bidi)
+ * text. Text is commonly displayed left to right (LTR), especially for
+ * Latin-based alphabets. However, languages like Arabic and Hebrew displays
+ * text right to left (RTL). When displaying text, LTR and RTL text can be
+ * combined together in the same paragraph. This class gives tools for working
+ * with unidirectional, and mixed direction paragraphs.
+ *
+ * See the Unicode Bidirectional Algorithm document for implementation details:
+ * https://unicode.org/reports/tr9/
+ */
+class Bidi final {
+ public:
+  Bidi();
+  ~Bidi();
+
+  // Not copyable or movable
+  Bidi(const Bidi&) = delete;
+  Bidi& operator=(const Bidi&) = delete;
+
+  /**
+   * This enum unambiguously classifies text runs as either being left to right,
+   * or right to left.
+   */
+  enum class Direction : uint8_t {
+    // Left to right text.
+    LTR = 0,
+    // Right to left text.
+    RTL = 1,
+  };
+
+  /**
+   * This enum indicates the text direction for the set paragraph. Some
+   * paragraphs are unidirectional, where they only have one direction, or a
+   * paragraph could use both LTR and RTL. In this case the paragraph's
+   * direction would be mixed.
+   */
+  enum ParagraphDirection { LTR, RTL, Mixed };
+
+  /**
+   * Embedding levels are numbers that indicate how deeply the bidi text is
+   * embedded, and the direction of text on that embedding level. When switching
+   * between strongly LTR code points and strongly RTL code points the embedding
+   * level normally switches between an embedding level of 0 (LTR) and 1 (RTL).
+   * The only time the embedding level increases is if the embedding code points
+   * are used. This is the Left-to-Right Embedding (LRE) code point (U+202A), or
+   * the Right-to-Left Embedding (RLE) code point (U+202B). The minimum
+   * embedding level of text is zero, and the maximum explicit depth is 125.
+   *
+   * The most significant bit is reserved for additional meaning. It can be used
+   * to signify in certain APIs that the text should by default be LTR or RTL if
+   * no strongly directional code points are found.
+   *
+   * Bug 1736595: At the time of this writing, some places in Gecko code use a 1
+   * in the most significant bit to indicate that an embedding level has not
+   * been set. This leads to an ambiguous understanding of what the most
+   * significant bit actually means.
+   */
+  class EmbeddingLevel {
+   public:
+    explicit EmbeddingLevel(uint8_t aValue) : mValue(aValue) {}
+    explicit EmbeddingLevel(int aValue)
+        : mValue(static_cast<uint8_t>(aValue)) {}
+
+    EmbeddingLevel() = default;
+
+    // Enable the copy operators, but disable move as this is only a uint8_t.
+    EmbeddingLevel(const EmbeddingLevel& other) = default;
+    EmbeddingLevel& operator=(const EmbeddingLevel& other) = default;
+
+    /**
+     * Determine the direction of the embedding level by looking at the least
+     * significant bit. If it is 0, then it is LTR. If it is 1, then it is RTL.
+     */
+    Bidi::Direction Direction();
+
+    /**
+     * Create a left-to-right embedding level.
+     */
+    static EmbeddingLevel LTR();
+
+    /**
+     * Create an right-to-left embedding level.
+     */
+    static EmbeddingLevel RTL();
+
+    /**
+     * When passed into `SetParagraph`, the direction is determined by first
+     * strongly directional character, with the default set to left-to-right if
+     * none is found.
+     *
+     * This is encoded with the highest bit set to 1.
+     */
+    static EmbeddingLevel DefaultLTR();
+
+    /**
+     * When passed into `SetParagraph`, the direction is determined by first
+     * strongly directional character, with the default set to right-to-left if
+     * none is found.
+     *
+     * * This is encoded with the highest and lowest bits set to 1.
+     */
+    static EmbeddingLevel DefaultRTL();
+
+    bool IsDefaultLTR() const;
+    bool IsDefaultRTL() const;
+    bool IsLTR() const;
+    bool IsRTL() const;
+    bool IsSameDirection(EmbeddingLevel aOther) const;
+
+    /**
+     * Get the underlying value as a uint8_t.
+     */
+    uint8_t Value() const;
+
+    /**
+     * Implicitly convert to the underlying value.
+     */
+    operator uint8_t() const { return mValue; }
+
+   private:
+    uint8_t mValue = 0;
+  };
+
+  /**
+   * Set the current paragraph of text to analyze for its bidi properties. This
+   * performs the Unicode bidi algorithm as specified by:
+   * https://unicode.org/reports/tr9/
+   *
+   * After setting the text, the other getter methods can be used to find out
+   * the directionality of the paragraph text.
+   */
+  ICUResult SetParagraph(Span<const char16_t> aParagraph,
+                         EmbeddingLevel aLevel);
+
+  /**
+   * Get the embedding level for the paragraph that was set by SetParagraph.
+   */
+  EmbeddingLevel GetParagraphEmbeddingLevel() const;
+
+  /**
+   * Get the directionality of the paragraph text that was set by SetParagraph.
+   */
+  ParagraphDirection GetParagraphDirection() const;
+
+  /**
+   * Get the number of runs. This function may invoke the actual reordering on
+   * the Bidi object, after SetParagraph may have resolved only the levels of
+   * the text. Therefore, `CountRuns` may have to allocate memory, and may fail
+   * doing so.
+   */
+  Result<int32_t, ICUError> CountRuns();
+
+  /**
+   * Get the next logical run. The logical runs are a run of text that has the
+   * same directionality and embedding level. These runs are in memory order,
+   * and not in display order.
+   *
+   * Important! `Bidi::CountRuns` must be called before calling this method.
+   *
+   * @param aLogicalStart is the offset into the paragraph text that marks the
+   *      logical start of the text.
+   * @param aLogicalLimitOut is an out param that is the length of the string
+   *      that makes up the logical run.
+   * @param aLevelOut is an out parameter that returns the embedding level for
+   *      the run
+   */
+  void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimitOut,
+                     EmbeddingLevel* aLevelOut);
+
+  /**
+   * This is a convenience function that does not use the ICU Bidi object.
+   * It is intended to be used for when an application has determined the
+   * embedding levels of objects (character sequences) and just needs to have
+   * them reordered (L2).
+   *
+   * @param aLevels is an array with `aLength` levels that have been
+   *      determined by the application.
+   *
+   * @param aLength is the number of levels in the array, or, semantically,
+   *      the number of objects to be reordered. It must be greater than 0.
+   *
+   * @param aIndexMap is a pointer to an array of `aLength`
+   *      indexes which will reflect the reordering of the characters.
+   *      The array does not need to be initialized.
+   *      The index map will result in
+   *        `aIndexMap[aVisualIndex]==aLogicalIndex`.
+   */
+  static void ReorderVisual(const EmbeddingLevel* aLevels, int32_t aLength,
+                            int32_t* aIndexMap);
+
+  /**
+   * Get one run's logical start, length, and directionality. In an RTL run, the
+   * character at the logical start is visually on the right of the displayed
+   * run. The length is the number of characters in the run.
+   * `Bidi::CountRuns` should be called before the runs are retrieved.
+   *
+   * @param aRunIndex is the number of the run in visual order, in the
+   *      range `[0..CountRuns-1]`.
+   *
+   * @param aLogicalStart is the first logical character index in the text.
+   *      The pointer may be `nullptr` if this index is not needed.
+   *
+   * @param aLength is the number of characters (at least one) in the run.
+   *      The pointer may be `nullptr` if this is not needed.
+   *
+   * Note that in right-to-left runs, the code places modifier letters before
+   * base characters and second surrogates before first ones.
+   */
+  Direction GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart,
+                         int32_t* aLength);
+
+ private:
+  ICUPointer<UBiDi> mBidi = ICUPointer<UBiDi>(nullptr);
+
+  /**
+   * An array of levels that is the same length as the paragraph from
+   * `Bidi::SetParagraph`.
+   */
+  const EmbeddingLevel* mLevels = nullptr;
+
+  /**
+   * The length of the paragraph from `Bidi::SetParagraph`.
+   */
+  int32_t mLength = 0;
+};
+
+}  // namespace mozilla::intl
+#endif
--- a/intl/components/src/Calendar.h
+++ b/intl/components/src/Calendar.h
@@ -116,22 +116,18 @@ class Calendar final {
    * Return BCP 47 Unicode locale extension type keywords.
    */
   static Result<Bcp47IdentifierEnumeration, ICUError>
   GetBcp47KeywordValuesForLocale(const char* aLocale,
                                  CommonlyUsed aCommonlyUsed = CommonlyUsed::No);
 
   ~Calendar();
 
-  /**
-   * TODO(Bug 1686965) - Temporarily get the underlying ICU object while
-   * migrating to the unified API. This should be removed when completing the
-   * migration.
-   */
-  UCalendar* UnsafeGetUCalendar() const { return mCalendar; }
+ private:
+  friend class DateIntervalFormat;
+  UCalendar* GetUCalendar() const { return mCalendar; }
 
- private:
   UCalendar* mCalendar = nullptr;
 };
 
 }  // namespace mozilla::intl
 
 #endif
new file mode 100644
--- /dev/null
+++ b/intl/components/src/DateIntervalFormat.cpp
@@ -0,0 +1,287 @@
+/* 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 "unicode/udateintervalformat.h"
+
+#include "DateTimeFormatUtils.h"
+#include "ScopedICUObject.h"
+
+#include "mozilla/intl/Calendar.h"
+#include "mozilla/intl/DateIntervalFormat.h"
+
+namespace mozilla::intl {
+
+/**
+ * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), steps 9-11.
+ *
+ * Examine the formatted value to see if any interval span field is present.
+ *
+ * https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
+ */
+static ICUResult DateFieldsPracticallyEqual(
+    const UFormattedValue* aFormattedValue, bool* aEqual) {
+  if (!aFormattedValue) {
+    return Err(ICUError::InternalError);
+  }
+
+  MOZ_ASSERT(aEqual);
+  *aEqual = false;
+  UErrorCode status = U_ZERO_ERROR;
+  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
+
+  // We're only interested in UFIELD_CATEGORY_DATE_INTERVAL_SPAN fields.
+  ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  bool hasSpan = ufmtval_nextPosition(aFormattedValue, fpos, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  // When no date interval span field was found, both dates are "practically
+  // equal" per PartitionDateTimeRangePattern.
+  *aEqual = !hasSpan;
+  return Ok();
+}
+
+/* static */
+Result<UniquePtr<DateIntervalFormat>, ICUError> DateIntervalFormat::TryCreate(
+    Span<const char> aLocale, Span<const char16_t> aSkeleton,
+    Span<const char16_t> aTimeZone) {
+  UErrorCode status = U_ZERO_ERROR;
+  UDateIntervalFormat* dif = udtitvfmt_open(
+      IcuLocale(AssertNullTerminatedString(aLocale)), aSkeleton.data(),
+      AssertedCast<int32_t>(aSkeleton.size()), aTimeZone.data(),
+      AssertedCast<int32_t>(aTimeZone.size()), &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return UniquePtr<DateIntervalFormat>(new DateIntervalFormat(dif));
+}
+
+DateIntervalFormat::~DateIntervalFormat() {
+  MOZ_ASSERT(mDateIntervalFormat);
+  udtitvfmt_close(mDateIntervalFormat.GetMut());
+}
+
+AutoFormattedDateInterval::AutoFormattedDateInterval() {
+  mFormatted = udtitvfmt_openResult(&mError);
+  if (U_FAILURE(mError)) {
+    mFormatted = nullptr;
+  }
+}
+
+const UFormattedValue* AutoFormattedDateInterval::Value() const {
+  if (!IsValid()) {
+    return nullptr;
+  }
+
+  UErrorCode status = U_ZERO_ERROR;
+  const UFormattedValue* value = udtitvfmt_resultAsValue(mFormatted, &status);
+  if (U_FAILURE(status)) {
+    return nullptr;
+  }
+
+  return value;
+}
+
+Result<Span<const char16_t>, ICUError> AutoFormattedDateInterval::ToSpan()
+    const {
+  if (!IsValid()) {
+    return Err(GetError());
+  }
+
+  const UFormattedValue* value = Value();
+  if (!value) {
+    return Err(ICUError::InternalError);
+  }
+
+  UErrorCode status = U_ZERO_ERROR;
+  int32_t strLength;
+  const char16_t* str = ufmtval_getString(value, &strLength, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return Span{str, AssertedCast<size_t>(strLength)};
+}
+
+AutoFormattedDateInterval::~AutoFormattedDateInterval() {
+  if (mFormatted) {
+    udtitvfmt_closeResult(mFormatted);
+  }
+}
+
+ICUResult DateIntervalFormat::TryFormatCalendar(
+    const Calendar& aStart, const Calendar& aEnd,
+    AutoFormattedDateInterval& aFormatted, bool* aPracticallyEqual) const {
+  MOZ_ASSERT(aFormatted.IsValid());
+
+  UErrorCode status = U_ZERO_ERROR;
+  udtitvfmt_formatCalendarToResult(
+      mDateIntervalFormat.GetConst(), aStart.GetUCalendar(),
+      aEnd.GetUCalendar(), aFormatted.GetUFormattedDateInterval(), &status);
+
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual));
+  return Ok();
+}
+
+ICUResult DateIntervalFormat::TryFormatDateTime(
+    double aStart, double aEnd, AutoFormattedDateInterval& aFormatted,
+    bool* aPracticallyEqual) const {
+  MOZ_ASSERT(aFormatted.IsValid());
+
+  UErrorCode status = U_ZERO_ERROR;
+  udtitvfmt_formatToResult(mDateIntervalFormat.GetConst(), aStart, aEnd,
+                           aFormatted.GetUFormattedDateInterval(), &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  MOZ_TRY(DateFieldsPracticallyEqual(aFormatted.Value(), aPracticallyEqual));
+  return Ok();
+}
+
+ICUResult DateIntervalFormat::TryFormattedToParts(
+    const AutoFormattedDateInterval& aFormatted,
+    DateTimePartVector& aParts) const {
+  MOZ_ASSERT(aFormatted.IsValid());
+  const UFormattedValue* value = aFormatted.Value();
+  if (!value) {
+    return Err(ICUError::InternalError);
+  }
+
+  size_t lastEndIndex = 0;
+  auto AppendPart = [&](DateTimePartType type, size_t endIndex,
+                        DateTimePartSource source) {
+    if (!aParts.emplaceBack(type, endIndex, source)) {
+      return false;
+    }
+
+    lastEndIndex = endIndex;
+    return true;
+  };
+
+  UErrorCode status = U_ZERO_ERROR;
+  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
+
+  size_t categoryEndIndex = 0;
+  DateTimePartSource source = DateTimePartSource::Shared;
+
+  while (true) {
+    bool hasMore = ufmtval_nextPosition(value, fpos, &status);
+    if (U_FAILURE(status)) {
+      return Err(ToICUError(status));
+    }
+    if (!hasMore) {
+      break;
+    }
+
+    int32_t category = ucfpos_getCategory(fpos, &status);
+    if (U_FAILURE(status)) {
+      return Err(ToICUError(status));
+    }
+
+    int32_t field = ucfpos_getField(fpos, &status);
+    if (U_FAILURE(status)) {
+      return Err(ToICUError(status));
+    }
+
+    int32_t beginIndexInt, endIndexInt;
+    ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
+    if (U_FAILURE(status)) {
+      return Err(ToICUError(status));
+    }
+
+    MOZ_ASSERT(beginIndexInt <= endIndexInt,
+               "field iterator returning invalid range");
+
+    size_t beginIndex = AssertedCast<size_t>(beginIndexInt);
+    size_t endIndex = AssertedCast<size_t>(endIndexInt);
+
+    // Indices are guaranteed to be returned in order (from left to right).
+    MOZ_ASSERT(lastEndIndex <= beginIndex,
+               "field iteration didn't return fields in order start to "
+               "finish as expected");
+
+    if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
+      // Append any remaining literal parts before changing the source kind.
+      if (lastEndIndex < beginIndex) {
+        if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) {
+          return Err(ICUError::InternalError);
+        }
+      }
+
+      // The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN has only
+      // two allowed values (0 or 1), indicating the begin of the start- resp.
+      // end-date.
+      MOZ_ASSERT(field == 0 || field == 1,
+                 "span category has unexpected value");
+
+      source = field == 0 ? DateTimePartSource::StartRange
+                          : DateTimePartSource::EndRange;
+      categoryEndIndex = endIndex;
+      continue;
+    }
+
+    // Ignore categories other than UFIELD_CATEGORY_DATE.
+    if (category != UFIELD_CATEGORY_DATE) {
+      continue;
+    }
+
+    DateTimePartType type =
+        ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(field));
+    if (lastEndIndex < beginIndex) {
+      if (!AppendPart(DateTimePartType::Literal, beginIndex, source)) {
+        return Err(ICUError::InternalError);
+      }
+    }
+
+    if (!AppendPart(type, endIndex, source)) {
+      return Err(ICUError::InternalError);
+    }
+
+    if (endIndex == categoryEndIndex) {
+      // Append any remaining literal parts before changing the source kind.
+      if (lastEndIndex < endIndex) {
+        if (!AppendPart(DateTimePartType::Literal, endIndex, source)) {
+          return Err(ICUError::InternalError);
+        }
+      }
+
+      source = DateTimePartSource::Shared;
+    }
+  }
+
+  // Append any final literal.
+  auto spanResult = aFormatted.ToSpan();
+  if (spanResult.isErr()) {
+    return spanResult.propagateErr();
+  }
+  size_t formattedSize = spanResult.unwrap().size();
+  if (lastEndIndex < formattedSize) {
+    if (!AppendPart(DateTimePartType::Literal, formattedSize, source)) {
+      return Err(ICUError::InternalError);
+    }
+  }
+
+  return Ok();
+}
+
+}  // namespace mozilla::intl
new file mode 100644
--- /dev/null
+++ b/intl/components/src/DateIntervalFormat.h
@@ -0,0 +1,159 @@
+/* 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 intl_components_DateIntervalFormat_h_
+#define intl_components_DateIntervalFormat_h_
+
+#include "mozilla/intl/DateTimePart.h"
+#include "mozilla/intl/ICU4CGlue.h"
+#include "mozilla/intl/ICUError.h"
+#include "mozilla/Result.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+
+#include "unicode/utypes.h"
+
+struct UDateIntervalFormat;
+struct UFormattedDateInterval;
+struct UFormattedValue;
+
+namespace mozilla::intl {
+class AutoFormattedDateInterval;
+class Calendar;
+
+/**
+ * This component is a Mozilla-focused API for the date range formatting
+ * provided by ICU. This DateIntervalFormat class helps to format the range
+ * between two date-time values.
+ *
+ * https://tc39.es/ecma402/#sec-formatdatetimerange
+ * https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
+ */
+class DateIntervalFormat final {
+ public:
+  /**
+   * Create a DateIntervalFormat object from locale, skeleton and time zone.
+   * The format of skeleton can be found in [1].
+   *
+   * Note: Skeleton will be removed in the future.
+   *
+   * [1]: https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
+   */
+  static Result<UniquePtr<DateIntervalFormat>, ICUError> TryCreate(
+      Span<const char> aLocale, Span<const char16_t> aSkeleton,
+      Span<const char16_t> aTimeZone);
+
+  ~DateIntervalFormat();
+
+  /**
+   * Format a date-time range between two Calendar objects.
+   *
+   * DateIntervalFormat cannot be changed to use a proleptic Gregorian
+   * calendar, so use this method if the start date is before the Gregorian
+   * calendar is introduced(October 15, 1582), otherwise use TryFormatDateTime
+   * instead.
+   *
+   * The result will be stored in aFormatted, caller can use
+   * AutoFormattedDateInterval::ToSpan() to get the formatted string, or pass
+   * the aFormatted to TryFormattedToParts to get the parts vector.
+   *
+   * aPracticallyEqual will be set to true if the date times of the two
+   * calendars are equal.
+   */
+  ICUResult TryFormatCalendar(const Calendar& aStart, const Calendar& aEnd,
+                              AutoFormattedDateInterval& aFormatted,
+                              bool* aPracticallyEqual) const;
+
+  /**
+   * Format a date-time range between two Unix epoch times in milliseconds.
+   *
+   * The result will be stored in aFormatted, caller can use
+   * AutoFormattedDateInterval::ToSpan() to get the formatted string, or pass
+   * the aFormatted to TryFormattedToParts to get the parts vector.
+   *
+   * aPracticallyEqual will be set to true if the date times of the two
+   * Unix epoch times are equal.
+   */
+  ICUResult TryFormatDateTime(double aStart, double aEnd,
+                              AutoFormattedDateInterval& aFormatted,
+                              bool* aPracticallyEqual) const;
+
+  /**
+   *  Convert the formatted DateIntervalFormat into several parts.
+   *
+   *  The caller get the formatted result from either TryFormatCalendar, or
+   *  TryFormatDateTime methods, and instantiate the DateTimePartVector. This
+   *  method will generate the parts and insert them into the vector.
+   *
+   *  See:
+   *  https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
+   */
+  ICUResult TryFormattedToParts(const AutoFormattedDateInterval& aFormatted,
+                                DateTimePartVector& aParts) const;
+
+ private:
+  DateIntervalFormat() = delete;
+  explicit DateIntervalFormat(UDateIntervalFormat* aDif)
+      : mDateIntervalFormat(aDif) {}
+  DateIntervalFormat(const DateIntervalFormat&) = delete;
+  DateIntervalFormat& operator=(const DateIntervalFormat&) = delete;
+
+  ICUPointer<UDateIntervalFormat> mDateIntervalFormat =
+      ICUPointer<UDateIntervalFormat>(nullptr);
+};
+
+/**
+ * A RAII class to hold the formatted value of DateIntervalFormat.
+ *
+ * The caller will need to create this AutoFormattedDateInterval on the stack,
+ * and call IsValid() method to check if the native object
+ * (UFormattedDateInterval) has been created properly, and then passes this
+ * object to the methods of DateIntervalFormat.
+ * The result of the DateIntervalFormat's method will be stored in this object,
+ * the caller can use ToSpan() method to get the formatted string, or pass it
+ * to DateIntervalFormat::TryFormattedToParts to get the DateTimePart vector.
+ *
+ * The formatted value will be released once this class is destructed.
+ */
+class MOZ_RAII AutoFormattedDateInterval {
+ public:
+  AutoFormattedDateInterval();
+  ~AutoFormattedDateInterval();
+
+  AutoFormattedDateInterval(const AutoFormattedDateInterval& other) = delete;
+  AutoFormattedDateInterval& operator=(const AutoFormattedDateInterval& other) =
+      delete;
+
+  AutoFormattedDateInterval(AutoFormattedDateInterval&& other) = delete;
+  AutoFormattedDateInterval& operator=(AutoFormattedDateInterval&& other) =
+      delete;
+
+  /**
+   * Check if the native UFormattedDateInterval was created successfully.
+   */
+  bool IsValid() const { return !!mFormatted; }
+
+  /**
+   *  Get error code if IsValid() returns false.
+   */
+  ICUError GetError() const { return ToICUError(mError); }
+
+  /**
+   * Get the formatted result.
+   */
+  Result<Span<const char16_t>, ICUError> ToSpan() const;
+
+ private:
+  friend class DateIntervalFormat;
+  UFormattedDateInterval* GetUFormattedDateInterval() const {
+    return mFormatted;
+  }
+
+  const UFormattedValue* Value() const;
+
+  UFormattedDateInterval* mFormatted = nullptr;
+  UErrorCode mError = U_ZERO_ERROR;
+};
+}  // namespace mozilla::intl
+
+#endif
--- a/intl/components/src/DateTimeFormat.cpp
+++ b/intl/components/src/DateTimeFormat.cpp
@@ -4,16 +4,17 @@
 
 #include <cstring>
 
 #include "unicode/ucal.h"
 #include "unicode/udat.h"
 #include "unicode/udatpg.h"
 #include "unicode/ures.h"
 
+#include "DateTimeFormatUtils.h"
 #include "ScopedICUObject.h"
 
 #include "mozilla/EnumSet.h"
 #include "mozilla/intl/Calendar.h"
 #include "mozilla/intl/DateTimeFormat.h"
 #include "mozilla/intl/DateTimePatternGenerator.h"
 
 namespace mozilla::intl {
@@ -1104,9 +1105,67 @@ const char* DateTimeFormat::ToString(Dat
       return "h12";
     case HourCycle::H23:
       return "h23";
     case HourCycle::H24:
       return "h24";
   }
   MOZ_CRASH("Unexpected DateTimeFormat::HourCycle");
 }
+
+ICUResult DateTimeFormat::TryFormatToParts(
+    UFieldPositionIterator* aFieldPositionIterator, size_t aSpanSize,
+    DateTimePartVector& aParts) const {
+  ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(
+      aFieldPositionIterator);
+
+  size_t lastEndIndex = 0;
+  auto AppendPart = [&](DateTimePartType type, size_t endIndex) {
+    // For the part defined in FormatDateTimeToParts, it doesn't have ||Source||
+    // property, we store Shared for simplicity,
+    if (!aParts.emplaceBack(type, endIndex, DateTimePartSource::Shared)) {
+      return false;
+    }
+
+    lastEndIndex = endIndex;
+    return true;
+  };
+
+  int32_t fieldInt, beginIndexInt, endIndexInt;
+  while ((fieldInt = ufieldpositer_next(aFieldPositionIterator, &beginIndexInt,
+                                        &endIndexInt)) >= 0) {
+    MOZ_ASSERT(beginIndexInt <= endIndexInt,
+               "field iterator returning invalid range");
+
+    size_t beginIndex = AssertedCast<size_t>(beginIndexInt);
+    size_t endIndex = AssertedCast<size_t>(endIndexInt);
+
+    // Technically this isn't guaranteed.  But it appears true in pratice,
+    // and http://bugs.icu-project.org/trac/ticket/12024 is expected to
+    // correct the documentation lapse.
+    MOZ_ASSERT(lastEndIndex <= beginIndex,
+               "field iteration didn't return fields in order start to "
+               "finish as expected");
+
+    DateTimePartType type =
+        ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(fieldInt));
+    if (lastEndIndex < beginIndex) {
+      if (!AppendPart(DateTimePartType::Literal, beginIndex)) {
+        return Err(ICUError::InternalError);
+      }
+    }
+
+    if (!AppendPart(type, endIndex)) {
+      return Err(ICUError::InternalError);
+    }
+  }
+
+  // Append any final literal.
+  if (lastEndIndex < aSpanSize) {
+    if (!AppendPart(DateTimePartType::Literal, aSpanSize)) {
+      return Err(ICUError::InternalError);
+    }
+  }
+
+  return Ok();
+}
+
 }  // namespace mozilla::intl
--- a/intl/components/src/DateTimeFormat.h
+++ b/intl/components/src/DateTimeFormat.h
@@ -4,16 +4,18 @@
 #ifndef intl_components_DateTimeFormat_h_
 #define intl_components_DateTimeFormat_h_
 #include <functional>
 #include "unicode/udat.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/intl/ICU4CGlue.h"
 #include "mozilla/intl/ICUError.h"
+
+#include "mozilla/intl/DateTimePart.h"
 #include "mozilla/intl/DateTimePatternGenerator.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Result.h"
 #include "mozilla/Span.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Utf8.h"
 #include "mozilla/Variant.h"
 #include "mozilla/Vector.h"
@@ -349,16 +351,54 @@ class DateTimeFormat final {
           aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
             return udat_format(mDateFormat, aUnixEpoch, target, length, nullptr,
                                status);
           });
     }
   };
 
   /**
+   * Format the Unix epoch time into a DateTimePartVector.
+   *
+   * The caller has to create the buffer and the vector and pass to this method.
+   * The formatted string will be stored in the buffer and formatted parts in
+   * the vector.
+   *
+   * aUnixEpoch is the number of milliseconds since 1 January 1970, UTC.
+   *
+   * See:
+   * https://tc39.es/ecma402/#sec-formatdatetimetoparts
+   */
+  template <typename B>
+  ICUResult TryFormatToParts(double aUnixEpoch, B& aBuffer,
+                             DateTimePartVector& aParts) const {
+    static_assert(std::is_same<typename B::CharType, char16_t>::value,
+                  "Only char16_t is supported (for UTF-16 support) now.");
+
+    UErrorCode status = U_ZERO_ERROR;
+    UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
+    if (U_FAILURE(status)) {
+      return Err(ToICUError(status));
+    }
+
+    auto result = FillBufferWithICUCall(
+        aBuffer, [this, aUnixEpoch, fpositer](UChar* chars, int32_t size,
+                                              UErrorCode* status) {
+          return udat_formatForFields(mDateFormat, aUnixEpoch, chars, size,
+                                      fpositer, status);
+        });
+    if (result.isErr()) {
+      ufieldpositer_close(fpositer);
+      return result.propagateErr();
+    }
+
+    return TryFormatToParts(fpositer, aBuffer.length(), aParts);
+  }
+
+  /**
    * Copies the pattern for the current DateTimeFormat to a buffer.
    *
    * Warning: This method should not be added to new code. In the near future we
    * plan to remove it.
    */
   template <typename B>
   ICUResult GetPattern(B& aBuffer) const {
     return FillBufferWithICUCall(
@@ -419,23 +459,16 @@ class DateTimeFormat final {
    * For the implementation, with ICU4C, this takes a string pattern and maps it
    * back to a ComponentsBag.
    */
   Result<ComponentsBag, ICUError> ResolveComponents();
 
   ~DateTimeFormat();
 
   /**
-   * TODO(Bug 1686965) - Temporarily get the underlying ICU object while
-   * migrating to the unified API. This should be removed when completing the
-   * migration.
-   */
-  UDateFormat* UnsafeGetUDateFormat() const { return mDateFormat; }
-
-  /**
    * Clones the Calendar from a DateTimeFormat, and sets its time with the
    * relative milliseconds since 1 January 1970, UTC.
    */
   Result<UniquePtr<Calendar>, ICUError> CloneCalendar(double aUnixEpoch) const;
 
   /**
    * Return the hour cycle used in the input pattern or Nothing if none was
    * found.
@@ -469,16 +502,19 @@ class DateTimeFormat final {
                                        udat_getAvailable>();
   }
 
  private:
   explicit DateTimeFormat(UDateFormat* aDateFormat);
 
   ICUResult CacheSkeleton(Span<const char16_t> aSkeleton);
 
+  ICUResult TryFormatToParts(UFieldPositionIterator* aFieldPositionIterator,
+                             size_t aSpanSize,
+                             DateTimePartVector& aParts) const;
   /**
    * Replaces all hour pattern characters in |patternOrSkeleton| to use the
    * matching hour representation for |hourCycle|.
    */
   static void ReplaceHourSymbol(Span<char16_t> aPatternOrSkeleton,
                                 DateTimeFormat::HourCycle aHourCycle);
 
   /**
new file mode 100644
--- /dev/null
+++ b/intl/components/src/DateTimeFormatUtils.cpp
@@ -0,0 +1,104 @@
+/* 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/Assertions.h"
+
+#include "DateTimeFormatUtils.h"
+
+namespace mozilla::intl {
+
+DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName) {
+  // See intl/icu/source/i18n/unicode/udat.h for a detailed field list.  This
+  // switch is deliberately exhaustive: cases might have to be added/removed
+  // if this code is compiled with a different ICU with more
+  // UDateFormatField enum initializers.  Please guard such cases with
+  // appropriate ICU version-testing #ifdefs, should cross-version divergence
+  // occur.
+  switch (fieldName) {
+    case UDAT_ERA_FIELD:
+      return DateTimePartType::Era;
+
+    case UDAT_YEAR_FIELD:
+    case UDAT_YEAR_WOY_FIELD:
+    case UDAT_EXTENDED_YEAR_FIELD:
+      return DateTimePartType::Year;
+
+    case UDAT_YEAR_NAME_FIELD:
+      return DateTimePartType::YearName;
+
+    case UDAT_MONTH_FIELD:
+    case UDAT_STANDALONE_MONTH_FIELD:
+      return DateTimePartType::Month;
+
+    case UDAT_DATE_FIELD:
+    case UDAT_JULIAN_DAY_FIELD:
+      return DateTimePartType::Day;
+
+    case UDAT_HOUR_OF_DAY1_FIELD:
+    case UDAT_HOUR_OF_DAY0_FIELD:
+    case UDAT_HOUR1_FIELD:
+    case UDAT_HOUR0_FIELD:
+      return DateTimePartType::Hour;
+
+    case UDAT_MINUTE_FIELD:
+      return DateTimePartType::Minute;
+
+    case UDAT_SECOND_FIELD:
+      return DateTimePartType::Second;
+
+    case UDAT_DAY_OF_WEEK_FIELD:
+    case UDAT_STANDALONE_DAY_FIELD:
+    case UDAT_DOW_LOCAL_FIELD:
+    case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
+      return DateTimePartType::Weekday;
+
+    case UDAT_AM_PM_FIELD:
+    case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
+      return DateTimePartType::DayPeriod;
+
+    case UDAT_TIMEZONE_FIELD:
+    case UDAT_TIMEZONE_GENERIC_FIELD:
+    case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
+      return DateTimePartType::TimeZoneName;
+
+    case UDAT_FRACTIONAL_SECOND_FIELD:
+      return DateTimePartType::FractionalSecondDigits;
+
+#ifndef U_HIDE_INTERNAL_API
+    case UDAT_RELATED_YEAR_FIELD:
+      return DateTimePartType::RelatedYear;
+#endif
+
+    case UDAT_DAY_OF_YEAR_FIELD:
+    case UDAT_WEEK_OF_YEAR_FIELD:
+    case UDAT_WEEK_OF_MONTH_FIELD:
+    case UDAT_MILLISECONDS_IN_DAY_FIELD:
+    case UDAT_TIMEZONE_RFC_FIELD:
+    case UDAT_QUARTER_FIELD:
+    case UDAT_STANDALONE_QUARTER_FIELD:
+    case UDAT_TIMEZONE_SPECIAL_FIELD:
+    case UDAT_TIMEZONE_ISO_FIELD:
+    case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
+    case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
+#ifndef U_HIDE_INTERNAL_API
+    case UDAT_TIME_SEPARATOR_FIELD:
+#endif
+      // These fields are all unsupported.
+      return DateTimePartType::Unknown;
+
+#ifndef U_HIDE_DEPRECATED_API
+    case UDAT_FIELD_COUNT:
+      MOZ_ASSERT_UNREACHABLE(
+          "format field sentinel value returned by "
+          "iterator!");
+#endif
+  }
+
+  MOZ_ASSERT_UNREACHABLE(
+      "unenumerated, undocumented format field returned "
+      "by iterator");
+  return DateTimePartType::Unknown;
+}
+
+}  // namespace mozilla::intl
new file mode 100644
--- /dev/null
+++ b/intl/components/src/DateTimeFormatUtils.h
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef intl_components_DateTimeFormatUtils_h_
+#define intl_components_DateTimeFormatUtils_h_
+#include "unicode/udat.h"
+
+#include "mozilla/intl/DateTimePart.h"
+
+namespace mozilla::intl {
+DateTimePartType ConvertUFormatFieldToPartType(UDateFormatField fieldName);
+}  // namespace mozilla::intl
+
+#endif
new file mode 100644
--- /dev/null
+++ b/intl/components/src/DateTimePart.h
@@ -0,0 +1,84 @@
+/* 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 intl_components_DateTimePart_h_
+#define intl_components_DateTimePart_h_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "mozilla/Vector.h"
+
+namespace mozilla::intl {
+
+enum class DateTimePartType : int16_t {
+  Literal,
+  Weekday,
+  Era,
+  Year,
+  YearName,
+  RelatedYear,
+  Month,
+  Day,
+  DayPeriod,
+  Hour,
+  Minute,
+  Second,
+  FractionalSecondDigits,
+  TimeZoneName,
+  Unknown
+};
+
+enum class DateTimePartSource : int16_t { Shared, StartRange, EndRange };
+
+/**
+ * The 'Part' object defined in FormatDateTimeToParts and
+ * FormatDateTimeRangeToParts
+ *
+ * Each part consists of three properties: ||Type||, ||Value|| and ||Source||,
+ * with the ||Source|| property is set to DateTimePartSource::Shared by default.
+ * (Note: From the spec, the part from FormatDateTimeToParts doesn't have the
+ * ||Source|| property, so if the caller is FormatDateTimeToParts, it should
+ * ignore the ||Source|| property).
+ *
+ * To store DateTimePart more efficiently, it doesn't store the ||Value|| of
+ * type string in this struct. Instead, it stores the end index of the string
+ * in the buffer(which is passed to DateTimeFormat::TryFormatToParts() or
+ * can be got by calling AutoFormattedDateInterval::ToSpan()). The begin index
+ * of the ||Value|| is the mEndIndex of the previous part.
+ *
+ *  Buffer
+ *  0               i                j
+ * +---------------+---------------+---------------+
+ * | Part[0].Value | Part[1].Value | Part[2].Value | ....
+ * +---------------+---------------+---------------+
+ *
+ *     Part[0].mEndIndex is i. Part[0].Value is stored in the Buffer[0..i].
+ *     Part[1].mEndIndex is j. Part[1].Value is stored in the Buffer[i..j].
+ *
+ * See:
+ * https://tc39.es/ecma402/#sec-formatdatetimetoparts
+ * https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
+ */
+struct DateTimePart {
+  DateTimePart(DateTimePartType type, size_t endIndex,
+               DateTimePartSource source)
+      : mEndIndex(endIndex), mType(type), mSource(source) {}
+
+  // See the above comments for details, mEndIndex is placed first for reducing
+  // padding.
+  size_t mEndIndex;
+  DateTimePartType mType;
+  DateTimePartSource mSource;
+};
+
+// The common parts are 'month', 'literal', 'day', 'literal', 'year', 'literal',
+// 'hour', 'literal', 'minute', 'literal', which are 10 parts, for DateTimeRange
+// the number will be doubled, so choosing 32 as the initial length to prevent
+// heap allocation.
+constexpr size_t INITIAL_DATETIME_PART_VECTOR_SIZE = 32;
+using DateTimePartVector =
+    mozilla::Vector<DateTimePart, INITIAL_DATETIME_PART_VECTOR_SIZE>;
+
+}  // namespace mozilla::intl
+#endif
--- a/intl/unicharutil/util/nsBidiUtils.h
+++ b/intl/unicharutil/util/nsBidiUtils.h
@@ -47,35 +47,16 @@ enum nsCharType {
 };
 
 /**
  * This specifies the language directional property of a character set.
  */
 typedef enum nsCharType nsCharType;
 
 /**
- * Find the direction of an embedding level or paragraph level set by
- * the Unicode Bidi Algorithm. (Even levels are left-to-right, odd
- * levels right-to-left.
- */
-#define IS_LEVEL_RTL(level) (((level)&1) == 1)
-
-/**
- * Check whether two bidi levels have the same parity and thus the same
- * directionality
- */
-#define IS_SAME_DIRECTION(level1, level2) (((level1 ^ level2) & 1) == 0)
-
-/**
- * Convert from nsBidiLevel to nsBidiDirection
- */
-#define DIRECTION_FROM_LEVEL(level) \
-  ((IS_LEVEL_RTL(level)) ? NSBIDI_RTL : NSBIDI_LTR)
-
-/**
  * definitions of bidirection character types by category
  */
 
 #define CHARTYPE_IS_RTL(val) \
   (((val) == eCharType_RightToLeft) || ((val) == eCharType_RightToLeftArabic))
 
 #define CHARTYPE_IS_WEAK(val)                       \
   (((val) == eCharType_EuropeanNumberSeparator) ||  \
--- a/js/public/experimental/JSStencil.h
+++ b/js/public/experimental/JSStencil.h
@@ -91,19 +91,19 @@ extern JS_PUBLIC_API bool StencilIsBorro
 extern JS_PUBLIC_API bool StencilCanLazilyParse(Stencil* stencil);
 
 // Instantiate a module Stencil and return the associated object. Inside the
 // engine this is a js::ModuleObject.
 extern JS_PUBLIC_API JSObject* InstantiateModuleStencil(
     JSContext* cx, const InstantiateOptions& options, Stencil* stencil);
 
 // Serialize the Stencil into the transcode buffer.
-extern JS_PUBLIC_API TranscodeResult
-EncodeStencil(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
-              Stencil* stencil, TranscodeBuffer& buffer);
+extern JS_PUBLIC_API TranscodeResult EncodeStencil(JSContext* cx,
+                                                   Stencil* stencil,
+                                                   TranscodeBuffer& buffer);
 
 // Deserialize data and create a new Stencil.
 extern JS_PUBLIC_API TranscodeResult
 DecodeStencil(JSContext* cx, const ReadOnlyCompileOptions& options,
               const TranscodeRange& range, Stencil** stencilOut);
 
 extern JS_PUBLIC_API size_t SizeOfStencil(Stencil* stencil,
                                           mozilla::MallocSizeOf mallocSizeOf);
--- a/js/src/builtin/intl/Collator.cpp
+++ b/js/src/builtin/intl/Collator.cpp
@@ -346,16 +346,34 @@ static mozilla::intl::Collator* NewIntlC
   if (optResult.isErr()) {
     ReportInternalError(cx, optResult.unwrapErr());
     return nullptr;
   }
 
   return coll.release();
 }
 
+static mozilla::intl::Collator* GetOrCreateCollator(
+    JSContext* cx, Handle<CollatorObject*> collator) {
+  // Obtain a cached mozilla::intl::Collator object.
+  mozilla::intl::Collator* coll = collator->getCollator();
+  if (coll) {
+    return coll;
+  }
+
+  coll = NewIntlCollator(cx, collator);
+  if (!coll) {
+    return nullptr;
+  }
+  collator->setCollator(coll);
+
+  intl::AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse);
+  return coll;
+}
+
 static bool intl_CompareStrings(JSContext* cx, mozilla::intl::Collator* coll,
                                 HandleString str1, HandleString str2,
                                 MutableHandleValue result) {
   MOZ_ASSERT(str1);
   MOZ_ASSERT(str2);
 
   if (str1 == str2) {
     result.setInt32(0);
@@ -384,26 +402,19 @@ bool js::intl_CompareStrings(JSContext* 
   MOZ_ASSERT(args.length() == 3);
   MOZ_ASSERT(args[0].isObject());
   MOZ_ASSERT(args[1].isString());
   MOZ_ASSERT(args[2].isString());
 
   Rooted<CollatorObject*> collator(cx,
                                    &args[0].toObject().as<CollatorObject>());
 
-  // Obtain a cached mozilla::intl::Collator object.
-  mozilla::intl::Collator* coll = collator->getCollator();
+  mozilla::intl::Collator* coll = GetOrCreateCollator(cx, collator);
   if (!coll) {
-    coll = NewIntlCollator(cx, collator);
-    if (!coll) {
-      return false;
-    }
-    collator->setCollator(coll);
-
-    intl::AddICUCellMemory(collator, CollatorObject::EstimatedMemoryUse);
+    return false;
   }
 
   // Use the UCollator to actually compare the strings.
   RootedString str1(cx, args[1].toString());
   RootedString str2(cx, args[2].toString());
   return intl_CompareStrings(cx, coll, str1, str2, args.rval());
 }
 
--- a/js/src/builtin/intl/CommonFunctions.cpp
+++ b/js/src/builtin/intl/CommonFunctions.cpp
@@ -15,17 +15,16 @@
 
 #include <algorithm>
 
 #include "gc/GCEnum.h"
 #include "gc/Zone.h"
 #include "gc/ZoneAllocator.h"
 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_INTERNAL_INTL_ERROR
 #include "js/Value.h"
-#include "unicode/uformattedvalue.h"
 #include "vm/JSContext.h"
 #include "vm/JSObject.h"
 #include "vm/SelfHosting.h"
 #include "vm/Stack.h"
 
 #include "vm/JSObject-inl.h"
 
 bool js::intl::InitializeObject(JSContext* cx, JS::Handle<JSObject*> obj,
@@ -143,22 +142,8 @@ void js::intl::AddICUCellMemory(JSObject
   // the JSObject's zone.
   AddCellMemory(obj, nbytes, MemoryUse::ICUObject);
 }
 
 void js::intl::RemoveICUCellMemory(JSFreeOp* fop, JSObject* obj,
                                    size_t nbytes) {
   fop->removeCellMemory(obj, nbytes, MemoryUse::ICUObject);
 }
-
-JSString* js::intl::FormattedValueToString(
-    JSContext* cx, const UFormattedValue* formattedValue) {
-  UErrorCode status = U_ZERO_ERROR;
-  int32_t strLength;
-  const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
-  if (U_FAILURE(status)) {
-    ReportInternalError(cx);
-    return nullptr;
-  }
-
-  return NewStringCopyN<CanGC>(cx, str,
-                               mozilla::AssertedCast<uint32_t>(strLength));
-}
--- a/js/src/builtin/intl/CommonFunctions.h
+++ b/js/src/builtin/intl/CommonFunctions.h
@@ -14,18 +14,16 @@
 #include <string.h>
 #include <type_traits>
 
 #include "js/RootingAPI.h"
 #include "js/Vector.h"
 #include "unicode/utypes.h"
 #include "vm/StringType.h"
 
-struct UFormattedValue;
-
 namespace mozilla::intl {
 enum class ICUError : uint8_t;
 }
 
 namespace js {
 
 namespace intl {
 
@@ -160,17 +158,13 @@ static JSString* CallICU(JSContext* cx, 
   }
 
   return NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
 }
 
 void AddICUCellMemory(JSObject* obj, size_t nbytes);
 
 void RemoveICUCellMemory(JSFreeOp* fop, JSObject* obj, size_t nbytes);
-
-JSString* FormattedValueToString(JSContext* cx,
-                                 const UFormattedValue* formattedValue);
-
 }  // namespace intl
 
 }  // namespace js
 
 #endif /* builtin_intl_CommonFunctions_h */
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -6,17 +6,19 @@
 
 /* Intl.DateTimeFormat implementation. */
 
 #include "builtin/intl/DateTimeFormat.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/intl/Calendar.h"
+#include "mozilla/intl/DateIntervalFormat.h"
 #include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePart.h"
 #include "mozilla/intl/DateTimePatternGenerator.h"
 #include "mozilla/intl/Locale.h"
 #include "mozilla/intl/TimeZone.h"
 #include "mozilla/Range.h"
 #include "mozilla/Span.h"
 
 #include "builtin/Array.h"
 #include "builtin/intl/CommonFunctions.h"
@@ -29,22 +31,16 @@
 #include "js/CharacterEncoding.h"
 #include "js/Date.h"
 #include "js/experimental/Intl.h"     // JS::AddMozDateTimeFormatConstructor
 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
 #include "js/GCAPI.h"
 #include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperties
 #include "js/PropertySpec.h"
 #include "js/StableStringChars.h"
-#include "unicode/udat.h"
-#include "unicode/udateintervalformat.h"
-#include "unicode/uenum.h"
-#include "unicode/ufieldpositer.h"
-#include "unicode/uloc.h"
-#include "unicode/utypes.h"
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/JSContext.h"
 #include "vm/PlainObject.h"  // js::PlainObject
 #include "vm/Runtime.h"
 #include "vm/WellKnownAtom.h"  // js_*_str
 
 #include "vm/JSObject-inl.h"
@@ -190,30 +186,31 @@ bool js::intl_DateTimeFormat(JSContext* 
   return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard);
 }
 
 void js::DateTimeFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
   MOZ_ASSERT(fop->onMainThread());
 
   auto* dateTimeFormat = &obj->as<DateTimeFormatObject>();
   mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
-  UDateIntervalFormat* dif = dateTimeFormat->getDateIntervalFormat();
+  mozilla::intl::DateIntervalFormat* dif =
+      dateTimeFormat->getDateIntervalFormat();
 
   if (df) {
     intl::RemoveICUCellMemory(
         fop, obj, DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
 
     delete df;
   }
 
   if (dif) {
     intl::RemoveICUCellMemory(
         fop, obj, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
 
-    udtitvfmt_close(dif);
+    delete dif;
   }
 }
 
 bool JS::AddMozDateTimeFormatConstructor(JSContext* cx,
                                          JS::Handle<JSObject*> intl) {
   RootedObject ctor(
       cx, GlobalObject::createConstructor(cx, MozDateTimeFormat,
                                           cx->names().DateTimeFormat, 0));
@@ -983,16 +980,35 @@ static mozilla::intl::DateTimeFormat* Ne
 
   // ECMAScript requires the Gregorian calendar to be used from the beginning
   // of ECMAScript time.
   df->SetStartTimeIfGregorian(StartOfTime);
 
   return df.release();
 }
 
+static mozilla::intl::DateTimeFormat* GetOrCreateDateTimeFormat(
+    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) {
+  // Obtain a cached mozilla::intl::DateTimeFormat object.
+  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
+  if (df) {
+    return df;
+  }
+
+  df = NewDateTimeFormat(cx, dateTimeFormat);
+  if (!df) {
+    return nullptr;
+  }
+  dateTimeFormat->setDateFormat(df);
+
+  intl::AddICUCellMemory(dateTimeFormat,
+                         DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
+  return df;
+}
+
 template <typename T>
 static bool SetResolvedProperty(JSContext* cx, HandleObject resolved,
                                 HandlePropertyName name,
                                 mozilla::Maybe<T> intlProp) {
   if (!intlProp) {
     return true;
   }
   JSString* str = NewStringCopyZ<CanGC>(
@@ -1014,27 +1030,20 @@ bool js::intl_resolveDateTimeFormatCompo
 
   Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
   dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
 
   RootedObject resolved(cx, &args[1].toObject());
 
   bool includeDateTimeFields = args[2].toBoolean();
 
-  // Obtain a cached mozilla::intl::DateTimeFormat object.
-  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
+  mozilla::intl::DateTimeFormat* df =
+      GetOrCreateDateTimeFormat(cx, dateTimeFormat);
   if (!df) {
-    df = NewDateTimeFormat(cx, dateTimeFormat);
-    if (!df) {
-      return false;
-    }
-    dateTimeFormat->setDateFormat(df);
-
-    intl::AddICUCellMemory(dateTimeFormat,
-                           DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
+    return false;
   }
 
   auto result = df->ResolveComponents();
   if (result.isErr()) {
     intl::ReportInternalError(cx, result.unwrapErr());
     return false;
   }
 
@@ -1130,234 +1139,157 @@ static bool intl_FormatDateTime(JSContex
   }
 
   result.setString(str);
   return true;
 }
 
 using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
 
-static FieldType GetFieldTypeForFormatField(UDateFormatField fieldName) {
-  // See intl/icu/source/i18n/unicode/udat.h for a detailed field list.  This
-  // switch is deliberately exhaustive: cases might have to be added/removed
-  // if this code is compiled with a different ICU with more
-  // UDateFormatField enum initializers.  Please guard such cases with
-  // appropriate ICU version-testing #ifdefs, should cross-version divergence
-  // occur.
-  switch (fieldName) {
-    case UDAT_ERA_FIELD:
+static FieldType GetFieldTypeForPartType(mozilla::intl::DateTimePartType type) {
+  switch (type) {
+    case mozilla::intl::DateTimePartType::Literal:
+      return &JSAtomState::literal;
+    case mozilla::intl::DateTimePartType::Era:
       return &JSAtomState::era;
-
-    case UDAT_YEAR_FIELD:
-    case UDAT_YEAR_WOY_FIELD:
-    case UDAT_EXTENDED_YEAR_FIELD:
+    case mozilla::intl::DateTimePartType::Year:
       return &JSAtomState::year;
-
-    case UDAT_YEAR_NAME_FIELD:
+    case mozilla::intl::DateTimePartType::YearName:
       return &JSAtomState::yearName;
-
-    case UDAT_MONTH_FIELD:
-    case UDAT_STANDALONE_MONTH_FIELD:
+    case mozilla::intl::DateTimePartType::RelatedYear:
+      return &JSAtomState::relatedYear;
+    case mozilla::intl::DateTimePartType::Month:
       return &JSAtomState::month;
-
-    case UDAT_DATE_FIELD:
-    case UDAT_JULIAN_DAY_FIELD:
+    case mozilla::intl::DateTimePartType::Day:
       return &JSAtomState::day;
-
-    case UDAT_HOUR_OF_DAY1_FIELD:
-    case UDAT_HOUR_OF_DAY0_FIELD:
-    case UDAT_HOUR1_FIELD:
-    case UDAT_HOUR0_FIELD:
+    case mozilla::intl::DateTimePartType::Hour:
       return &JSAtomState::hour;
-
-    case UDAT_MINUTE_FIELD:
+    case mozilla::intl::DateTimePartType::Minute:
       return &JSAtomState::minute;
-
-    case UDAT_SECOND_FIELD:
+    case mozilla::intl::DateTimePartType::Second:
       return &JSAtomState::second;
-
-    case UDAT_DAY_OF_WEEK_FIELD:
-    case UDAT_STANDALONE_DAY_FIELD:
-    case UDAT_DOW_LOCAL_FIELD:
-    case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
+    case mozilla::intl::DateTimePartType::Weekday:
       return &JSAtomState::weekday;
-
-    case UDAT_AM_PM_FIELD:
-      return &JSAtomState::dayPeriod;
-
-    case UDAT_TIMEZONE_FIELD:
-    case UDAT_TIMEZONE_GENERIC_FIELD:
-    case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
-      return &JSAtomState::timeZoneName;
-
-    case UDAT_FRACTIONAL_SECOND_FIELD:
-      return &JSAtomState::fractionalSecond;
-
-    case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
+    case mozilla::intl::DateTimePartType::DayPeriod:
       return &JSAtomState::dayPeriod;
-
-#ifndef U_HIDE_INTERNAL_API
-    case UDAT_RELATED_YEAR_FIELD:
-      return &JSAtomState::relatedYear;
-#endif
-
-    case UDAT_DAY_OF_YEAR_FIELD:
-    case UDAT_WEEK_OF_YEAR_FIELD:
-    case UDAT_WEEK_OF_MONTH_FIELD:
-    case UDAT_MILLISECONDS_IN_DAY_FIELD:
-    case UDAT_TIMEZONE_RFC_FIELD:
-    case UDAT_QUARTER_FIELD:
-    case UDAT_STANDALONE_QUARTER_FIELD:
-    case UDAT_TIMEZONE_SPECIAL_FIELD:
-    case UDAT_TIMEZONE_ISO_FIELD:
-    case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
-    case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
-#ifndef U_HIDE_INTERNAL_API
-    case UDAT_TIME_SEPARATOR_FIELD:
-#endif
-      // These fields are all unsupported.
+    case mozilla::intl::DateTimePartType::TimeZoneName:
+      return &JSAtomState::timeZoneName;
+    case mozilla::intl::DateTimePartType::FractionalSecondDigits:
+      return &JSAtomState::fractionalSecond;
+    case mozilla::intl::DateTimePartType::Unknown:
       return &JSAtomState::unknown;
-
-#ifndef U_HIDE_DEPRECATED_API
-    case UDAT_FIELD_COUNT:
-      MOZ_ASSERT_UNREACHABLE(
-          "format field sentinel value returned by "
-          "iterator!");
-#endif
   }
 
-  MOZ_ASSERT_UNREACHABLE(
+  MOZ_CRASH(
       "unenumerated, undocumented format field returned "
       "by iterator");
-  return nullptr;
 }
 
-static bool intl_FormatToPartsDateTime(JSContext* cx,
-                                       const mozilla::intl::DateTimeFormat* df,
-                                       ClippedTime x, FieldType source,
-                                       MutableHandleValue result) {
-  MOZ_ASSERT(x.isValid());
-
-  UErrorCode status = U_ZERO_ERROR;
-  UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return false;
+static FieldType GetFieldTypeForPartSource(
+    mozilla::intl::DateTimePartSource source) {
+  switch (source) {
+    case mozilla::intl::DateTimePartSource::Shared:
+      return &JSAtomState::shared;
+    case mozilla::intl::DateTimePartSource::StartRange:
+      return &JSAtomState::startRange;
+    case mozilla::intl::DateTimePartSource::EndRange:
+      return &JSAtomState::endRange;
   }
-  ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(
-      fpositer);
+
+  MOZ_CRASH(
+      "unenumerated, undocumented format field returned "
+      "by iterator");
+}
 
-  RootedString overallResult(cx);
-  overallResult = CallICU(cx, [df, x, fpositer](UChar* chars, int32_t size,
-                                                UErrorCode* status) {
-    return udat_formatForFields(
-        // TODO(Bug 1686965) - The use of UnsafeGetUDateFormat is a temporary
-        // migration step until the field position iterator is supported.
-        df->UnsafeGetUDateFormat(), x.toDouble(), chars, size, fpositer,
-        status);
-  });
+// A helper function to create an ArrayObject from DateTimePart objects.
+// When hasNoSource is true, we don't need to create the ||Source|| property for
+// the DateTimePart object.
+static bool CreateDateTimePartArray(
+    JSContext* cx, mozilla::Span<const char16_t> formattedSpan,
+    bool hasNoSource, const mozilla::intl::DateTimePartVector& parts,
+    MutableHandleValue result) {
+  RootedString overallResult(cx, NewStringCopy<CanGC>(cx, formattedSpan));
   if (!overallResult) {
     return false;
   }
 
-  RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
+  RootedArrayObject partsArray(cx,
+                               NewDenseFullyAllocatedArray(cx, parts.length()));
   if (!partsArray) {
     return false;
   }
+  partsArray->ensureDenseInitializedLength(0, parts.length());
 
   if (overallResult->length() == 0) {
     // An empty string contains no parts, so avoid extra work below.
     result.setObject(*partsArray);
     return true;
   }
 
-  size_t lastEndIndex = 0;
-
   RootedObject singlePart(cx);
   RootedValue val(cx);
 
-  auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
+  size_t index = 0;
+  size_t beginIndex = 0;
+  for (const mozilla::intl::DateTimePart& part : parts) {
     singlePart = NewPlainObject(cx);
     if (!singlePart) {
       return false;
     }
 
+    FieldType type = GetFieldTypeForPartType(part.mType);
     val = StringValue(cx->names().*type);
     if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
       return false;
     }
 
-    JSLinearString* partSubstr = NewDependentString(
-        cx, overallResult, beginIndex, endIndex - beginIndex);
-    if (!partSubstr) {
+    MOZ_ASSERT(part.mEndIndex > beginIndex);
+    JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex,
+                                                 part.mEndIndex - beginIndex);
+    if (!partStr) {
       return false;
     }
-
-    val = StringValue(partSubstr);
+    val = StringValue(partStr);
     if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
       return false;
     }
 
-    if (source) {
+    if (!hasNoSource) {
+      FieldType source = GetFieldTypeForPartSource(part.mSource);
       val = StringValue(cx->names().*source);
       if (!DefineDataProperty(cx, singlePart, cx->names().source, val)) {
         return false;
       }
     }
 
-    if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
-      return false;
-    }
-
-    lastEndIndex = endIndex;
-    return true;
-  };
-
-  int32_t fieldInt, beginIndexInt, endIndexInt;
-  while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt,
-                                        &endIndexInt)) >= 0) {
-    MOZ_ASSERT(beginIndexInt >= 0);
-    MOZ_ASSERT(endIndexInt >= 0);
-    MOZ_ASSERT(beginIndexInt <= endIndexInt,
-               "field iterator returning invalid range");
-
-    size_t beginIndex(beginIndexInt);
-    size_t endIndex(endIndexInt);
-
-    // Technically this isn't guaranteed.  But it appears true in pratice,
-    // and http://bugs.icu-project.org/trac/ticket/12024 is expected to
-    // correct the documentation lapse.
-    MOZ_ASSERT(lastEndIndex <= beginIndex,
-               "field iteration didn't return fields in order start to "
-               "finish as expected");
-
-    if (FieldType type = GetFieldTypeForFormatField(
-            static_cast<UDateFormatField>(fieldInt))) {
-      if (lastEndIndex < beginIndex) {
-        if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
-          return false;
-        }
-      }
-
-      if (!AppendPart(type, beginIndex, endIndex)) {
-        return false;
-      }
-    }
+    beginIndex = part.mEndIndex;
+    partsArray->initDenseElement(index++, ObjectValue(*singlePart));
   }
 
-  // Append any final literal.
-  if (lastEndIndex < overallResult->length()) {
-    if (!AppendPart(&JSAtomState::literal, lastEndIndex,
-                    overallResult->length())) {
-      return false;
-    }
+  MOZ_ASSERT(index == parts.length());
+  MOZ_ASSERT(beginIndex == formattedSpan.size());
+  result.setObject(*partsArray);
+  return true;
+}
+
+static bool intl_FormatToPartsDateTime(JSContext* cx,
+                                       const mozilla::intl::DateTimeFormat* df,
+                                       ClippedTime x, bool hasNoSource,
+                                       MutableHandleValue result) {
+  MOZ_ASSERT(x.isValid());
+
+  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
+  mozilla::intl::DateTimePartVector parts;
+  auto r = df->TryFormatToParts(x.toDouble(), buffer, parts);
+  if (r.isErr()) {
+    intl::ReportInternalError(cx, r.unwrapErr());
+    return false;
   }
 
-  result.setObject(*partsArray);
-  return true;
+  return CreateDateTimePartArray(cx, buffer, hasNoSource, parts, result);
 }
 
 bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 3);
   MOZ_ASSERT(args[0].isObject());
   MOZ_ASSERT(args[1].isNumber());
   MOZ_ASSERT(args[2].isBoolean());
@@ -1370,41 +1302,33 @@ bool js::intl_FormatDateTime(JSContext* 
   ClippedTime x = TimeClip(args[1].toNumber());
   if (!x.isValid()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_DATE_NOT_FINITE, "DateTimeFormat",
                               formatToParts ? "formatToParts" : "format");
     return false;
   }
 
-  // Obtain a cached DateTimeFormat object.
-  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
+  mozilla::intl::DateTimeFormat* df =
+      GetOrCreateDateTimeFormat(cx, dateTimeFormat);
   if (!df) {
-    df = NewDateTimeFormat(cx, dateTimeFormat);
-    if (!df) {
-      return false;
-    }
-    dateTimeFormat->setDateFormat(df);
-
-    intl::AddICUCellMemory(dateTimeFormat,
-                           DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
+    return false;
   }
 
-  // Use the UDateFormat to actually format the time stamp.
-  FieldType source = nullptr;
-  return formatToParts
-             ? intl_FormatToPartsDateTime(cx, df, x, source, args.rval())
-             : intl_FormatDateTime(cx, df, x, args.rval());
+  // Use the DateTimeFormat to actually format the time stamp.
+  return formatToParts ? intl_FormatToPartsDateTime(
+                             cx, df, x, /* hasNoSource */ true, args.rval())
+                       : intl_FormatDateTime(cx, df, x, args.rval());
 }
 
 /**
- * Returns a new UDateIntervalFormat with the locale and date-time formatting
+ * Returns a new DateIntervalFormat with the locale and date-time formatting
  * options of the given DateTimeFormat.
  */
-static UDateIntervalFormat* NewUDateIntervalFormat(
+static mozilla::intl::DateIntervalFormat* NewDateIntervalFormat(
     JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
     mozilla::intl::DateTimeFormat& mozDtf) {
   RootedValue value(cx);
   RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
   if (!internals) {
     return nullptr;
   }
 
@@ -1438,35 +1362,57 @@ static UDateIntervalFormat* NewUDateInte
 
   FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx);
   auto skelResult = mozDtf.GetOriginalSkeleton(skeleton, hcPattern);
   if (skelResult.isErr()) {
     intl::ReportInternalError(cx, skelResult.unwrapErr());
     return nullptr;
   }
 
-  UErrorCode status = U_ZERO_ERROR;
-  UDateIntervalFormat* dif = udtitvfmt_open(
-      IcuLocale(locale.get()), skeleton.data(), skeleton.length(),
-      timeZoneChars.data(), timeZoneChars.size(), &status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
+  auto dif = mozilla::intl::DateIntervalFormat::TryCreate(
+      mozilla::MakeStringSpan(locale.get()), skeleton, timeZoneChars);
+
+  if (dif.isErr()) {
+    js::intl::ReportInternalError(cx, dif.unwrapErr());
     return nullptr;
   }
 
+  return dif.unwrap().release();
+}
+
+static mozilla::intl::DateIntervalFormat* GetOrCreateDateIntervalFormat(
+    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
+    mozilla::intl::DateTimeFormat& mozDtf) {
+  // Obtain a cached DateIntervalFormat object.
+  mozilla::intl::DateIntervalFormat* dif =
+      dateTimeFormat->getDateIntervalFormat();
+  if (dif) {
+    return dif;
+  }
+
+  dif = NewDateIntervalFormat(cx, dateTimeFormat, mozDtf);
+  if (!dif) {
+    return nullptr;
+  }
+  dateTimeFormat->setDateIntervalFormat(dif);
+
+  intl::AddICUCellMemory(
+      dateTimeFormat,
+      DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
   return dif;
 }
 
 /**
  * PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
  */
-static const UFormattedValue* PartitionDateTimeRangePattern(
+static bool PartitionDateTimeRangePattern(
     JSContext* cx, const mozilla::intl::DateTimeFormat* df,
-    const UDateIntervalFormat* dif, UFormattedDateInterval* formatted,
-    ClippedTime x, ClippedTime y) {
+    const mozilla::intl::DateIntervalFormat* dif,
+    mozilla::intl::AutoFormattedDateInterval& formatted, ClippedTime x,
+    ClippedTime y, bool* equal) {
   MOZ_ASSERT(x.isValid());
   MOZ_ASSERT(y.isValid());
   MOZ_ASSERT(x.toDouble() <= y.toDouble());
 
   // We can't access the calendar used by UDateIntervalFormat to change it to a
   // proleptic Gregorian calendar. Instead we need to call a different formatter
   // function which accepts UCalendar instead of UDate.
   // But creating new UCalendar objects for each call is slow, so when we can
@@ -1475,335 +1421,125 @@ static const UFormattedValue* PartitionD
 
   // The Gregorian change date "1582-10-15T00:00:00.000Z".
   constexpr double GregorianChangeDate = -12219292800000.0;
 
   // Add a full day to account for time zone offsets.
   constexpr double GregorianChangeDatePlusOneDay =
       GregorianChangeDate + msPerDay;
 
-  UErrorCode status = U_ZERO_ERROR;
+  mozilla::intl::ICUResult result = Ok();
   if (x.toDouble() < GregorianChangeDatePlusOneDay) {
     // Create calendar objects for the start and end date by cloning the date
     // formatter calendar. The date formatter calendar already has the correct
     // time zone set and was changed to use a proleptic Gregorian calendar.
     auto startCal = df->CloneCalendar(x.toDouble());
     if (startCal.isErr()) {
       intl::ReportInternalError(cx, startCal.unwrapErr());
-      return nullptr;
+      return false;
     }
 
     auto endCal = df->CloneCalendar(y.toDouble());
     if (endCal.isErr()) {
       intl::ReportInternalError(cx, endCal.unwrapErr());
-      return nullptr;
+      return false;
     }
 
-    udtitvfmt_formatCalendarToResult(
-        dif, startCal.unwrap()->UnsafeGetUCalendar(),
-        endCal.unwrap()->UnsafeGetUCalendar(), formatted, &status);
+    result = dif->TryFormatCalendar(*startCal.unwrap(), *endCal.unwrap(),
+                                    formatted, equal);
   } else {
     // The common fast path which doesn't require creating calendar objects.
-    udtitvfmt_formatToResult(dif, x.toDouble(), y.toDouble(), formatted,
-                             &status);
-  }
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return nullptr;
-  }
-
-  const UFormattedValue* formattedValue =
-      udtitvfmt_resultAsValue(formatted, &status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return nullptr;
+    result =
+        dif->TryFormatDateTime(x.toDouble(), y.toDouble(), formatted, equal);
   }
 
-  return formattedValue;
-}
-
-/**
- * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), steps 9-11.
- *
- * Examine the formatted value to see if any interval span field is present.
- */
-static bool DateFieldsPracticallyEqual(JSContext* cx,
-                                       const UFormattedValue* formattedValue,
-                                       bool* equal) {
-  UErrorCode status = U_ZERO_ERROR;
-  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return false;
-  }
-  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
-
-  // We're only interested in UFIELD_CATEGORY_DATE_INTERVAL_SPAN fields.
-  ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
+  if (result.isErr()) {
+    intl::ReportInternalError(cx, result.unwrapErr());
     return false;
   }
 
-  bool hasSpan = ufmtval_nextPosition(formattedValue, fpos, &status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return false;
-  }
-
-  // When no date interval span field was found, both dates are "practically
-  // equal" per PartitionDateTimeRangePattern.
-  *equal = !hasSpan;
   return true;
 }
 
 /**
  * FormatDateTimeRange( dateTimeFormat, x, y )
  */
 static bool FormatDateTimeRange(JSContext* cx,
                                 const mozilla::intl::DateTimeFormat* df,
-                                const UDateIntervalFormat* dif, ClippedTime x,
-                                ClippedTime y, MutableHandleValue result) {
-  UErrorCode status = U_ZERO_ERROR;
-  UFormattedDateInterval* formatted = udtitvfmt_openResult(&status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return false;
-  }
-  ScopedICUObject<UFormattedDateInterval, udtitvfmt_closeResult> toClose(
-      formatted);
-
-  const UFormattedValue* formattedValue =
-      PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y);
-  if (!formattedValue) {
+                                const mozilla::intl::DateIntervalFormat* dif,
+                                ClippedTime x, ClippedTime y,
+                                MutableHandleValue result) {
+  mozilla::intl::AutoFormattedDateInterval formatted;
+  if (!formatted.IsValid()) {
+    intl::ReportInternalError(cx, formatted.GetError());
     return false;
   }
 
-  // PartitionDateTimeRangePattern, steps 9-11.
   bool equal;
-  if (!DateFieldsPracticallyEqual(cx, formattedValue, &equal)) {
+  if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) {
     return false;
   }
 
   // PartitionDateTimeRangePattern, step 12.
   if (equal) {
     return intl_FormatDateTime(cx, df, x, result);
   }
 
-  JSString* resultStr = intl::FormattedValueToString(cx, formattedValue);
+  auto spanResult = formatted.ToSpan();
+  if (spanResult.isErr()) {
+    intl::ReportInternalError(cx, spanResult.unwrapErr());
+    return false;
+  }
+  JSString* resultStr = NewStringCopy<CanGC>(cx, spanResult.unwrap());
   if (!resultStr) {
     return false;
   }
 
   result.setString(resultStr);
   return true;
 }
 
 /**
  * FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
  */
-static bool FormatDateTimeRangeToParts(JSContext* cx,
-                                       const mozilla::intl::DateTimeFormat* df,
-                                       const UDateIntervalFormat* dif,
-                                       ClippedTime x, ClippedTime y,
-                                       MutableHandleValue result) {
-  UErrorCode status = U_ZERO_ERROR;
-  UFormattedDateInterval* formatted = udtitvfmt_openResult(&status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
-    return false;
-  }
-  ScopedICUObject<UFormattedDateInterval, udtitvfmt_closeResult> toClose(
-      formatted);
-
-  const UFormattedValue* formattedValue =
-      PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y);
-  if (!formattedValue) {
+static bool FormatDateTimeRangeToParts(
+    JSContext* cx, const mozilla::intl::DateTimeFormat* df,
+    const mozilla::intl::DateIntervalFormat* dif, ClippedTime x, ClippedTime y,
+    MutableHandleValue result) {
+  mozilla::intl::AutoFormattedDateInterval formatted;
+  if (!formatted.IsValid()) {
+    intl::ReportInternalError(cx, formatted.GetError());
     return false;
   }
 
-  // PartitionDateTimeRangePattern, steps 9-11.
   bool equal;
-  if (!DateFieldsPracticallyEqual(cx, formattedValue, &equal)) {
+  if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) {
     return false;
   }
 
   // PartitionDateTimeRangePattern, step 12.
   if (equal) {
-    FieldType source = &JSAtomState::shared;
-    return intl_FormatToPartsDateTime(cx, df, x, source, result);
+    return intl_FormatToPartsDateTime(cx, df, x, /* hasNoSource */ false,
+                                      result);
   }
 
-  RootedString overallResult(cx,
-                             intl::FormattedValueToString(cx, formattedValue));
-  if (!overallResult) {
-    return false;
-  }
-
-  RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
-  if (!partsArray) {
+  mozilla::intl::DateTimePartVector parts;
+  auto r = dif->TryFormattedToParts(formatted, parts);
+  if (r.isErr()) {
+    intl::ReportInternalError(cx, r.unwrapErr());
     return false;
   }
 
-  size_t lastEndIndex = 0;
-  RootedObject singlePart(cx);
-  RootedValue val(cx);
-
-  auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex,
-                        FieldType source) {
-    singlePart = NewPlainObject(cx);
-    if (!singlePart) {
-      return false;
-    }
-
-    val = StringValue(cx->names().*type);
-    if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
-      return false;
-    }
-
-    JSLinearString* partSubstr = NewDependentString(
-        cx, overallResult, beginIndex, endIndex - beginIndex);
-    if (!partSubstr) {
-      return false;
-    }
-
-    val = StringValue(partSubstr);
-    if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
-      return false;
-    }
-
-    val = StringValue(cx->names().*source);
-    if (!DefineDataProperty(cx, singlePart, cx->names().source, val)) {
-      return false;
-    }
-
-    if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
-      return false;
-    }
-
-    lastEndIndex = endIndex;
-    return true;
-  };
-
-  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
+  auto spanResult = formatted.ToSpan();
+  if (spanResult.isErr()) {
+    intl::ReportInternalError(cx, spanResult.unwrapErr());
     return false;
   }
-  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
-
-  size_t categoryEndIndex = 0;
-  FieldType source = &JSAtomState::shared;
-
-  while (true) {
-    bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
-    if (U_FAILURE(status)) {
-      intl::ReportInternalError(cx);
-      return false;
-    }
-    if (!hasMore) {
-      break;
-    }
-
-    int32_t category = ucfpos_getCategory(fpos, &status);
-    if (U_FAILURE(status)) {
-      intl::ReportInternalError(cx);
-      return false;
-    }
-
-    int32_t field = ucfpos_getField(fpos, &status);
-    if (U_FAILURE(status)) {
-      intl::ReportInternalError(cx);
-      return false;
-    }
-
-    int32_t beginIndexInt, endIndexInt;
-    ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
-    if (U_FAILURE(status)) {
-      intl::ReportInternalError(cx);
-      return false;
-    }
-
-    MOZ_ASSERT(beginIndexInt >= 0);
-    MOZ_ASSERT(endIndexInt >= 0);
-    MOZ_ASSERT(beginIndexInt <= endIndexInt,
-               "field iterator returning invalid range");
-
-    size_t beginIndex = size_t(beginIndexInt);
-    size_t endIndex = size_t(endIndexInt);
-
-    // Indices are guaranteed to be returned in order (from left to right).
-    MOZ_ASSERT(lastEndIndex <= beginIndex,
-               "field iteration didn't return fields in order start to "
-               "finish as expected");
-
-    if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
-      // Append any remaining literal parts before changing the source kind.
-      if (lastEndIndex < beginIndex) {
-        if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex,
-                        source)) {
-          return false;
-        }
-      }
-
-      // The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN has only
-      // two allowed values (0 or 1), indicating the begin of the start- resp.
-      // end-date.
-      MOZ_ASSERT(field == 0 || field == 1,
-                 "span category has unexpected value");
-
-      source = field == 0 ? &JSAtomState::startRange : &JSAtomState::endRange;
-      categoryEndIndex = endIndex;
-      continue;
-    }
-
-    // Ignore categories other than UFIELD_CATEGORY_DATE.
-    if (category != UFIELD_CATEGORY_DATE) {
-      continue;
-    }
-
-    // Append the field if supported. If not supported, append it as part of the
-    // next literal part.
-    if (FieldType type =
-            GetFieldTypeForFormatField(static_cast<UDateFormatField>(field))) {
-      if (lastEndIndex < beginIndex) {
-        if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex,
-                        source)) {
-          return false;
-        }
-      }
-
-      if (!AppendPart(type, beginIndex, endIndex, source)) {
-        return false;
-      }
-    }
-
-    if (endIndex == categoryEndIndex) {
-      // Append any remaining literal parts before changing the source kind.
-      if (lastEndIndex < endIndex) {
-        if (!AppendPart(&JSAtomState::literal, lastEndIndex, endIndex,
-                        source)) {
-          return false;
-        }
-      }
-
-      source = &JSAtomState::shared;
-    }
-  }
-
-  // Append any final literal.
-  if (lastEndIndex < overallResult->length()) {
-    if (!AppendPart(&JSAtomState::literal, lastEndIndex,
-                    overallResult->length(), source)) {
-      return false;
-    }
-  }
-
-  result.setObject(*partsArray);
-  return true;
+  return CreateDateTimePartArray(cx, spanResult.unwrap(),
+                                 /* hasNoSource */ false, parts, result);
 }
 
 bool js::intl_FormatDateTimeRange(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 4);
   MOZ_ASSERT(args[0].isObject());
   MOZ_ASSERT(args[1].isNumber());
   MOZ_ASSERT(args[2].isNumber());
@@ -1831,40 +1567,25 @@ bool js::intl_FormatDateTimeRange(JSCont
         formatToParts ? "formatRangeToParts" : "formatRange");
     return false;
   }
 
   // Self-hosted code should have checked this condition.
   MOZ_ASSERT(x.toDouble() <= y.toDouble(),
              "start date mustn't be after the end date");
 
-  // Obtain a cached mozilla::intl::DateTimeFormat object.
-  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
+  mozilla::intl::DateTimeFormat* df =
+      GetOrCreateDateTimeFormat(cx, dateTimeFormat);
   if (!df) {
-    df = NewDateTimeFormat(cx, dateTimeFormat);
-    if (!df) {
-      return false;
-    }
-    dateTimeFormat->setDateFormat(df);
-
-    intl::AddICUCellMemory(dateTimeFormat,
-                           DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
+    return false;
   }
 
-  // Obtain a cached UDateIntervalFormat object.
-  UDateIntervalFormat* dif = dateTimeFormat->getDateIntervalFormat();
+  mozilla::intl::DateIntervalFormat* dif =
+      GetOrCreateDateIntervalFormat(cx, dateTimeFormat, *df);
   if (!dif) {
-    dif = NewUDateIntervalFormat(cx, dateTimeFormat, *df);
-    if (!dif) {
-      return false;
-    }
-    dateTimeFormat->setDateIntervalFormat(dif);
-
-    intl::AddICUCellMemory(
-        dateTimeFormat,
-        DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
+    return false;
   }
 
-  // Use the UDateIntervalFormat to actually format the time range.
+  // Use the DateIntervalFormat to actually format the time range.
   return formatToParts
              ? FormatDateTimeRangeToParts(cx, df, dif, x, y, args.rval())
              : FormatDateTimeRange(cx, df, dif, x, y, args.rval());
 }
--- a/js/src/builtin/intl/DateTimeFormat.h
+++ b/js/src/builtin/intl/DateTimeFormat.h
@@ -8,66 +8,66 @@
 #define builtin_intl_DateTimeFormat_h
 
 #include "builtin/intl/CommonFunctions.h"
 #include "builtin/SelfHostingDefines.h"
 #include "js/Class.h"
 #include "js/RootingAPI.h"
 #include "vm/NativeObject.h"
 
-struct UDateIntervalFormat;
-
 namespace mozilla::intl {
 class DateTimeFormat;
-}
+class DateIntervalFormat;
+}  // namespace mozilla::intl
 
 namespace js {
 
 class DateTimeFormatObject : public NativeObject {
  public:
   static const JSClass class_;
   static const JSClass& protoClass_;
 
   static constexpr uint32_t INTERNALS_SLOT = 0;
-  static constexpr uint32_t UDATE_FORMAT_SLOT = 1;
-  static constexpr uint32_t UDATE_INTERVAL_FORMAT_SLOT = 2;
+  static constexpr uint32_t DATE_FORMAT_SLOT = 1;
+  static constexpr uint32_t DATE_INTERVAL_FORMAT_SLOT = 2;
   static constexpr uint32_t SLOT_COUNT = 3;
 
   static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
                 "INTERNALS_SLOT must match self-hosting define for internals "
                 "object slot");
 
   // Estimated memory use for UDateFormat (see IcuMemoryUsage).
   static constexpr size_t UDateFormatEstimatedMemoryUse = 105402;
 
   // Estimated memory use for UDateIntervalFormat (see IcuMemoryUsage).
   static constexpr size_t UDateIntervalFormatEstimatedMemoryUse = 133064;
 
   mozilla::intl::DateTimeFormat* getDateFormat() const {
-    const auto& slot = getFixedSlot(UDATE_FORMAT_SLOT);
+    const auto& slot = getFixedSlot(DATE_FORMAT_SLOT);
     if (slot.isUndefined()) {
       return nullptr;
     }
     return static_cast<mozilla::intl::DateTimeFormat*>(slot.toPrivate());
   }
 
   void setDateFormat(mozilla::intl::DateTimeFormat* dateFormat) {
-    setFixedSlot(UDATE_FORMAT_SLOT, PrivateValue(dateFormat));
+    setFixedSlot(DATE_FORMAT_SLOT, PrivateValue(dateFormat));
   }
 
-  UDateIntervalFormat* getDateIntervalFormat() const {
-    const auto& slot = getFixedSlot(UDATE_INTERVAL_FORMAT_SLOT);
+  mozilla::intl::DateIntervalFormat* getDateIntervalFormat() const {
+    const auto& slot = getFixedSlot(DATE_INTERVAL_FORMAT_SLOT);
     if (slot.isUndefined()) {
       return nullptr;
     }
-    return static_cast<UDateIntervalFormat*>(slot.toPrivate());
+    return static_cast<mozilla::intl::DateIntervalFormat*>(slot.toPrivate());
   }
 
-  void setDateIntervalFormat(UDateIntervalFormat* dateIntervalFormat) {
-    setFixedSlot(UDATE_INTERVAL_FORMAT_SLOT, PrivateValue(dateIntervalFormat));
+  void setDateIntervalFormat(
+      mozilla::intl::DateIntervalFormat* dateIntervalFormat) {
+    setFixedSlot(DATE_INTERVAL_FORMAT_SLOT, PrivateValue(dateIntervalFormat));
   }
 
  private:
   static const JSClassOps classOps_;
   static const ClassSpec classSpec_;
 
   static void finalize(JSFreeOp* fop, JSObject* obj);
 };
--- a/js/src/builtin/intl/ListFormat.cpp
+++ b/js/src/builtin/intl/ListFormat.cpp
@@ -205,16 +205,34 @@ static mozilla::intl::ListFormat* NewLis
   if (result.isOk()) {
     return result.unwrap().release();
   }
 
   js::intl::ReportInternalError(cx, result.unwrapErr());
   return nullptr;
 }
 
+static mozilla::intl::ListFormat* GetOrCreateListFormat(
+    JSContext* cx, Handle<ListFormatObject*> listFormat) {
+  // Obtain a cached mozilla::intl::ListFormat object.
+  mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot();
+  if (lf) {
+    return lf;
+  }
+
+  lf = NewListFormat(cx, listFormat);
+  if (!lf) {
+    return nullptr;
+  }
+  listFormat->setListFormatSlot(lf);
+
+  intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse);
+  return lf;
+}
+
 /**
  * FormatList ( listFormat, list )
  */
 static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf,
                        const mozilla::intl::ListFormat::StringList& list,
                        MutableHandleValue result) {
   intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx);
   auto formatResult = lf->Format(list, formatBuffer);
@@ -295,26 +313,19 @@ bool js::intl_FormatList(JSContext* cx, 
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 3);
 
   Rooted<ListFormatObject*> listFormat(
       cx, &args[0].toObject().as<ListFormatObject>());
 
   bool formatToParts = args[2].toBoolean();
 
-  // Obtain a cached mozilla::intl::ListFormat object.
-  mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot();
+  mozilla::intl::ListFormat* lf = GetOrCreateListFormat(cx, listFormat);
   if (!lf) {
-    lf = NewListFormat(cx, listFormat);
-    if (!lf) {
-      return false;
-    }
-    listFormat->setListFormatSlot(lf);
-
-    intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse);
+    return false;
   }
 
   // Collect all strings and their lengths.
   //
   // 'strings' takes the ownership of those strings, and 'list' will be passed
   // to mozilla::intl::ListFormat as a Span.
   Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx);
   mozilla::intl::ListFormat::StringList list;
--- a/js/src/builtin/intl/NumberFormat.cpp
+++ b/js/src/builtin/intl/NumberFormat.cpp
@@ -773,16 +773,54 @@ static Formatter* NewNumberFormat(JSCont
   if (result.isOk()) {
     return result.unwrap().release();
   }
 
   intl::ReportInternalError(cx, result.unwrapErr());
   return nullptr;
 }
 
+static mozilla::intl::NumberFormat* GetOrCreateNumberFormat(
+    JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
+  // Obtain a cached mozilla::intl::NumberFormat object.
+  mozilla::intl::NumberFormat* nf = numberFormat->getNumberFormatter();
+  if (nf) {
+    return nf;
+  }
+
+  nf = NewNumberFormat<mozilla::intl::NumberFormat>(cx, numberFormat);
+  if (!nf) {
+    return nullptr;
+  }
+  numberFormat->setNumberFormatter(nf);
+
+  intl::AddICUCellMemory(numberFormat, NumberFormatObject::EstimatedMemoryUse);
+  return nf;
+}
+
+static mozilla::intl::NumberRangeFormat* GetOrCreateNumberRangeFormat(
+    JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
+  // Obtain a cached mozilla::intl::NumberRangeFormat object.
+  mozilla::intl::NumberRangeFormat* nrf =
+      numberFormat->getNumberRangeFormatter();
+  if (nrf) {
+    return nrf;
+  }
+
+  nrf = NewNumberFormat<mozilla::intl::NumberRangeFormat>(cx, numberFormat);
+  if (!nrf) {
+    return nullptr;
+  }
+  numberFormat->setNumberRangeFormatter(nrf);
+
+  intl::AddICUCellMemory(numberFormat,
+                         NumberFormatObject::EstimatedRangeFormatterMemoryUse);
+  return nrf;
+}
+
 static FieldType GetFieldTypeForNumberPartType(
     mozilla::intl::NumberPartType type) {
   switch (type) {
     case mozilla::intl::NumberPartType::ApproximatelySign:
       return &JSAtomState::approximatelySign;
     case mozilla::intl::NumberPartType::Compact:
       return &JSAtomState::compact;
     case mozilla::intl::NumberPartType::Currency:
@@ -1121,27 +1159,19 @@ bool js::intl_FormatNumber(JSContext* cx
 
   RootedValue value(cx, args[1]);
 #ifdef NIGHTLY_BUILD
   if (!ToIntlMathematicalValue(cx, &value)) {
     return false;
   }
 #endif
 
-  // Obtain a cached mozilla::intl::NumberFormat object.
-  mozilla::intl::NumberFormat* nf = numberFormat->getNumberFormatter();
+  mozilla::intl::NumberFormat* nf = GetOrCreateNumberFormat(cx, numberFormat);
   if (!nf) {
-    nf = NewNumberFormat<mozilla::intl::NumberFormat>(cx, numberFormat);
-    if (!nf) {
-      return false;
-    }
-    numberFormat->setNumberFormatter(nf);
-
-    intl::AddICUCellMemory(numberFormat,
-                           NumberFormatObject::EstimatedMemoryUse);
+    return false;
   }
 
   // Actually format the number
   using ICUError = mozilla::intl::ICUError;
 
   bool formatToParts = args[2].toBoolean();
   mozilla::Result<std::u16string_view, ICUError> result =
       mozilla::Err(ICUError::InternalError);
@@ -1461,28 +1491,20 @@ bool js::intl_FormatNumberRange(JSContex
     return false;
   }
 
   if (!ValidateNumberRange(cx, &start, startApprox, &end, endApprox,
                            formatToParts)) {
     return false;
   }
 
-  // Obtain a cached mozilla::intl::NumberFormat object.
   using NumberRangeFormat = mozilla::intl::NumberRangeFormat;
-  NumberRangeFormat* nf = numberFormat->getNumberRangeFormatter();
+  NumberRangeFormat* nf = GetOrCreateNumberRangeFormat(cx, numberFormat);
   if (!nf) {
-    nf = NewNumberFormat<NumberRangeFormat>(cx, numberFormat);
-    if (!nf) {
-      return false;
-    }
-    numberFormat->setNumberRangeFormatter(nf);
-
-    intl::AddICUCellMemory(
-        numberFormat, NumberFormatObject::EstimatedRangeFormatterMemoryUse);
+    return false;
   }
 
   auto valueRepresentableAsDouble = [](const Value& val, double* num) {
     if (val.isNumber()) {
       *num = val.toNumber();
       return true;
     }
     if (val.isBigInt()) {
--- a/js/src/builtin/intl/PluralRules.cpp
+++ b/js/src/builtin/intl/PluralRules.cpp
@@ -286,37 +286,48 @@ static mozilla::intl::PluralRules* NewPl
   if (result.isErr()) {
     intl::ReportInternalError(cx, result.unwrapErr());
     return nullptr;
   }
 
   return result.unwrap().release();
 }
 
+static mozilla::intl::PluralRules* GetOrCreatePluralRules(
+    JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
+  // Obtain a cached PluralRules object.
+  mozilla::intl::PluralRules* pr = pluralRules->getPluralRules();
+  if (pr) {
+    return pr;
+  }
+
+  pr = NewPluralRules(cx, pluralRules);
+  if (!pr) {
+    return nullptr;
+  }
+  pluralRules->setPluralRules(pr);
+
+  intl::AddICUCellMemory(pluralRules,
+                         PluralRulesObject::UPluralRulesEstimatedMemoryUse);
+  return pr;
+}
+
 bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 2);
 
   Rooted<PluralRulesObject*> pluralRules(
       cx, &args[0].toObject().as<PluralRulesObject>());
 
   double x = args[1].toNumber();
 
-  // Obtain a cached PluralRules object.
   using PluralRules = mozilla::intl::PluralRules;
-  PluralRules* pr = pluralRules->getPluralRules();
+  PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
   if (!pr) {
-    pr = NewPluralRules(cx, pluralRules);
-    if (!pr) {
-      return false;
-    }
-    pluralRules->setPluralRules(pr);
-
-    intl::AddICUCellMemory(pluralRules,
-                           PluralRulesObject::UPluralRulesEstimatedMemoryUse);
+    return false;
   }
 
   auto keywordResult = pr->Select(x);
   if (keywordResult.isErr()) {
     intl::ReportInternalError(cx, keywordResult.unwrapErr());
     return false;
   }
 
@@ -340,28 +351,20 @@ bool js::intl_SelectPluralRuleRange(JSCo
 
   if (x > y) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_START_AFTER_END_NUMBER, "PluralRules",
                               "selectRange");
     return false;
   }
 
-  // Obtain a cached PluralRules object.
   using PluralRules = mozilla::intl::PluralRules;
-  PluralRules* pr = pluralRules->getPluralRules();
+  PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
   if (!pr) {
-    pr = NewPluralRules(cx, pluralRules);
-    if (!pr) {
-      return false;
-    }
-    pluralRules->setPluralRules(pr);
-
-    intl::AddICUCellMemory(pluralRules,
-                           PluralRulesObject::UPluralRulesEstimatedMemoryUse);
+    return false;
   }
 
   auto keywordResult = pr->SelectRange(x, y);
   if (keywordResult.isErr()) {
     intl::ReportInternalError(cx, keywordResult.unwrapErr());
     return false;
   }
 
@@ -377,28 +380,20 @@ bool js::intl_SelectPluralRuleRange(JSCo
 
 bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 1);
 
   Rooted<PluralRulesObject*> pluralRules(
       cx, &args[0].toObject().as<PluralRulesObject>());
 
-  // Obtain a cached PluralRules object.
   using PluralRules = mozilla::intl::PluralRules;
-  PluralRules* pr = pluralRules->getPluralRules();
+  PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
   if (!pr) {
-    pr = NewPluralRules(cx, pluralRules);
-    if (!pr) {
-      return false;
-    }
-    pluralRules->setPluralRules(pr);
-
-    intl::AddICUCellMemory(pluralRules,
-                           PluralRulesObject::UPluralRulesEstimatedMemoryUse);
+    return false;
   }
 
   auto categoriesResult = pr->Categories();
   if (categoriesResult.isErr()) {
     intl::ReportInternalError(cx, categoriesResult.unwrapErr());
     return false;
   }
   auto categories = categoriesResult.unwrap();
--- a/js/src/builtin/intl/RelativeTimeFormat.cpp
+++ b/js/src/builtin/intl/RelativeTimeFormat.cpp
@@ -262,16 +262,36 @@ static mozilla::intl::RelativeTimeFormat
   if (result.isOk()) {
     return result.unwrap().release();
   }
 
   intl::ReportInternalError(cx, result.unwrapErr());
   return nullptr;
 }
 
+static mozilla::intl::RelativeTimeFormat* GetOrCreateRelativeTimeFormat(
+    JSContext* cx, Handle<RelativeTimeFormatObject*> relativeTimeFormat) {
+  // Obtain a cached RelativeDateTimeFormatter object.
+  mozilla::intl::RelativeTimeFormat* rtf =
+      relativeTimeFormat->getRelativeTimeFormatter();
+  if (rtf) {
+    return rtf;
+  }
+
+  rtf = NewRelativeTimeFormatter(cx, relativeTimeFormat);
+  if (!rtf) {
+    return nullptr;
+  }
+  relativeTimeFormat->setRelativeTimeFormatter(rtf);
+
+  intl::AddICUCellMemory(relativeTimeFormat,
+                         RelativeTimeFormatObject::EstimatedMemoryUse);
+  return rtf;
+}
+
 bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 4);
   MOZ_ASSERT(args[0].isObject());
   MOZ_ASSERT(args[1].isNumber());
   MOZ_ASSERT(args[2].isString());
   MOZ_ASSERT(args[3].isBoolean());
 
@@ -284,28 +304,20 @@ bool js::intl_FormatRelativeTime(JSConte
   double t = args[1].toNumber();
   if (!mozilla::IsFinite(t)) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat",
                               formatToParts ? "formatToParts" : "format");
     return false;
   }
 
-  // Obtain a cached URelativeDateTimeFormatter object.
   mozilla::intl::RelativeTimeFormat* rtf =
-      relativeTimeFormat->getRelativeTimeFormatter();
+      GetOrCreateRelativeTimeFormat(cx, relativeTimeFormat);
   if (!rtf) {
-    rtf = NewRelativeTimeFormatter(cx, relativeTimeFormat);
-    if (!rtf) {
-      return false;
-    }
-    relativeTimeFormat->setRelativeTimeFormatter(rtf);
-
-    intl::AddICUCellMemory(relativeTimeFormat,
-                           RelativeTimeFormatObject::EstimatedMemoryUse);
+    return false;
   }
 
   intl::FieldType jsUnitType;
   using FormatUnit = mozilla::intl::RelativeTimeFormat::FormatUnit;
   FormatUnit relTimeUnit;
   {
     JSLinearString* unit = args[2].toString()->ensureLinear(cx);
     if (!unit) {
--- a/js/src/frontend/Stencil.cpp
+++ b/js/src/frontend/Stencil.cpp
@@ -4086,19 +4086,17 @@ JSObject* JS::InstantiateModuleStencil(J
   Rooted<CompilationGCOutput> gcOutput(cx);
   if (!InstantiateStencils(cx, input.get(), *stencil, gcOutput.get())) {
     return nullptr;
   }
 
   return gcOutput.get().module;
 }
 
-JS::TranscodeResult JS::EncodeStencil(JSContext* cx,
-                                      const JS::ReadOnlyCompileOptions& options,
-                                      JS::Stencil* stencil,
+JS::TranscodeResult JS::EncodeStencil(JSContext* cx, JS::Stencil* stencil,
                                       TranscodeBuffer& buffer) {
   XDRStencilEncoder encoder(cx, buffer);
   XDRResult res = encoder.codeStencil(*stencil);
   if (res.isErr()) {
     return res.unwrapErr();
   }
   return TranscodeResult::Ok;
 }
--- a/js/src/jsapi-tests/testStencil.cpp
+++ b/js/src/jsapi-tests/testStencil.cpp
@@ -208,17 +208,17 @@ BEGIN_TEST(testStencil_Transcode) {
     CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
 
     JS::CompileOptions options(cx);
     RefPtr<JS::Stencil> stencil =
         JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
     CHECK(stencil);
 
     // Encode Stencil to XDR
-    JS::TranscodeResult res = JS::EncodeStencil(cx, options, stencil, buffer);
+    JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
     CHECK(res == JS::TranscodeResult::Ok);
     CHECK(!buffer.empty());
 
     // Instantiate and Run
     JS::InstantiateOptions instantiateOptions(options);
     JS::RootedScript script(
         cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
     JS::RootedValue rval(cx);
@@ -286,17 +286,17 @@ BEGIN_TEST(testStencil_TranscodeBorrowin
     CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
 
     JS::CompileOptions options(cx);
     RefPtr<JS::Stencil> stencil =
         JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
     CHECK(stencil);
 
     // Encode Stencil to XDR
-    JS::TranscodeResult res = JS::EncodeStencil(cx, options, stencil, buffer);
+    JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
     CHECK(res == JS::TranscodeResult::Ok);
     CHECK(!buffer.empty());
   }
 
   JS::RootedScript script(cx);
   {
     JS::TranscodeRange range(buffer.begin(), buffer.length());
     JS::CompileOptions options(cx);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2240,17 +2240,17 @@ static bool StartIncrementalEncoding(JSC
   }
 
   auto* source = stencil->source.get();
 
   if (!initial->steal(cx, std::move(*stencil))) {
     return false;
   }
 
-  if (!source->startIncrementalEncoding(cx, options, std::move(initial))) {
+  if (!source->startIncrementalEncoding(cx, std::move(initial))) {
     return false;
   }
 
   return true;
 }
 
 static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/vm/CompilationAndEvaluation.cpp
+++ b/js/src/vm/CompilationAndEvaluation.cpp
@@ -111,17 +111,17 @@ static JSScript* CompileSourceBufferAndS
     }
 
     script = gcOutput.get().script;
     if (!script) {
       return nullptr;
     }
   }
 
-  if (!script->scriptSource()->startIncrementalEncoding(cx, options,
+  if (!script->scriptSource()->startIncrementalEncoding(cx,
                                                         std::move(stencil))) {
     return nullptr;
   }
 
   return script;
 }
 
 JSScript* JS::CompileAndStartIncrementalEncoding(
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -2215,23 +2215,22 @@ JSScript* GlobalHelperThreadState::finis
         ReportOutOfMemory(cx);
         return nullptr;
       }
       if (!initial->steal(cx, std::move(*parseTask->stencil_))) {
         return nullptr;
       }
 
       if (!script->scriptSource()->startIncrementalEncoding(
-              cx, parseTask->options, std::move(initial))) {
+              cx, std::move(initial))) {
         return nullptr;
       }
     } else if (parseTask->extensibleStencil_) {
       if (!script->scriptSource()->startIncrementalEncoding(
-              cx, parseTask->options,
-              std::move(parseTask->extensibleStencil_))) {
+              cx, std::move(parseTask->extensibleStencil_))) {
         return nullptr;
       }
     }
   }
 
   return script;
 }
 
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1782,17 +1782,17 @@ bool js::SynchronouslyCompressSource(JSC
 
 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                           JS::ScriptSourceInfo* info) const {
   info->misc += mallocSizeOf(this);
   info->numScripts++;
 }
 
 bool ScriptSource::startIncrementalEncoding(
-    JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+    JSContext* cx,
     UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
   // Encoding failures are reported by the xdrFinalizeEncoder function.
   if (containsAsmJS()) {
     return true;
   }
 
   // Remove the reference to the source, to avoid the circular reference.
   initial->source = nullptr;
@@ -1803,17 +1803,17 @@ bool ScriptSource::startIncrementalEncod
     return false;
   }
 
   AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
   auto failureCase =
       mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); });
 
   XDRResult res = xdrEncoder_->setInitial(
-      cx, options,
+      cx,
       std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(initial));
   if (res.isErr()) {
     // On encoding failure, let failureCase destroy encoder and return true
     // to avoid failing any currently executing script.
     return JS::IsTranscodeFailureResult(res.unwrapErr());
   }
 
   failureCase.release();
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -1024,17 +1024,17 @@ class ScriptSource {
 
   bool containsAsmJS() const { return containsAsmJS_; }
   void setContainsAsmJS() { containsAsmJS_ = true; }
 
   // Return wether an XDR encoder is present or not.
   bool hasEncoder() const { return bool(xdrEncoder_); }
 
   [[nodiscard]] bool startIncrementalEncoding(
-      JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+      JSContext* cx,
       UniquePtr<frontend::ExtensibleCompilationStencil>&& initial);
 
   [[nodiscard]] bool addDelazificationToIncrementalEncoding(
       JSContext* cx, const frontend::CompilationStencil& stencil);
 
   // Linearize the encoded content in the |buffer| provided as argument to
   // |xdrEncodeTopLevel|, and free the XDR encoder.  In case of errors, the
   // |buffer| is considered undefined.
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -286,17 +286,17 @@ XDRResult XDRStencilEncoder::codeStencil
 
 XDRIncrementalStencilEncoder::~XDRIncrementalStencilEncoder() {
   if (merger_) {
     js_delete(merger_);
   }
 }
 
 XDRResult XDRIncrementalStencilEncoder::setInitial(
-    JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+    JSContext* cx,
     UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
   MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(*initial));
 
   merger_ = cx->new_<frontend::CompilationStencilMerger>();
   if (!merger_) {
     return mozilla::Err(JS::TranscodeResult::Throw);
   }
 
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -496,17 +496,17 @@ class XDRIncrementalStencilEncoder {
   XDRIncrementalStencilEncoder() = default;
 
   ~XDRIncrementalStencilEncoder();
 
   XDRResult linearize(JSContext* cx, JS::TranscodeBuffer& buffer,
                       js::ScriptSource* ss);
 
   XDRResult setInitial(
-      JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+      JSContext* cx,
       UniquePtr<frontend::ExtensibleCompilationStencil>&& initial);
   XDRResult addDelazification(
       JSContext* cx, const frontend::CompilationStencil& delazification);
 };
 
 } /* namespace js */
 
 #endif /* vm_Xdr_h */
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -1158,21 +1158,17 @@ ScriptPreloader::CachedStencil::CachedSt
   mProcessTypes = {};
 }
 
 bool ScriptPreloader::CachedStencil::XDREncode(JSContext* cx) {
   auto cleanup = MakeScopeExit([&]() { MaybeDropStencil(); });
 
   mXDRData.construct<JS::TranscodeBuffer>();
 
-  JS::CompileOptions compileOptions(cx);
-  FillCompileOptionsForCachedStencil(compileOptions);
-
-  JS::TranscodeResult code =
-      JS::EncodeStencil(cx, compileOptions, mStencil, Buffer());
+  JS::TranscodeResult code = JS::EncodeStencil(cx, mStencil, Buffer());
   if (code == JS::TranscodeResult::Ok) {
     mXDRRange.emplace(Buffer().begin(), Buffer().length());
     mSize = Range().length();
     return true;
   }
   mXDRData.destroy();
   JS_ClearPendingException(cx);
   return false;
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -840,17 +840,17 @@ nsresult mozJSComponentLoader::ObjectFor
 
   // Write to startup cache only when we didn't have any cache for the script
   // and compiled it.
   if (storeIntoStartupCache) {
     MOZ_ASSERT(options.sourceIsLazy);
     MOZ_ASSERT(stencil);
 
     // We successfully compiled the script, so cache it.
-    rv = WriteCachedStencil(cache, cachePath, cx, options, stencil);
+    rv = WriteCachedStencil(cache, cachePath, cx, stencil);
 
     // Don't treat failure to write as fatal, since we might be working
     // with a read-only cache.
     if (NS_SUCCEEDED(rv)) {
       LOG(("Successfully wrote to cache\n"));
     } else {
       LOG(("Failed to write to cache\n"));
     }
--- a/js/xpconnect/loader/mozJSLoaderUtils.cpp
+++ b/js/xpconnect/loader/mozJSLoaderUtils.cpp
@@ -44,20 +44,19 @@ nsresult ReadCachedStencil(StartupCache*
   }
 
   JS::TranscodeRange range(AsBytes(Span(buf, len)));
   JS::TranscodeResult code = JS::DecodeStencil(cx, options, range, stencilOut);
   return HandleTranscodeResult(cx, code);
 }
 
 nsresult WriteCachedStencil(StartupCache* cache, nsACString& uri, JSContext* cx,
-                            const JS::ReadOnlyCompileOptions& options,
                             JS::Stencil* stencil) {
   JS::TranscodeBuffer buffer;
-  JS::TranscodeResult code = JS::EncodeStencil(cx, options, stencil, buffer);
+  JS::TranscodeResult code = JS::EncodeStencil(cx, stencil, buffer);
   if (code != JS::TranscodeResult::Ok) {
     return HandleTranscodeResult(cx, code);
   }
 
   size_t size = buffer.length();
   if (size > UINT32_MAX) {
     return NS_ERROR_FAILURE;
   }
--- a/js/xpconnect/loader/mozJSLoaderUtils.h
+++ b/js/xpconnect/loader/mozJSLoaderUtils.h
@@ -20,12 +20,11 @@ class StartupCache;
 
 nsresult ReadCachedStencil(mozilla::scache::StartupCache* cache,
                            nsACString& uri, JSContext* cx,
                            const JS::ReadOnlyCompileOptions& options,
                            JS::Stencil** stencilOut);
 
 nsresult WriteCachedStencil(mozilla::scache::StartupCache* cache,
                             nsACString& uri, JSContext* cx,
-                            const JS::ReadOnlyCompileOptions& options,
                             JS::Stencil* stencil);
 
 #endif /* mozJSLoaderUtils_h */
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -193,18 +193,17 @@ static bool EvalStencil(JSContext* cx, H
 
     nsCString uriStr;
     if (storeIntoPreloadCache && NS_SUCCEEDED(uri->GetSpec(uriStr))) {
       ScriptPreloader::GetSingleton().NoteStencil(uriStr, cachePath, stencil);
     }
 
     if (storeIntoStartupCache) {
       JSAutoRealm ar(cx, script);
-      WriteCachedStencil(StartupCache::GetSingleton(), cachePath, cx, options,
-                         stencil);
+      WriteCachedStencil(StartupCache::GetSingleton(), cachePath, cx, stencil);
     }
   }
 
   return true;
 }
 
 bool mozJSSubScriptLoader::ReadStencil(
     JS::Stencil** stencilOut, nsIURI* uri, JSContext* cx,
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -34,17 +34,16 @@ XPIDL_MODULE = "layout_base"
 
 EXPORTS += [
     "CaretAssociationHint.h",
     "FrameProperties.h",
     "LayoutConstants.h",
     "LayoutLogging.h",
     "MobileViewportManager.h",
     "nsAutoLayoutPhase.h",
-    "nsBidi.h",
     "nsBidiPresUtils.h",
     "nsCaret.h",
     "nsChangeHint.h",
     "nsCompatibility.h",
     "nsCounterManager.h",
     "nsCSSFrameConstructor.h",
     "nsFrameManager.h",
     "nsFrameTraversal.h",
@@ -104,17 +103,16 @@ UNIFIED_SOURCES += [
     "AccessibleCaretManager.cpp",
     "DisplayPortUtils.cpp",
     "GeckoMVMContext.cpp",
     "GeometryUtils.cpp",
     "LayoutLogging.cpp",
     "LayoutTelemetryTools.cpp",
     "MobileViewportManager.cpp",
     "MotionPathUtils.cpp",
-    "nsBidi.cpp",
     "nsBidiPresUtils.cpp",
     "nsCaret.cpp",
     "nsCounterManager.cpp",
     "nsCSSColorUtils.cpp",
     "nsCSSFrameConstructor.cpp",
     "nsDocumentViewer.cpp",
     "nsFrameManager.cpp",
     "nsFrameTraversal.cpp",
deleted file mode 100644
--- a/layout/base/nsBidi.cpp
+++ /dev/null
@@ -1,42 +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/. */
-
-#include "nsBidi.h"
-
-nsresult nsBidi::CountRuns(int32_t* aRunCount) {
-  UErrorCode errorCode = U_ZERO_ERROR;
-  *aRunCount = ubidi_countRuns(mBiDi, &errorCode);
-  if (U_SUCCESS(errorCode)) {
-    mLength = ubidi_getProcessedLength(mBiDi);
-    mLevels = mLength > 0 ? ubidi_getLevels(mBiDi, &errorCode) : nullptr;
-  }
-  return ICUUtils::UErrorToNsResult(errorCode);
-}
-
-void nsBidi::GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
-                           nsBidiLevel* aLevel) {
-  MOZ_ASSERT(mLevels, "CountRuns hasn't been run?");
-  MOZ_RELEASE_ASSERT(aLogicalStart < mLength, "Out of bound");
-  // This function implements an alternative approach to get logical
-  // run that is based on levels of characters, which would avoid O(n^2)
-  // performance issue when used in a loop over runs.
-  // Per comment in ubidi_getLogicalRun, that function doesn't use this
-  // approach because levels have special interpretation when reordering
-  // mode is UBIDI_REORDER_RUNS_ONLY. Since we don't use this mode in
-  // Gecko, it should be safe to just use levels for this function.
-  MOZ_ASSERT(ubidi_getReorderingMode(mBiDi) != UBIDI_REORDER_RUNS_ONLY,
-             "Don't support UBIDI_REORDER_RUNS_ONLY mode");
-
-  nsBidiLevel level = mLevels[aLogicalStart];
-  int32_t limit;
-  for (limit = aLogicalStart + 1; limit < mLength; limit++) {
-    if (mLevels[limit] != level) {
-      break;
-    }
-  }
-  *aLogicalLimit = limit;
-  *aLevel = level;
-}
deleted file mode 100644
--- a/layout/base/nsBidi.h
+++ /dev/null
@@ -1,208 +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 nsBidi_h__
-#define nsBidi_h__
-
-#include "unicode/ubidi.h"
-#include "ICUUtils.h"
-#include "nsIFrame.h"  // for nsBidiLevel/nsBidiDirection declarations
-
-// nsBidi implemented as a simple wrapper around the bidi reordering engine
-// from ICU.
-// We could eliminate this and let callers use the ICU functions directly
-// once we no longer care about building without ICU available.
-
-class nsBidi {
- public:
-  /** @brief Default constructor.
-   *
-   * The nsBidi object is initially empty. It is assigned
-   * the Bidi properties of a paragraph by <code>SetPara()</code>.
-   */
-  nsBidi() { mBiDi = ubidi_open(); }
-
-  /** @brief Destructor. */
-  ~nsBidi() { ubidi_close(mBiDi); }
-
-  /**
-   * Perform the Unicode Bidi algorithm.
-   *
-   * @param aText is a pointer to the single-paragraph text that the
-   *      Bidi algorithm will be performed on
-   *      (step (P1) of the algorithm is performed externally).
-   *      <strong>The text must be (at least) <code>aLength</code> long.
-   *      </strong>
-   *
-   * @param aLength is the length of the text; if <code>aLength==-1</code> then
-   *      the text must be zero-terminated.
-   *
-   * @param aParaLevel specifies the default level for the paragraph;
-   *      it is typically 0 (LTR) or 1 (RTL).
-   *      If the function shall determine the paragraph level from the text,
-   *      then <code>aParaLevel</code> can be set to
-   *      either <code>NSBIDI_DEFAULT_LTR</code>
-   *      or <code>NSBIDI_DEFAULT_RTL</code>;
-   *      if there is no strongly typed character, then
-   *      the desired default is used (0 for LTR or 1 for RTL).
-   *      Any other value between 0 and <code>NSBIDI_MAX_EXPLICIT_LEVEL</code>
-   *      is also valid, with odd levels indicating RTL.
-   */
-  nsresult SetPara(const char16_t* aText, int32_t aLength,
-                   nsBidiLevel aParaLevel) {
-    UErrorCode error = U_ZERO_ERROR;
-    ubidi_setPara(mBiDi, reinterpret_cast<const UChar*>(aText), aLength,
-                  aParaLevel, nullptr, &error);
-    return ICUUtils::UErrorToNsResult(error);
-  }
-
-  /**
-   * Get the directionality of the text.
-   *
-   * @param aDirection receives a <code>NSBIDI_XXX</code> value that indicates
-   *       if the entire text represented by this object is unidirectional,
-   *       and which direction, or if it is mixed-directional.
-   *
-   * @see nsBidiDirection
-   */
-  nsBidiDirection GetDirection() {
-    return nsBidiDirection(ubidi_getDirection(mBiDi));
-  }
-
-  /**
-   * Get the paragraph level of the text.
-   *
-   * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating
-   *                   the paragraph level
-   *
-   * @see nsBidiLevel
-   */
-  nsBidiLevel GetParaLevel() { return ubidi_getParaLevel(mBiDi); }
-
-  /**
-   * Get a logical run.
-   * This function returns information about a run and is used
-   * to retrieve runs in logical order.<p>
-   * This is especially useful for line-breaking on a paragraph.
-   * <code>CountRuns</code> should be called before this.
-   * before the runs are retrieved.
-   *
-   * @param aLogicalStart is the first character of the run.
-   *
-   * @param aLogicalLimit will receive the limit of the run.
-   *      The l-value that you point to here may be the
-   *      same expression (variable) as the one for
-   *      <code>aLogicalStart</code>.
-   *      This pointer cannot be <code>nullptr</code>.
-   *
-   * @param aLevel will receive the level of the run.
-   *      This pointer cannot be <code>nullptr</code>.
-   */
-  void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
-                     nsBidiLevel* aLevel);
-
-  /**
-   * Get the number of runs.
-   * This function may invoke the actual reordering on the
-   * <code>nsBidi</code> object, after <code>SetPara</code>
-   * may have resolved only the levels of the text. Therefore,
-   * <code>CountRuns</code> may have to allocate memory,
-   * and may fail doing so.
-   *
-   * @param aRunCount will receive the number of runs.
-   */
-  nsresult CountRuns(int32_t* aRunCount);
-
-  /**
-   * Get one run's logical start, length, and directionality,
-   * which can be 0 for LTR or 1 for RTL.
-   * In an RTL run, the character at the logical start is
-   * visually on the right of the displayed run.
-   * The length is the number of characters in the run.<p>
-   * <code>CountRuns</code> should be called
-   * before the runs are retrieved.
-   *
-   * @param aRunIndex is the number of the run in visual order, in the
-   *      range <code>[0..CountRuns-1]</code>.
-   *
-   * @param aLogicalStart is the first logical character index in the text.
-   *      The pointer may be <code>nullptr</code> if this index is not needed.
-   *
-   * @param aLength is the number of characters (at least one) in the run.
-   *      The pointer may be <code>nullptr</code> if this is not needed.
-   *
-   * @returns the directionality of the run,
-   *       <code>NSBIDI_LTR==0</code> or <code>NSBIDI_RTL==1</code>,
-   *       never <code>NSBIDI_MIXED</code>.
-   *
-   * @see CountRuns<p>
-   *
-   * Example:
-   * @code
-   *  int32_t i, count, logicalStart, visualIndex=0, length;
-   *  nsBidiDirection dir;
-   *  pBidi->CountRuns(&count);
-   *  for(i=0; i<count; ++i) {
-   *    dir = pBidi->GetVisualRun(i, &logicalStart, &length);
-   *    if(NSBIDI_LTR==dir) {
-   *      do { // LTR
-   *        show_char(text[logicalStart++], visualIndex++);
-   *      } while(--length>0);
-   *    } else {
-   *      logicalStart+=length;  // logicalLimit
-   *      do { // RTL
-   *        show_char(text[--logicalStart], visualIndex++);
-   *      } while(--length>0);
-   *    }
-   *  }
-   * @endcode
-   *
-   * Note that in right-to-left runs, code like this places
-   * modifier letters before base characters and second surrogates
-   * before first ones.
-   */
-  nsBidiDirection GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart,
-                               int32_t* aLength) {
-    return nsBidiDirection(
-        ubidi_getVisualRun(mBiDi, aRunIndex, aLogicalStart, aLength));
-  }
-
-  /**
-   * This is a convenience function that does not use a nsBidi object.
-   * It is intended to be used for when an application has determined the levels
-   * of objects (character sequences) and just needs to have them reordered
-   * (L2). This is equivalent to using <code>GetVisualMap</code> on a
-   * <code>nsBidi</code> object.
-   *
-   * @param aLevels is an array with <code>aLength</code> levels that have been
-   *      determined by the application.
-   *
-   * @param aLength is the number of levels in the array, or, semantically,
-   *      the number of objects to be reordered.
-   *      It must be <code>aLength>0</code>.
-   *
-   * @param aIndexMap is a pointer to an array of <code>aLength</code>
-   *      indexes which will reflect the reordering of the characters.
-   *      The array does not need to be initialized.<p>
-   *      The index map will result in
-   *        <code>aIndexMap[aVisualIndex]==aLogicalIndex</code>.
-   */
-  static void ReorderVisual(const nsBidiLevel* aLevels, int32_t aLength,
-                            int32_t* aIndexMap) {
-    ubidi_reorderVisual(aLevels, aLength, aIndexMap);
-  }
-
- private:
-  nsBidi(const nsBidi&) = delete;
-  void operator=(const nsBidi&) = delete;
-
-  UBiDi* mBiDi;
-  // The two fields below are updated when CountRuns is called.
-  const nsBidiLevel* mLevels = nullptr;
-  int32_t mLength = 0;
-};
-
-#endif  // _nsBidi_h_
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -1,16 +1,18 @@
 /* -*- 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 "nsBidiPresUtils.h"
 
+#include "mozilla/intl/Bidi.h"
+#include "mozilla/Casting.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/dom/Text.h"
 
 #include "gfxContext.h"
 #include "nsFontMetrics.h"
 #include "nsGkAtoms.h"
@@ -34,16 +36,17 @@
 #include "nsRubyBaseContainerFrame.h"
 #include "nsRubyTextContainerFrame.h"
 #include <algorithm>
 
 #undef NOISY_BIDI
 #undef REALLY_NOISY_BIDI
 
 using namespace mozilla;
+using EmbeddingLevel = mozilla::intl::Bidi::EmbeddingLevel;
 
 static const char16_t kSpace = 0x0020;
 static const char16_t kZWSP = 0x200B;
 static const char16_t kLineSeparator = 0x2028;
 static const char16_t kObjectSubstitute = 0xFFFC;
 static const char16_t kLRE = 0x202A;
 static const char16_t kRLE = 0x202B;
 static const char16_t kLRO = 0x202D;
@@ -158,17 +161,17 @@ struct MOZ_STACK_CLASS BidiParagraphData
   nsAutoString mBuffer;
   AutoTArray<char16_t, 16> mEmbeddingStack;
   AutoTArray<FrameInfo, 16> mLogicalFrames;
   nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex;
   // Cached presentation context for the frames we're processing.
   nsPresContext* mPresContext;
   bool mIsVisual;
   bool mRequiresBidi;
-  nsBidiLevel mParaLevel;
+  EmbeddingLevel mParaLevel;
   nsIContent* mPrevContent;
 
   /**
    * This class is designed to manage the process of mapping a frame to
    * the line that it's in, when we know that (a) the frames we ask it
    * about are always in the block's lines and (b) each successive frame
    * we ask it about is the same as or after (in depth-first search
    * order) the previous.
@@ -330,47 +333,59 @@ struct MOZ_STACK_CLASS BidiParagraphData
           mIsVisual = false;
           break;
         }
       }
     }
   }
 
   nsresult SetPara() {
-    return mPresContext->GetBidiEngine().SetPara(mBuffer.get(), BufferLength(),
-                                                 mParaLevel);
+    if (mPresContext->GetBidiEngine()
+            .SetParagraph(mBuffer, mParaLevel)
+            .isErr()) {
+      return NS_ERROR_FAILURE;
+    };
+    return NS_OK;
   }
 
   /**
-   * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL.
-   * GetParaLevel() returns the actual (resolved) paragraph level which is
-   * always either NSBIDI_LTR or NSBIDI_RTL
+   * mParaLevel can be intl::Bidi::Direction::LTR as well as
+   * intl::Bidi::Direction::LTR or intl::Bidi::Direction::RTL.
+   * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
+   * which is always either intl::Bidi::Direction::LTR or
+   * intl::Bidi::Direction::RTL
    */
-  nsBidiLevel GetParaLevel() {
-    nsBidiLevel paraLevel = mParaLevel;
-    if (paraLevel == NSBIDI_DEFAULT_LTR || paraLevel == NSBIDI_DEFAULT_RTL) {
-      paraLevel = mPresContext->GetBidiEngine().GetParaLevel();
+  EmbeddingLevel GetParagraphEmbeddingLevel() {
+    EmbeddingLevel paraLevel = mParaLevel;
+    if (paraLevel == EmbeddingLevel::DefaultLTR() ||
+        paraLevel == EmbeddingLevel::DefaultRTL()) {
+      paraLevel = mPresContext->GetBidiEngine().GetParagraphEmbeddingLevel();
     }
     return paraLevel;
   }
 
-  nsBidiDirection GetDirection() {
-    return mPresContext->GetBidiEngine().GetDirection();
+  intl::Bidi::ParagraphDirection GetParagraphDirection() {
+    return mPresContext->GetBidiEngine().GetParagraphDirection();
   }
 
   nsresult CountRuns(int32_t* runCount) {
-    return mPresContext->GetBidiEngine().CountRuns(runCount);
+    auto result = mPresContext->GetBidiEngine().CountRuns();
+    if (result.isErr()) {
+      return NS_ERROR_FAILURE;
+    }
+    *runCount = result.unwrap();
+    return NS_OK;
   }
 
   void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
-                     nsBidiLevel* aLevel) {
+                     EmbeddingLevel* aLevel) {
     mPresContext->GetBidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
                                                 aLevel);
     if (mIsVisual) {
-      *aLevel = GetParaLevel();
+      *aLevel = GetParagraphEmbeddingLevel();
     }
   }
 
   void ResetData() {
     mLogicalFrames.Clear();
     mContentToFrameIndex.Clear();
     mBuffer.SetLength(0);
     mPrevContent = nullptr;
@@ -460,33 +475,33 @@ struct MOZ_STACK_CLASS BidiParagraphData
     }
   }
 };
 
 struct MOZ_STACK_CLASS BidiLineData {
   AutoTArray<nsIFrame*, 16> mLogicalFrames;
   AutoTArray<nsIFrame*, 16> mVisualFrames;
   AutoTArray<int32_t, 16> mIndexMap;
-  AutoTArray<uint8_t, 16> mLevels;
+  AutoTArray<EmbeddingLevel, 16> mLevels;
   bool mIsReordered;
 
   BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
     /**
      * Initialize the logically-ordered array of frames using the top-level
      * frames of a single line
      */
     bool isReordered = false;
     bool hasRTLFrames = false;
     bool hasVirtualControls = false;
 
-    auto appendFrame = [&](nsIFrame* frame, nsBidiLevel level) {
+    auto appendFrame = [&](nsIFrame* frame, EmbeddingLevel level) {
       mLogicalFrames.AppendElement(frame);
       mLevels.AppendElement(level);
       mIndexMap.AppendElement(0);
-      if (IS_LEVEL_RTL(level)) {
+      if (level.IsRTL()) {
         hasRTLFrames = true;
       }
     };
 
     bool firstFrame = true;
     for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
          frame = frame->GetNextSibling()) {
       FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
@@ -497,18 +512,18 @@ struct MOZ_STACK_CLASS BidiLineData {
         appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
         hasVirtualControls = true;
       }
       appendFrame(frame, bidiData.embeddingLevel);
       firstFrame = false;
     }
 
     // Reorder the line
-    nsBidi::ReorderVisual(mLevels.Elements(), FrameCount(),
-                          mIndexMap.Elements());
+    mozilla::intl::Bidi::ReorderVisual(mLevels.Elements(), FrameCount(),
+                                       mIndexMap.Elements());
 
     // Strip virtual frames
     if (hasVirtualControls) {
       auto originalCount = mLogicalFrames.Length();
       AutoTArray<int32_t, 16> realFrameMap;
       realFrameMap.SetCapacity(originalCount);
       size_t count = 0;
       for (auto i : IntegerRange(originalCount)) {
@@ -860,17 +875,18 @@ nsresult nsBidiPresUtils::ResolveParagra
   }
   aBpd->mBuffer.ReplaceChar(kSeparators, kSpace);
 
   int32_t runCount;
 
   nsresult rv = aBpd->SetPara();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsBidiLevel embeddingLevel = aBpd->GetParaLevel();
+  intl::Bidi::EmbeddingLevel embeddingLevel =
+      aBpd->GetParagraphEmbeddingLevel();
 
   rv = aBpd->CountRuns(&runCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
   int32_t runLength = 0;     // the length of the current run of text
   int32_t logicalLimit = 0;  // the end of the current run + 1
   int32_t numRun = -1;
   int32_t fragmentLength = 0;  // the length of the current text frame
@@ -892,18 +908,19 @@ nsresult nsBidiPresUtils::ResolveParagra
       frameCount, runCount);
 #    ifdef REALLY_NOISY_BIDI
   printf(" block frame tree=:\n");
   aBpd->mCurrentBlock->List(stdout);
 #    endif
 #  endif
 #endif
 
-  if (runCount == 1 && frameCount == 1 && aBpd->GetDirection() == NSBIDI_LTR &&
-      aBpd->GetParaLevel() == 0) {
+  if (runCount == 1 && frameCount == 1 &&
+      aBpd->GetParagraphDirection() == intl::Bidi::ParagraphDirection::LTR &&
+      aBpd->GetParagraphEmbeddingLevel() == 0) {
     // We have a single left-to-right frame in a left-to-right paragraph,
     // without bidi isolation from the surrounding text.
     // Make sure that the embedding level and base level frame properties aren't
     // set (because if they are this frame used to have some other direction,
     // so we can't do this optimization), and we're done.
     nsIFrame* frame = aBpd->FrameAt(0);
     if (frame != NS_BIDI_CONTROL_FRAME) {
       FrameBidiData bidiData = frame->GetBidiData();
@@ -915,23 +932,23 @@ nsresult nsBidiPresUtils::ResolveParagra
 #endif
         frame->AddStateBits(NS_FRAME_IS_BIDI);
         return NS_OK;
       }
     }
   }
 
   BidiParagraphData::FrameInfo lastRealFrame;
-  nsBidiLevel lastEmbeddingLevel = kBidiLevelNone;
-  nsBidiLevel precedingControl = kBidiLevelNone;
+  EmbeddingLevel lastEmbeddingLevel = kBidiLevelNone;
+  EmbeddingLevel precedingControl = kBidiLevelNone;
 
   auto storeBidiDataToFrame = [&]() {
     FrameBidiData bidiData;
     bidiData.embeddingLevel = embeddingLevel;
-    bidiData.baseLevel = aBpd->GetParaLevel();
+    bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel();
     // If a control character doesn't have a lower embedding level than
     // both the preceding and the following frame, it isn't something
     // needed for getting the correct result. This optimization should
     // remove almost all of embeds and overrides, and some of isolates.
     if (precedingControl >= embeddingLevel ||
         precedingControl >= lastEmbeddingLevel) {
       bidiData.precedingControl = kBidiLevelNone;
     } else {
@@ -1501,21 +1518,21 @@ nsIFrame* nsBidiPresUtils::GetFirstLeaf(
   }
   return firstLeaf;
 }
 
 FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) {
   return GetFirstLeaf(aFrame)->GetBidiData();
 }
 
-nsBidiLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
+EmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
   return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
 }
 
-nsBidiLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
+EmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
   const nsIFrame* firstLeaf = aFrame;
   while (!IsBidiLeaf(firstLeaf)) {
     firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
   }
   return firstLeaf->GetBaseLevel();
 }
 
 void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
@@ -1866,17 +1883,17 @@ nscoord nsBidiPresUtils::RepositionInlin
   } else {
     index = count - 1;
     step = -1;
     limit = -1;
   }
   for (; index != limit; index += step) {
     frame = aBld->VisualFrameAt(index);
     start += RepositionFrame(
-        frame, !(IS_LEVEL_RTL(aBld->mLevels[aBld->mIndexMap[index]])), start,
+        frame, !(aBld->mLevels[aBld->mIndexMap[index]].IsRTL()), start,
         &continuationStates, aLineWM, false, aContainerSize);
   }
   return start;
 }
 
 bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
                                      int32_t aNumFramesOnLine,
                                      nsIFrame** aFirstVisual,
@@ -2074,17 +2091,17 @@ RemoveDiacritics(char16_t* aText,
       aText[i - offset] = aText[i];
     }
     aTextLength = i - offset;
     aText[aTextLength] = 0;
   }
 }
 #endif
 
-void nsBidiPresUtils::CalculateCharType(nsBidi* aBidiEngine,
+void nsBidiPresUtils::CalculateCharType(intl::Bidi* aBidiEngine,
                                         const char16_t* aText, int32_t& aOffset,
                                         int32_t aCharTypeLimit,
                                         int32_t& aRunLimit, int32_t& aRunLength,
                                         int32_t& aRunCount, uint8_t& aCharType,
                                         uint8_t& aPrevCharType)
 
 {
   bool strongTypeFound = false;
@@ -2138,79 +2155,83 @@ void nsBidiPresUtils::CalculateCharType(
 
       strongTypeFound = true;
       aCharType = charType;
     }
   }
   aOffset = offset;
 }
 
-nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, int32_t aLength,
-                                      nsBidiLevel aBaseLevel,
+nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength,
+                                      EmbeddingLevel aBaseLevel,
                                       nsPresContext* aPresContext,
                                       BidiProcessor& aprocessor, Mode aMode,
                                       nsBidiPositionResolve* aPosResolve,
                                       int32_t aPosResolveCount, nscoord* aWidth,
-                                      nsBidi* aBidiEngine) {
+                                      mozilla::intl::Bidi* aBidiEngine) {
   NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0),
                "Incorrect aPosResolve / aPosResolveCount arguments");
 
-  int32_t runCount;
-
   nsAutoString textBuffer(aText, aLength);
   textBuffer.ReplaceChar(kSeparators, kSpace);
   const char16_t* text = textBuffer.get();
 
-  nsresult rv = aBidiEngine->SetPara(text, aLength, aBaseLevel);
-  if (NS_FAILED(rv)) return rv;
+  if (aBidiEngine->SetParagraph(Span(text, aLength), aBaseLevel).isErr()) {
+    return NS_ERROR_FAILURE;
+  }
 
-  rv = aBidiEngine->CountRuns(&runCount);
-  if (NS_FAILED(rv)) return rv;
+  auto result = aBidiEngine->CountRuns();
+  if (result.isErr()) {
+    return NS_ERROR_FAILURE;
+  }
+  int32_t runCount = result.unwrap();
 
   nscoord xOffset = 0;
   nscoord width, xEndRun = 0;
   nscoord totalWidth = 0;
   int32_t i, start, limit, length;
   uint32_t visualStart = 0;
   uint8_t charType;
   uint8_t prevType = eCharType_LeftToRight;
 
   for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
     aPosResolve[nPosResolve].visualIndex = kNotFound;
     aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
     aPosResolve[nPosResolve].visualWidth = kNotFound;
   }
 
   for (i = 0; i < runCount; i++) {
-    nsBidiDirection dir = aBidiEngine->GetVisualRun(i, &start, &length);
+    mozilla::intl::Bidi::Direction dir =
+        aBidiEngine->GetVisualRun(i, &start, &length);
 
-    nsBidiLevel level;
+    EmbeddingLevel level;
     aBidiEngine->GetLogicalRun(start, &limit, &level);
 
-    dir = DIRECTION_FROM_LEVEL(level);
+    dir = level.Direction();
     int32_t subRunLength = limit - start;
     int32_t lineOffset = start;
-    int32_t typeLimit = std::min(limit, aLength);
+    int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength));
     int32_t subRunCount = 1;
     int32_t subRunLimit = typeLimit;
 
     /*
      * If |level| is even, i.e. the direction of the run is left-to-right, we
      * render the subruns from left to right and increment the x-coordinate
      * |xOffset| by the width of each subrun after rendering.
      *
      * If |level| is odd, i.e. the direction of the run is right-to-left, we
      * render the subruns from right to left. We begin by incrementing |xOffset|
      * by the width of the whole run, and then decrement it by the width of each
      * subrun before rendering. After rendering all the subruns, we restore the
      * x-coordinate of the end of the run for the start of the next run.
      */
 
-    if (dir == NSBIDI_RTL) {
-      aprocessor.SetText(text + start, subRunLength, dir);
+    if (dir == intl::Bidi::Direction::RTL) {
+      aprocessor.SetText(text + start, subRunLength,
+                         intl::Bidi::Direction::RTL);
       width = aprocessor.GetWidth();
       xOffset += width;
       xEndRun = xOffset;
     }
 
     while (subRunCount > 0) {
       // CalculateCharType can increment subRunCount if the run
       // contains mixed character types
@@ -2222,17 +2243,17 @@ nsresult nsBidiPresUtils::ProcessText(co
       if (int32_t(runVisualText.Length()) < subRunLength)
         return NS_ERROR_OUT_OF_MEMORY;
       FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
                         subRunLength, (nsCharType)charType);
 
       aprocessor.SetText(runVisualText.get(), subRunLength, dir);
       width = aprocessor.GetWidth();
       totalWidth += width;
-      if (dir == NSBIDI_RTL) {
+      if (dir == mozilla::intl::Bidi::Direction::RTL) {
         xOffset -= width;
       }
       if (aMode == MODE_DRAW) {
         aprocessor.DrawText(xOffset, width);
       }
 
       /*
        * The caller may request to calculate the visual position of one
@@ -2292,17 +2313,17 @@ nsresult nsBidiPresUtils::ProcessText(co
              *    ^^^^^^ (subWidth)
              *    ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
              *          ^^ (posResolve->visualWidth)
              */
             nscoord subWidth;
             // The position in the text where this run's "left part" begins.
             const char16_t* visualLeftPart;
             const char16_t* visualRightSide;
-            if (dir == NSBIDI_RTL) {
+            if (dir == mozilla::intl::Bidi::Direction::RTL) {
               // One day, son, this could all be replaced with
               // mPresContext->GetBidiEngine().GetVisualIndex() ...
               posResolve->visualIndex =
                   visualStart +
                   (subRunLength - (posResolve->logicalIndex + 1 - start));
               // Skipping to the "left part".
               visualLeftPart = text + posResolve->logicalIndex + 1;
               // Skipping to the right side of the current character
@@ -2321,26 +2342,26 @@ nsresult nsBidiPresUtils::ProcessText(co
             subWidth = aprocessor.GetWidth();
             aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
             posResolve->visualLeftTwips = xOffset + subWidth;
             posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
           }
         }
       }
 
-      if (dir == NSBIDI_LTR) {
+      if (dir == intl::Bidi::Direction::LTR) {
         xOffset += width;
       }
 
       --subRunCount;
       start = lineOffset;
       subRunLimit = typeLimit;
       subRunLength = typeLimit - lineOffset;
     }  // while
-    if (dir == NSBIDI_RTL) {
+    if (dir == intl::Bidi::Direction::RTL) {
       xOffset = xEndRun;
     }
 
     visualStart += length;
   }  // for
 
   if (aWidth) {
     *aWidth = totalWidth;
@@ -2362,18 +2383,18 @@ class MOZ_STACK_CLASS nsIRenderingContex
         mFontMetrics(aFontMetrics),
         mPt(aPt),
         mText(nullptr),
         mLength(0) {}
 
   ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); }
 
   virtual void SetText(const char16_t* aText, int32_t aLength,
-                       nsBidiDirection aDirection) override {
-    mFontMetrics->SetTextRunRTL(aDirection == NSBIDI_RTL);
+                       intl::Bidi::Direction aDirection) override {
+    mFontMetrics->SetTextRunRTL(aDirection == intl::Bidi::Direction::RTL);
     mText = aText;
     mLength = aLength;
   }
 
   virtual nscoord GetWidth() override {
     return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
                                                mTextRunConstructionDrawTarget);
   }
@@ -2394,34 +2415,35 @@ class MOZ_STACK_CLASS nsIRenderingContex
   DrawTarget* mTextRunConstructionDrawTarget;
   nsFontMetrics* mFontMetrics;
   nsPoint mPt;
   const char16_t* mText;
   int32_t mLength;
 };
 
 nsresult nsBidiPresUtils::ProcessTextForRenderingContext(
-    const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel,
+    const char16_t* aText, int32_t aLength, EmbeddingLevel aBaseLevel,
     nsPresContext* aPresContext, gfxContext& aRenderingContext,
     DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
     Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve,
     int32_t aPosResolveCount, nscoord* aWidth) {
   nsIRenderingContextBidiProcessor processor(&aRenderingContext,
                                              aTextRunConstructionDrawTarget,
                                              &aFontMetrics, nsPoint(aX, aY));
   return ProcessText(aText, aLength, aBaseLevel, aPresContext, processor, aMode,
                      aPosResolve, aPosResolveCount, aWidth,
                      &aPresContext->GetBidiEngine());
 }
 
 /* static */
-nsBidiLevel nsBidiPresUtils::BidiLevelFromStyle(ComputedStyle* aComputedStyle) {
+EmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle(
+    ComputedStyle* aComputedStyle) {
   if (aComputedStyle->StyleTextReset()->mUnicodeBidi &
       NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
-    return NSBIDI_DEFAULT_LTR;
+    return EmbeddingLevel::DefaultLTR();
   }
 
   if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) {
-    return NSBIDI_RTL;
+    return EmbeddingLevel::RTL();
   }
 
-  return NSBIDI_LTR;
+  return EmbeddingLevel::LTR();
 }
--- a/layout/base/nsBidiPresUtils.h
+++ b/layout/base/nsBidiPresUtils.h
@@ -3,17 +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/. */
 
 #ifndef nsBidiPresUtils_h___
 #define nsBidiPresUtils_h___
 
 #include "gfxContext.h"
-#include "nsBidi.h"
+#include "mozilla/intl/Bidi.h"
 #include "nsBidiUtils.h"
 #include "nsHashKeys.h"
 #include "nsCoord.h"
 #include "nsTArray.h"
 #include "nsLineBox.h"
 
 #ifdef DrawText
 #  undef DrawText
@@ -159,17 +159,17 @@ class nsBidiPresUtils {
      *  passing an index would be impossible.
      *
      * @param aText The string of text.
      * @param aLength The length of the string of text.
      * @param aDirection The direction of the text. The string will never have
      *  mixed direction.
      */
     virtual void SetText(const char16_t* aText, int32_t aLength,
-                         nsBidiDirection aDirection) = 0;
+                         mozilla::intl::Bidi::Direction aDirection) = 0;
 
     /**
      * Returns the measured width of the text given in SetText. If SetText was
      * not called with valid parameters, the result of this call is undefined.
      * This call is guaranteed to only be called once between SetText calls.
      * Will be invoked before DrawText.
      */
     virtual nscoord GetWidth() = 0;
@@ -224,52 +224,46 @@ class nsBidiPresUtils {
 
   /**
    * Reorder plain text using the Unicode Bidi algorithm and send it to
    * a rendering context for rendering.
    *
    * @param[in] aText  the string to be rendered (in logical order)
    * @param aLength the number of characters in the string
    * @param aBaseLevel the base embedding level of the string
-   *  odd values are right-to-left; even values are left-to-right, plus special
-   *  constants as follows (defined in nsBidi.h)
-   *  NSBIDI_LTR - left-to-right string
-   *  NSBIDI_RTL - right-to-left string
-   *  NSBIDI_DEFAULT_LTR - auto direction determined by first strong character,
-   *                       default is left-to-right
-   *  NSBIDI_DEFAULT_RTL - auto direction determined by first strong character,
-   *                       default is right-to-left
-   *
    * @param aPresContext the presentation context
    * @param aRenderingContext the rendering context to render to
    * @param aTextRunConstructionContext the rendering context to be used to
    * construct the textrun (affects font hinting)
    * @param aX the x-coordinate to render the string
    * @param aY the y-coordinate to render the string
    * @param[in,out] aPosResolve array of logical positions to resolve into
    * visual positions; can be nullptr if this functionality is not required
    * @param aPosResolveCount number of items in the aPosResolve array
    */
-  static nsresult RenderText(
-      const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel,
-      nsPresContext* aPresContext, gfxContext& aRenderingContext,
-      DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
-      nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve = nullptr,
-      int32_t aPosResolveCount = 0) {
+  static nsresult RenderText(const char16_t* aText, int32_t aLength,
+                             mozilla::intl::Bidi::EmbeddingLevel aBaseLevel,
+                             nsPresContext* aPresContext,
+                             gfxContext& aRenderingContext,
+                             DrawTarget* aTextRunConstructionDrawTarget,
+                             nsFontMetrics& aFontMetrics, nscoord aX,
+                             nscoord aY,
+                             nsBidiPositionResolve* aPosResolve = nullptr,
+                             int32_t aPosResolveCount = 0) {
     return ProcessTextForRenderingContext(
         aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
         aTextRunConstructionDrawTarget, aFontMetrics, MODE_DRAW, aX, aY,
         aPosResolve, aPosResolveCount, nullptr);
   }
 
-  static nscoord MeasureTextWidth(const char16_t* aText, int32_t aLength,
-                                  nsBidiLevel aBaseLevel,
-                                  nsPresContext* aPresContext,
-                                  gfxContext& aRenderingContext,
-                                  nsFontMetrics& aFontMetrics) {
+  static nscoord MeasureTextWidth(
+      const char16_t* aText, int32_t aLength,
+      mozilla::intl::Bidi::EmbeddingLevel aBaseLevel,
+      nsPresContext* aPresContext, gfxContext& aRenderingContext,
+      nsFontMetrics& aFontMetrics) {
     nscoord length;
     nsresult rv = ProcessTextForRenderingContext(
         aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
         aRenderingContext.GetDrawTarget(), aFontMetrics, MODE_MEASURE, 0, 0,
         nullptr, 0, &length);
     return NS_SUCCEEDED(rv) ? length : 0;
   }
 
@@ -312,109 +306,103 @@ class nsBidiPresUtils {
   /**
    * Get the bidi data of the given (inline) frame.
    */
   static mozilla::FrameBidiData GetFrameBidiData(nsIFrame* aFrame);
 
   /**
    * Get the bidi embedding level of the given (inline) frame.
    */
-  static nsBidiLevel GetFrameEmbeddingLevel(nsIFrame* aFrame);
+  static mozilla::intl::Bidi::EmbeddingLevel GetFrameEmbeddingLevel(
+      nsIFrame* aFrame);
 
   /**
    * Get the bidi base level of the given (inline) frame.
    */
-  static nsBidiLevel GetFrameBaseLevel(const nsIFrame* aFrame);
+  static mozilla::intl::Bidi::EmbeddingLevel GetFrameBaseLevel(
+      const nsIFrame* aFrame);
 
   /**
-   * Get an nsBidiDirection representing the direction implied by the
-   * bidi base level of the frame.
-   * @return NSBIDI_LTR (left-to-right) or NSBIDI_RTL (right-to-left)
-   *  NSBIDI_MIXED will never be returned.
+   * Get a mozilla::intl::Bidi::Direction representing the direction implied by
+   * the bidi base level of the frame.
+   * @return mozilla::intl::Bidi::Direction
    */
-  static nsBidiDirection ParagraphDirection(const nsIFrame* aFrame) {
-    return DIRECTION_FROM_LEVEL(GetFrameBaseLevel(aFrame));
+  static mozilla::intl::Bidi::Direction ParagraphDirection(
+      const nsIFrame* aFrame) {
+    return GetFrameBaseLevel(aFrame).Direction();
   }
 
   /**
-   * Get an nsBidiDirection representing the direction implied by the
-   * bidi embedding level of the frame.
-   * @return NSBIDI_LTR (left-to-right) or NSBIDI_RTL (right-to-left)
-   *  NSBIDI_MIXED will never be returned.
+   * Get a mozilla::intl::Bidi::Direction representing the direction implied by
+   * the bidi embedding level of the frame.
+   * @return mozilla::intl::Bidi::Direction
    */
-  static nsBidiDirection FrameDirection(nsIFrame* aFrame) {
-    return DIRECTION_FROM_LEVEL(GetFrameEmbeddingLevel(aFrame));
+  static mozilla::intl::Bidi::Direction FrameDirection(nsIFrame* aFrame) {
+    return GetFrameEmbeddingLevel(aFrame).Direction();
   }
 
   static bool IsFrameInParagraphDirection(nsIFrame* aFrame) {
     return ParagraphDirection(aFrame) == FrameDirection(aFrame);
   }
 
   // This is faster than nsBidiPresUtils::IsFrameInParagraphDirection,
   // because it uses the frame pointer passed in without drilling down to
   // the leaf frame.
   static bool IsReversedDirectionFrame(const nsIFrame* aFrame) {
     mozilla::FrameBidiData bidiData = aFrame->GetBidiData();
-    return !IS_SAME_DIRECTION(bidiData.embeddingLevel, bidiData.baseLevel);
+    return !bidiData.embeddingLevel.IsSameDirection(bidiData.baseLevel);
   }
 
   enum Mode { MODE_DRAW, MODE_MEASURE };
 
   /**
    * Reorder plain text using the Unicode Bidi algorithm and send it to
    * a processor for rendering or measuring
    *
    * @param[in] aText  the string to be processed (in logical order)
    * @param aLength the number of characters in the string
    * @param aBaseLevel the base embedding level of the string
-   *  odd values are right-to-left; even values are left-to-right, plus special
-   *  constants as follows (defined in nsBidi.h)
-   *  NSBIDI_LTR - left-to-right string
-   *  NSBIDI_RTL - right-to-left string
-   *  NSBIDI_DEFAULT_LTR - auto direction determined by first strong character,
-   *                       default is left-to-right
-   *  NSBIDI_DEFAULT_RTL - auto direction determined by first strong character,
-   *                       default is right-to-left
-   *
    * @param aPresContext the presentation context
    * @param aprocessor the bidi processor
    * @param aMode the operation to process
    *  MODE_DRAW - invokes DrawText on the processor for each substring
    *  MODE_MEASURE - does not invoke DrawText on the processor
    *  Note that the string is always measured, regardless of mode
    * @param[in,out] aPosResolve array of logical positions to resolve into
    *  visual positions; can be nullptr if this functionality is not required
    * @param aPosResolveCount number of items in the aPosResolve array
    * @param[out] aWidth Pointer to where the width will be stored (may be null)
    */
-  static nsresult ProcessText(const char16_t* aText, int32_t aLength,
-                              nsBidiLevel aBaseLevel,
+  static nsresult ProcessText(const char16_t* aText, size_t aLength,
+                              mozilla::intl::Bidi::EmbeddingLevel aBaseLevel,
                               nsPresContext* aPresContext,
                               BidiProcessor& aprocessor, Mode aMode,
                               nsBidiPositionResolve* aPosResolve,
                               int32_t aPosResolveCount, nscoord* aWidth,
-                              nsBidi* aBidiEngine);
+                              mozilla::intl::Bidi* aBidiEngine);
 
   /**
    * Use style attributes to determine the base paragraph level to pass to the
    * bidi algorithm.
    *
-   * If |unicode-bidi| is set to "[-moz-]plaintext", returns NSBIDI_DEFAULT_LTR,
-   * in other words the direction is determined from the first strong character
-   * in the text according to rules P2 and P3 of the bidi algorithm, or LTR if
-   * there is no strong character.
+   * If |unicode-bidi| is set to "[-moz-]plaintext", returns
+   * EmbeddingLevel::DefaultLTR, in other words the direction is determined from
+   * the first strong character in the text according to rules P2 and P3 of the
+   * bidi algorithm, or LTR if there is no strong character.
    *
-   * Otherwise returns NSBIDI_LTR or NSBIDI_RTL depending on the value of
-   * |direction|
+   * Otherwise returns EmbeddingLevel::LTR or EmbeddingLevel::RTL depending on
+   * the value of |direction|
    */
-  static nsBidiLevel BidiLevelFromStyle(mozilla::ComputedStyle* aComputedStyle);
+  static mozilla::intl::Bidi::EmbeddingLevel BidiLevelFromStyle(
+      mozilla::ComputedStyle* aComputedStyle);
 
  private:
   static nsresult ProcessTextForRenderingContext(
-      const char16_t* aText, int32_t aLength, nsBidiLevel aBaseLevel,
+      const char16_t* aText, int32_t aLength,
+      mozilla::intl::Bidi::EmbeddingLevel aBaseLevel,
       nsPresContext* aPresContext, gfxContext& aRenderingContext,
       DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
       Mode aMode,
       nscoord aX,                         // DRAW only
       nscoord aY,                         // DRAW only
       nsBidiPositionResolve* aPosResolve, /* may be null */
       int32_t aPosResolveCount, nscoord* aWidth /* may be null */);
 
@@ -567,18 +555,18 @@ class nsBidiPresUtils {
    * @param aFirstIndex  index of aFrame in mLogicalFrames
    * @param aLastIndex   index of the last frame to be removed
    *
    * @see Resolve()
    * @see EnsureBidiContinuation()
    */
   static void RemoveBidiContinuation(BidiParagraphData* aBpd, nsIFrame* aFrame,
                                      int32_t aFirstIndex, int32_t aLastIndex);
-  static void CalculateCharType(nsBidi* aBidiEngine, const char16_t* aText,
-                                int32_t& aOffset, int32_t aCharTypeLimit,
-                                int32_t& aRunLimit, int32_t& aRunLength,
-                                int32_t& aRunCount, uint8_t& aCharType,
-                                uint8_t& aPrevCharType);
+  static void CalculateCharType(mozilla::intl::Bidi* aBidiEngine,
+                                const char16_t* aText, int32_t& aOffset,
+                                int32_t aCharTypeLimit, int32_t& aRunLimit,
+                                int32_t& aRunLength, int32_t& aRunCount,
+                                uint8_t& aCharType, uint8_t& aPrevCharType);
 
   static void StripBidiControlCharacters(char16_t* aText, int32_t& aTextLength);
 };
 
 #endif /* nsBidiPresUtils_h___ */
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -7,16 +7,17 @@
 /* the caret is the text cursor used, e.g., when editing */
 
 #include "nsCaret.h"
 
 #include <algorithm>
 
 #include "gfxUtils.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/intl/Bidi.h"
 #include "nsCOMPtr.h"
 #include "nsFontMetrics.h"
 #include "nsITimer.h"
 #include "nsFrameSelection.h"
 #include "nsIFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsIContent.h"
 #include "nsIFrameInlines.h"
@@ -34,16 +35,18 @@
 #include "mozilla/dom/Selection.h"
 #include "nsIBidiKeyboard.h"
 #include "nsContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
+using EmbeddingLevel = mozilla::intl::Bidi::EmbeddingLevel;
+
 // The bidi indicator hangs off the caret to one side, to show which
 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
 // an insignificant dot
 static const int32_t kMinBidiIndicatorPixels = 2;
 
 // The default caret blinking rate (in ms of blinking interval)
 static const uint32_t kDefaultCaretBlinkRate = 500;
 
@@ -384,17 +387,18 @@ nsIFrame* nsCaret::GetFrameAndOffset(con
   }
 
   if (!focusNode || !focusNode->IsContent() || !aSelection) {
     return nullptr;
   }
 
   nsIContent* contentNode = focusNode->AsContent();
   nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
-  nsBidiLevel bidiLevel = frameSelection->GetCaretBidiLevel();
+  mozilla::intl::Bidi::EmbeddingLevel bidiLevel =
+      frameSelection->GetCaretBidiLevel();
 
   return nsCaret::GetCaretFrameForNodeOffset(
       frameSelection, contentNode, focusOffset, frameSelection->GetHint(),
       bidiLevel, aUnadjustedFrame, aFrameOffset);
 }
 
 /* static */
 nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
@@ -639,17 +643,18 @@ void nsCaret::StopBlinking() {
   if (mBlinkTimer) {
     mBlinkTimer->Cancel();
     mBlinkRate = 0;
   }
 }
 
 nsIFrame* nsCaret::GetCaretFrameForNodeOffset(
     nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
-    int32_t aOffset, CaretAssociationHint aFrameHint, nsBidiLevel aBidiLevel,
+    int32_t aOffset, CaretAssociationHint aFrameHint,
+    mozilla::intl::Bidi::EmbeddingLevel aBidiLevel,
     nsIFrame** aReturnUnadjustedFrame, int32_t* aReturnOffset) {
   if (!aFrameSelection) {
     return nullptr;
   }
 
   PresShell* presShell = aFrameSelection->GetPresShell();
   if (!presShell) {
     return nullptr;
@@ -691,18 +696,20 @@ nsIFrame* nsCaret::GetCaretFrameForNodeO
     // If there has been a reflow, take the caret Bidi level to be the level of
     // the current frame
     if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
       aBidiLevel = theFrame->GetEmbeddingLevel();
     }
 
     nsIFrame* frameBefore;
     nsIFrame* frameAfter;
-    nsBidiLevel levelBefore;  // Bidi level of the character before the caret
-    nsBidiLevel levelAfter;   // Bidi level of the character after the caret
+    mozilla::intl::Bidi::EmbeddingLevel
+        levelBefore;  // Bidi level of the character before the caret
+    mozilla::intl::Bidi::EmbeddingLevel
+        levelAfter;  // Bidi level of the character after the caret
 
     auto [start, end] = theFrame->GetOffsets();
     if (start == 0 || end == 0 || start == theFrameOffset ||
         end == theFrameOffset) {
       nsPrevNextBidiLevels levels =
           aFrameSelection->GetPrevNextBidiLevels(aContentNode, aOffset, false);
 
       /* Boundary condition, we need to know the Bidi levels of the characters
@@ -715,105 +722,111 @@ nsIFrame* nsCaret::GetCaretFrameForNodeO
 
         if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) {
           aBidiLevel = std::max(aBidiLevel,
                                 std::min(levelBefore, levelAfter));  // rule c3
           aBidiLevel = std::min(aBidiLevel,
                                 std::max(levelBefore, levelAfter));  // rule c4
           if (aBidiLevel == levelBefore ||                           // rule c1
               (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
-               IS_SAME_DIRECTION(aBidiLevel, levelBefore)) ||  // rule c5
+               aBidiLevel.IsSameDirection(levelBefore)) ||  // rule c5
               (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
-               IS_SAME_DIRECTION(aBidiLevel, levelBefore)))  // rule c9
+               aBidiLevel.IsSameDirection(levelBefore)))  // rule c9
           {
             if (theFrame != frameBefore) {
               if (frameBefore) {  // if there is a frameBefore, move into it
                 theFrame = frameBefore;
                 std::tie(start, end) = theFrame->GetOffsets();
                 theFrameOffset = end;
               } else {
                 // if there is no frameBefore, we must be at the beginning of
                 // the line so we stay with the current frame. Exception: when
                 // the first frame on the line has a different Bidi level from
                 // the paragraph level, there is no real frame for the caret to
                 // be in. We have to find the visually first frame on the line.
-                nsBidiLevel baseLevel = frameAfter->GetBaseLevel();
+                mozilla::intl::Bidi::EmbeddingLevel baseLevel =
+                    frameAfter->GetBaseLevel();
                 if (baseLevel != levelAfter) {
                   nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0,
                                          nsPoint(0, 0), false, true, false,
                                          true, false);
                   if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
                     theFrame = pos.mResultFrame;
                     theFrameOffset = pos.mContentOffset;
                   }
                 }
               }
             }
           } else if (aBidiLevel == levelAfter ||  // rule c2
                      (aBidiLevel > levelBefore && aBidiLevel < levelAfter &&
-                      IS_SAME_DIRECTION(aBidiLevel, levelAfter)) ||  // rule c6
+                      aBidiLevel.IsSameDirection(levelAfter)) ||  // rule c6
                      (aBidiLevel < levelBefore && aBidiLevel > levelAfter &&
-                      IS_SAME_DIRECTION(aBidiLevel, levelAfter)))  // rule c10
+                      aBidiLevel.IsSameDirection(levelAfter)))  // rule c10
           {
             if (theFrame != frameAfter) {
               if (frameAfter) {
                 // if there is a frameAfter, move into it
                 theFrame = frameAfter;
                 std::tie(start, end) = theFrame->GetOffsets();
                 theFrameOffset = start;
               } else {
                 // if there is no frameAfter, we must be at the end of the line
                 // so we stay with the current frame.
                 // Exception: when the last frame on the line has a different
                 // Bidi level from the paragraph level, there is no real frame
                 // for the caret to be in. We have to find the visually last
                 // frame on the line.
-                nsBidiLevel baseLevel = frameBefore->GetBaseLevel();
+                mozilla::intl::Bidi::EmbeddingLevel baseLevel =
+                    frameBefore->GetBaseLevel();
                 if (baseLevel != levelBefore) {
                   nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0,
                                          nsPoint(0, 0), false, true, false,
                                          true, false);
                   if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
                     theFrame = pos.mResultFrame;
                     theFrameOffset = pos.mContentOffset;
                   }
                 }
               }
             }
           } else if (aBidiLevel > levelBefore &&
                      aBidiLevel < levelAfter &&  // rule c7/8
                      // before and after have the same parity
-                     IS_SAME_DIRECTION(levelBefore, levelAfter) &&
+                     levelBefore.IsSameDirection(levelAfter) &&
                      // caret has different parity
-                     !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) {
+                     !aBidiLevel.IsSameDirection(levelAfter)) {
             if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(
                     frameAfter, eDirNext, aBidiLevel, &theFrame))) {
               std::tie(start, end) = theFrame->GetOffsets();
               levelAfter = theFrame->GetEmbeddingLevel();
-              if (IS_LEVEL_RTL(aBidiLevel))  // c8: caret to the right of the
-                                             // rightmost character
-                theFrameOffset = IS_LEVEL_RTL(levelAfter) ? start : end;
-              else  // c7: caret to the left of the leftmost character
-                theFrameOffset = IS_LEVEL_RTL(levelAfter) ? end : start;
+              if (aBidiLevel.IsRTL()) {
+                // c8: caret to the right of the rightmost character
+                theFrameOffset = levelAfter.IsRTL() ? start : end;
+              } else {
+                // c7: caret to the left of the leftmost character
+                theFrameOffset = levelAfter.IsRTL() ? end : start;
+              }
             }
           } else if (aBidiLevel < levelBefore &&
                      aBidiLevel > levelAfter &&  // rule c11/12
                      // before and after have the same parity
-                     IS_SAME_DIRECTION(levelBefore, levelAfter) &&
+                     levelBefore.IsSameDirection(levelAfter) &&
                      // caret has different parity
-                     !IS_SAME_DIRECTION(aBidiLevel, levelAfter)) {
+                     !aBidiLevel.IsSameDirection(levelAfter)) {
             if (NS_SUCCEEDED(aFrameSelection->GetFrameFromLevel(
                     frameBefore, eDirPrevious, aBidiLevel, &theFrame))) {
               std::tie(start, end) = theFrame->GetOffsets();
               levelBefore = theFrame->GetEmbeddingLevel();
-              if (IS_LEVEL_RTL(aBidiLevel))  // c12: caret to the left of the
-                                             // leftmost character
-                theFrameOffset = IS_LEVEL_RTL(levelBefore) ? end : start;
-              else  // c11: caret to the right of the rightmost character
-                theFrameOffset = IS_LEVEL_RTL(levelBefore) ? start : end;
+              if (aBidiLevel.IsRTL()) {
+                // c12: caret to the left of the leftmost character
+                theFrameOffset = levelBefore.IsRTL() ? end : start;
+              } else {
+                // c11: caret to the right of the rightmost character
+                theFrameOffset = levelBefore.IsRTL() ? start : end;
+              }
             }
           }
         }
       }
     }
   }
 
   *aReturnOffset = theFrameOffset;
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* the caret is the text cursor used, e.g., when editing */
 
 #ifndef nsCaret_h__
 #define nsCaret_h__
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Selection.h"
 #include "nsCoord.h"
 #include "nsISelectionListener.h"
 #include "nsIWeakReferenceUtils.h"
 #include "CaretAssociationHint.h"
 #include "nsPoint.h"
 #include "nsRect.h"
@@ -174,17 +175,18 @@ class nsCaret final : public nsISelectio
    * aSelection is not collapsed.
    * This rect does not include any extra decorations for bidi.
    * @param aRect must be non-null
    */
   static nsIFrame* GetGeometry(const mozilla::dom::Selection* aSelection,
                                nsRect* aRect);
   static nsIFrame* GetCaretFrameForNodeOffset(
       nsFrameSelection* aFrameSelection, nsIContent* aContentNode,
-      int32_t aOffset, CaretAssociationHint aFrameHint, uint8_t aBidiLevel,
+      int32_t aOffset, CaretAssociationHint aFrameHint,
+      mozilla::intl::Bidi::EmbeddingLevel aBidiLevel,
       nsIFrame** aReturnUnadjustedFrame, int32_t* aReturnOffset);
   static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
                                     nscoord* aBidiIndicatorSize);
 
   // Get the frame and frame offset based on the focus node and focus offset
   // of aSelection. If aOverrideNode and aOverride are provided, use them
   // instead.
   // @param aFrameOffset return the frame offset if non-null.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/InspectorFontFace.h"
 #include "mozilla/dom/KeyframeEffect.h"
 #include "mozilla/dom/SVGViewportElement.h"
 #include "mozilla/dom/UIEvent.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/PathHelpers.h"
@@ -1537,17 +1538,20 @@ nsIFrame* nsLayoutUtils::GetNearestOverf
 // static
 nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
                                       const nsRect& aScrolledFrameOverflowArea,
                                       const nsSize& aScrollPortSize,
                                       StyleDirection aDirection) {
   WritingMode wm = aScrolledFrame->GetWritingMode();
   // Potentially override the frame's direction to use the direction found
   // by ScrollFrameHelper::GetScrolledFrameDir()
-  wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl ? 1 : 0);
+  wm.SetDirectionFromBidiLevel(
+      aDirection == StyleDirection::Rtl
+          ? mozilla::intl::Bidi::EmbeddingLevel::RTL()
+          : mozilla::intl::Bidi::EmbeddingLevel::LTR());
 
   nscoord x1 = aScrolledFrameOverflowArea.x,
           x2 = aScrolledFrameOverflowArea.XMost(),
           y1 = aScrolledFrameOverflowArea.y,
           y2 = aScrolledFrameOverflowArea.YMost();
 
   const bool isHorizontalWM = !wm.IsVertical();
   const bool isVerticalWM = wm.IsVertical();
@@ -5547,17 +5551,18 @@ nscoord nsLayoutUtils::AppUnitWidthOfStr
 
 nscoord nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString,
                                                 uint32_t aLength,
                                                 const nsIFrame* aFrame,
                                                 nsFontMetrics& aFontMetrics,
                                                 gfxContext& aContext) {
   nsPresContext* presContext = aFrame->PresContext();
   if (presContext->BidiEnabled()) {
-    nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style());
+    mozilla::intl::Bidi::EmbeddingLevel level =
+        nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style());
     return nsBidiPresUtils::MeasureTextWidth(
         aString, aLength, level, presContext, aContext, aFontMetrics);
   }
   aFontMetrics.SetTextRunRTL(false);
   aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical());
   aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation);
   return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
                                              aContext.GetDrawTarget());
@@ -5626,17 +5631,18 @@ void nsLayoutUtils::DrawString(const nsI
     aFontMetrics.SetVertical(WritingMode(aComputedStyle).IsVertical());
   }
 
   aFontMetrics.SetTextOrientation(
       aComputedStyle->StyleVisibility()->mTextOrientation);
 
   nsPresContext* presContext = aFrame->PresContext();
   if (presContext->BidiEnabled()) {
-    nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle);
+    mozilla::intl::Bidi::EmbeddingLevel level =
+        nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle);
     rv = nsBidiPresUtils::RenderText(aString, aLength, level, presContext,
                                      *aContext, aContext->GetDrawTarget(),
                                      aFontMetrics, aPoint.x, aPoint.y);
   }
   if (NS_FAILED(rv)) {
     aFontMetrics.SetTextRunRTL(false);
     DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext);
   }
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -95,17 +95,16 @@
 #include "mozilla/dom/ImageTracker.h"
 
 // Needed for Start/Stop of Image Animation
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 
 #include "nsBidiUtils.h"
 #include "nsServiceManagerUtils.h"
-#include "nsBidi.h"
 
 #include "mozilla/dom/URL.h"
 #include "mozilla/ServoCSSParser.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
@@ -284,18 +283,17 @@ nsPresContext::nsPresContext(dom::Docume
       mQuirkSheetAdded(false),
       mHadNonBlankPaint(false),
       mHadContentfulPaint(false),
       mHadNonTickContentfulPaint(false),
       mHadContentfulPaintComposite(false),
 #ifdef DEBUG
       mInitialized(false),
 #endif
-      mColorSchemeOverride(dom::PrefersColorSchemeOverride::None)
-{
+      mColorSchemeOverride(dom::PrefersColorSchemeOverride::None) {
 #ifdef DEBUG
   PodZero(&mLayoutPhaseCount);
 #endif
 
   if (!IsDynamic()) {
     mImageAnimationMode = imgIContainer::kDontAnimMode;
     mNeverAnimate = true;
   } else {
@@ -2678,21 +2676,21 @@ uint64_t nsPresContext::GetRestyleGenera
 
 uint64_t nsPresContext::GetUndisplayedRestyleGeneration() const {
   if (!mRestyleManager) {
     return 0;
   }
   return mRestyleManager->GetUndisplayedRestyleGeneration();
 }
 
-nsBidi& nsPresContext::GetBidiEngine() {
+mozilla::intl::Bidi& nsPresContext::GetBidiEngine() {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mBidiEngine) {
-    mBidiEngine.reset(new nsBidi());
+    mBidiEngine.reset(new mozilla::intl::Bidi());
   }
   return *mBidiEngine;
 }
 
 void nsPresContext::FlushFontFeatureValues() {
   if (!mPresShell) {
     return;  // we've been torn down
   }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* a presentation of a document, part 1 */
 
 #ifndef nsPresContext_h___
 #define nsPresContext_h___
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/AppUnits.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/MediaEmulationData.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/PreferenceSheet.h"
 #include "mozilla/PresShellForwards.h"
@@ -38,17 +39,16 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsChangeHint.h"
 #include "gfxTypes.h"
 #include "gfxRect.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "Units.h"
 
-class nsBidi;
 class nsIPrintSettings;
 class nsDocShell;
 class nsIDocShell;
 class nsITheme;
 class nsITimer;
 class nsIContent;
 class nsIFrame;
 class nsFrameManager;
@@ -1074,17 +1074,17 @@ class nsPresContext : public nsISupports
   bool HasWarnedAboutTooLargeDashedOrDottedRadius() const {
     return mHasWarnedAboutTooLargeDashedOrDottedRadius;
   }
 
   void SetHasWarnedAboutTooLargeDashedOrDottedRadius() {
     mHasWarnedAboutTooLargeDashedOrDottedRadius = true;
   }
 
-  nsBidi& GetBidiEngine();
+  mozilla::intl::Bidi& GetBidiEngine();
 
   gfxFontFeatureValueSet* GetFontFeatureValuesLookup() const {
     return mFontFeatureValuesLookup;
   }
 
  protected:
   friend class nsRunnableMethod<nsPresContext>;
   void ThemeChangedInternal();
@@ -1202,17 +1202,17 @@ class nsPresContext : public nsISupports
   gfxSize mLastFontInflationScreenSize;
 
   int32_t mCurAppUnitsPerDevPixel;
   int32_t mAutoQualityMinFontSizePixelsPref;
 
   nsCOMPtr<nsITheme> mTheme;
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
 
-  mozilla::UniquePtr<nsBidi> mBidiEngine;
+  mozilla::UniquePtr<mozilla::intl::Bidi> mBidiEngine;
 
   AutoTArray<TransactionInvalidations, 4> mTransactions;
 
   // text performance metrics
   mozilla::UniquePtr<gfxTextPerfMetrics> mTextPerf;
 
   mozilla::UniquePtr<gfxMissingFontRecorder> mMissingFonts;
 
--- a/layout/base/tests/chrome/printpreview_helper.xhtml
+++ b/layout/base/tests/chrome/printpreview_helper.xhtml
@@ -1269,58 +1269,33 @@ async function runTest45() {
   let test = "print_page_size4.html";
   let ref = "print_page_size4_ref.html";
   await compareFiles(test, ref, fuzz);
   requestAnimationFrame(() => setTimeout(runTest46));
 }
 
 // Test that small elements don't get clipped from the bottom of the page when
 // using a < 1.0 scaling factor.
-// TODO: enable this when bug 1725486 is fixed.
-async function runTest46_DISABLED() {
+async function runTest46() {
   var fuzz = { maxDifferent: 0, maxDifference: 0 };
   let test = "bug1722890.html";
   let ref = "bug1722890_ref.html";
   await compareFiles(test, ref, {
     maxDifferent: fuzz.maxDifferent,
     maxDifference: fuzz.maxDifference,
     test: {
       settings: {
-        scaling: 0.5
+        scaling: 0.5,
+        shrinkToFit: false
       }
     },
     ref: {
       settings: {
-        scaling: 1.0
-      }
-    }
-  });
-  finish();
-}
-
-// Tests whether or not scaling from print preview tests actually works.
-// Scaling currently doesn't work, and this test will begin to fail if scaling
-// gets fixed. In that case, we can enable runTest46_DISABLED instead of this
-// test.
-// See bug 1725486 for this issue.
-async function runTest46() {
-  // We can't load the exact same thing twice, as that won't fire the second
-  // load event and the test will just timeout.
-  let src = '<div style="background: blue; width: 50px; height: 50px;"></div>';
-  let test = "data:text/html,<!-- test -->" + src;
-  let ref = "data:text/html,<!-- ref -->" + src;
-  await compareFiles(test, ref, {
-    test: {
-      settings: {
-        scaling: 0.5
-      }
-    },
-    ref: {
-      settings: {
-        scaling: 1.0
+        scaling: 1.0,
+        shrinkToFit: false
       }
     }
   });
   finish();
 }
 
 ]]></script>
 <table style="border: 1px solid black;" xmlns="http://www.w3.org/1999/xhtml">
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WritingModes_h_
 #define WritingModes_h_
 
 #include <ostream>
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/EnumeratedRange.h"
 
 #include "nsRect.h"
 #include "nsBidiUtils.h"
 
 // It is the caller's responsibility to operate on logical-coordinate objects
 // with matched writing modes. Failure to do so will be a runtime bug; the
@@ -519,18 +520,18 @@ class WritingMode {
    * If it turns out that our bidi direction already matches what plaintext
    * resolution determined, there's nothing to do here. If it didn't (i.e. if
    * the rtl-ness doesn't match), then we correct the direction by flipping the
    * same bits that get flipped in the constructor's CSS 'direction'-based
    * chunk.
    *
    * XXX change uint8_t to UBiDiLevel after bug 924851
    */
-  void SetDirectionFromBidiLevel(uint8_t level) {
-    if (IS_LEVEL_RTL(level) == IsBidiLTR()) {
+  void SetDirectionFromBidiLevel(mozilla::intl::Bidi::EmbeddingLevel level) {
+    if (level.IsRTL() == IsBidiLTR()) {
       mWritingMode ^= StyleWritingMode::RTL | StyleWritingMode::INLINE_REVERSED;
     }
   }
 
   /**
    * Compare two WritingModes for equality.
    */
   bool operator==(const WritingMode& aOther) const {
--- a/layout/generic/nsFrameList.cpp
+++ b/layout/generic/nsFrameList.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "nsFrameList.h"
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/ArenaObjectID.h"
 #include "mozilla/PresShell.h"
 #include "nsBidiPresUtils.h"
 #include "nsContainerFrame.h"
 #include "nsGkAtoms.h"
 #include "nsILineIterator.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
@@ -300,25 +301,26 @@ void nsFrameList::List(FILE* out) const 
 #endif
 
 nsIFrame* nsFrameList::GetPrevVisualFor(nsIFrame* aFrame) const {
   if (!mFirstChild) return nullptr;
 
   nsIFrame* parent = mFirstChild->GetParent();
   if (!parent) return aFrame ? aFrame->GetPrevSibling() : LastChild();
 
-  nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(mFirstChild);
+  mozilla::intl::Bidi::Direction paraDir =
+      nsBidiPresUtils::ParagraphDirection(mFirstChild);
 
   nsAutoLineIterator iter = parent->GetLineIterator();
   if (!iter) {
     // Parent is not a block Frame
     if (parent->IsLineFrame()) {
       // Line frames are not bidi-splittable, so need to consider bidi
       // reordering
-      if (paraDir == NSBIDI_LTR) {
+      if (paraDir == mozilla::intl::Bidi::Direction::LTR) {
         return nsBidiPresUtils::GetFrameToLeftOf(aFrame, mFirstChild, -1);
       } else {  // RTL
         return nsBidiPresUtils::GetFrameToRightOf(aFrame, mFirstChild, -1);
       }
     } else {
       // Just get the next or prev sibling, depending on block and frame
       // direction.
       if (nsBidiPresUtils::IsFrameInParagraphDirection(mFirstChild)) {
@@ -340,55 +342,56 @@ nsIFrame* nsFrameList::GetPrevVisualFor(
     thisLine = iter->GetNumLines();
   }
 
   nsIFrame* frame = nullptr;
 
   if (aFrame) {
     auto line = iter->GetLine(thisLine).unwrap();
 
-    if (paraDir == NSBIDI_LTR) {
+    if (paraDir == mozilla::intl::Bidi::Direction::LTR) {
       frame = nsBidiPresUtils::GetFrameToLeftOf(aFrame, line.mFirstFrameOnLine,
                                                 line.mNumFramesOnLine);
     } else {  // RTL
       frame = nsBidiPresUtils::GetFrameToRightOf(aFrame, line.mFirstFrameOnLine,
                                                  line.mNumFramesOnLine);
     }
   }
 
   if (!frame && thisLine > 0) {
     // Get the last frame of the previous line
     auto line = iter->GetLine(thisLine - 1).unwrap();
 
-    if (paraDir == NSBIDI_LTR) {
+    if (paraDir == mozilla::intl::Bidi::Direction::LTR) {
       frame = nsBidiPresUtils::GetFrameToLeftOf(nullptr, line.mFirstFrameOnLine,
                                                 line.mNumFramesOnLine);
     } else {  // RTL
       frame = nsBidiPresUtils::GetFrameToRightOf(
           nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine);
     }
   }
   return frame;
 }
 
 nsIFrame* nsFrameList::GetNextVisualFor(nsIFrame* aFrame) const {
   if (!mFirstChild) return nullptr;
 
   nsIFrame* parent = mFirstChild->GetParent();
   if (!parent) return aFrame ? aFrame->GetPrevSibling() : mFirstChild;
 
-  nsBidiDirection paraDir = nsBidiPresUtils::ParagraphDirection(mFirstChild);
+  mozilla::intl::Bidi::Direction paraDir =
+      nsBidiPresUtils::ParagraphDirection(mFirstChild);
 
   nsAutoLineIterator iter = parent->GetLineIterator();
   if (!iter) {
     // Parent is not a block Frame
     if (parent->IsLineFrame()) {
       // Line frames are not bidi-splittable, so need to consider bidi
       // reordering
-      if (paraDir == NSBIDI_LTR) {
+      if (paraDir == mozilla::intl::Bidi::Direction::LTR) {
         return nsBidiPresUtils::GetFrameToRightOf(aFrame, mFirstChild, -1);
       } else {  // RTL
         return nsBidiPresUtils::GetFrameToLeftOf(aFrame, mFirstChild, -1);
       }
     } else {
       // Just get the next or prev sibling, depending on block and frame
       // direction.
       if (nsBidiPresUtils::IsFrameInParagraphDirection(mFirstChild)) {
@@ -410,31 +413,31 @@ nsIFrame* nsFrameList::GetNextVisualFor(
     thisLine = -1;
   }
 
   nsIFrame* frame = nullptr;
 
   if (aFrame) {
     auto line = iter->GetLine(thisLine).unwrap();
 
-    if (paraDir == NSBIDI_LTR) {
+    if (paraDir == mozilla::intl::Bidi::Direction::LTR) {
       frame = nsBidiPresUtils::GetFrameToRightOf(aFrame, line.mFirstFrameOnLine,
                                                  line.mNumFramesOnLine);
     } else {  // RTL
       frame = nsBidiPresUtils::GetFrameToLeftOf(aFrame, line.mFirstFrameOnLine,
                                                 line.mNumFramesOnLine);
     }
   }
 
   int32_t numLines = iter->GetNumLines();
   if (!frame && thisLine < numLines - 1) {
     // Get the first frame of the next line
     auto line = iter->GetLine(thisLine + 1).unwrap();
 
-    if (paraDir == NSBIDI_LTR) {
+    if (paraDir == mozilla::intl::Bidi::Direction::LTR) {
       frame = nsBidiPresUtils::GetFrameToRightOf(
           nullptr, line.mFirstFrameOnLine, line.mNumFramesOnLine);
     } else {  // RTL
       frame = nsBidiPresUtils::GetFrameToLeftOf(nullptr, line.mFirstFrameOnLine,
                                                 line.mNumFramesOnLine);
     }
   }
   return frame;
--- a/layout/generic/nsFrameSelection.cpp
+++ b/layout/generic/nsFrameSelection.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Implementation of nsFrameSelection
  */
 
 #include "nsFrameSelection.h"
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/Logging.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/ScrollTypes.h"
@@ -594,33 +595,35 @@ nsresult nsFrameSelection::ConstrainFram
   //
 
   aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
 
   return NS_OK;
 }
 
 void nsFrameSelection::SetCaretBidiLevelAndMaybeSchedulePaint(
-    nsBidiLevel aLevel) {
+    mozilla::intl::Bidi::EmbeddingLevel aLevel) {
   // If the current level is undefined, we have just inserted new text.
   // In this case, we don't want to reset the keyboard language
   mCaret.mBidiLevel = aLevel;
 
   RefPtr<nsCaret> caret;
   if (mPresShell && (caret = mPresShell->GetCaret())) {
     caret->SchedulePaint();
   }
 }
 
-nsBidiLevel nsFrameSelection::GetCaretBidiLevel() const {
+mozilla::intl::Bidi::EmbeddingLevel nsFrameSelection::GetCaretBidiLevel()
+    const {
   return mCaret.mBidiLevel;
 }
 
 void nsFrameSelection::UndefineCaretBidiLevel() {
-  mCaret.mBidiLevel |= BIDI_LEVEL_UNDEFINED;
+  mCaret.mBidiLevel = mozilla::intl::Bidi::EmbeddingLevel(mCaret.mBidiLevel |
+                                                          BIDI_LEVEL_UNDEFINED);
 }
 
 #ifdef PRINT_RANGE
 void printRange(nsRange* aDomRange) {
   if (!aDomRange) {
     printf("NULL Range\n");
   }
   nsINode* startNode = aDomRange->GetStartContainer();
@@ -657,19 +660,20 @@ static nsINode* GetClosestInclusiveTable
     current = current->GetParent();
   }
   return nullptr;
 }
 
 static nsDirection GetCaretDirection(const nsIFrame& aFrame,
                                      nsDirection aDirection,
                                      bool aVisualMovement) {
-  const nsBidiDirection paragraphDirection =
+  const mozilla::intl::Bidi::Direction paragraphDirection =
       nsBidiPresUtils::ParagraphDirection(&aFrame);
-  return (aVisualMovement && paragraphDirection == NSBIDI_RTL)
+  return (aVisualMovement &&
+          paragraphDirection == mozilla::intl::Bidi::Direction::RTL)
              ? nsDirection(1 - aDirection)
              : aDirection;
 }
 
 nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
                                      bool aContinueSelection,
                                      const nsSelectionAmount aAmount,
                                      CaretMovementStyle aMovementStyle) {
@@ -923,17 +927,18 @@ nsPrevNextBidiLevels nsFrameSelection::G
     nsIContent* aNode, uint32_t aContentOffset, CaretAssociateHint aHint,
     bool aJumpLines) {
   // Get the level of the frames on each side
   nsIFrame* currentFrame;
   int32_t currentOffset;
   nsDirection direction;
 
   nsPrevNextBidiLevels levels{};
-  levels.SetData(nullptr, nullptr, 0, 0);
+  levels.SetData(nullptr, nullptr, mozilla::intl::Bidi::EmbeddingLevel::LTR(),
+                 mozilla::intl::Bidi::EmbeddingLevel::LTR());
 
   currentFrame = GetFrameForNodeOffset(
       aNode, static_cast<int32_t>(aContentOffset), aHint, &currentOffset);
   if (!currentFrame) {
     return levels;
   }
 
   auto [frameStart, frameEnd] = currentFrame->GetOffsets();
@@ -942,29 +947,30 @@ nsPrevNextBidiLevels nsFrameSelection::G
     direction = eDirPrevious;
   } else if (frameStart == currentOffset) {
     direction = eDirPrevious;
   } else if (frameEnd == currentOffset) {
     direction = eDirNext;
   } else {
     // we are neither at the beginning nor at the end of the frame, so we have
     // no worries
-    nsBidiLevel currentLevel = currentFrame->GetEmbeddingLevel();
+    mozilla::intl::Bidi::EmbeddingLevel currentLevel =
+        currentFrame->GetEmbeddingLevel();
     levels.SetData(currentFrame, currentFrame, currentLevel, currentLevel);
     return levels;
   }
 
   nsIFrame* newFrame =
       currentFrame
           ->GetFrameFromDirection(direction, false, aJumpLines, true, false)
           .mFrame;
 
   FrameBidiData currentBidi = currentFrame->GetBidiData();
-  nsBidiLevel currentLevel = currentBidi.embeddingLevel;
-  nsBidiLevel newLevel =
+  mozilla::intl::Bidi::EmbeddingLevel currentLevel = currentBidi.embeddingLevel;
+  mozilla::intl::Bidi::EmbeddingLevel newLevel =
       newFrame ? newFrame->GetEmbeddingLevel() : currentBidi.baseLevel;
 
   // If not jumping lines, disregard br frames, since they might be positioned
   // incorrectly.
   // XXX This could be removed once bug 339786 is fixed.
   if (!aJumpLines) {
     if (currentFrame->IsBrFrame()) {
       currentFrame = nullptr;
@@ -979,22 +985,23 @@ nsPrevNextBidiLevels nsFrameSelection::G
   if (direction == eDirNext)
     levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
   else
     levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
 
   return levels;
 }
 
-nsresult nsFrameSelection::GetFrameFromLevel(nsIFrame* aFrameIn,
-                                             nsDirection aDirection,
-                                             nsBidiLevel aBidiLevel,
-                                             nsIFrame** aFrameOut) const {
+nsresult nsFrameSelection::GetFrameFromLevel(
+    nsIFrame* aFrameIn, nsDirection aDirection,
+    mozilla::intl::Bidi::EmbeddingLevel aBidiLevel,
+    nsIFrame** aFrameOut) const {
   NS_ENSURE_STATE(mPresShell);
-  nsBidiLevel foundLevel = 0;
+  mozilla::intl::Bidi::EmbeddingLevel foundLevel =
+      mozilla::intl::Bidi::EmbeddingLevel::LTR();
   nsIFrame* foundFrame = aFrameIn;
 
   nsCOMPtr<nsIFrameEnumerator> frameTraversal;
   nsresult result;
   nsCOMPtr<nsIFrameTraversal> trav(
       do_CreateInstance(kFrameTraversalCID, &result));
   if (NS_FAILED(result)) return result;
 
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.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 nsFrameSelection_h___
 #define nsFrameSelection_h___
 
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/Result.h"
 #include "mozilla/TextRange.h"
 #include "mozilla/UniquePtr.h"
 #include "nsIFrame.h"
@@ -20,17 +21,17 @@
 #include "nsISelectionListener.h"
 #include "nsITableCellLayout.h"
 #include "WordMovementType.h"
 #include "CaretAssociationHint.h"
 #include "nsBidiPresUtils.h"
 
 class nsRange;
 
-#define BIDI_LEVEL_UNDEFINED 0x80
+#define BIDI_LEVEL_UNDEFINED mozilla::intl::Bidi::EmbeddingLevel(0x80)
 
 //----------------------------------------------------------------------
 
 // Selection interface
 
 struct SelectionDetails {
   SelectionDetails()
       : mStart(), mEnd(), mSelectionType(mozilla::SelectionType::eInvalid) {
@@ -167,26 +168,27 @@ struct MOZ_STACK_CLASS nsPeekOffsetStruc
   // logically after the caret".
   //
   // Used with: eSelectLine, eSelectBeginLine, eSelectEndLine.
   mozilla::CaretAssociationHint mAttach;
 };
 
 struct nsPrevNextBidiLevels {
   void SetData(nsIFrame* aFrameBefore, nsIFrame* aFrameAfter,
-               nsBidiLevel aLevelBefore, nsBidiLevel aLevelAfter) {
+               mozilla::intl::Bidi::EmbeddingLevel aLevelBefore,
+               mozilla::intl::Bidi::EmbeddingLevel aLevelAfter) {
     mFrameBefore = aFrameBefore;
     mFrameAfter = aFrameAfter;
     mLevelBefore = aLevelBefore;
     mLevelAfter = aLevelAfter;
   }
   nsIFrame* mFrameBefore;
   nsIFrame* mFrameAfter;
-  nsBidiLevel mLevelBefore;
-  nsBidiLevel mLevelAfter;
+  mozilla::intl::Bidi::EmbeddingLevel mLevelBefore;
+  mozilla::intl::Bidi::EmbeddingLevel mLevelAfter;
 };
 
 namespace mozilla {
 class SelectionChangeEventDispatcher;
 namespace dom {
 class Selection;
 }  // namespace dom
 
@@ -470,22 +472,23 @@ class nsFrameSelection final {
   enum class SelectionIntoView { IfChanged, Yes };
   MOZ_CAN_RUN_SCRIPT nsresult PageMove(bool aForward, bool aExtend,
                                        nsIFrame* aFrame,
                                        SelectionIntoView aSelectionIntoView);
 
   void SetHint(CaretAssociateHint aHintRight) { mCaret.mHint = aHintRight; }
   CaretAssociateHint GetHint() const { return mCaret.mHint; }
 
-  void SetCaretBidiLevelAndMaybeSchedulePaint(nsBidiLevel aLevel);
+  void SetCaretBidiLevelAndMaybeSchedulePaint(
+      mozilla::intl::Bidi::EmbeddingLevel aLevel);
 
   /**
    * GetCaretBidiLevel gets the caret bidi level.
    */
-  nsBidiLevel GetCaretBidiLevel() const;
+  mozilla::intl::Bidi::EmbeddingLevel GetCaretBidiLevel() const;
 
   /**
    * UndefineCaretBidiLevel sets the caret bidi level to "undefined".
    */
   void UndefineCaretBidiLevel();
 
   /**
    * PhysicalMove will generally be called from the nsiselectioncontroller
@@ -690,17 +693,17 @@ class nsFrameSelection final {
    *
    * @param aPresContext is the context to use
    * @param aFrameIn is the frame to start from
    * @param aDirection is the direction to scan
    * @param aBidiLevel is the level to search for
    * @param aFrameOut will hold the frame returned
    */
   nsresult GetFrameFromLevel(nsIFrame* aFrameIn, nsDirection aDirection,
-                             nsBidiLevel aBidiLevel,
+                             mozilla::intl::Bidi::EmbeddingLevel aBidiLevel,
                              nsIFrame** aFrameOut) const;
 
   /**
    * MaintainSelection will track the normal selection as being "sticky".
    * Dragging or extending selection will never allow for a subset
    * (or the whole) of the maintained selection to become unselected.
    * Primary use: double click selecting then dragging on second click
    *
@@ -1035,25 +1038,26 @@ class nsFrameSelection final {
   int16_t mSelectionChangeReasons = nsISelectionListener::NO_REASON;
   // For visual display purposes.
   int16_t mDisplaySelection = nsISelectionController::SELECTION_OFF;
 
   struct Caret {
     // Hint to tell if the selection is at the end of this line or beginning of
     // next.
     CaretAssociateHint mHint = mozilla::CARET_ASSOCIATE_BEFORE;
-    nsBidiLevel mBidiLevel = BIDI_LEVEL_UNDEFINED;
+    mozilla::intl::Bidi::EmbeddingLevel mBidiLevel = BIDI_LEVEL_UNDEFINED;
 
     bool IsVisualMovement(bool aContinueSelection,
                           CaretMovementStyle aMovementStyle) const;
   };
 
   Caret mCaret;
 
-  nsBidiLevel mKbdBidiLevel = NSBIDI_LTR;
+  mozilla::intl::Bidi::EmbeddingLevel mKbdBidiLevel =
+      mozilla::intl::Bidi::EmbeddingLevel::LTR();
 
   class DesiredCaretPos {
    public:
     // the position requested by the Key Handling for up down
     nsresult FetchPos(nsPoint& aDesiredCaretPos,
                       const mozilla::PresShell& aPresShell,
                       mozilla::dom::Selection& aNormalSelection) const;
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -16,16 +16,17 @@
 #include "nsCOMPtr.h"
 #include "nsIContentViewer.h"
 #include "nsPresContext.h"
 #include "nsView.h"
 #include "nsViewportInfo.h"
 #include "nsContainerFrame.h"
 #include "nsGkAtoms.h"
 #include "nsNameSpaceManager.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/dom/DocumentInlines.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "nsFontMetrics.h"
 #include "nsBoxLayoutState.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "nsScrollbarFrame.h"
 #include "nsINode.h"
 #include "nsIScrollbarMediator.h"
@@ -7112,17 +7113,18 @@ nsRect ScrollFrameHelper::GetScrolledRec
 }
 
 StyleDirection ScrollFrameHelper::GetScrolledFrameDir() const {
   // If the scrolled frame has unicode-bidi: plaintext, the paragraph
   // direction set by the text content overrides the direction of the frame
   if (mScrolledFrame->StyleTextReset()->mUnicodeBidi &
       NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
     if (nsIFrame* child = mScrolledFrame->PrincipalChildList().FirstChild()) {
-      return nsBidiPresUtils::ParagraphDirection(child) == NSBIDI_LTR
+      return nsBidiPresUtils::ParagraphDirection(child) ==
+                     mozilla::intl::Bidi::Direction::LTR
                  ? StyleDirection::Ltr
                  : StyleDirection::Rtl;
     }
   }
   return IsBidiLTR() ? StyleDirection::Ltr : StyleDirection::Rtl;
 }
 
 nsRect ScrollFrameHelper::GetUnsnappedScrolledRectInternal(
--- a/layout/generic/nsIFrame.cpp
+++ b/layout/generic/nsIFrame.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/dom/DocumentInlines.h"
 #include "mozilla/dom/AncestorIterator.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "mozilla/dom/ImageTracker.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/PresShellInlines.h"
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/StaticAnalysisFunctions.h"
 #include "mozilla/StaticPrefs_layout.h"
 #include "mozilla/StaticPrefs_print.h"
 #include "mozilla/SVGMaskFrame.h"
@@ -1692,17 +1693,18 @@ nsRect nsIFrame::GetPaddingRect() const 
 }
 
 WritingMode nsIFrame::WritingModeForLine(WritingMode aSelfWM,
                                          nsIFrame* aSubFrame) const {
   MOZ_ASSERT(aSelfWM == GetWritingMode());
   WritingMode writingMode = aSelfWM;
 
   if (StyleTextReset()->mUnicodeBidi & NS_STYLE_UNICODE_BIDI_PLAINTEXT) {
-    nsBidiLevel frameLevel = nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
+    mozilla::intl::Bidi::EmbeddingLevel frameLevel =
+        nsBidiPresUtils::GetFrameBaseLevel(aSubFrame);
     writingMode.SetDirectionFromBidiLevel(frameLevel);
   }
 
   return writingMode;
 }
 
 nsRect nsIFrame::GetMarginRect() const {
   return GetMarginRectRelativeToSelf() + GetPosition();
@@ -7918,18 +7920,19 @@ void nsIFrame::ListGeneric(nsACString& a
   bool hasNormalPosition;
   nsPoint normalPosition = GetNormalPosition(&hasNormalPosition);
   if (hasNormalPosition) {
     aTo += nsPrintfCString(" normal-position=%s",
                            ConvertToString(normalPosition, aFlags).c_str());
   }
   if (HasProperty(BidiDataProperty())) {
     FrameBidiData bidi = GetBidiData();
-    aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel,
-                           bidi.embeddingLevel, bidi.precedingControl);
+    aTo += nsPrintfCString(" bidi(%d,%d,%d)", bidi.baseLevel.Value(),
+                           bidi.embeddingLevel.Value(),
+                           bidi.precedingControl.Value());
   }
   if (IsTransformed()) {
     aTo += nsPrintfCString(" transformed");
   }
   if (ChildrenHavePerspective()) {
     aTo += nsPrintfCString(" perspective");
   }
   if (Extend3DContext()) {
@@ -8100,17 +8103,17 @@ nsresult nsIFrame::GetPointFromOffset(in
       // Find the direction of the frame from the EmbeddingLevelProperty,
       // which is the resolved bidi level set in
       // nsBidiPresUtils::ResolveParagraph (odd levels = right-to-left).
       // If the embedding level isn't set, just use the CSS direction
       // property.
       bool hasBidiData;
       FrameBidiData bidiData = GetProperty(BidiDataProperty(), &hasBidiData);
       bool isRTL = hasBidiData
-                       ? IS_LEVEL_RTL(bidiData.embeddingLevel)
+                       ? bidiData.embeddingLevel.IsRTL()
                        : StyleVisibility()->mDirection == StyleDirection::Rtl;
       if ((!isRTL && inOffset > newOffset) ||
           (isRTL && inOffset <= newOffset)) {
         pt = contentRect.TopRight();
       }
     }
   }
   *outPoint = pt;
@@ -9097,17 +9100,18 @@ Result<bool, nsresult> nsIFrame::IsVisua
   MOZ_TRY(aLineIterator->CheckLineOrder(aLine, &isReordered, &firstFrame,
                                         &lastFrame));
 
   nsIFrame** framePtr = aDirection == eDirPrevious ? &firstFrame : &lastFrame;
   if (!*framePtr) {
     return true;
   }
 
-  bool frameIsRTL = (nsBidiPresUtils::FrameDirection(*framePtr) == NSBIDI_RTL);
+  bool frameIsRTL = (nsBidiPresUtils::FrameDirection(*framePtr) ==
+                     mozilla::intl::Bidi::Direction::RTL);
   if ((frameIsRTL == lineIsRTL) == (aDirection == eDirPrevious)) {
     nsIFrame::GetFirstLeaf(framePtr);
   } else {
     nsIFrame::GetLastLeaf(framePtr);
   }
   return *framePtr == this;
 }
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -73,16 +73,17 @@
 #include "nsStyleStruct.h"
 #include "Visibility.h"
 #include "nsChangeHint.h"
 #include "mozilla/ComputedStyleInlines.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/CompositorHitTestInfo.h"
 #include "mozilla/gfx/MatrixFwd.h"
+#include "mozilla/intl/Bidi.h"
 #include "nsDisplayItemTypes.h"
 #include "nsPresContext.h"
 #include "nsTHashSet.h"
 
 #ifdef ACCESSIBILITY
 #  include "mozilla/a11y/AccTypes.h"
 #endif
 
@@ -354,86 +355,16 @@ class nsReflowStatus final {
 };
 
 #define NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics) \
   aStatus.UpdateTruncated(aReflowInput, aMetrics);
 
 // Convert nsReflowStatus to a human-readable string.
 std::ostream& operator<<(std::ostream& aStream, const nsReflowStatus& aStatus);
 
-/**
- * nsBidiLevel is the type of the level values in our Unicode Bidi
- * implementation.
- * It holds an embedding level and indicates the visual direction
- * by its bit 0 (even/odd value).<p>
- *
- * <li><code>aParaLevel</code> can be set to the
- * pseudo-level values <code>NSBIDI_DEFAULT_LTR</code>
- * and <code>NSBIDI_DEFAULT_RTL</code>.</li></ul>
- *
- * @see nsBidi::SetPara
- *
- * <p>The related constants are not real, valid level values.
- * <code>NSBIDI_DEFAULT_XXX</code> can be used to specify
- * a default for the paragraph level for
- * when the <code>SetPara</code> function
- * shall determine it but there is no
- * strongly typed character in the input.<p>
- *
- * Note that the value for <code>NSBIDI_DEFAULT_LTR</code> is even
- * and the one for <code>NSBIDI_DEFAULT_RTL</code> is odd,
- * just like with normal LTR and RTL level values -
- * these special values are designed that way. Also, the implementation
- * assumes that NSBIDI_MAX_EXPLICIT_LEVEL is odd.
- *
- * @see NSBIDI_DEFAULT_LTR
- * @see NSBIDI_DEFAULT_RTL
- * @see NSBIDI_LEVEL_OVERRIDE
- * @see NSBIDI_MAX_EXPLICIT_LEVEL
- */
-typedef uint8_t nsBidiLevel;
-
-/**
- * Paragraph level setting.
- * If there is no strong character, then set the paragraph level to 0
- * (left-to-right).
- */
-#define NSBIDI_DEFAULT_LTR 0xfe
-
-/**
- * Paragraph level setting.
- * If there is no strong character, then set the paragraph level to 1
- * (right-to-left).
- */
-#define NSBIDI_DEFAULT_RTL 0xff
-
-/**
- * Maximum explicit embedding level.
- * (The maximum resolved level can be up to
- * <code>NSBIDI_MAX_EXPLICIT_LEVEL+1</code>).
- */
-#define NSBIDI_MAX_EXPLICIT_LEVEL 125
-
-/** Bit flag for level input.
- *  Overrides directional properties.
- */
-#define NSBIDI_LEVEL_OVERRIDE 0x80
-
-/**
- * <code>nsBidiDirection</code> values indicate the text direction.
- */
-enum nsBidiDirection {
-  /** All left-to-right text This is a 0 value. */
-  NSBIDI_LTR,
-  /** All right-to-left text This is a 1 value. */
-  NSBIDI_RTL,
-  /** Mixed-directional text. */
-  NSBIDI_MIXED
-};
-
 namespace mozilla {
 
 // https://drafts.csswg.org/css-align-3/#baseline-sharing-group
 enum class BaselineSharingGroup {
   // NOTE Used as an array index so must be 0 and 1.
   First = 0,
   Last = 1,
 };
@@ -468,25 +399,26 @@ struct IntrinsicSize {
 
   bool operator==(const IntrinsicSize& rhs) const {
     return width == rhs.width && height == rhs.height;
   }
   bool operator!=(const IntrinsicSize& rhs) const { return !(*this == rhs); }
 };
 
 // Pseudo bidi embedding level indicating nonexistence.
-static const nsBidiLevel kBidiLevelNone = 0xff;
+static const mozilla::intl::Bidi::EmbeddingLevel kBidiLevelNone =
+    mozilla::intl::Bidi::EmbeddingLevel(0xff);
 
 struct FrameBidiData {
-  nsBidiLevel baseLevel;
-  nsBidiLevel embeddingLevel;
+  mozilla::intl::Bidi::EmbeddingLevel baseLevel;
+  mozilla::intl::Bidi::EmbeddingLevel embeddingLevel;
   // The embedding level of virtual bidi formatting character before
   // this frame if any. kBidiLevelNone is used to indicate nonexistence
   // or unnecessity of such virtual character.
-  nsBidiLevel precedingControl;
+  mozilla::intl::Bidi::EmbeddingLevel precedingControl;
 };
 
 }  // namespace mozilla
 
 /// Generic destructor for frame properties. Calls delete.
 template <typename T>
 static void DeleteValue(T* aPropertyValue) {
   delete aPropertyValue;
@@ -1393,19 +1325,23 @@ class nsIFrame : public nsQueryFrame {
     bool exists;
     mozilla::FrameBidiData bidiData = GetProperty(BidiDataProperty(), &exists);
     if (!exists) {
       bidiData.precedingControl = mozilla::kBidiLevelNone;
     }
     return bidiData;
   }
 
-  nsBidiLevel GetBaseLevel() const { return GetBidiData().baseLevel; }
-
-  nsBidiLevel GetEmbeddingLevel() const { return GetBidiData().embeddingLevel; }
+  mozilla::intl::Bidi::EmbeddingLevel GetBaseLevel() const {
+    return GetBidiData().baseLevel;
+  }
+
+  mozilla::intl::Bidi::EmbeddingLevel GetEmbeddingLevel() const {
+    return GetBidiData().embeddingLevel;
+  }
 
   /**
    * Return the distance between the border edge of the frame and the
    * margin edge of the frame.  Like GetRect(), returns the dimensions
    * as of the most recent reflow.
    *
    * This doesn't include any margin collapsing that may have occurred.
    * It also doesn't consider GetSkipSides()/GetLogicalSkipSides(), so
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -7,16 +7,17 @@
 /* rendering object for replaced elements with image data */
 
 #include "nsImageFrame.h"
 
 #include "TextDrawTarget.h"
 #include "gfx2DGlue.h"
 #include "gfxContext.h"
 #include "gfxUtils.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/dom/ImageTracker.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
@@ -1429,41 +1430,41 @@ void nsImageFrame::DisplayAltText(nsPres
     uint32_t maxFit;  // number of characters that fit
     nscoord strWidth =
         MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm);
 
     // Display the text
     nsresult rv = NS_ERROR_FAILURE;
 
     if (aPresContext->BidiEnabled()) {
-      nsBidiDirection dir;
+      mozilla::intl::Bidi::EmbeddingLevel level;
       nscoord x, y;
 
       if (isVertical) {
         x = pt.x + maxDescent;
         if (wm.IsBidiLTR()) {
           y = aRect.y;
-          dir = NSBIDI_LTR;
+          level = mozilla::intl::Bidi::EmbeddingLevel::LTR();
         } else {
           y = aRect.YMost() - strWidth;
-          dir = NSBIDI_RTL;
+          level = mozilla::intl::Bidi::EmbeddingLevel::RTL();
         }
       } else {
         y = pt.y + maxAscent;
         if (wm.IsBidiLTR()) {
           x = aRect.x;
-          dir = NSBIDI_LTR;
+          level = mozilla::intl::Bidi::EmbeddingLevel::LTR();
         } else {
           x = aRect.XMost() - strWidth;
-          dir = NSBIDI_RTL;
+          level = mozilla::intl::Bidi::EmbeddingLevel::RTL();
         }
       }
 
       rv = nsBidiPresUtils::RenderText(
-          str, maxFit, dir, aPresContext, aRenderingContext,
+          str, maxFit, level, aPresContext, aRenderingContext,
           aRenderingContext.GetDrawTarget(), *fm, x, y);
     }
     if (NS_FAILED(rv)) {
       nsLayoutUtils::DrawUniDirString(str, maxFit,
                                       isVertical
                                           ? nsPoint(pt.x + maxDescent, pt.y)
                                           : nsPoint(pt.x, pt.y + maxAscent),
                                       *fm, aRenderingContext);
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -64,16 +64,17 @@
 #include "nsTextFragment.h"
 #include "nsGkAtoms.h"
 #include "nsFrameSelection.h"
 #include "nsRange.h"
 #include "nsCSSRendering.h"
 #include "nsContentUtils.h"
 #include "nsLineBreaker.h"
 #include "nsIFrameInlines.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/intl/WordBreaker.h"
 #include "mozilla/ServoStyleSet.h"
 
 #include <algorithm>
 #include <limits>
 #include <type_traits>
 #ifdef ACCESSIBILITY
 #  include "nsAccessibilityService.h"
@@ -1930,17 +1931,17 @@ bool BuildTextRunsScanner::ContinueTextR
     }
 
     // Map inline-end and inline-start to physical sides for checking presence
     // of non-zero margin/border/padding.
     Side side1 = wm.PhysicalSide(eLogicalSideIEnd);
     Side side2 = wm.PhysicalSide(eLogicalSideIStart);
     // If the frames have an embedding level that is opposite to the writing
     // mode, we need to swap which sides we're checking.
-    if (IS_LEVEL_RTL(aFrame1->GetEmbeddingLevel()) == wm.IsBidiLTR()) {
+    if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) {
       std::swap(side1, side2);
     }
 
     if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) ||
         PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) {
       return false;
     }
   }
@@ -2391,17 +2392,17 @@ already_AddRefed<gfxTextRun> BuildTextRu
   }
 
   if (flags2 & nsTextFrameUtils::Flags::HasTab) {
     flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
   }
   if (flags2 & nsTextFrameUtils::Flags::HasShy) {
     flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
   }
-  if (mBidiEnabled && (IS_LEVEL_RTL(firstFrame->GetEmbeddingLevel()))) {
+  if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) {
     flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
   }
   if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
     flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace;
   }
   if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
     flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
   }
--- a/layout/painting/moz.build
+++ b/layout/painting/moz.build
@@ -10,16 +10,17 @@ with Files("**"):
 EXPORTS += [
     "ActiveLayerTracker.h",
     "DisplayItemClip.h",
     "DisplayItemClipChain.h",
     "DisplayListClipState.h",
     "HitTestInfo.h",
     "LayerState.h",
     "MatrixStack.h",
+    "nsCSSRendering.h",
     "nsCSSRenderingBorders.h",
     "nsCSSRenderingGradients.h",
     "nsDisplayItemTypes.h",
     "nsDisplayItemTypesList.h",
     "nsDisplayList.h",
     "nsDisplayListArenaTypes.h",
     "nsDisplayListInvalidation.h",
     "nsImageRenderer.h",
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -4742,39 +4742,23 @@ void nsDisplayOpacity::Paint(nsDisplayLi
   // TODO: Compute a bounds rect to pass to PushLayer for a smaller
   // allocation.
   aCtx->GetDrawTarget()->PushLayer(false, GetOpacity(), nullptr, gfx::Matrix());
   GetChildren()->Paint(aBuilder, aCtx,
                        mFrame->PresContext()->AppUnitsPerDevPixel());
   aCtx->GetDrawTarget()->PopLayer();
 }
 
-/**
- * This doesn't take into account layer scaling --- the layer may be
- * rendered at a higher (or lower) resolution, affecting the retained layer
- * size --- but this should be good enough.
- */
-static bool IsItemTooSmallForActiveLayer(nsIFrame* aFrame) {
-  nsIntRect visibleDevPixels =
-      aFrame->InkOverflowRectRelativeToSelf().ToOutsidePixels(
-          aFrame->PresContext()->AppUnitsPerDevPixel());
-  return visibleDevPixels.Size() <
-         nsIntSize(StaticPrefs::layout_min_active_layer_size(),
-                   StaticPrefs::layout_min_active_layer_size());
-}
-
 /* static */
 bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder,
-                                        nsIFrame* aFrame,
-                                        bool aEnforceMinimumSize) {
+                                        nsIFrame* aFrame) {
   return EffectCompositor::HasAnimationsForCompositor(
              aFrame, DisplayItemType::TYPE_OPACITY) ||
          (ActiveLayerTracker::IsStyleAnimated(
-              aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()) &&
-          !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(aFrame)));
+             aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()));
 }
 
 bool nsDisplayOpacity::CanApplyOpacity(WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aBuilder) const {
   return !EffectCompositor::HasAnimationsForCompositor(
       mFrame, DisplayItemType::TYPE_OPACITY);
 }
 
@@ -6874,29 +6858,27 @@ void nsDisplayTransform::Paint(nsDisplay
   }
 
   RefPtr<SourceSurface> untransformedSurf = untransformedDT->Snapshot();
 
   trans.PreTranslate(pixelBounds.X(), pixelBounds.Y(), 0);
   aCtx->GetDrawTarget()->Draw3DTransformedSurface(untransformedSurf, trans);
 }
 
-bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder,
-                                       bool aEnforceMinimumSize) const {
+bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const {
   // If EffectCompositor::HasAnimationsForCompositor() is true then we can
   // completely bypass the main thread for this animation, so it is always
   // worthwhile.
   // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is
   // already involved so there is less to be gained.
   // Therefore we check that the *post-transform* bounds of this item are
   // big enough to justify an active layer.
   return EffectCompositor::HasAnimationsForCompositor(
              mFrame, DisplayItemType::TYPE_TRANSFORM) ||
-         (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame) &&
-          !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(mFrame)));
+         (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame));
 }
 
 nsRect nsDisplayTransform::TransformUntransformedBounds(
     nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const {
   bool snap;
   const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap);
   // GetTransform always operates in dev pixels.
   const float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -5006,18 +5006,18 @@ class nsDisplayOpacity : public nsDispla
 
   /**
    * Returns true if ShouldFlattenAway() applied opacity to children.
    */
   bool OpacityAppliedToChildren() const {
     return mChildOpacityState == ChildOpacityState::Applied;
   }
 
-  static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
-                               bool aEnforceMinimumSize = true);
+  static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder,
+                               nsIFrame* aFrame);
   void WriteDebugInfo(std::stringstream& aStream) override;
   bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
   bool CreateWebRenderCommands(
       wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
       const StackingContextHelper& aSc,
       layers::RenderRootStateManager* aManager,
       nsDisplayListBuilder* aDisplayListBuilder) override;
 
@@ -6251,18 +6251,17 @@ class nsDisplayTransform : public nsPain
    * affect the decision on other frames in the preserve 3d tree.
    * |aDirtyRect| is updated to the area that should be prerendered.
    */
   static PrerenderInfo ShouldPrerenderTransformedContent(
       nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect);
 
   bool CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) override;
 
-  bool MayBeAnimated(nsDisplayListBuilder* aBuilder,
-                     bool aEnforceMinimumSize = true) const;
+  bool MayBeAnimated(nsDisplayListBuilder* aBuilder) const;
 
   void WriteDebugInfo(std::stringstream& aStream) override;
 
   bool CanMoveAsync() override {
     return EffectCompositor::HasAnimationsForCompositor(
         mFrame, DisplayItemType::TYPE_TRANSFORM);
   }
 
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -28,17 +28,16 @@ headers = [
     "mozilla/LookAndFeel.h",
     "mozilla/GeckoBindings.h",
     "mozilla/ServoBindings.h",
     "mozilla/ComputedStyle.h",
     "mozilla/PresShell.h",
     "mozilla/ServoTraversalStatistics.h",
     "mozilla/SizeOfState.h",
     "nsCSSProps.h",
-    "nsContentUtils.h",
     "nsMappedAttributes.h",
     "nsNameSpaceManager.h",
 ]
 raw-lines = [
     # FIXME(emilio): Incrementally remove these "pub use"s. Probably
     # mozilla::css and mozilla::dom are easier.
     "pub use self::root::*;",
     "pub use self::root::mozilla::*;",
@@ -175,17 +174,17 @@ whitelist-vars = [
     "NS_RADIUS_.*",
     "BORDER_COLOR_.*",
     "BORDER_STYLE_.*",
     "CSS_PSEUDO_ELEMENT_.*",
     "SERVO_CSS_PSEUDO_ELEMENT_FLAGS_.*",
     "kNameSpaceID_.*",
     "kGenericFont_.*",
     "kPresContext_.*",
-    "nsContentUtils_.*",
+    "nsNameSpaceManager_.*",
     "GECKO_IS_NIGHTLY",
     "NS_SAME_AS_FOREGROUND_COLOR",
     "mozilla::detail::gGkAtoms",
     "mozilla::detail::kGkAtomsArrayOffset",
     "mozilla::dom::SVGPathSeg_Binding::PATHSEG_.*",
 ]
 # TODO(emilio): A bunch of types here can go away once we generate bindings and
 # structs together.
--- a/layout/xul/nsTextBoxFrame.cpp
+++ b/layout/xul/nsTextBoxFrame.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsTextBoxFrame.h"
 
 #include "gfx2DGlue.h"
 #include "gfxUtils.h"
+#include "mozilla/intl/Bidi.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/layers/RenderRootStateManager.h"
 #include "mozilla/gfx/2D.h"
 #include "nsFontMetrics.h"
 #include "nsReadableUtils.h"
@@ -479,17 +480,18 @@ void nsTextBoxFrame::DrawText(gfxContext
       aOverrideColor ? *aOverrideColor : StyleText()->mColor.ToColor());
   ColorPattern colorPattern(color);
   aRenderingContext.SetDeviceColor(color);
 
   nsresult rv = NS_ERROR_FAILURE;
 
   if (mState & NS_FRAME_IS_BIDI) {
     presContext->SetBidiEnabled();
-    nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(Style());
+    mozilla::intl::Bidi::EmbeddingLevel level =
+        nsBidiPresUtils::BidiLevelFromStyle(Style());
     if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
       // We let the RenderText function calculate the mnemonic's
       // underline position for us.
       nsBidiPositionResolve posResolve;
       posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex;
       rv = nsBidiPresUtils::RenderText(
           mCroppedTitle.get(), mCroppedTitle.Length(), level, presContext,
           aRenderingContext, refDrawTarget, *fontMet, baselinePt.x,
--- a/memory/build/Utils.h
+++ b/memory/build/Utils.h
@@ -15,30 +15,16 @@
 // Helper for log2 of powers of 2 at compile time.
 template <size_t N>
 struct Log2 : mozilla::tl::CeilingLog2<N> {
   using mozilla::tl::CeilingLog2<N>::value;
   static_assert(1ULL << value == N, "Number is not a power of 2");
 };
 #define LOG2(N) Log2<N>::value
 
-// Like Log2, but ignores 0.
-template <size_t N>
-struct Log2Or0 : mozilla::tl::CeilingLog2<N> {
-  using mozilla::tl::CeilingLog2<N>::value;
-  static_assert(1ULL << value == N, "Number is not a power of 2");
-};
-template <>
-struct Log2Or0<0> {
-  // This makes no sense but neither does any other value.  It's just enough
-  // that this can be used on the unused side of a conditional expression.
-  static const size_t value = 0;
-};
-#define LOG2_OR_0(N) Log2Or0<N>::value
-
 enum class Order {
   eLess = -1,
   eEqual = 0,
   eGreater = 1,
 };
 
 // Compare two integers. Returns whether the first integer is Less,
 // Equal or Greater than the second integer.
--- a/memory/build/malloc_decls.h
+++ b/memory/build/malloc_decls.h
@@ -58,21 +58,24 @@ NOTHROW_MALLOC_DECL(memalign, void*, siz
 NOTHROW_MALLOC_DECL(posix_memalign, int, void**, size_t, size_t)
 NOTHROW_MALLOC_DECL(aligned_alloc, void*, size_t, size_t)
 NOTHROW_MALLOC_DECL(valloc, void*, size_t)
 NOTHROW_MALLOC_DECL(malloc_usable_size, size_t, usable_ptr_t)
 MALLOC_DECL(malloc_good_size, size_t, size_t)
 #  endif
 
 #  if MALLOC_FUNCS & MALLOC_FUNCS_JEMALLOC
-// The 2nd argument points to an optional array exactly JEMALLOC_MAX_STATS_BINS
-// long to be filled in (if non-null). Any unused bin has it's size set to zero.
+// The 2nd argument points to an optional array exactly
+// jemalloc_stats_num_bins() long to be filled in (if non-null).
 MALLOC_DECL(jemalloc_stats_internal, void, jemalloc_stats_t*,
             jemalloc_bin_stats_t*)
 
+// Return the size of the jemalloc_bin_stats_t array.
+MALLOC_DECL(jemalloc_stats_num_bins, size_t)
+
 // On some operating systems (Mac), we use madvise(MADV_FREE) to hand pages
 // back to the operating system.  On Mac, the operating system doesn't take
 // this memory back immediately; instead, the OS takes it back only when the
 // machine is running out of physical memory.
 //
 // This is great from the standpoint of efficiency, but it makes measuring our
 // actual RSS difficult, because pages which we've MADV_FREE'd shouldn't count
 // against our RSS.
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -478,31 +478,31 @@ static size_t gPageSize;
 #endif
 
 #ifdef MALLOC_STATIC_PAGESIZE
 #  define DECLARE_GLOBAL(type, name)
 #  define DEFINE_GLOBALS
 #  define END_GLOBALS
 #  define DEFINE_GLOBAL(type) static const type
 #  define GLOBAL_LOG2 LOG2
-#  define GLOBAL_LOG2_OR_0 LOG2_OR_0
 #  define GLOBAL_ASSERT_HELPER1(x) static_assert(x, #  x)
 #  define GLOBAL_ASSERT_HELPER2(x, y) static_assert(x, y)
 #  define GLOBAL_ASSERT(...)                                               \
     MACRO_CALL(                                                            \
         MOZ_PASTE_PREFIX_AND_ARG_COUNT(GLOBAL_ASSERT_HELPER, __VA_ARGS__), \
         (__VA_ARGS__))
+#  define GLOBAL_CONSTEXPR constexpr
 #else
 #  define DECLARE_GLOBAL(type, name) static type name;
 #  define DEFINE_GLOBALS static void DefineGlobals() {
 #  define END_GLOBALS }
 #  define DEFINE_GLOBAL(type)
 #  define GLOBAL_LOG2 FloorLog2
-#  define GLOBAL_LOG2_OR_0 FloorLog2
 #  define GLOBAL_ASSERT MOZ_RELEASE_ASSERT
+#  define GLOBAL_CONSTEXPR
 #endif
 
 DECLARE_GLOBAL(size_t, gMaxSubPageClass)
 DECLARE_GLOBAL(uint8_t, gNumSubPageClasses)
 DECLARE_GLOBAL(uint8_t, gPageSize2Pow)
 DECLARE_GLOBAL(size_t, gPageSizeMask)
 DECLARE_GLOBAL(size_t, gChunkNumPages)
 DECLARE_GLOBAL(size_t, gChunkHeaderNumPages)
@@ -515,20 +515,22 @@ DEFINE_GLOBAL(size_t)
 gMaxSubPageClass = gPageSize / 2 >= kMinSubPageClass ? gPageSize / 2 : 0;
 
 // Max size class for bins.
 #define gMaxBinClass \
   (gMaxSubPageClass ? gMaxSubPageClass : kMaxQuantumWideClass)
 
 // Number of sub-page bins.
 DEFINE_GLOBAL(uint8_t)
-gNumSubPageClasses =
-    static_cast<uint8_t>(gMaxSubPageClass ? GLOBAL_LOG2_OR_0(gMaxSubPageClass) -
-                                                LOG2(kMinSubPageClass) + 1
-                                          : 0);
+gNumSubPageClasses = []() GLOBAL_CONSTEXPR -> uint8_t {
+  if GLOBAL_CONSTEXPR (gMaxSubPageClass != 0) {
+    return FloorLog2(gMaxSubPageClass) - LOG2(kMinSubPageClass) + 1;
+  }
+  return 0;
+}();
 
 DEFINE_GLOBAL(uint8_t) gPageSize2Pow = GLOBAL_LOG2(gPageSize);
 DEFINE_GLOBAL(size_t) gPageSizeMask = gPageSize - 1;
 
 // Number of pages in a chunk.
 DEFINE_GLOBAL(size_t) gChunkNumPages = kChunkSize >> gPageSize2Pow;
 
 // Number of pages necessary for a chunk header.
@@ -3959,18 +3961,16 @@ static bool malloc_init_hard() {
         _getprogname(),
         "Compile-time page size does not divide the runtime one.\n");
     MOZ_CRASH();
   }
 #else
   gRealPageSize = gPageSize = (size_t)result;
 #endif
 
-  MOZ_RELEASE_ASSERT(JEMALLOC_MAX_STATS_BINS >= NUM_SMALL_CLASSES);
-
   // Get runtime configuration.
   if ((opts = getenv("MALLOC_OPTIONS"))) {
     for (i = 0; opts[i] != '\0'; i++) {
       unsigned j, nreps;
       bool nseen;
 
       // Parse repetition count, if any.
       for (nreps = 0, nseen = false;; i++, nseen = true) {
@@ -4319,20 +4319,17 @@ inline void MozJemalloc::jemalloc_stats_
   if (!aStats) {
     return;
   }
   if (!malloc_init()) {
     memset(aStats, 0, sizeof(*aStats));
     return;
   }
   if (aBinStats) {
-    // An assertion in malloc_init_hard will guarantee that
-    // JEMALLOC_MAX_STATS_BINS >= NUM_SMALL_CLASSES.
-    memset(aBinStats, 0,
-           sizeof(jemalloc_bin_stats_t) * JEMALLOC_MAX_STATS_BINS);
+    memset(aBinStats, 0, sizeof(jemalloc_bin_stats_t) * NUM_SMALL_CLASSES);
   }
 
   // Gather runtime settings.
   aStats->opt_junk = opt_junk;
   aStats->opt_zero = opt_zero;
   aStats->quantum = kQuantum;
   aStats->quantum_max = kMaxQuantumClass;
   aStats->quantum_wide = kQuantumWide;
@@ -4448,16 +4445,21 @@ inline void MozJemalloc::jemalloc_stats_
   aStats->mapped += non_arena_mapped;
   aStats->bookkeeping += chunk_header_size;
   aStats->waste -= chunk_header_size;
 
   MOZ_ASSERT(aStats->mapped >= aStats->allocated + aStats->waste +
                                    aStats->page_cache + aStats->bookkeeping);
 }
 
+template <>
+inline size_t MozJemalloc::jemalloc_stats_num_bins() {
+  return NUM_SMALL_CLASSES;
+}
+
 #ifdef MALLOC_DOUBLE_PURGE
 
 // Explicitly remove all of this chunk's MADV_FREE'd pages from memory.
 static void hard_purge_chunk(arena_chunk_t* aChunk) {
   // See similar logic in arena_t::Purge().
   for (size_t i = gChunkHeaderNumPages; i < gChunkNumPages; i++) {
     // Find all adjacent pages with CHUNK_MAP_MADVISED set.
     size_t npages;
--- a/memory/build/mozjemalloc_types.h
+++ b/memory/build/mozjemalloc_types.h
@@ -108,19 +108,16 @@ typedef struct {
                              // bin stats array entry is unused (no more bins).
   size_t num_non_full_runs;  // The number of non-full runs
   size_t num_runs;           // The number of runs in this bin
   size_t bytes_unused;       // The unallocated bytes across all these bins
   size_t bytes_total;        // The total storage area for runs in this bin,
   size_t bytes_per_run;      // The number of bytes per run, including headers.
 } jemalloc_bin_stats_t;
 
-// This is the total number of bins.
-#define JEMALLOC_MAX_STATS_BINS 51
-
 enum PtrInfoTag {
   // The pointer is not currently known to the allocator.
   // 'addr', 'size', and 'arenaId' are always 0.
   TagUnknown,
 
   // The pointer is within a live allocation.
   // 'addr', 'size', and 'arenaId' describe the allocation.
   TagLiveAlloc,
--- a/memory/build/mozmemory.h
+++ b/memory/build/mozmemory.h
@@ -6,16 +6,17 @@
 
 #ifndef mozmemory_h
 #define mozmemory_h
 
 // This header is meant to be used when the following functions are
 // necessary:
 //   - malloc_good_size (used to be called je_malloc_usable_in_advance)
 //   - jemalloc_stats
+//   - jemalloc_stats_num_bins
 //   - jemalloc_purge_freed_pages
 //   - jemalloc_free_dirty_pages