Merge mozilla-central to inbound. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Fri, 03 May 2019 07:24:54 +0300
changeset 531268 a9d7796acb8e2444e1452e3ab2dbb9c037994ea4
parent 531267 05656abf3e6c6862ba89990fd9ba3854fe5eb5f1 (current diff)
parent 531210 083106d8fc7407c880a3a044c83d4e15e5961063 (diff)
child 531269 b758384a42e27280c1b11f2a60ff2a378949583e
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/debugger/src/utils/breakable-lines.js
devtools/client/debugger/src/utils/tests/breakable-lines.spec.js
dom/notification/NotificationDB.jsm
testing/web-platform/meta/cookies/http-state/__dir__.ini
testing/web-platform/meta/cookies/http-state/charset-tests.html.ini
testing/web-platform/meta/cookies/http-state/comma-tests.html.ini
testing/web-platform/meta/webrtc/RTCPeerConnection-setLocalDescription-answer.html.ini
toolkit/components/xulstore/XULStore.jsm
toolkit/content/widgets/menu.xml
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -357,17 +357,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "bookmark_sync"
 version = "0.1.0"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cstr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "dogear 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dogear 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "moz_task 0.1.0",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "storage 0.1.0",
  "storage_variant 0.1.0",
  "thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -914,17 +914,17 @@ dependencies = [
  "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.88 (git+https://github.com/servo/serde?branch=deserialize_from_enums10)",
  "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "dogear"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "dtoa"
@@ -3672,17 +3672,17 @@ dependencies = [
 "checksum darling_macro 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "244e8987bd4e174385240cde20a3657f607fb0797563c28255c353b5819a07b1"
 "checksum deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8a6abb26e16e8d419b5c78662aa9f82857c2386a073da266840e474d5055ec86"
 "checksum derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f57d78cf3bd45270dad4e70c21ec77a960b36c7a841ff9db76aaa775a8fb871"
 "checksum devd-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c9ac481c38baf400d3b732e4a06850dfaa491d1b6379a249d9d40d14c2434c"
 "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
 "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
 "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
 "checksum docopt 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db2906c2579b5b7207fc1e328796a9a8835dc44e22dbe8e460b1d636f9a7b225"
-"checksum dogear 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d54506b6b209740d0a7a35ca5976db1ad2ed1aa168acc3561efc6a84fa95afe"
+"checksum dogear 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "30ac4a8e8f834f02deb2266b1f279aa5494e990c625d8be8f2988a7c708ba1f8"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum dtoa-short 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "068d4026697c1a18f0b0bb8cfcad1b0c151b90d8edb9bf4c235ad68128920d1d"
 "checksum dwrote 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c31c624339dab99c223a4b26c2e803b7c248adaca91549ce654c76f39a03f5c8"
 "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
 "checksum ena 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "25b4e5febb25f08c49f1b07dc33a182729a6b21edfb562b5aef95f78e0dbe5bb"
 "checksum encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "769ecb8b33323998e482b218c0d13cd64c267609023b4b7ec3ee740714c318ee"
 "checksum encoding_rs 0.8.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0535f350c60aac0b87ccf28319abc749391e912192255b0c00a2c12c6917bd73"
 "checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad"
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -980,17 +980,17 @@ pref("app.productInfo.baseURL", "https:/
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
 
 pref("browser.security.newcerterrorpage.mitm.enabled", true);
 pref("security.certerrors.recordEventTelemetry", true);
 pref("security.certerrors.permanentOverride", true);
 pref("security.certerrors.mitm.priming.enabled", true);
 pref("security.certerrors.mitm.priming.endpoint", "https://mitmdetection.services.mozilla.com/");
-pref("security.certerrors.mitm.auto_enable_enterprise_roots", false);
+pref("security.certerrors.mitm.auto_enable_enterprise_roots", true);
 
 // Whether to start the private browsing mode at application startup
 pref("browser.privatebrowsing.autostart", false);
 
 // Whether to show the new private browsing UI with in-content search box.
 pref("browser.privatebrowsing.searchUI", true);
 
 // Whether the bookmark panel should be shown when bookmarking a page.
--- a/browser/app/winlauncher/ErrorHandler.cpp
+++ b/browser/app/winlauncher/ErrorHandler.cpp
@@ -678,23 +678,19 @@ static unsigned __stdcall SendPingThread
 }
 
 #endif  // defined(MOZ_TELEMETRY_REPORTING)
 
 static bool SendPing(const mozilla::LauncherError& aError) {
 #if defined(MOZ_TELEMETRY_REPORTING)
 #  if defined(MOZ_LAUNCHER_PROCESS)
   mozilla::LauncherRegistryInfo regInfo;
-  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState>
-      launcherEnabled = regInfo.IsEnabled();
-  if (launcherEnabled.isErr() ||
-      launcherEnabled.unwrap() ==
-          mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled) {
-    // If the launcher is force disabled, we do not send any pings
-    // (since studies and thus telemetry have been opted out)
+  mozilla::LauncherResult<bool> telemetryEnabled = regInfo.IsTelemetryEnabled();
+  if (telemetryEnabled.isErr() || !telemetryEnabled.unwrap()) {
+    // Do not send anything if telemetry has been opted out
     return false;
   }
 #  endif  // defined(MOZ_LAUNCHER_PROCESS)
 
   // We send this ping when the launcher process fails. After we start the
   // SendPingThread, this thread falls back from running as the launcher process
   // to running as the browser main thread. Once this happens, it will be unsafe
   // to set up PoisonIOInterposer (since we have already spun up a background
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -158,38 +158,43 @@ static mozilla::Maybe<bool> RunAsLaunche
                         static_cast<const wchar_t**>(nullptr),
                         mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND) {
     return mozilla::Some(false);
   }
 
   bool runAsLauncher = DoLauncherProcessChecks(argc, argv);
 
 #if defined(MOZ_LAUNCHER_PROCESS)
+  bool forceLauncher = runAsLauncher &&
+                       mozilla::CheckArg(argc, argv, L"force-launcher",
+                                         static_cast<const wchar_t**>(nullptr),
+                                         mozilla::CheckArgFlag::RemoveArg) ==
+                       mozilla::ARG_FOUND;
+
   mozilla::LauncherRegistryInfo::ProcessType desiredType =
       runAsLauncher ? mozilla::LauncherRegistryInfo::ProcessType::Launcher
                     : mozilla::LauncherRegistryInfo::ProcessType::Browser;
+
+  mozilla::LauncherRegistryInfo::CheckOption checkOption =
+      forceLauncher ? mozilla::LauncherRegistryInfo::CheckOption::Force
+                    : mozilla::LauncherRegistryInfo::CheckOption::Default;
+
   mozilla::LauncherRegistryInfo regInfo;
   mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType>
-      runAsType = regInfo.Check(desiredType);
+      runAsType = regInfo.Check(desiredType, checkOption);
 
   if (runAsType.isErr()) {
     mozilla::HandleLauncherError(runAsType);
     return mozilla::Nothing();
   }
 
   runAsLauncher = runAsType.unwrap() ==
                   mozilla::LauncherRegistryInfo::ProcessType::Launcher;
 #endif  // defined(MOZ_LAUNCHER_PROCESS)
 
-  // We must check for force-launcher *after* we do LauncherRegistryInfo checks
-  runAsLauncher |=
-      mozilla::CheckArg(argc, argv, L"force-launcher",
-                        static_cast<const wchar_t**>(nullptr),
-                        mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
-
   if (!runAsLauncher) {
     // In this case, we will be proceeding to run as the browser.
     // We should check MOZ_DEBUG_BROWSER_* env vars.
     MaybeBreakForBrowserDebugging();
   }
 
   return mozilla::Some(runAsLauncher);
 }
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -172,16 +172,22 @@ var whitelist = [
   {file: "chrome://devtools/skin/images/next.svg", isFromDevTools: true},
   // Bug 1526672
   {file: "resource://app/localization/en-US/browser/touchbar/touchbar.ftl",
    platforms: ["linux", "win"]},
   // Referenced by the webcompat system addon for localization
   {file: "resource://gre/localization/en-US/toolkit/about/aboutCompat.ftl"},
 ];
 
+if (!AppConstants.MOZ_NEW_NOTIFICATION_STORE) {
+  // kvstore.jsm wraps the API in nsIKeyValue.idl in a more ergonomic API
+  // It landed in bug 1490496, and we expect to start using it shortly.
+  whitelist.push({file: "resource://gre/modules/kvstore.jsm"});
+}
+
 whitelist = new Set(whitelist.filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
   (!item.skipUnofficial || !AppConstants.MOZILLA_OFFICIAL) &&
   (!item.platforms || item.platforms.includes(AppConstants.platform))
 ).map(item => item.file));
 
 const ignorableWhitelist = new Set([
   // The following files are outside of the omni.ja file, so we only catch them
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -604,16 +604,37 @@ var Policies = {
   "ExtensionUpdate": {
     onBeforeAddons(manager, param) {
       if (!param) {
         setAndLockPref("extensions.update.enabled", param);
       }
     },
   },
 
+  "FirefoxHome": {
+    onBeforeAddons(manager, param) {
+      let locked = param.Locked || false;
+      if ("Search" in param) {
+        setDefaultPref("browser.newtabpage.activity-stream.showSearch", param.Search, locked);
+      }
+      if ("TopSites" in param) {
+        setDefaultPref("browser.newtabpage.activity-stream.feeds.topsites", param.TopSites, locked);
+      }
+      if ("Highlights" in param) {
+        setDefaultPref("browser.newtabpage.activity-stream.feeds.section.highlights", param.Highlights, locked);
+      }
+      if ("Pocket" in param) {
+        setDefaultPref("browser.newtabpage.activity-stream.feeds.section.topstories", param.Pocket, locked);
+      }
+      if ("Snippets" in param) {
+        setDefaultPref("browser.newtabpage.activity-stream.feeds.snippets", param.Snippets, locked);
+      }
+    },
+  },
+
   "FlashPlugin": {
     onBeforeUIStartup(manager, param) {
       addAllowDenyPermissions("plugin:flash", param.Allow, param.Block);
 
       const FLASH_NEVER_ACTIVATE = 0;
       const FLASH_ASK_TO_ACTIVATE = 1;
       const FLASH_ALWAYS_ACTIVATE = 2;
 
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -326,16 +326,40 @@
         }
       }
     },
 
     "ExtensionUpdate": {
       "type": "boolean"
     },
 
+    "FirefoxHome": {
+      "type": "object",
+      "properties": {
+        "Search": {
+          "type": "boolean"
+        },
+        "TopSites": {
+          "type": "boolean"
+        },
+        "Highlights": {
+          "type": "boolean"
+        },
+        "Pocket": {
+          "type": "boolean"
+        },
+        "Snippets": {
+          "type": "boolean"
+        },
+        "Locked": {
+          "type": "boolean"
+        }
+      }
+    },
+
     "FlashPlugin": {
       "type": "object",
       "properties": {
         "Allow": {
           "type": "array",
           "strict": false,
           "items": {
             "type": "origin"
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -46,16 +46,17 @@ skip-if = (verify && debug && (os == 'ma
 [browser_policy_disable_safemode.js]
 [browser_policy_disable_shield.js]
 [browser_policy_disable_telemetry.js]
 [browser_policy_display_bookmarks.js]
 [browser_policy_display_menu.js]
 [browser_policy_extensions.js]
 [browser_policy_extensionsettings.js]
 [browser_policy_locale.js]
+[browser_policy_firefoxhome.js]
 [browser_policy_override_postupdatepage.js]
 [browser_policy_permissions.js]
 [browser_policy_proxy.js]
 [browser_policy_preferences.js]
 [browser_policy_search_engine.js]
 [browser_policy_searchbar.js]
 [browser_policy_set_homepage.js]
 [browser_policy_support_menu.js]
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
@@ -422,16 +422,31 @@ const POLICIES_TESTS = [
   {
     policies: {
       "SearchSuggestEnabled": false,
     },
     lockedPrefs: {
       "browser.urlbar.suggest.searches": false,
     },
   },
+
+  // POLICY: FirefoxHome
+  {
+    policies: {
+      "FirefoxHome": {
+        "Pocket": false,
+        "Snippets": false,
+        "Locked": true,
+      },
+    },
+    lockedPrefs: {
+      "browser.newtabpage.activity-stream.feeds.snippets": false,
+      "browser.newtabpage.activity-stream.feeds.section.topstories": false,
+    },
+  },
 ];
 
 add_task(async function test_policy_simple_prefs() {
   for (let test of POLICIES_TESTS) {
     await setupPolicyEngineWithJson({
       "policies": test.policies,
     });
 
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_firefoxhome.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_firefox_home_without_policy_without_pocket() {
+  let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
+    opening: "about:home",
+    waitForStateStop: true});
+
+  await ContentTask.spawn(tab.linkedBrowser, {}, function() {
+    let search = content.document.querySelector(".search-wrapper");
+    isnot(search, null, "Search section should be there.");
+    let topsites = content.document.querySelector("section[data-section-id='topsites']");
+    isnot(topsites, null, "Top Sites section should be there.");
+    let highlights = content.document.querySelector("section[data-section-id='highlights']");
+    isnot(highlights, null, "Highlights section should be there.");
+  });
+  BrowserTestUtils.removeTab(tab);
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_firefox_home_with_policy() {
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "FirefoxHome": {
+        "Search": false,
+        "TopSites": false,
+        "Highlights": false,
+        "Snippets": false,
+      },
+    },
+  });
+
+  let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
+    opening: "about:home",
+    waitForStateStop: true});
+
+  await ContentTask.spawn(tab.linkedBrowser, {}, function() {
+    let search = content.document.querySelector(".search-wrapper");
+    is(search, null, "Search section should not be there.");
+    let topsites = content.document.querySelector("section[data-section-id='topsites']");
+    is(topsites, null, "Top Sites section should not be there.");
+    let highlights = content.document.querySelector("section[data-section-id='highlights']");
+    is(highlights, null, "Highlights section should not be there.");
+  });
+  BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -178,18 +178,18 @@ async function assignCookiesUnderFirstPa
   });
 
   BrowserTestUtils.removeTab(tabInfo.tab);
 }
 
 async function generateCookies(aThirdParty) {
   // we generate two different cookies for two first party domains.
   let cookies = [];
-  cookies.push(Math.random().toString());
-  cookies.push(Math.random().toString());
+  cookies.push(Math.random().toString() + "=1");
+  cookies.push(Math.random().toString() + "=1");
 
   let firstSiteURL;
   let secondSiteURL;
 
   if (aThirdParty) {
     // Add cookies into the third party site with different first party domain.
     firstSiteURL = THIRD_PARTY_SITE;
     secondSiteURL = THIRD_PARTY_SITE;
--- a/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -123,18 +123,18 @@ function waitOnFaviconLoaded(aFaviconURL
     (uri, attr, value, id) => attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
                               value === aFaviconURL,
     "history");
 }
 
 async function generateCookies(aHost) {
   // we generate two different cookies for two userContextIds.
   let cookies = [];
-  cookies.push(Math.random().toString());
-  cookies.push(Math.random().toString());
+  cookies.push(Math.random().toString() + "=1");
+  cookies.push(Math.random().toString() + "=1");
 
   // Then, we add cookies into the site for 'personal' and 'work'.
   let tabInfoA = await openTabInUserContext(aHost, USER_CONTEXT_ID_PERSONAL);
   let tabInfoB = await openTabInUserContext(aHost, USER_CONTEXT_ID_WORK);
 
   await ContentTask.spawn(tabInfoA.browser, cookies[0], async function(value) {
     content.document.cookie = value;
   });
--- a/browser/components/preferences/browserLanguages.js
+++ b/browser/components/preferences/browserLanguages.js
@@ -245,18 +245,19 @@ class SortedItemSelectList {
     items.splice(i, 0, item);
     popup.insertBefore(this.createItem(item), menulist.getItemAtIndex(i));
 
     menulist.disabled = menulist.itemCount == 0;
   }
 
   createItem({label, value, className, disabled}) {
     let item = document.createElement("menuitem");
-    item.value = value;
     item.setAttribute("label", label);
+    if (value)
+      item.value = value;
     if (className)
       item.classList.add(className);
     if (disabled)
       item.setAttribute("disabled", "true");
     return item;
   }
 
   /**
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -184,18 +184,18 @@ add_task(async function test_favicon_pri
 
   // Create a private browsing window.
   let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true });
   let pageURI = makeURI(TEST_PAGE);
 
   // Generate two random cookies for non-private window and private window
   // respectively.
   let cookies = [];
-  cookies.push(Math.random().toString());
-  cookies.push(Math.random().toString());
+  cookies.push(Math.random().toString() + "=1");
+  cookies.push(Math.random().toString() + "=1");
 
   // Open a tab in private window and add a cookie into it.
   await assignCookies(privateWindow.gBrowser, TEST_SITE, cookies[0]);
 
   // Open a tab in non-private window and add a cookie into it.
   await assignCookies(gBrowser, TEST_SITE, cookies[1]);
 
   // Add the observer earlier in case we don't capture events in time.
@@ -239,21 +239,22 @@ add_task(async function test_favicon_cac
 
   Services.cache2.clear();
 
   // Clear all favicons in Places.
   await clearAllPlacesFavicons();
 
   // Add an observer for making sure the favicon has been loaded and cached.
   let promiseFaviconLoaded = waitOnFaviconLoaded(FAVICON_CACHE_URI);
+  let promiseFaviconResponse = waitOnFaviconResponse(FAVICON_CACHE_URI);
 
   // Open a tab for the non-private window.
   let tabInfoNonPrivate = await openTab(gBrowser, TEST_CACHE_PAGE);
 
-  let response = await waitOnFaviconResponse(FAVICON_CACHE_URI);
+  let response = await promiseFaviconResponse;
 
   await promiseFaviconLoaded;
 
   // Check that the favicon response has come from the network and it has the
   // correct privateBrowsingId.
   is(response.topic, "http-on-examine-response", "The favicon image should be loaded through network.");
   is(response.privateBrowsingId, 0, "We should observe the network response for the non-private tab.");
 
@@ -261,21 +262,23 @@ add_task(async function test_favicon_cac
   let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true });
 
   // The page must be bookmarked for favicon requests to go through in PB mode.
   await PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
     url: TEST_CACHE_PAGE,
   });
 
+  promiseFaviconResponse = waitOnFaviconResponse(FAVICON_CACHE_URI);
+
   // Open a tab for the private window.
   let tabInfoPrivate = await openTab(privateWindow.gBrowser, TEST_CACHE_PAGE);
 
   // Wait for the favicon response of the private tab.
-  response = await waitOnFaviconResponse(FAVICON_CACHE_URI);
+  response = await promiseFaviconResponse;
 
   // Make sure the favicon is loaded through the network and its privateBrowsingId is correct.
   is(response.topic, "http-on-examine-response", "The favicon image should be loaded through the network again.");
   is(response.privateBrowsingId, 1, "We should observe the network response for the private tab.");
 
   BrowserTestUtils.removeTab(tabInfoPrivate.tab);
   BrowserTestUtils.removeTab(tabInfoNonPrivate.tab);
   await BrowserTestUtils.closeWindow(privateWindow);
--- a/browser/components/sessionstore/test/browser_sessionStoreContainer.js
+++ b/browser/components/sessionstore/test/browser_sessionStoreContainer.js
@@ -109,17 +109,17 @@ add_task(async function test() {
     "set": [ [ "privacy.userContext.enabled", true ] ],
   });
 
   Services.cookies.removeAll();
 
   for (let userContextId of Object.keys(USER_CONTEXTS)) {
     // Load the page in 3 different contexts and set a cookie
     // which should only be visible in that context.
-    let cookie = USER_CONTEXTS[userContextId];
+    let cookie = USER_CONTEXTS[userContextId] + "=1";
 
     // Open our tab in the given user context.
     let { tab, browser } = await openTabInUserContext(userContextId);
 
     await Promise.all([
       waitForNewCookie(),
       ContentTask.spawn(browser, cookie,
         passedCookie => content.document.cookie = passedCookie),
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -485,17 +485,23 @@ Section "-Application" APP_IDX
   ${CreateRegKey} "$TmpVal" "$0" 0
 
   ${If} $TmpVal == "HKLM"
     ; Set the permitted LSP Categories
     ${SetAppLSPCategories} ${LSP_CATEGORIES}
   ${EndIf}
 
 !ifdef MOZ_LAUNCHER_PROCESS
-  ${DisableLauncherProcessByDefault}
+  ; Launcher telemetry is opt-out, so we always enable it by default in new
+  ; installs. We always use HKCU because this value is a reflection of a pref
+  ; from the user profile. While this is not a perfect abstraction (given the
+  ; possibility of multiple Firefox profiles owned by the same Windows user), it
+  ; is more accurate than a machine-wide setting, and should be accurate in the
+  ; majority of cases.
+  WriteRegDWORD HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Telemetry" 1
 !endif
 
   ; Create shortcuts
   ${LogHeader} "Adding Shortcuts"
 
   ; Remove the start menu shortcuts and directory if the SMPROGRAMS section
   ; exists in the shortcuts_log.ini and the SMPROGRAMS. The installer's shortcut
   ; creation code will create the shortcut in the root of the Start Menu
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -166,17 +166,17 @@
       ; IsAdmin check and the permissions check, the maintenance service
       ; will just fail to be attempted to be installed.
       nsExec::Exec "$\"$INSTDIR\maintenanceservice_installer.exe$\""
     ${EndIf}
   ${EndIf}
 !endif
 
 !ifdef MOZ_LAUNCHER_PROCESS
-  ${DisableLauncherProcessByDefault}
+  ${ResetLauncherProcessDefaults}
 !endif
 
 !macroend
 !define PostUpdate "!insertmacro PostUpdate"
 
 ; Update the last modified time on the Start Menu shortcut, so that its icon
 ; gets refreshed. Should be called on Win8+ after UpdateShortcutBranding.
 !macro TouchStartMenuShortcut
@@ -1577,27 +1577,18 @@ Function SetAsDefaultAppUser
     UAC::ExecCodeSegment $0
   ${EndIf}
 FunctionEnd
 !define SetAsDefaultAppUser "Call SetAsDefaultAppUser"
 
 !endif ; NO_LOG
 
 !ifdef MOZ_LAUNCHER_PROCESS
-!macro DisableLauncherProcessByDefault
-  ClearErrors
-  ${ReadRegQWORD} $0 HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Launcher"
-  ${If} ${Errors}
-    ClearErrors
-    ${ReadRegQWORD} $0 HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Browser"
-    ${If} ${Errors}
-      ClearErrors
-      ReadRegDWORD $0 HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Image"
-      ${If} ${Errors}
-        ClearErrors
-        ; New install that hasn't seen this yet; disable by default
-        ${WriteRegQWORD} HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Browser" 0
-      ${EndIf}
-    ${EndIf}
-  ${EndIf}
+!macro ResetLauncherProcessDefaults
+  # By deleting these values, we remove remnants of any force-disable settings
+  # that may have been set during the SHIELD study in 67. Note that this setting
+  # was only intended to distinguish between test and control groups for the
+  # purposes of the study, not as a user preference.
+  DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Launcher"
+  DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Browser"
 !macroend
-!define DisableLauncherProcessByDefault "!insertmacro DisableLauncherProcessByDefault"
+!define ResetLauncherProcessDefaults "!insertmacro ResetLauncherProcessDefaults"
 !endif
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -415,16 +415,17 @@ Section "Uninstall"
   ${If} "$INSTDIR\AccessibleHandler.dll" == "$R1"
     ${UnregisterDLL} "$INSTDIR\AccessibleHandler.dll"
   ${EndIf}
 
 !ifdef MOZ_LAUNCHER_PROCESS
   DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Launcher"
   DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Browser"
   DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Image"
+  DeleteRegValue HKCU ${MOZ_LAUNCHER_SUBKEY} "$INSTDIR\${FileMainEXE}|Telemetry"
 !endif
 
   ${un.RemovePrecompleteEntries} "false"
 
   ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
     Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\defaults\pref"
--- a/browser/locales/en-US/browser/policies/policies-descriptions.ftl
+++ b/browser/locales/en-US/browser/policies/policies-descriptions.ftl
@@ -83,16 +83,18 @@ policy-EnableTrackingProtection = Enable
 
 # A “locked” extension can’t be disabled or removed by the user. This policy
 # takes 3 keys (“Install”, ”Uninstall”, ”Locked”), you can either keep them in
 # English or translate them as verbs.
 policy-Extensions = Install, uninstall or lock extensions. The Install option takes URLs or paths as parameters. The Uninstall and Locked options take extension IDs.
 
 policy-ExtensionUpdate = Enable or disable automatic extension updates.
 
+policy-FirefoxHome = Configure Firefox Home.
+
 policy-FlashPlugin = Allow or deny usage of the Flash plugin.
 
 policy-HardwareAcceleration = If false, turn off hardware acceleration.
 
 # “lock” means that the user won’t be able to change this setting
 policy-Homepage = Set and optionally lock the homepage.
 
 policy-InstallAddonsPermission = Allow certain websites to install add-ons.
--- a/config/system-headers.mozbuild
+++ b/config/system-headers.mozbuild
@@ -1043,17 +1043,17 @@ system_headers = [
     'X11/Xos.h',
     'X11/Xutil.h',
     'xcb/shm.h',
     'xcb/xcb.h',
     'xlocale.h',
     'zmouse.h',
 ]
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+if CONFIG['OS_TARGET'] == 'Android':
     system_headers += [
         'android/api-level.h',
         'android/ashmem.h',
         'android_audio/AudioSystem.h',
         'android/log.h',
         'android/looper.h',
         'android/native_window.h',
         'android/native_window_jni.h',
--- a/devtools/client/aboutdebugging-new/src/components/App.css
+++ b/devtools/client/aboutdebugging-new/src/components/App.css
@@ -12,17 +12,17 @@
  *  +-------------+-------------------------------+
  *
  * Some of the values (font sizes, widths, etc.) are the same as
  * about:preferences, which uses the shared common.css
  */
 
 .app {
   /* from common */
-  --sidebar-width: 260px;
+  --sidebar-width: 280px;
   --app-top-padding: 70px;
   --app-bottom-padding: 40px;
   --app-left-padding: 34px;
 
   box-sizing: border-box;
   width: 100vw;
   height: 100vh;
   overflow: hidden; /* we don't want the sidebar to scroll, only the main content */
--- a/devtools/client/debugger/src/actions/breakpoints/breakpointPositions.js
+++ b/devtools/client/debugger/src/actions/breakpoints/breakpointPositions.js
@@ -1,45 +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/>. */
 
 // @flow
 
-import SourceMaps, {
+import {
   isOriginalId,
+  isGeneratedId,
   originalToGeneratedId
 } from "devtools-source-map";
 import { uniqBy, zip } from "lodash";
 
 import {
   getSource,
   getSourceFromId,
   hasBreakpointPositions,
+  hasBreakpointPositionsForLine,
   getBreakpointPositionsForSource,
   getSourceActorsForSource
 } from "../../selectors";
 
 import type {
-  SourceId,
   MappedLocation,
   Range,
   SourceLocation,
   BreakpointPositions,
   Context
 } from "../../types";
+
 import { makeBreakpointId } from "../../utils/breakpoint";
 import {
   memoizeableAction,
   type MemoizedAction
 } from "../../utils/memoizableAction";
+import type { ThunkArgs } from "../../actions/types";
 
 async function mapLocations(
   generatedLocations: SourceLocation[],
-  { sourceMaps }: { sourceMaps: typeof SourceMaps }
+  { sourceMaps }: ThunkArgs
 ) {
   const originalLocations = await sourceMaps.getOriginalLocations(
     generatedLocations
   );
 
   return zip(originalLocations, generatedLocations).map(
     ([location, generatedLocation]) => ({ location, generatedLocation })
   );
@@ -70,17 +73,39 @@ function convertToList(results, source) 
         sourceUrl: url
       });
     }
   }
 
   return positions;
 }
 
-async function _setBreakpointPositions(cx, sourceId, thunkArgs) {
+function groupByLine(results, sourceId, line) {
+  const isOriginal = isOriginalId(sourceId);
+  const positions = {};
+
+  // Ensure that we have an entry for the line fetched
+  if (typeof line === "number") {
+    positions[line] = [];
+  }
+
+  for (const result of results) {
+    const location = isOriginal ? result.location : result.generatedLocation;
+
+    if (!positions[location.line]) {
+      positions[location.line] = [];
+    }
+
+    positions[location.line].push(result);
+  }
+
+  return positions;
+}
+
+async function _setBreakpointPositions(cx, sourceId, line, thunkArgs) {
   const { client, dispatch, getState, sourceMaps } = thunkArgs;
   let generatedSource = getSource(getState(), sourceId);
   if (!generatedSource) {
     return;
   }
 
   let results = {};
   if (isOriginalId(sourceId)) {
@@ -107,31 +132,37 @@ async function _setBreakpointPositions(c
           column: 0
         };
       }
 
       const bps = await client.getBreakpointPositions(
         getSourceActorsForSource(getState(), generatedSource.id),
         range
       );
-      for (const line in bps) {
-        results[line] = (results[line] || []).concat(bps[line]);
+      for (const bpLine in bps) {
+        results[bpLine] = (results[bpLine] || []).concat(bps[bpLine]);
       }
     }
   } else {
+    if (typeof line !== "number") {
+      throw new Error("Line is required for generated sources");
+    }
+
     results = await client.getBreakpointPositions(
-      getSourceActorsForSource(getState(), generatedSource.id)
+      getSourceActorsForSource(getState(), generatedSource.id),
+      { start: { line, column: 0 }, end: { line: line + 1, column: 0 } }
     );
   }
 
   let positions = convertToList(results, generatedSource);
   positions = await mapLocations(positions, thunkArgs);
 
   positions = filterBySource(positions, sourceId);
   positions = filterByUniqLocation(positions);
+  positions = groupByLine(positions, sourceId, line);
 
   const source = getSource(getState(), sourceId);
   // NOTE: it's possible that the source was removed during a navigate
   if (!source) {
     return;
   }
 
   dispatch({
@@ -139,45 +170,38 @@ async function _setBreakpointPositions(c
     cx,
     source: source,
     positions
   });
 
   return positions;
 }
 
-const runningFetches = {};
-export function isFetchingBreakpoints(id: SourceId) {
-  return id in runningFetches;
+function generatedSourceActorKey(state, sourceId) {
+  const generatedSource = getSource(
+    state,
+    isOriginalId(sourceId) ? originalToGeneratedId(sourceId) : sourceId
+  );
+  const actors = generatedSource
+    ? getSourceActorsForSource(state, generatedSource.id).map(
+        ({ actor }) => actor
+      )
+    : [];
+  return [sourceId, ...actors].join(":");
 }
 
 export const setBreakpointPositions: MemoizedAction<
-  { cx: Context, sourceId: string },
+  { cx: Context, sourceId: string, line?: number },
   ?BreakpointPositions
 > = memoizeableAction("setBreakpointPositions", {
-  hasValue: ({ sourceId }, { getState }) =>
-    hasBreakpointPositions(getState(), sourceId),
-  getValue: ({ sourceId }, { getState }) =>
+  hasValue: ({ sourceId, line }, { getState }) =>
+    isGeneratedId(sourceId) && line
+      ? hasBreakpointPositionsForLine(getState(), sourceId, line)
+      : hasBreakpointPositions(getState(), sourceId),
+  getValue: ({ sourceId, line }, { getState }) =>
     getBreakpointPositionsForSource(getState(), sourceId),
-  createKey({ sourceId }, { getState }) {
-    const generatedSource = getSource(
-      getState(),
-      isOriginalId(sourceId) ? originalToGeneratedId(sourceId) : sourceId
-    );
-    const actors = generatedSource
-      ? getSourceActorsForSource(getState(), generatedSource.id).map(
-          ({ actor }) => actor
-        )
-      : [];
-    return [sourceId, ...actors].join(":");
+  createKey({ sourceId, line }, { getState }) {
+    const key = generatedSourceActorKey(getState(), sourceId);
+    return isGeneratedId(sourceId) && line ? `${key}-${line}` : key;
   },
-  action: async ({ cx, sourceId }, thunkArgs) => {
-    runningFetches[sourceId] = (runningFetches[sourceId] | 0) + 1;
-    try {
-      return await _setBreakpointPositions(cx, sourceId, thunkArgs);
-    } finally {
-      runningFetches[sourceId] -= 0;
-      if (runningFetches[sourceId] === 0) {
-        delete runningFetches[sourceId];
-      }
-    }
-  }
+  action: async ({ cx, sourceId, line }, thunkArgs) =>
+    _setBreakpointPositions(cx, sourceId, line, thunkArgs)
 });
--- a/devtools/client/debugger/src/actions/breakpoints/modify.js
+++ b/devtools/client/debugger/src/actions/breakpoints/modify.js
@@ -102,19 +102,19 @@ export function addBreakpoint(
   initialLocation: SourceLocation,
   options: BreakpointOptions = {},
   disabled: boolean = false,
   shouldCancel: () => boolean = () => false
 ) {
   return async ({ dispatch, getState, sourceMaps, client }: ThunkArgs) => {
     recordEvent("add_breakpoint");
 
-    const { sourceId, column } = initialLocation;
+    const { sourceId, column, line } = initialLocation;
 
-    await dispatch(setBreakpointPositions({ cx, sourceId }));
+    await dispatch(setBreakpointPositions({ cx, sourceId, line }));
 
     const position: ?BreakpointPosition = column
       ? getBreakpointPositionsForLocation(getState(), initialLocation)
       : getFirstBreakpointPosition(getState(), initialLocation);
 
     if (!position) {
       return;
     }
--- a/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js
+++ b/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js
@@ -31,18 +31,19 @@ import type {
   Context
 } from "../../types";
 
 async function findBreakpointPosition(
   cx: Context,
   { getState, dispatch },
   location: SourceLocation
 ) {
+  const { sourceId, line } = location;
   const positions: BreakpointPositions = await dispatch(
-    setBreakpointPositions({ cx, sourceId: location.sourceId })
+    setBreakpointPositions({ cx, sourceId, line })
   );
 
   const position = findPosition(positions, location);
   return position && position.generatedLocation;
 }
 
 async function findNewLocation(
   cx: Context,
--- a/devtools/client/debugger/src/actions/breakpoints/tests/breakpointPositions.spec.js
+++ b/devtools/client/debugger/src/actions/breakpoints/tests/breakpointPositions.spec.js
@@ -14,93 +14,99 @@ import {
 import { createSource } from "../../tests/helpers/threadClient";
 
 describe("breakpointPositions", () => {
   it("fetches positions", async () => {
     const fooContent = createSource("foo", "");
 
     const store = createStore({
       getBreakpointPositions: async () => ({ "9": [1] }),
+      getBreakableLines: async () => [],
       sourceContents: async () => fooContent
     });
 
     const { dispatch, getState, cx } = store;
     const source = await dispatch(
       actions.newGeneratedSource(makeSource("foo"))
     );
     await dispatch(actions.loadSourceById(cx, source.id));
 
-    dispatch(actions.setBreakpointPositions({ cx, sourceId: "foo" }));
+    dispatch(actions.setBreakpointPositions({ cx, sourceId: "foo", line: 9 }));
 
     await waitForState(store, state =>
       selectors.hasBreakpointPositions(state, "foo")
     );
 
     expect(
       selectors.getBreakpointPositionsForSource(getState(), "foo")
-    ).toEqual([
-      {
-        location: {
-          line: 9,
-          column: 1,
-          sourceId: "foo",
-          sourceUrl: "http://localhost:8000/examples/foo"
-        },
-        generatedLocation: {
-          line: 9,
-          column: 1,
-          sourceId: "foo",
-          sourceUrl: "http://localhost:8000/examples/foo"
+    ).toEqual({
+      [9]: [
+        {
+          location: {
+            line: 9,
+            column: 1,
+            sourceId: "foo",
+            sourceUrl: "http://localhost:8000/examples/foo"
+          },
+          generatedLocation: {
+            line: 9,
+            column: 1,
+            sourceId: "foo",
+            sourceUrl: "http://localhost:8000/examples/foo"
+          }
         }
-      }
-    ]);
+      ]
+    });
   });
 
   it("doesn't re-fetch positions", async () => {
     const fooContent = createSource("foo", "");
 
     let resolve = _ => {};
     let count = 0;
     const store = createStore({
       getBreakpointPositions: () =>
         new Promise(r => {
           count++;
           resolve = r;
         }),
+      getBreakableLines: async () => [],
       sourceContents: async () => fooContent
     });
 
     const { dispatch, getState, cx } = store;
     const source = await dispatch(
       actions.newGeneratedSource(makeSource("foo"))
     );
     await dispatch(actions.loadSourceById(cx, source.id));
 
-    dispatch(actions.setBreakpointPositions({ cx, sourceId: "foo" }));
-    dispatch(actions.setBreakpointPositions({ cx, sourceId: "foo" }));
+    dispatch(actions.setBreakpointPositions({ cx, sourceId: "foo", line: 9 }));
+    dispatch(actions.setBreakpointPositions({ cx, sourceId: "foo", line: 9 }));
 
     resolve({ "9": [1] });
     await waitForState(store, state =>
       selectors.hasBreakpointPositions(state, "foo")
     );
 
     expect(
       selectors.getBreakpointPositionsForSource(getState(), "foo")
-    ).toEqual([
-      {
-        location: {
-          line: 9,
-          column: 1,
-          sourceId: "foo",
-          sourceUrl: "http://localhost:8000/examples/foo"
-        },
-        generatedLocation: {
-          line: 9,
-          column: 1,
-          sourceId: "foo",
-          sourceUrl: "http://localhost:8000/examples/foo"
+    ).toEqual({
+      [9]: [
+        {
+          location: {
+            line: 9,
+            column: 1,
+            sourceId: "foo",
+            sourceUrl: "http://localhost:8000/examples/foo"
+          },
+          generatedLocation: {
+            line: 9,
+            column: 1,
+            sourceId: "foo",
+            sourceUrl: "http://localhost:8000/examples/foo"
+          }
         }
-      }
-    ]);
+      ]
+    });
 
     expect(count).toEqual(1);
   });
 });
--- a/devtools/client/debugger/src/actions/breakpoints/tests/breakpoints.spec.js
+++ b/devtools/client/debugger/src/actions/breakpoints/tests/breakpoints.spec.js
@@ -12,17 +12,18 @@ import {
   getTelemetryEvents
 } from "../../../utils/test-head";
 
 import { simpleMockThreadClient } from "../../tests/helpers/threadClient.js";
 
 function mockClient(positionsResponse = {}) {
   return {
     ...simpleMockThreadClient,
-    getBreakpointPositions: async () => positionsResponse
+    getBreakpointPositions: async () => positionsResponse,
+    getBreakableLines: async () => []
   };
 }
 
 describe("breakpoints", () => {
   it("should add a breakpoint", async () => {
     const { dispatch, getState, cx } = createStore(mockClient({ "2": [1] }));
     const loc1 = {
       sourceId: "a",
--- a/devtools/client/debugger/src/actions/pause/tests/pause.spec.js
+++ b/devtools/client/debugger/src/actions/pause/tests/pause.spec.js
@@ -65,17 +65,18 @@ const mockThreadClient = {
         case "foo-wasm/originalSource":
           return resolve({
             source: "fn fooBar() {}\nfn barZoo() { fooBar() }",
             contentType: "text/rust"
           });
       }
     });
   },
-  getBreakpointPositions: async () => ({})
+  getBreakpointPositions: async () => ({}),
+  getBreakableLines: async () => []
 };
 
 const mockFrameId = "1";
 
 function createPauseInfo(
   frameLocation = { sourceId: "foo1", line: 2 },
   frameOpts = {}
 ) {
@@ -167,17 +168,20 @@ describe("pause", () => {
       const cx = selectors.getThreadContext(getState());
       const getNextStepSpy = jest.spyOn(parser, "getNextStep");
       dispatch(actions.stepOver(cx));
       expect(getNextStepSpy).toBeCalled();
       getNextStepSpy.mockRestore();
     });
 
     it("should step over when paused after an await", async () => {
-      const store = createStore(mockThreadClient);
+      const store = createStore({
+        ...mockThreadClient,
+        getBreakpointPositions: async () => ({ [2]: [1] })
+      });
       const { dispatch, getState } = store;
       const mockPauseInfo = createPauseInfo({
         sourceId: "await",
         line: 2,
         column: 6
       });
 
       await dispatch(actions.newGeneratedSource(makeSource("await")));
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/src/actions/sources/breakableLines.js
@@ -0,0 +1,51 @@
+/* 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/>. */
+
+// @flow
+
+import { isOriginalId } from "devtools-source-map";
+import { getSourceActorsForSource, getBreakableLines } from "../../selectors";
+import { setBreakpointPositions } from "../breakpoints/breakpointPositions";
+import { union } from "lodash";
+import type { Context } from "../../types";
+import type { ThunkArgs } from "../../actions/types";
+
+function calculateBreakableLines(positions) {
+  const lines = [];
+  for (const line in positions) {
+    if (positions[line].length > 0) {
+      lines.push(Number(line));
+    }
+  }
+
+  return lines;
+}
+
+export function setBreakableLines(cx: Context, sourceId: string) {
+  return async ({ getState, dispatch, client }: ThunkArgs) => {
+    let breakableLines;
+    if (isOriginalId(sourceId)) {
+      const positions = await dispatch(
+        setBreakpointPositions({ cx, sourceId })
+      );
+      breakableLines = calculateBreakableLines(positions);
+    } else {
+      breakableLines = await client.getBreakableLines(
+        getSourceActorsForSource(getState(), sourceId)
+      );
+    }
+
+    const existingBreakableLines = getBreakableLines(getState(), sourceId);
+    if (existingBreakableLines) {
+      breakableLines = union(existingBreakableLines, breakableLines);
+    }
+
+    dispatch({
+      type: "SET_BREAKABLE_LINES",
+      cx,
+      sourceId,
+      breakableLines
+    });
+  };
+}
--- a/devtools/client/debugger/src/actions/sources/index.js
+++ b/devtools/client/debugger/src/actions/sources/index.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 export * from "./blackbox";
+export * from "./breakableLines";
 export * from "./loadSourceText";
 export * from "./newSources";
 export * from "./prettyPrint";
 export * from "./select";
 export { setSymbols } from "./symbols";
--- a/devtools/client/debugger/src/actions/sources/loadSourceText.js
+++ b/devtools/client/debugger/src/actions/sources/loadSourceText.js
@@ -10,19 +10,20 @@ import {
   getSourceFromId,
   getSourceWithContent,
   getSourceContent,
   getGeneratedSource,
   getSourcesEpoch,
   getBreakpointsForSource,
   getSourceActorsForSource
 } from "../../selectors";
-import { setBreakpointPositions, addBreakpoint } from "../breakpoints";
+import { addBreakpoint } from "../breakpoints";
 
 import { prettyPrintSource } from "./prettyPrint";
+import { setBreakableLines } from "./breakableLines";
 import { isFulfilled } from "../../utils/async-value";
 
 import * as parser from "../../workers/parser";
 import { isOriginal, isPretty } from "../../utils/source";
 import {
   memoizeableAction,
   type MemoizedAction
 } from "../../utils/memoizableAction";
@@ -111,18 +112,18 @@ async function loadSourceTextPromise(
 
   if (!newSource.isWasm && content) {
     parser.setSource(
       newSource.id,
       isFulfilled(content)
         ? content.value
         : { type: "text", value: "", contentType: undefined }
     );
-    dispatch(setBreakpointPositions({ cx, sourceId: newSource.id }));
 
+    await dispatch(setBreakableLines(cx, source.id));
     // Update the text in any breakpoints for this source by re-adding them.
     const breakpoints = getBreakpointsForSource(getState(), source.id);
     for (const { location, options, disabled } of breakpoints) {
       await dispatch(addBreakpoint(cx, location, options, disabled));
     }
   }
 
   return newSource;
--- a/devtools/client/debugger/src/actions/sources/moz.build
+++ b/devtools/client/debugger/src/actions/sources/moz.build
@@ -4,15 +4,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
 
 ]
 
 CompiledModules(
     'blackbox.js',
+    'breakableLines.js',
     'index.js',
     'loadSourceText.js',
     'newSources.js',
     'prettyPrint.js',
     'select.js',
     'symbols.js'
 )
--- a/devtools/client/debugger/src/actions/sources/newSources.js
+++ b/devtools/client/debugger/src/actions/sources/newSources.js
@@ -14,40 +14,41 @@ import { flatten } from "lodash";
 
 import {
   stringToSourceActorId,
   type SourceActor
 } from "../../reducers/source-actors";
 import { insertSourceActors } from "../../actions/source-actors";
 import { makeSourceId } from "../../client/firefox/create";
 import { toggleBlackBox } from "./blackbox";
-import { syncBreakpoint, setBreakpointPositions } from "../breakpoints";
+import { syncBreakpoint } from "../breakpoints";
 import { loadSourceText } from "./loadSourceText";
-import { isFetchingBreakpoints } from "../breakpoints/breakpointPositions";
 import { togglePrettyPrint } from "./prettyPrint";
-import { selectLocation } from "../sources";
+import { selectLocation, setBreakableLines } from "../sources";
 import {
   getRawSourceURL,
   isPrettyURL,
   isOriginal,
   isUrlExtension,
   isInlineScript
 } from "../../utils/source";
 import {
   getBlackBoxList,
   getSource,
   getSourceFromId,
   hasSourceActor,
   getPendingSelectedLocation,
   getPendingBreakpointsForSource,
-  getContext
+  getContext,
+  isSourceLoadingOrLoaded
 } from "../../selectors";
 
 import { prefs } from "../../utils/prefs";
 import sourceQueue from "../../utils/source-queue";
+import { ContextError } from "../../utils/context";
 
 import type {
   Source,
   SourceId,
   Context,
   OriginalSourceData,
   GeneratedSourceData,
   QueuedSourceData
@@ -308,17 +309,16 @@ export function newGeneratedSources(sour
       // We are sometimes notified about a new source multiple times if we
       // request a new source list and also get a source event from the server.
       if (!hasSourceActor(getState(), actorId)) {
         newSourceActors.push({
           id: actorId,
           actor: source.actor,
           thread,
           source: newId,
-
           isBlackBoxed: source.isBlackBoxed,
           sourceMapURL: source.sourceMapURL,
           url: source.url,
           introductionUrl: source.introductionUrl,
           introductionType: source.introductionType
         });
       }
 
@@ -326,35 +326,32 @@ export function newGeneratedSources(sour
     }
 
     const newSources: Array<Source> = (Object.values(newSourcesObj): Array<
       any
     >);
 
     const cx = getContext(getState());
     dispatch(addSources(cx, newSources));
-
-    const sourceIDsNeedingPositions = newSourceActors
-      .map(actor => actor.source)
-      .filter(sourceId => {
-        const source = getSource(getState(), sourceId);
-        return (
-          source && isInlineScript(source) && isFetchingBreakpoints(sourceId)
-        );
-      });
-
     dispatch(insertSourceActors(newSourceActors));
 
-    // Adding new sources may have cleared this file's breakpoint positions
-    // in cases where a new <script> loaded in the HTML, so we manually
-    // re-request new breakpoint positions.
-    for (const sourceId of sourceIDsNeedingPositions) {
-      dispatch(setBreakpointPositions({ cx, sourceId }));
+    for (const newSourceActor of newSourceActors) {
+      // Fetch breakable lines for new HTML scripts
+      // when the HTML file has started loading
+      if (
+        isInlineScript(newSourceActor) &&
+        isSourceLoadingOrLoaded(getState(), newSourceActor.source)
+      ) {
+        dispatch(setBreakableLines(cx, newSourceActor.source)).catch(error => {
+          if (!(error instanceof ContextError)) {
+            throw error;
+          }
+        });
+      }
     }
-
     await dispatch(checkNewSources(cx, newSources));
 
     return resultIds.map(id => getSourceFromId(getState(), id));
   };
 }
 
 function addSources(cx, sources: Array<Source>) {
   return ({ dispatch, getState }: ThunkArgs) => {
--- a/devtools/client/debugger/src/actions/sources/tests/blackbox.spec.js
+++ b/devtools/client/debugger/src/actions/sources/tests/blackbox.spec.js
@@ -8,17 +8,20 @@ import {
   actions,
   selectors,
   createStore,
   makeSource
 } from "../../../utils/test-head";
 
 describe("blackbox", () => {
   it("should blackbox a source", async () => {
-    const store = createStore({ blackBox: async () => true });
+    const store = createStore({
+      blackBox: async () => true,
+      getBreakableLines: async () => []
+    });
     const { dispatch, getState, cx } = store;
 
     const foo1Source = await dispatch(
       actions.newGeneratedSource(makeSource("foo1"))
     );
     await dispatch(actions.toggleBlackBox(cx, foo1Source));
 
     const fooSource = selectors.getSource(getState(), "foo1");
--- a/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js
+++ b/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js
@@ -56,17 +56,18 @@ describe("loadSourceText", () => {
   it("should update breakpoint text when a source loads", async () => {
     const fooOrigContent = createSource("fooOrig", "var fooOrig = 42;");
     const fooGenContent = createSource("fooGen", "var fooGen = 42;");
 
     const store = createStore(
       {
         ...sourceThreadClient,
         sourceContents: async () => fooGenContent,
-        getBreakpointPositions: async () => ({ "1": [0] })
+        getBreakpointPositions: async () => ({ "1": [0] }),
+        getBreakableLines: async () => []
       },
       {},
       {
         getGeneratedRangesForOriginal: async () => [
           { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }
         ],
         getOriginalLocations: async items =>
           items.map(item => ({
@@ -150,17 +151,18 @@ describe("loadSourceText", () => {
     let resolve;
     let count = 0;
     const { dispatch, getState, cx } = createStore({
       sourceContents: () =>
         new Promise(r => {
           count++;
           resolve = r;
         }),
-      getBreakpointPositions: async () => ({})
+      getBreakpointPositions: async () => ({}),
+      getBreakableLines: async () => []
     });
     const id = "foo";
 
     await dispatch(actions.newGeneratedSource(makeSource(id)));
 
     let source = selectors.getSourceFromId(getState(), id);
     dispatch(actions.loadSourceText({ cx, source }));
 
@@ -187,17 +189,18 @@ describe("loadSourceText", () => {
     let resolve;
     let count = 0;
     const { dispatch, getState, cx } = createStore({
       sourceContents: () =>
         new Promise(r => {
           count++;
           resolve = r;
         }),
-      getBreakpointPositions: async () => ({})
+      getBreakpointPositions: async () => ({}),
+      getBreakableLines: async () => []
     });
     const id = "foo";
 
     await dispatch(actions.newGeneratedSource(makeSource(id)));
     let source = selectors.getSourceFromId(getState(), id);
     const loading = dispatch(actions.loadSourceText({ cx, source }));
 
     if (!resolve) {
--- a/devtools/client/debugger/src/actions/tests/ast.spec.js
+++ b/devtools/client/debugger/src/actions/tests/ast.spec.js
@@ -30,17 +30,18 @@ const threadClient = {
   sourceContents: async ({ source }) => ({
     source: sourceTexts[source],
     contentType: "text/javascript"
   }),
   getFrameScopes: async () => {},
   evaluate: async expression => ({ result: evaluationResult[expression] }),
   evaluateExpressions: async expressions =>
     expressions.map(expression => ({ result: evaluationResult[expression] })),
-  getBreakpointPositions: async () => ({})
+  getBreakpointPositions: async () => ({}),
+  getBreakableLines: async () => []
 };
 
 const sourceMaps = {
   getOriginalSourceText: async ({ id }) => ({
     id,
     text: sourceTexts[id],
     contentType: "text/javascript"
   }),
--- a/devtools/client/debugger/src/actions/tests/expressions.spec.js
+++ b/devtools/client/debugger/src/actions/tests/expressions.spec.js
@@ -32,17 +32,18 @@ const mockThreadClient = {
             } else {
               resolve("boo");
             }
           })
       )
     ),
   getFrameScopes: async () => {},
   sourceContents: () => ({ source: "", contentType: "text/javascript" }),
-  getBreakpointPositions: async () => [],
+  getBreakpointPositions: async () => ({}),
+  getBreakableLines: async () => [],
   autocomplete: () => {
     return new Promise(resolve => {
       resolve({
         from: "foo",
         matches: ["toLocaleString", "toSource", "toString", "toolbar", "top"],
         matchProp: "to"
       });
     });
--- a/devtools/client/debugger/src/actions/tests/helpers/threadClient.js
+++ b/devtools/client/debugger/src/actions/tests/helpers/threadClient.js
@@ -73,10 +73,11 @@ export const sourceThreadClient = {
 
       reject(`unknown source: ${source}`);
     });
   },
   setBreakpoint: async () => {},
   threadClient: async () => {},
   getFrameScopes: async () => {},
   evaluateExpressions: async () => {},
-  getBreakpointPositions: async () => ({})
+  getBreakpointPositions: async () => ({}),
+  getBreakableLines: async () => []
 };
--- a/devtools/client/debugger/src/actions/tests/navigation.spec.js
+++ b/devtools/client/debugger/src/actions/tests/navigation.spec.js
@@ -23,16 +23,17 @@ const {
 } = selectors;
 
 const threadClient = {
   sourceContents: async () => ({
     source: "function foo1() {\n  const foo = 5; return foo;\n}",
     contentType: "text/javascript"
   }),
   getBreakpointPositions: async () => ({}),
+  getBreakableLines: async () => [],
   detachWorkers: () => {}
 };
 
 describe("navigation", () => {
   it("connect sets the debuggeeUrl", async () => {
     const { dispatch, getState } = createStore({
       fetchWorkers: () => Promise.resolve([]),
       getMainThread: () => "FakeThread"
--- a/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js
+++ b/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js
@@ -44,17 +44,18 @@ import {
 
 import sourceMaps from "devtools-source-map";
 
 import { makePendingLocationId } from "../../utils/breakpoint";
 function mockClient(bpPos = {}) {
   return {
     ...simpleMockThreadClient,
 
-    getBreakpointPositions: async () => bpPos
+    getBreakpointPositions: async () => bpPos,
+    getBreakableLines: async () => []
   };
 }
 
 function mockSourceMaps() {
   return {
     ...sourceMaps,
     getOriginalSourceText: async source => ({
       id: source.id,
--- a/devtools/client/debugger/src/actions/tests/project-text-search.spec.js
+++ b/devtools/client/debugger/src/actions/tests/project-text-search.spec.js
@@ -34,17 +34,18 @@ const sources = {
   "bar:formatted": {
     source: "function bla(x, y) {\n const bar = 4; return 2;\n}",
     contentType: "text/javascript"
   }
 };
 
 const threadClient = {
   sourceContents: async ({ source }) => sources[source],
-  getBreakpointPositions: async () => ({})
+  getBreakpointPositions: async () => ({}),
+  getBreakableLines: async () => []
 };
 
 describe("project text search", () => {
   it("should add a project text search query", () => {
     const { dispatch, getState, cx } = createStore();
     const mockQuery = "foo";
 
     dispatch(actions.addSearchQuery(cx, mockQuery));
--- a/devtools/client/debugger/src/actions/tests/setProjectDirectoryRoot.spec.js
+++ b/devtools/client/debugger/src/actions/tests/setProjectDirectoryRoot.spec.js
@@ -39,17 +39,19 @@ describe("setProjectDirectoryRoot", () =
     const { dispatch, getState, cx } = createStore();
     dispatch(actions.setProjectDirectoryRoot(cx, "/example.com/foo"));
     dispatch(actions.clearProjectDirectoryRoot(cx));
     dispatch(actions.setProjectDirectoryRoot(cx, "/example.com/bar"));
     expect(getProjectDirectoryRoot(getState())).toBe("/example.com/bar");
   });
 
   it("should filter sources", async () => {
-    const store = createStore({});
+    const store = createStore({
+      getBreakableLines: async () => []
+    });
     const { dispatch, getState, cx } = store;
     await dispatch(actions.newGeneratedSource(makeSource("js/scopes.js")));
     await dispatch(actions.newGeneratedSource(makeSource("lib/vendor.js")));
 
     dispatch(actions.setProjectDirectoryRoot(cx, "localhost:8000/examples/js"));
 
     const filteredSourcesByThread = getDisplayedSources(getState());
     const filteredSources = Object.values(filteredSourcesByThread)[0];
@@ -58,21 +60,25 @@ describe("setProjectDirectoryRoot", () =
     expect(firstSource.url).toEqual(
       "http://localhost:8000/examples/js/scopes.js"
     );
 
     expect(firstSource.relativeUrl).toEqual("scopes.js");
   });
 
   it("should update the child directory ", () => {
-    const { dispatch, getState, cx } = createStore();
+    const { dispatch, getState, cx } = createStore({
+      getBreakableLines: async () => []
+    });
     dispatch(actions.setProjectDirectoryRoot(cx, "example.com"));
     dispatch(actions.setProjectDirectoryRoot(cx, "example.com/foo/bar"));
     expect(getProjectDirectoryRoot(getState())).toBe("example.com/foo/bar");
   });
 
   it("should update the child directory when domain name is Webpack://", () => {
-    const { dispatch, getState, cx } = createStore();
+    const { dispatch, getState, cx } = createStore({
+      getBreakableLines: async () => []
+    });
     dispatch(actions.setProjectDirectoryRoot(cx, "webpack://"));
     dispatch(actions.setProjectDirectoryRoot(cx, "webpack:///app"));
     expect(getProjectDirectoryRoot(getState())).toBe("webpack:///app");
   });
 });
--- a/devtools/client/debugger/src/actions/tests/ui.spec.js
+++ b/devtools/client/debugger/src/actions/tests/ui.spec.js
@@ -1,32 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
-import {
-  createStore,
-  selectors,
-  actions,
-  makeSource
-} from "../../utils/test-head";
+import { createStore, selectors, actions } from "../../utils/test-head";
 
 const {
   getActiveSearch,
   getFrameworkGroupingState,
   getPaneCollapse,
-  getHighlightedLineRange,
-  getProjectDirectoryRoot,
-  getDisplayedSources
+  getHighlightedLineRange
 } = selectors;
 
-import type { Source } from "../../types";
-
 describe("ui", () => {
   it("should toggle the visible state of project search", () => {
     const { dispatch, getState } = createStore();
     expect(getActiveSearch(getState())).toBe(null);
     dispatch(actions.setActiveSearch("project"));
     expect(getActiveSearch(getState())).toBe("project");
   });
 
@@ -77,70 +68,8 @@ describe("ui", () => {
   it("should clear highlight lines", () => {
     const { dispatch, getState } = createStore();
     const range = { start: 3, end: 5, sourceId: "2" };
     dispatch(actions.highlightLineRange(range));
     dispatch(actions.clearHighlightLineRange());
     expect(getHighlightedLineRange(getState())).toEqual({});
   });
 });
-
-describe("setProjectDirectoryRoot", () => {
-  it("should set domain directory as root", () => {
-    const { dispatch, getState, cx } = createStore();
-    dispatch(actions.setProjectDirectoryRoot(cx, "example.com"));
-    expect(getProjectDirectoryRoot(getState())).toBe("example.com");
-  });
-
-  it("should set a directory as root directory", () => {
-    const { dispatch, getState, cx } = createStore();
-    dispatch(actions.setProjectDirectoryRoot(cx, "/example.com/foo"));
-    expect(getProjectDirectoryRoot(getState())).toBe("/example.com/foo");
-  });
-
-  it("should add to the directory ", () => {
-    const { dispatch, getState, cx } = createStore();
-    dispatch(actions.setProjectDirectoryRoot(cx, "/example.com/foo"));
-    dispatch(actions.setProjectDirectoryRoot(cx, "/foo/bar"));
-    expect(getProjectDirectoryRoot(getState())).toBe("/example.com/foo/bar");
-  });
-
-  it("should update the directory ", () => {
-    const { dispatch, getState, cx } = createStore();
-    dispatch(actions.setProjectDirectoryRoot(cx, "/example.com/foo"));
-    dispatch(actions.clearProjectDirectoryRoot(cx));
-    dispatch(actions.setProjectDirectoryRoot(cx, "/example.com/bar"));
-    expect(getProjectDirectoryRoot(getState())).toBe("/example.com/bar");
-  });
-
-  it("should filter sources", async () => {
-    const store = createStore({});
-    const { dispatch, getState, cx } = store;
-    await dispatch(actions.newGeneratedSource(makeSource("js/scopes.js")));
-    await dispatch(actions.newGeneratedSource(makeSource("lib/vendor.js")));
-
-    dispatch(actions.setProjectDirectoryRoot(cx, "localhost:8000/examples/js"));
-
-    const filteredSourcesByThread = getDisplayedSources(getState());
-    const filteredSources = Object.values(filteredSourcesByThread)[0];
-    const firstSource: Source = (Object.values(filteredSources)[0]: any);
-
-    expect(firstSource.url).toEqual(
-      "http://localhost:8000/examples/js/scopes.js"
-    );
-
-    expect(firstSource.relativeUrl).toEqual("scopes.js");
-  });
-
-  it("should update the child directory ", () => {
-    const { dispatch, getState, cx } = createStore();
-    dispatch(actions.setProjectDirectoryRoot(cx, "example.com"));
-    dispatch(actions.setProjectDirectoryRoot(cx, "example.com/foo/bar"));
-    expect(getProjectDirectoryRoot(getState())).toBe("example.com/foo/bar");
-  });
-
-  it("should update the child directory when domain name is Webpack://", () => {
-    const { dispatch, getState, cx } = createStore();
-    dispatch(actions.setProjectDirectoryRoot(cx, "webpack://"));
-    dispatch(actions.setProjectDirectoryRoot(cx, "webpack:///app"));
-    expect(getProjectDirectoryRoot(getState())).toBe("webpack:///app");
-  });
-});
--- a/devtools/client/debugger/src/actions/types/SourceAction.js
+++ b/devtools/client/debugger/src/actions/types/SourceAction.js
@@ -69,9 +69,15 @@ export type SourceAction =
       +type: "CLOSE_TAB",
       +url: string,
       +tabs: any
     |}
   | {|
       +type: "CLOSE_TABS",
       +sources: Array<Source>,
       +tabs: any
+    |}
+  | {|
+      type: "SET_BREAKABLE_LINES",
+      +cx: Context,
+      breakableLines: number[],
+      sourceId: string
     |};
--- a/devtools/client/debugger/src/actions/types/index.js
+++ b/devtools/client/debugger/src/actions/types/index.js
@@ -29,16 +29,17 @@ import type { Panel } from "../../client
  * Argument parameters via Thunk middleware for {@link https://github.com/gaearon/redux-thunk|Redux Thunk}
  *
  * @memberof actions/breakpoints
  * @static
  * @typedef {Object} ThunkArgs
  */
 export type ThunkArgs = {
   dispatch: (action: any) => Promise<any>,
+  forkedDispatch: (action: any) => Promise<any>,
   getState: () => State,
   client: typeof clientCommands,
   sourceMaps: SourceMaps,
   panel: Panel
 };
 
 export type Thunk = ThunkArgs => any;
 
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -120,61 +120,45 @@ function forEachWorkerThread(iteratee) {
   // Currently, worker threads are not guaranteed to respond to all requests,
   // if we send a request while they are shutting down. See bug 1529163.
   if (shouldWaitForWorkers) {
     return Promise.all(promises);
   }
 }
 
 function resume(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).resume(resolve);
-  });
+  return lookupThreadClient(thread).resume();
 }
 
 function stepIn(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).stepIn(resolve);
-  });
+  return lookupThreadClient(thread).stepIn();
 }
 
 function stepOver(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).stepOver(resolve);
-  });
+  return lookupThreadClient(thread).stepOver();
 }
 
 function stepOut(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).stepOut(resolve);
-  });
+  return lookupThreadClient(thread).stepOut();
 }
 
 function rewind(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).rewind(resolve);
-  });
+  return lookupThreadClient(thread).rewind();
 }
 
 function reverseStepIn(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).reverseStepIn(resolve);
-  });
+  return lookupThreadClient(thread).reverseStepIn();
 }
 
 function reverseStepOver(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).reverseStepOver(resolve);
-  });
+  return lookupThreadClient(thread).reverseStepOver();
 }
 
 function reverseStepOut(thread: string): Promise<*> {
-  return new Promise(resolve => {
-    lookupThreadClient(thread).reverseStepOut(resolve);
-  });
+  return lookupThreadClient(thread).reverseStepOut();
 }
 
 function breakOnNext(thread: string): Promise<*> {
   return lookupThreadClient(thread).breakOnNext();
 }
 
 async function sourceContents({
   actor,
@@ -460,33 +444,58 @@ async function getBreakpointPositions(
   actors: Array<SourceActor>,
   range: ?Range
 ): Promise<{ [string]: number[] }> {
   const sourcePositions = {};
 
   for (const { thread, actor } of actors) {
     const sourceThreadClient = lookupThreadClient(thread);
     const sourceFront = sourceThreadClient.source({ actor });
-    const positions = await sourceFront.getBreakpointPositionsCompressed(
-      range
-    );
+    const positions = await sourceFront.getBreakpointPositionsCompressed(range);
 
     for (const line of Object.keys(positions)) {
       let columns = positions[line];
       const existing = sourcePositions[line];
       if (existing) {
         columns = [...new Set([...existing, ...columns])];
       }
 
       sourcePositions[line] = columns;
     }
   }
   return sourcePositions;
 }
 
+async function getBreakableLines(actors: Array<SourceActor>) {
+  let lines = [];
+  for (const { thread, actor } of actors) {
+    const sourceThreadClient = lookupThreadClient(thread);
+    const sourceFront = sourceThreadClient.source({ actor });
+    let actorLines = [];
+    try {
+      actorLines = await sourceFront.getBreakableLines();
+    } catch (e) {
+      // Handle backward compatibility
+      if (
+        e.message &&
+        e.message.match(/does not recognize the packet type getBreakableLines/)
+      ) {
+        const pos = await sourceFront.getBreakpointPositionsCompressed();
+        actorLines = Object.keys(pos).map(line => Number(line));
+      } else if (!e.message || !e.message.match(/Connection closed/)) {
+        throw e;
+      }
+    }
+
+    lines = [...new Set([...lines, ...actorLines])];
+  }
+
+  return lines;
+}
+
 const clientCommands = {
   autocomplete,
   blackBox,
   createObjectClient,
   loadObjectProperties,
   releaseActor,
   interrupt,
   pauseGrip,
@@ -497,16 +506,17 @@ const clientCommands = {
   rewind,
   reverseStepIn,
   reverseStepOut,
   reverseStepOver,
   breakOnNext,
   sourceContents,
   getSourceForActor,
   getBreakpointPositions,
+  getBreakableLines,
   hasBreakpoint,
   setBreakpoint,
   setXHRBreakpoint,
   removeXHRBreakpoint,
   removeBreakpoint,
   evaluate,
   evaluateInFrame,
   evaluateExpressions,
--- a/devtools/client/debugger/src/client/firefox/types.js
+++ b/devtools/client/debugger/src/client/firefox/types.js
@@ -301,17 +301,18 @@ export type FunctionGrip = {|
 export type SourceClient = {
   source: () => { source: any, contentType?: string },
   _activeThread: ThreadClient,
   actor: string,
   getBreakpointPositionsCompressed: (range: ?Range) => Promise<any>,
   prettyPrint: number => Promise<*>,
   disablePrettyPrint: () => Promise<*>,
   blackBox: (range?: Range) => Promise<*>,
-  unblackBox: (range?: Range) => Promise<*>
+  unblackBox: (range?: Range) => Promise<*>,
+  getBreakableLines: () => Promise<number[]>
 };
 
 /**
  * ObjectClient
  * @memberof firefox
  * @static
  */
 export type ObjectClient = {
--- a/devtools/client/debugger/src/reducers/ast.js
+++ b/devtools/client/debugger/src/reducers/ast.js
@@ -39,26 +39,24 @@ export type PreviewValue = {|
   location: AstLocation,
   cursorPos: any,
   tokenPos: AstLocation,
   updating: false
 |};
 
 export type ASTState = {
   +symbols: SymbolsMap,
-  +emptyLines: EmptyLinesMap,
   +outOfScopeLocations: ?Array<AstLocation>,
   +inScopeLines: ?Array<number>,
   +preview: Preview
 };
 
 export function initialASTState(): ASTState {
   return {
     symbols: {},
-    emptyLines: {},
     outOfScopeLocations: null,
     inScopeLines: null,
     preview: null
   };
 }
 
 function update(state: ASTState = initialASTState(), action: Action): ASTState {
   switch (action.type) {
--- a/devtools/client/debugger/src/reducers/breakpoints.js
+++ b/devtools/client/debugger/src/reducers/breakpoints.js
@@ -8,17 +8,16 @@
  * Breakpoints reducer
  * @module reducers/breakpoints
  */
 
 import { isGeneratedId } from "devtools-source-map";
 import { isEqual } from "lodash";
 
 import { makeBreakpointId } from "../utils/breakpoint";
-import "../utils/breakable-lines";
 
 // eslint-disable-next-line max-len
 import { getBreakpointsList as getBreakpointsListSelector } from "../selectors/breakpoints";
 
 import type {
   XHRBreakpoint,
   Breakpoint,
   BreakpointId,
--- a/devtools/client/debugger/src/reducers/sources.js
+++ b/devtools/client/debugger/src/reducers/sources.js
@@ -29,17 +29,16 @@ import {
   makeReduceAllQuery,
   makeMapWithArgs,
   type Resource,
   type ResourceState,
   type ReduceQuery,
   type ReduceAllQuery
 } from "../utils/resource";
 
-import { findBreakableLines } from "../utils/breakable-lines";
 import { findPosition } from "../utils/breakpoint/breakpointPositions";
 import * as asyncValue from "../utils/async-value";
 import type { AsyncValue, SettledValue } from "../utils/async-value";
 import { originalToGeneratedId } from "devtools-source-map";
 import { prefs } from "../utils/prefs";
 
 import {
   hasSourceActor,
@@ -61,17 +60,19 @@ import type {
   BreakpointPositions
 } from "../types";
 import type { PendingSelectedLocation, Selector } from "./types";
 import type { Action, DonePromiseAction, FocusItem } from "../actions/types";
 import type { LoadSourceAction } from "../actions/types/SourceAction";
 import { uniq } from "lodash";
 
 export type SourcesMap = { [SourceId]: Source };
-type SourcesContentMap = { [SourceId]: SettledValue<SourceContent> | null };
+type SourcesContentMap = {
+  [SourceId]: AsyncValue<SourceContent> | null
+};
 export type BreakpointPositionsMap = { [SourceId]: BreakpointPositions };
 export type SourcesMapByThread = { [ThreadId]: SourcesMap };
 type SourceActorMap = { [SourceId]: Array<SourceActorId> };
 
 type UrlsMap = { [string]: SourceId[] };
 type PlainUrlsMap = { [string]: string[] };
 
 type SourceResource = Resource<{
@@ -196,29 +197,36 @@ function update(
         updateBlackBoxList(url, isBlackBoxed);
         return updateBlackboxFlag(state, id, isBlackBoxed);
       }
       break;
 
     case "SET_PROJECT_DIRECTORY_ROOT":
       return updateProjectDirectoryRoot(state, action.url);
 
+    case "SET_BREAKABLE_LINES": {
+      const { breakableLines, sourceId } = action;
+      return {
+        ...state,
+        breakableLines: {
+          ...state.breakableLines,
+          [sourceId]: breakableLines
+        }
+      };
+    }
+
     case "ADD_BREAKPOINT_POSITIONS": {
       const { source, positions } = action;
-      const breakableLines = findBreakableLines(source, positions);
+      const breakpointPositions = state.breakpointPositions[source.id];
 
       return {
         ...state,
         breakpointPositions: {
           ...state.breakpointPositions,
-          [source.id]: positions
-        },
-        breakableLines: {
-          ...state.breakableLines,
-          [source.id]: breakableLines
+          [source.id]: { ...breakpointPositions, ...positions }
         }
       };
     }
     case "NAVIGATE":
       return {
         ...initialSourcesState(),
         epoch: state.epoch + 1
       };
@@ -380,17 +388,17 @@ function updateLoadedState(
   // If there was a navigation between the time the action was started and
   // completed, we don't want to update the store.
   if (action.epoch !== state.epoch || !(sourceId in state.content)) {
     return state;
   }
 
   let content;
   if (action.status === "start") {
-    content = null;
+    content = asyncValue.pending();
   } else if (action.status === "error") {
     content = asyncValue.rejected(action.error);
   } else if (typeof action.value.text === "string") {
     content = asyncValue.fulfilled({
       type: "text",
       value: action.value.text,
       contentType: action.value.contentType
     });
@@ -736,34 +744,42 @@ export function getSourceWithContent(
     state.sources.sources,
     state.sources.content,
     id
   );
 }
 export function getSourceContent(
   state: OuterState,
   id: SourceId
-): AsyncValue<SourceContent> | null {
+): SettledValue<SourceContent> | null {
   // Assert the resource exists.
   getResource(state.sources.sources, id);
+  const content = state.sources.content[id];
 
-  return state.sources.content[id] || null;
+  if (!content || content.state === "pending") {
+    return null;
+  }
+
+  return content;
 }
 
 const contentLookup: WeakMap<Source, SourceWithContent> = new WeakMap();
 function getSourceWithContentInner(
   sources: SourceResourceState,
   content: SourcesContentMap,
   id: SourceId
 ): SourceWithContent {
   const source = getResource(sources, id);
-  const contentValue = content[source.id];
+  let contentValue = content[source.id];
 
   let result = contentLookup.get(source);
   if (!result || result.content !== contentValue) {
+    if (contentValue && contentValue.state === "pending") {
+      contentValue = null;
+    }
     result = {
       source,
       content: contentValue
     };
     contentLookup.set(source, result);
   }
 
   return result;
@@ -897,16 +913,25 @@ export function getBreakpointPositionsFo
 
 export function hasBreakpointPositions(
   state: OuterState,
   sourceId: string
 ): boolean {
   return !!getBreakpointPositionsForSource(state, sourceId);
 }
 
+export function hasBreakpointPositionsForLine(
+  state: OuterState,
+  sourceId: string,
+  line: number
+): boolean {
+  const positions = getBreakpointPositionsForSource(state, sourceId);
+  return !!(positions && positions[line]);
+}
+
 export function getBreakpointPositionsForLocation(
   state: OuterState,
   location: SourceLocation
 ): ?MappedLocation {
   const { sourceId } = location;
   const positions = getBreakpointPositionsForSource(state, sourceId);
   return findPosition(positions, location);
 }
@@ -922,9 +947,14 @@ export function getBreakableLines(state:
 export const getSelectedBreakableLines: Selector<Set<number>> = createSelector(
   state => {
     const sourceId = getSelectedSourceId(state);
     return sourceId && state.sources.breakableLines[sourceId];
   },
   breakableLines => new Set(breakableLines || [])
 );
 
+export function isSourceLoadingOrLoaded(state: OuterState, sourceId: string) {
+  const content = state.sources.content[sourceId];
+  return content !== null;
+}
+
 export default update;
--- a/devtools/client/debugger/src/selectors/test/visibleColumnBreakpoints.spec.js
+++ b/devtools/client/debugger/src/selectors/test/visibleColumnBreakpoints.spec.js
@@ -32,40 +32,40 @@ function bp(line, column) {
 }
 
 describe("visible column breakpoints", () => {
   it("simple", () => {
     const viewport = {
       start: { line: 1, column: 0 },
       end: { line: 10, column: 10 }
     };
-    const pausePoints = [pp(1, 1), pp(1, 5), pp(3, 1)];
+    const pausePoints = { [1]: [pp(1, 1), pp(1, 5)], [3]: [pp(3, 1)] };
     const breakpoints = [bp(1, 1), bp(4, 0), bp(4, 3)];
 
     const columnBps = getColumnBreakpoints(pausePoints, breakpoints, viewport);
     expect(columnBps).toMatchSnapshot();
   });
 
   it("ignores single breakpoints", () => {
     const viewport = {
       start: { line: 1, column: 0 },
       end: { line: 10, column: 10 }
     };
-    const pausePoints = [pp(1, 1), pp(1, 3), pp(2, 1)];
+    const pausePoints = { [1]: [pp(1, 1), pp(1, 3)], [2]: [pp(2, 1)] };
     const breakpoints = [bp(1, 1)];
     const columnBps = getColumnBreakpoints(pausePoints, breakpoints, viewport);
     expect(columnBps).toMatchSnapshot();
   });
 
   it("only shows visible breakpoints", () => {
     const viewport = {
       start: { line: 1, column: 0 },
       end: { line: 10, column: 10 }
     };
-    const pausePoints = [pp(1, 1), pp(1, 3), pp(20, 1)];
+    const pausePoints = { [1]: [pp(1, 1), pp(1, 3)], [20]: [pp(20, 1)] };
     const breakpoints = [bp(1, 1)];
 
     const columnBps = getColumnBreakpoints(pausePoints, breakpoints, viewport);
     expect(columnBps).toMatchSnapshot();
   });
 });
 
 describe("getFirstBreakpointPosition", () => {
--- a/devtools/client/debugger/src/selectors/visibleColumnBreakpoints.js
+++ b/devtools/client/debugger/src/selectors/visibleColumnBreakpoints.js
@@ -103,51 +103,61 @@ function filterVisible(positions, select
 function filterByBreakpoints(positions, selectedSource, breakpointMap) {
   return positions.filter(position => {
     const location = getSelectedLocation(position, selectedSource);
     return breakpointMap[location.line];
   });
 }
 
 function formatPositions(
-  positions: BreakpointPositions,
+  positions: BreakpointPosition[],
   selectedSource,
   breakpointMap
 ) {
   return (positions: any).map((position: BreakpointPosition) => {
     const location = getSelectedLocation(position, selectedSource);
     return {
       location,
       breakpoint: findBreakpoint(location, breakpointMap)
     };
   });
 }
 
+function convertToList(
+  breakpointPositions: BreakpointPositions
+): BreakpointPosition[] {
+  return ([].concat(...Object.values(breakpointPositions)): any);
+}
+
 export function getColumnBreakpoints(
   positions: ?BreakpointPositions,
   breakpoints: ?(Breakpoint[]),
   viewport: Range,
   selectedSource: ?Source
 ) {
   if (!positions) {
     return [];
   }
 
   // We only want to show a column breakpoint if several conditions are matched
   // - it is the first breakpoint to appear at an the original location
   // - the position is in the current viewport
   // - there is atleast one other breakpoint on that line
   // - there is a breakpoint on that line
   const breakpointMap = groupBreakpoints(breakpoints, selectedSource);
+  let newPositions = convertToList(positions);
+  newPositions = filterByLineCount(newPositions, selectedSource);
+  newPositions = filterVisible(newPositions, selectedSource, viewport);
+  newPositions = filterByBreakpoints(
+    newPositions,
+    selectedSource,
+    breakpointMap
+  );
 
-  positions = filterByLineCount(positions, selectedSource);
-  positions = filterVisible(positions, selectedSource, viewport);
-  positions = filterByBreakpoints(positions, selectedSource, breakpointMap);
-
-  return formatPositions(positions, selectedSource, breakpointMap);
+  return formatPositions(newPositions, selectedSource, breakpointMap);
 }
 
 const getVisibleBreakpointPositions = createSelector(
   getSelectedSource,
   getBreakpointPositions,
   (source, positions) => source && positions[source.id]
 );
 
@@ -167,12 +177,12 @@ export function getFirstBreakpointPositi
 ): ?BreakpointPosition {
   const positions = getBreakpointPositionsForSource(state, sourceId);
   const source = getSource(state, sourceId);
 
   if (!source || !positions) {
     return;
   }
 
-  return sortSelectedLocations(positions, source).find(
+  return sortSelectedLocations(convertToList(positions), source).find(
     position => getSelectedLocation(position, source).line == line
   );
 }
--- a/devtools/client/debugger/src/test/tests-setup.js
+++ b/devtools/client/debugger/src/test/tests-setup.js
@@ -111,17 +111,17 @@ function mockIndexeddDB() {
       store[key] = value;
     }
   };
 }
 
 // NOTE: We polyfill finally because TRY uses node 8
 if (!global.Promise.prototype.finally) {
   global.Promise.prototype.finally = function finallyPolyfill(callback) {
-    var constructor = this.constructor;
+    const constructor = this.constructor;
 
     return this.then(
       function(value) {
         return constructor.resolve(callback()).then(function() {
           return value;
         });
       },
       function(reason) {
--- a/devtools/client/debugger/src/types.js
+++ b/devtools/client/debugger/src/types.js
@@ -464,11 +464,11 @@ export type Cancellable = {
   cancel: () => void
 };
 
 export type EventListenerBreakpoints = string[];
 
 export type SourceDocuments = { [string]: Object };
 
 export type BreakpointPosition = MappedLocation;
-export type BreakpointPositions = BreakpointPosition[];
+export type BreakpointPositions = { [number]: BreakpointPosition[] };
 
 export type { Context, ThreadContext } from "./utils/context";
deleted file mode 100644
--- a/devtools/client/debugger/src/utils/breakable-lines.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-// @flow
-
-import { getSelectedLocation } from "./selected-location";
-import type { BreakpointPositions, Source } from "../types";
-
-export function findBreakableLines(
-  source: Source,
-  breakpointPositions: BreakpointPositions
-): number[] {
-  if (!breakpointPositions || source.isWasm) {
-    return [];
-  }
-
-  return Array.from(
-    new Set(
-      breakpointPositions.map(point => getSelectedLocation(point, source).line)
-    )
-  );
-}
--- a/devtools/client/debugger/src/utils/breakpoint/breakpointPositions.js
+++ b/devtools/client/debugger/src/utils/breakpoint/breakpointPositions.js
@@ -11,12 +11,16 @@ import type { BreakpointPositions, Sourc
 export function findPosition(
   positions: ?BreakpointPositions,
   location: SourceLocation
 ) {
   if (!positions) {
     return null;
   }
 
-  return positions.find(pos =>
+  const lineBps = positions[location.line];
+  if (!lineBps) {
+    return null;
+  }
+  return lineBps.find(pos =>
     comparePosition(getSelectedLocation(pos, location), location)
   );
 }
--- a/devtools/client/debugger/src/utils/context.js
+++ b/devtools/client/debugger/src/utils/context.js
@@ -47,33 +47,35 @@ export type ThreadContext = {|
 
   // Whether the current thread is paused. This is determined from the other
   // Context properties and is here for convenient access.
   +isPaused: boolean
 |};
 
 export type Context = NavigateContext | ThreadContext;
 
+export class ContextError extends Error {}
+
 export function validateNavigateContext(state: State, cx: Context) {
   const newcx = getThreadContext(state);
 
   if (newcx.navigateCounter != cx.navigateCounter) {
-    throw new Error("Page has navigated");
+    throw new ContextError("Page has navigated");
   }
 }
 
 function validateThreadContext(state: State, cx: ThreadContext) {
   const newcx = getThreadContext(state);
 
   if (cx.thread != newcx.thread) {
-    throw new Error("Current thread has changed");
+    throw new ContextError("Current thread has changed");
   }
 
   if (cx.pauseCounter != newcx.pauseCounter) {
-    throw new Error("Current thread has paused or resumed");
+    throw new ContextError("Current thread has paused or resumed");
   }
 }
 
 export function validateContext(state: State, cx: Context) {
   validateNavigateContext(state, cx);
 
   if ("thread" in cx) {
     validateThreadContext(state, (cx: any));
--- a/devtools/client/debugger/src/utils/moz.build
+++ b/devtools/client/debugger/src/utils/moz.build
@@ -12,17 +12,16 @@ DIRS += [
 ]
 
 CompiledModules(
     'assert.js',
     'ast.js',
     'async-value.js',
     'asyncStoreHelper.js',
     'bootstrap.js',
-    'breakable-lines.js',
     'build-query.js',
     'clipboard.js',
     'connect.js',
     'context.js',
     'dbg.js',
     'defer.js',
     'DevToolsUtils.js',
     'expressions.js',
--- a/devtools/client/debugger/src/utils/source.js
+++ b/devtools/client/debugger/src/utils/source.js
@@ -16,17 +16,23 @@ import { endTruncateStr } from "./utils"
 import { truncateMiddleText } from "../utils/text";
 import { parse as parseURL } from "../utils/url";
 import { renderWasmText } from "./wasm";
 import { toEditorPosition } from "./editor";
 export { isMinified } from "./isMinified";
 import { getURL, getFileExtension } from "./sources-tree";
 import { prefs, features } from "./prefs";
 
-import type { SourceId, Source, SourceContent, SourceLocation } from "../types";
+import type {
+  SourceId,
+  Source,
+  SourceActor,
+  SourceContent,
+  SourceLocation
+} from "../types";
 import { isFulfilled, type AsyncValue } from "./async-value";
 import type { Symbols } from "../reducers/types";
 
 type transformUrlCallback = string => string;
 
 export const sourceTypes = {
   coffee: "coffeescript",
   js: "javascript",
@@ -392,17 +398,17 @@ export function getMode(
 
   if (isHTMLLike) {
     return { name: "htmlmixed" };
   }
 
   return { name: "text" };
 }
 
-export function isInlineScript(source: Source): boolean {
+export function isInlineScript(source: SourceActor): boolean {
   return source.introductionType === "scriptElement";
 }
 
 export function getTextAtPosition(
   sourceId: SourceId,
   asyncContent: AsyncValue<SourceContent> | null,
   location: SourceLocation
 ) {
deleted file mode 100644
--- a/devtools/client/debugger/src/utils/tests/breakable-lines.spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-// @flow
-
-import { findBreakableLines } from "../breakable-lines";
-import { createSourceObject } from "../test-head";
-
-function ml(gLine) {
-  const generatedLocation = { line: gLine, column: 0, sourceId: "foo" };
-  return { generatedLocation, location: generatedLocation };
-}
-
-describe("breakableLines", () => {
-  it("no positions", () => {
-    const source = createSourceObject("foo");
-    const lines = findBreakableLines(source, []);
-    expect(lines).toEqual([]);
-  });
-
-  it("one position", () => {
-    const source = createSourceObject("foo");
-    const lines = findBreakableLines(source, [ml(1)]);
-    expect(lines).toEqual([1]);
-  });
-
-  it("outside positions are not included", () => {
-    const source = createSourceObject("foo");
-    const lines = findBreakableLines(source, [ml(1), ml(2), ml(10)]);
-    expect(lines).toEqual([1, 2, 10]);
-  });
-});
--- a/devtools/client/debugger/test/mochitest/browser_dbg-chrome-debugging.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-chrome-debugging.js
@@ -28,20 +28,19 @@ function initDebuggerClient() {
 function onNewSource(event, packet) {
   if (packet.source.url.startsWith("chrome:")) {
     ok(true, "Received a new chrome source: " + packet.source.url);
     gThreadClient.removeListener("newSource", onNewSource);
     gNewChromeSource.resolve();
   }
 }
 
-function resumeAndCloseConnection() {
-  return new Promise(resolve => {
-    gThreadClient.resume(() => resolve(gClient.close()));
-  });
+async function resumeAndCloseConnection() {
+  await gThreadClient.resume();
+  return gClient.close();
 }
 
 registerCleanupFunction(function() {
   gClient = null;
   gThreadClient = null;
   gNewChromeSource = null;
 
   customLoader = null;
--- a/devtools/client/debugger/test/mochitest/browser_dbg-editor-highlight.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-editor-highlight.js
@@ -4,18 +4,17 @@
 
 // Tests that the editor will always highight the right line, no
 // matter if the source text doesn't exist yet or even if the source
 // doesn't exist.
 
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html", "long.js");
   const {
-    selectors: { getSource, getSourceContent },
-    getState
+    selectors: { getSource, getSourceContent }
   } = dbg;
   const sourceUrl = `${EXAMPLE_URL}long.js`;
 
   // The source itself doesn't even exist yet, and using
   // `selectSourceURL` will set a pending request to load this source
   // and highlight a specific line.
 
   await selectSource(dbg, sourceUrl, 66);
--- a/devtools/client/debugger/test/mochitest/browser_dbg-inline-cache.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-inline-cache.js
@@ -86,16 +86,17 @@ add_task(async function() {
   makeChanges();
 
   info("Reload inside debugger with toolbox caching disabled (attempt 1)");
   await reloadTabAndDebugger(tab, dbg);
   pageValue = await getPageValue(tab);
   is(pageValue, "let x = 2;", "Content loads from network, has doc value 2");
   await waitForLoadedSource(dbg, "inline-cache.html");
   dbgValue = findSourceContent(dbg, "inline-cache.html");
+
   info(`Debugger text: ${dbgValue.value}`);
   ok(
     dbgValue.value.includes(pageValue),
     "Debugger loads from network, gets value 2 like page"
   );
 
   makeChanges();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg-tabs.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-tabs.js
@@ -11,31 +11,31 @@ PromiseTestUtils.whitelistRejectionsGlob
 
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html", "simple1", "simple2");
 
   await selectSource(dbg, "simple1");
   await selectSource(dbg, "simple2");
   is(countTabs(dbg), 2);
 
-  // Test reloading the debugger
+  info("Test reloading the debugger");
   await reload(dbg, "simple1", "simple2");
   is(countTabs(dbg), 2);
   await waitForSelectedSource(dbg);
 
-  // Test reloading the debuggee a second time
+  info("Test reloading the debuggee a second time");
   await reload(dbg, "simple1", "simple2");
   is(countTabs(dbg), 2);
   await waitForSelectedSource(dbg);
 });
 
 add_task(async function() {
   const dbg = await initDebugger("doc-scripts.html", "simple1", "simple2");
 
   await selectSource(dbg, "simple1");
   await selectSource(dbg, "simple2");
   await closeTab(dbg, "simple1");
   await closeTab(dbg, "simple2");
 
-  // Test reloading the debugger
+  info("Test reloading the debugger");
   await reload(dbg, "simple1", "simple2");
   is(countTabs(dbg), 0);
 });
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -220,17 +220,17 @@ function waitForSelectedLocation(dbg, li
     return location && location.line == line;
   });
 }
 
 function waitForSelectedSource(dbg, url) {
   const {
     getSelectedSourceWithContent,
     hasSymbols,
-    hasBreakpointPositions
+    getBreakableLines
   } = dbg.selectors;
 
   return waitForState(
     dbg,
     state => {
       const { source, content } = getSelectedSourceWithContent() || {};
       if (!content) {
         return false;
@@ -240,17 +240,17 @@ function waitForSelectedSource(dbg, url)
         return true;
       }
 
       const newSource = findSource(dbg, url, { silent: true });
       if (newSource.id != source.id) {
         return false;
       }
 
-      return hasSymbols(source) && hasBreakpointPositions(source.id);
+      return hasSymbols(source) && getBreakableLines(source.id);
     },
     "selected source"
   );
 }
 
 /**
  * Assert that the debugger is not currently paused.
  * @memberof mochitest/asserts
@@ -303,17 +303,18 @@ function assertPausedLocation(dbg) {
   assertDebugLine(dbg, pauseLine);
 
   ok(isVisibleInEditor(dbg, getCM(dbg).display.gutters), "gutter is visible");
 }
 
 function assertDebugLine(dbg, line) {
   // Check the debug line
   const lineInfo = getCM(dbg).lineInfo(line - 1);
-  const { source, content } = dbg.selectors.getSelectedSourceWithContent() || {};
+  const { source, content } =
+    dbg.selectors.getSelectedSourceWithContent() || {};
   if (source && !content) {
     const url = source.url;
     ok(
       false,
       `Looks like the source ${url} is still loading. Try adding waitForLoadedSource in the test.`
     );
     return;
   }
@@ -499,17 +500,18 @@ function waitForTime(ms) {
 }
 
 function isSelectedFrameSelected(dbg, state) {
   const frame = dbg.selectors.getVisibleSelectedFrame();
 
   // Make sure the source text is completely loaded for the
   // source we are paused in.
   const sourceId = frame.location.sourceId;
-  const { source, content } = dbg.selectors.getSelectedSourceWithContent() || {};
+  const { source, content } =
+    dbg.selectors.getSelectedSourceWithContent() || {};
 
   if (!source) {
     return false;
   }
 
   if (!content) {
     return false;
   }
@@ -605,17 +607,19 @@ function findSource(dbg, url, { silent }
   }
 
   return source;
 }
 
 function findSourceContent(dbg, url, opts) {
   const source = findSource(dbg, url, opts);
 
-  if (!source) return null;
+  if (!source) {
+    return null;
+  }
 
   const content = dbg.selectors.getSourceContent(source.id);
 
   if (!content) {
     return null;
   }
 
   if (content.state !== "fulfilled") {
@@ -640,17 +644,19 @@ function waitForLoadedSource(dbg, url) {
   );
 }
 
 function waitForLoadedSources(dbg) {
   return waitForState(
     dbg,
     state => {
       const sources = dbg.selectors.getSourceList();
-      return sources.every(source => !!dbg.selectors.getSourceContent(source.id));
+      return sources.every(
+        source => !!dbg.selectors.getSourceContent(source.id)
+      );
     },
     "loaded source"
   );
 }
 
 function getContext(dbg) {
   return dbg.selectors.getContext();
 }
@@ -839,18 +845,17 @@ function setBreakpointOptions(dbg, sourc
     getContext(dbg),
     { sourceId, line, column },
     options
   );
 }
 
 function findBreakpoint(dbg, url, line) {
   const source = findSource(dbg, url);
-  const column = getFirstBreakpointColumn(dbg, { line, sourceId: source.id });
-  return dbg.selectors.getBreakpoint({ sourceId: source.id, line, column });
+  return dbg.selectors.getBreakpointsForSource(source.id, line)[0];
 }
 
 async function loadAndAddBreakpoint(dbg, filename, line, column) {
   const {
     selectors: { getBreakpoint, getBreakpointCount, getBreakpointsMap }
   } = dbg;
 
   await waitForSources(dbg, filename);
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -495,25 +495,25 @@ var gDevToolsBrowser = exports.gDevTools
         switch (threadClient.state) {
           case "paused":
             // When the debugger is already paused.
             threadClient.resumeThenPause();
             callback();
             break;
           case "attached":
             // When the debugger is already open.
-            threadClient.interrupt(() => {
+            threadClient.interrupt().then(() => {
               threadClient.resumeThenPause();
               callback();
             });
             break;
           case "resuming":
             // The debugger is newly opened.
             threadClient.addOneTimeListener("resumed", () => {
-              threadClient.interrupt(() => {
+              threadClient.interrupt().then(() => {
                 threadClient.resumeThenPause();
                 callback();
               });
             });
             break;
           default:
             throw Error("invalid thread client state in slow script debug handler: " +
                         threadClient.state);
--- a/devtools/client/shared/components/reps/images/jump-definition.svg
+++ b/devtools/client/shared/components/reps/images/jump-definition.svg
@@ -1,8 +1,8 @@
 <!-- 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/. -->
-<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" stroke="#000" fill="none" stroke-linecap="round">
+<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" stroke="context-stroke" fill="none" stroke-linecap="round">
   <path d="M5.5 3.5l2 2M5.5 7.5l2-2"/>
   <path d="M7 5.5H4.006c-1.012 0-1.995 1.017-2.011 2.024-.005.023-.005 1.347 0 3.971" stroke-linejoin="round"/>
   <path d="M10.5 5.5h4M9.5 3.5h5M9.5 7.5h5"/>
 </svg>
\ No newline at end of file
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -350,27 +350,30 @@ button.open-inspector {
 .open-inspector:hover {
   background-color: var(--theme-icon-checked-color);
 }
 
 /******************************************************************************/
 /* Jump to definition button */
 
 button.jump-definition {
-  mask: url("resource://devtools/client/shared/components/reps/images/jump-definition.svg")
-    no-repeat;
   display: inline-block;
-  background-color: var(--theme-icon-color);
   height: 16px;
   margin-left: 0.25em;
   vertical-align: middle;
+  background: 0% 50% url("resource://devtools/client/shared/components/reps/images/jump-definition.svg")
+    no-repeat;
+  border-color: transparent;
+  stroke: var(--theme-icon-color);
+  -moz-context-properties: stroke;
+  cursor: pointer;
 }
 
 .jump-definition:hover {
-  background-color: var(--theme-icon-checked-color);
+  stroke: var(--theme-icon-checked-color);
 }
 
 /******************************************************************************/
 /* Invoke getter button */
 
 button.invoke-getter {
   mask: url("resource://devtools/client/shared/components/reps/images/input.svg")
     no-repeat;
@@ -438,13 +441,16 @@ button.invoke-getter {
   margin-inline-start: -1px;
 }
 
 /* Focused styles */
 .tree.object-inspector .tree-node.focused * {
   color: inherit;
 }
 
-.tree-node.focused button.jump-definition,
 .tree-node.focused button.open-inspector,
 .tree-node.focused button.invoke-getter {
   background-color: currentColor;
 }
+
+.tree-node.focused .jump-definition.focused {
+  stroke: var(--theme-selection-background);
+}
--- a/devtools/client/shared/test/browser_tableWidget_mouse_interaction.js
+++ b/devtools/client/shared/test/browser_tableWidget_mouse_interaction.js
@@ -193,21 +193,22 @@ var testMouseInteraction = async functio
   // hiding second column
   // event listener for popupshown
   info("right click on the first column header");
   node = table.tbody.firstChild.firstChild.firstChild;
   let onPopupShown = once(table.menupopup, "popupshown");
   click(node, 2);
   await onPopupShown;
 
-  is(table.menupopup.querySelectorAll("[disabled]").length, 1,
+  is(table.menupopup.querySelectorAll("menuitem[disabled]").length, 1,
      "Only 1 menuitem is disabled");
-  is(table.menupopup.querySelector("[disabled]"),
+  is(table.menupopup.querySelector("menuitem[disabled]"),
      table.menupopup.querySelector("[data-id='col1']"),
      "Which is the unique column");
+
   // popup should be open now
   // clicking on second column label
   let onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
   node = table.menupopup.querySelector("[data-id='col2']");
   info("selecting to hide the second column");
   ok(!table.tbody.children[2].hasAttribute("hidden"),
      "Column is not hidden before hiding it");
@@ -221,17 +222,17 @@ var testMouseInteraction = async functio
   // hiding third column
   // event listener for popupshown
   info("right clicking on the first column header");
   node = table.tbody.firstChild.firstChild.firstChild;
   onPopupShown = once(table.menupopup, "popupshown");
   click(node, 2);
   await onPopupShown;
 
-  is(table.menupopup.querySelectorAll("[disabled]").length, 1,
+  is(table.menupopup.querySelectorAll("menuitem[disabled]").length, 1,
      "Only 1 menuitem is disabled");
   // popup should be open now
   // clicking on second column label
   onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
   node = table.menupopup.querySelector("[data-id='col3']");
   info("selecting to hide the second column");
   ok(!table.tbody.children[4].hasAttribute("hidden"),
@@ -246,22 +247,22 @@ var testMouseInteraction = async functio
   // opening again to see if 2 items are disabled now
   // event listener for popupshown
   info("right clicking on the first column header");
   node = table.tbody.firstChild.firstChild.firstChild;
   onPopupShown = once(table.menupopup, "popupshown");
   click(node, 2);
   await onPopupShown;
 
-  is(table.menupopup.querySelectorAll("[disabled]").length, 2,
+  is(table.menupopup.querySelectorAll("menuitem[disabled]").length, 2,
      "2 menuitems are disabled now as only 2 columns remain visible");
-  is(table.menupopup.querySelectorAll("[disabled]")[0],
+  is(table.menupopup.querySelectorAll("menuitem[disabled]")[0],
      table.menupopup.querySelector("[data-id='col1']"),
      "First is the unique column");
-  is(table.menupopup.querySelectorAll("[disabled]")[1],
+  is(table.menupopup.querySelectorAll("menuitem[disabled]")[1],
      table.menupopup.querySelector("[data-id='col4']"),
      "Second is the last column");
 
   // showing back 2nd column
   // popup should be open now
   // clicking on second column label
   onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -476,22 +476,29 @@
 }
 
 .event-header:not(:first-child) {
   border-width: 1px 0 0 0;
 }
 
 .event-tooltip-debugger-icon {
   -moz-context-properties: stroke;
-  stroke: currentColor;
-  background: url(resource://devtools/client/shared/components/reps/images/jump-definition.svg);
+  stroke: var(--theme-icon-color);
+  background-image: url("resource://devtools/client/shared/components/reps/images/jump-definition.svg");
+  background-repeat: no-repeat;
+  background-position: center;
+  width: 20px;
+  height: 18px;
+  border-radius: 2px;
+  margin-inline-end: 4px;
+  flex-shrink: 0;
 }
 
-.theme-dark .event-tooltip-debugger-icon {
-  filter: invert(70%);
+.event-tooltip-debugger-icon:hover {
+  background-color: var(--toolbarbutton-hover-background);
 }
 
 .devtools-tooltip-events-container {
   height: 100%;
   overflow-y: auto;
 }
 
 .event-tooltip-event-type,
@@ -513,29 +520,16 @@
   flex-shrink: 1;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   /* Force ellipsis to be displayed on the left */
   direction: rtl;
 }
 
-.event-tooltip-debugger-icon,
-.event-tooltip-debugger-spacer {
-  width: 16px;
-  height: 16px;
-  margin-inline-end: 4px;
-  opacity: 0.6;
-  flex-shrink: 0;
-}
-
-.event-tooltip-debugger-icon:hover {
-  opacity: 1;
-}
-
 .event-tooltip-content-box {
   display: none;
   height: 100px;
   overflow: hidden;
   margin-inline-end: 0;
   border: 1px solid var(--theme-splitter-color);
   border-width: 1px 0 0 0;
 }
--- a/devtools/docs/backend/client-api.md
+++ b/devtools/docs/backend/client-api.md
@@ -194,19 +194,19 @@ function debugTab() {
   });
 }
 
 /**
  * Handler for location changes.
  */
 function onTab() {
   // Detach from the previous thread.
-  client.activeThread.detach(() => {
+  client.activeThread.detach().then(() => {
     // Detach from the previous tab.
-    client.detach(() => {
+    client.detach().then(() => {
       // Start debugging the new tab.
       debugTab();
     });
   });
 }
 
 /**
  * Helper function to inspect the provided frame.
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -255,16 +255,28 @@ const SourceActor = ActorClassWithSpec(s
         this._contentType = result.contentType;
         return result;
       }, error => {
         this._reportLoadSourceError(error);
         throw error;
       });
   },
 
+  getBreakableLines() {
+    const positions = this.getBreakpointPositions();
+    const lines = new Set();
+    for (const position of positions) {
+      if (!lines.has(position.line)) {
+        lines.add(position.line);
+      }
+    }
+
+    return Array.from(lines);
+  },
+
   getBreakpointPositions(query) {
     const {
       start: {
         line: startLine = 0,
         column: startColumn = 0,
       } = {},
       end: {
         line: endLine = Infinity,
@@ -286,17 +298,17 @@ const SourceActor = ActorClassWithSpec(s
       }
 
       const offsets = script.getPossibleBreakpoints();
       for (const { lineNumber, columnNumber } of offsets) {
         if (
           lineNumber < startLine ||
           (lineNumber === startLine && columnNumber < startColumn) ||
           lineNumber > endLine ||
-          (lineNumber === endLine && columnNumber > endColumn)
+          (lineNumber === endLine && columnNumber >= endColumn)
         ) {
           continue;
         }
 
         positions.push({
           line: lineNumber,
           column: columnNumber,
         });
--- a/devtools/server/tests/unit/test_blackboxing-02.js
+++ b/devtools/server/tests/unit/test_blackboxing-02.js
@@ -30,17 +30,17 @@ function run_test() {
 }
 
 const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
 const SOURCE_URL = "http://example.com/source.js";
 
 function test_black_box() {
   gClient.addOneTimeListener("paused", async function(event, packet) {
     gThreadClient.setBreakpoint({ sourceUrl: BLACK_BOXED_URL, line: 2 }, {});
-    gThreadClient.resume(test_black_box_breakpoint);
+    gThreadClient.resume().then(test_black_box_breakpoint);
   });
 
   /* eslint-disable no-multi-spaces, no-undef */
   Cu.evalInSandbox(
     "" + function doStuff(k) { // line 1
       const arg = 15;            // line 2 - Break here
       k(arg);                  // line 3
     },                         // line 4
@@ -63,43 +63,45 @@ function test_black_box() {
     "1.8",
     SOURCE_URL,
     1
   );
   /* eslint-enable no-multi-spaces, no-undef */
 }
 
 function test_black_box_breakpoint() {
-  gThreadClient.getSources(async function({error, sources}) {
+  gThreadClient.getSources().then(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
     const sourceFront = gThreadClient.source(
       sources.filter(s => s.url == BLACK_BOXED_URL)[0]
     );
 
     await blackBox(sourceFront);
 
     gClient.addOneTimeListener("paused", function(event, packet) {
       Assert.equal(
         packet.why.type, "debuggerStatement",
         "We should pass over the breakpoint since the source is black boxed.");
-      gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceFront));
+      gThreadClient.resume().then(test_unblack_box_breakpoint.bind(null, sourceFront));
     });
     gDebuggee.runTest();
   });
 }
 
 async function test_unblack_box_breakpoint(sourceFront) {
   await unBlackBox(sourceFront);
   gClient.addOneTimeListener("paused", function(event, packet) {
     Assert.equal(packet.why.type, "breakpoint",
                  "We should hit the breakpoint again");
 
     // We will hit the debugger statement on resume, so do this
     // nastiness to skip over it.
     gClient.addOneTimeListener(
       "paused",
-      gThreadClient.resume.bind(
-        gThreadClient,
-        finishClient.bind(null, gClient)));
+      async () => {
+        await gThreadClient.resume();
+        finishClient(gClient);
+      }
+    );
     gThreadClient.resume();
   });
   gDebuggee.runTest();
 }
--- a/devtools/server/tests/unit/test_blackboxing-03.js
+++ b/devtools/server/tests/unit/test_blackboxing-03.js
@@ -61,35 +61,33 @@ function test_black_box() {
     gDebuggee,
     "1.8",
     SOURCE_URL,
     1
   );
   /* eslint-enable no-multi-spaces, no-undef */
 }
 
-function test_black_box_dbg_statement() {
-  gThreadClient.getSources(async function({error, sources}) {
-    Assert.ok(!error, "Should not get an error: " + error);
-    const sourceFront = await getSource(gThreadClient, BLACK_BOXED_URL);
+async function test_black_box_dbg_statement() {
+  await gThreadClient.getSources();
+  const sourceFront = await getSource(gThreadClient, BLACK_BOXED_URL);
 
-    await blackBox(sourceFront);
+  await blackBox(sourceFront);
 
-    gThreadClient.addOneTimeListener("paused", async function(event, packet) {
-      Assert.equal(packet.why.type, "breakpoint",
-                   "We should pass over the debugger statement.");
+  gThreadClient.addOneTimeListener("paused", async function(event, packet) {
+    Assert.equal(packet.why.type, "breakpoint",
+                 "We should pass over the debugger statement.");
 
-      const source = await getSourceById(gThreadClient, packet.frame.where.actor);
-      gThreadClient.removeBreakpoint({ sourceUrl: source.url, line: 4 }, {});
+    const source = await getSourceById(gThreadClient, packet.frame.where.actor);
+    gThreadClient.removeBreakpoint({ sourceUrl: source.url, line: 4 }, {});
 
-      await gThreadClient.resume();
-      await test_unblack_box_dbg_statement(sourceFront);
-    });
-    gDebuggee.runTest();
+    await gThreadClient.resume();
+    await test_unblack_box_dbg_statement(sourceFront);
   });
+  gDebuggee.runTest();
 }
 
 async function test_unblack_box_dbg_statement(sourceFront) {
   await unBlackBox(sourceFront);
 
   gClient.addOneTimeListener("paused", function(event, packet) {
     Assert.equal(packet.why.type, "debuggerStatement",
                  "We should stop at the debugger statement again");
--- a/devtools/server/tests/unit/test_blackboxing-04.js
+++ b/devtools/server/tests/unit/test_blackboxing-04.js
@@ -60,17 +60,17 @@ function test_black_box() {
     "1.8",
     SOURCE_URL,
     1
   );
   /* eslint-enable no-multi-spaces, no-undef */
 }
 
 function test_black_box_paused() {
-  gThreadClient.getSources(async function({error, sources}) {
+  gThreadClient.getSources().then(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
     const sourceFront = gThreadClient.source(
       sources.filter(s => s.url == BLACK_BOXED_URL)[0]
     );
 
     const pausedInSource = await blackBox(sourceFront);
     Assert.ok(pausedInSource,
       "We should be notified that we are currently paused in this source");
--- a/devtools/server/tests/unit/test_blackboxing-05.js
+++ b/devtools/server/tests/unit/test_blackboxing-05.js
@@ -64,17 +64,17 @@ function test_black_box() {
     "1.8",
     SOURCE_URL,
     1
   );
   /* eslint-enable no-multi-spaces, no-unreachable, no-undef */
 }
 
 function test_black_box_exception() {
-  gThreadClient.getSources(async function({error, sources}) {
+  gThreadClient.getSources().then(async function({error, sources}) {
     Assert.ok(!error, "Should not get an error: " + error);
     const sourceFront = await getSource(gThreadClient, BLACK_BOXED_URL);
     await blackBox(sourceFront);
     gThreadClient.pauseOnExceptions(true, false);
 
     gClient.addOneTimeListener("paused", async function(event, packet) {
       const source = await getSourceById(gThreadClient, packet.frame.where.actor);
 
--- a/devtools/server/tests/unit/test_breakpoint-03.js
+++ b/devtools/server/tests/unit/test_breakpoint-03.js
@@ -46,17 +46,17 @@ add_task(threadClientTest(({ threadClien
         Assert.equal(packet.why.type, "breakpoint");
         Assert.equal(packet.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         Assert.equal(debuggee.a, 1);
         Assert.equal(debuggee.b, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function(response) {
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         });
       });
 
       threadClient.resume();
     });
 
     // Use `evalInSandbox` to make the debugger treat it as normal
     // globally-scoped code, where breakpoint sliding rules apply.
--- a/devtools/server/tests/unit/test_breakpoint-04.js
+++ b/devtools/server/tests/unit/test_breakpoint-04.js
@@ -26,17 +26,17 @@ add_task(threadClientTest(({ threadClien
         Assert.equal(packet.frame.where.line, location.line);
         Assert.equal(packet.why.type, "breakpoint");
         // Check that the breakpoint worked.
         Assert.equal(debuggee.a, 1);
         Assert.equal(debuggee.b, undefined);
 
         // Remove the breakpoint.
         threadClient.removeBreakpoint(location);
-        threadClient.resume(resolve);
+        threadClient.resume().then(resolve);
       });
 
       // Continue until the breakpoint is hit.
       threadClient.resume();
     });
 
     /* eslint-disable */
     Cu.evalInSandbox(
--- a/devtools/server/tests/unit/test_breakpoint-05.js
+++ b/devtools/server/tests/unit/test_breakpoint-05.js
@@ -31,17 +31,17 @@ add_task(threadClientTest(({ threadClien
           Assert.equal(packet.why.type, "breakpoint");
           Assert.equal(packet.why.actors[0], bpClient.actor);
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a, 1);
           Assert.equal(debuggee.b, undefined);
 
           // Remove the breakpoint.
           bpClient.remove(function(response) {
-            threadClient.resume(resolve);
+            threadClient.resume().then(resolve);
           });
         });
 
         // Continue until the breakpoint is hit.
         threadClient.resume();
       });
     });
 
--- a/devtools/server/tests/unit/test_breakpoint-06.js
+++ b/devtools/server/tests/unit/test_breakpoint-06.js
@@ -31,17 +31,17 @@ add_task(threadClientTest(({ threadClien
           Assert.equal(packet.why.type, "breakpoint");
           Assert.equal(packet.why.actors[0], bpClient.actor);
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a, 1);
           Assert.equal(debuggee.b, undefined);
 
           // Remove the breakpoint.
           bpClient.remove(function(response) {
-            threadClient.resume(resolve);
+            threadClient.resume().then(resolve);
           });
         });
 
         // Continue until the breakpoint is hit.
         threadClient.resume();
       });
     });
 
--- a/devtools/server/tests/unit/test_breakpoint-07.js
+++ b/devtools/server/tests/unit/test_breakpoint-07.js
@@ -31,17 +31,17 @@ add_task(threadClientTest(({ threadClien
           Assert.equal(packet.why.type, "breakpoint");
           Assert.equal(packet.why.actors[0], bpClient.actor);
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a, 1);
           Assert.equal(debuggee.b, undefined);
 
           // Remove the breakpoint.
           bpClient.remove(function(response) {
-            threadClient.resume(resolve);
+            threadClient.resume().then(resolve);
           });
         });
 
         // Continue until the breakpoint is hit.
         threadClient.resume();
       });
     });
 
--- a/devtools/server/tests/unit/test_breakpoint-08.js
+++ b/devtools/server/tests/unit/test_breakpoint-08.js
@@ -33,17 +33,17 @@ add_task(threadClientTest(({ threadClien
         Assert.equal(packet.why.type, "breakpoint");
         Assert.equal(packet.why.actors[0], response.bpClient.actor);
         // Check that the breakpoint worked.
         Assert.equal(debuggee.a, 1);
         Assert.equal(debuggee.b, undefined);
 
         // Remove the breakpoint.
         response.bpClient.remove(function(response) {
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         });
       });
 
       // Continue until the breakpoint is hit.
       threadClient.resume();
     });
 
     /* eslint-disable */
--- a/devtools/server/tests/unit/test_breakpoint-09.js
+++ b/devtools/server/tests/unit/test_breakpoint-09.js
@@ -29,17 +29,17 @@ add_task(threadClientTest(({ threadClien
         Assert.equal(debuggee.a, undefined);
 
         // Remove the breakpoint.
         threadClient.removeBreakpoint(location);
         done = true;
         threadClient.addOneTimeListener("paused",
                                         function(event, packet) {
               // The breakpoint should not be hit again.
-                                          threadClient.resume(function() {
+                                          threadClient.resume().then(function() {
                                             Assert.ok(false);
                                           });
                                         });
         threadClient.resume();
       });
 
       // Continue until the breakpoint is hit.
       threadClient.resume();
--- a/devtools/server/tests/unit/test_breakpoint-10.js
+++ b/devtools/server/tests/unit/test_breakpoint-10.js
@@ -46,17 +46,17 @@ add_task(threadClientTest(({ threadClien
           Assert.equal(packet.type, "paused");
           Assert.equal(packet.why.type, "breakpoint");
           // Check that the breakpoint worked.
           Assert.equal(debuggee.i, 1);
 
           // Remove the breakpoint.
           threadClient.removeBreakpoint(location2);
 
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         });
 
         // Continue until the breakpoint is hit again.
         threadClient.resume();
       });
 
       // Continue until the breakpoint is hit.
       threadClient.resume();
--- a/devtools/server/tests/unit/test_breakpoint-11.js
+++ b/devtools/server/tests/unit/test_breakpoint-11.js
@@ -48,17 +48,17 @@ add_task(threadClientTest(({ threadClien
           Assert.equal(packet.why.type, "breakpoint");
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a.b, 1);
           Assert.equal(debuggee.res, undefined);
 
           // Remove the breakpoint.
           threadClient.removeBreakpoint(location2);
 
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         });
 
         // Continue until the breakpoint is hit again.
         threadClient.resume();
       });
 
       // Continue until the breakpoint is hit.
       threadClient.resume();
--- a/devtools/server/tests/unit/test_breakpoint-12.js
+++ b/devtools/server/tests/unit/test_breakpoint-12.js
@@ -72,17 +72,17 @@ add_task(threadClientTest(({ threadClien
           // Check that the breakpoint worked.
           Assert.equal(debuggee.a, 1);
           Assert.equal(debuggee.b, undefined);
 
           threadClient.addOneTimeListener("paused", function(event, packet) {
             // We don't expect any more pauses after the breakpoint was hit once.
             Assert.ok(false);
           });
-          threadClient.resume(function() {
+          threadClient.resume().then(function() {
             // Give any remaining breakpoints a chance to trigger.
             do_timeout(1000, resolve);
           });
         });
         // Continue until the breakpoint is hit.
         threadClient.resume();
       });
     }
--- a/devtools/server/tests/unit/test_breakpoint-13.js
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -65,17 +65,17 @@ add_task(threadClientTest(({ threadClien
       }
 
       // Remove the breakpoint and finish.
       const waiter = waitForPause(threadClient);
       threadClient.stepIn();
       await waiter;
       threadClient.removeBreakpoint(location);
 
-      threadClient.resume(resolve);
+      threadClient.resume().then(resolve);
     });
 
     /* eslint-disable */
     Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                      "function foo() {\n" + // line0 + 1
                      "  this.a = 1;\n" +    // line0 + 2 <-- Breakpoint is set here.
                      "}\n" +                // line0 + 3
                      "debugger;\n" +        // line0 + 4
--- a/devtools/server/tests/unit/test_breakpoint-14.js
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -62,17 +62,17 @@ add_task(threadClientTest(({ threadClien
         callback(packet);
       }
 
       // Remove the breakpoint and finish.
       const waiter = waitForPause(threadClient);
       threadClient.stepOver();
       await waiter;
       threadClient.removeBreakpoint(location);
-      threadClient.resume(resolve);
+      threadClient.resume().then(resolve);
     });
 
     /* eslint-disable */
     Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                      "function foo() {\n" + // line0 + 1
                      "  this.a = 1;\n" +    // line0 + 2 <-- Breakpoint is set here.
                      "}\n" +                // line0 + 3
                      "debugger;\n" +        // line0 + 4
--- a/devtools/server/tests/unit/test_breakpoint-16.js
+++ b/devtools/server/tests/unit/test_breakpoint-16.js
@@ -34,17 +34,17 @@ add_task(threadClientTest(({ threadClien
 
         Assert.equal(debuggee.acc, timesBreakpointHit);
         Assert.equal(packet.frame.environment.bindings.variables.i.value,
                      timesBreakpointHit);
 
         if (++timesBreakpointHit === 3) {
           threadClient.removeListener("paused", onPaused);
           threadClient.removeBreakpoint(location);
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         } else {
           threadClient.resume();
         }
       });
 
       // Continue until the breakpoint is hit.
       threadClient.resume();
     });
--- a/devtools/server/tests/unit/test_breakpoint-17.js
+++ b/devtools/server/tests/unit/test_breakpoint-17.js
@@ -98,12 +98,12 @@ function test_remove_one(first, second, 
 
           resolve();
           return;
         }
 
         Assert.ok(false, "Should never get here");
       });
 
-      threadClient.resume(() => debuggee.foo());
+      threadClient.resume().then(() => debuggee.foo());
     });
   });
 }
--- a/devtools/server/tests/unit/test_conditional_breakpoint-01.js
+++ b/devtools/server/tests/unit/test_conditional_breakpoint-01.js
@@ -42,17 +42,17 @@ function test_simple_breakpoint() {
 
       // Check the return value.
       Assert.equal(packet.why.type, "breakpoint");
       Assert.equal(packet.frame.where.line, 3);
 
       // Remove the breakpoint.
       gThreadClient.removeBreakpoint(location);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
 
     // Continue until the breakpoint is hit.
     gThreadClient.resume();
   });
 
--- a/devtools/server/tests/unit/test_conditional_breakpoint-02.js
+++ b/devtools/server/tests/unit/test_conditional_breakpoint-02.js
@@ -39,17 +39,17 @@ function test_simple_breakpoint() {
     gThreadClient.addOneTimeListener("paused", function(event, packet) {
       // Check the return value.
       Assert.equal(packet.why.type, "breakpoint");
       Assert.equal(packet.frame.where.line, 4);
 
       // Remove the breakpoint.
       gThreadClient.removeBreakpoint(location2);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
 
     // Continue until the breakpoint is hit.
     gThreadClient.resume();
   });
 
--- a/devtools/server/tests/unit/test_conditional_breakpoint-03.js
+++ b/devtools/server/tests/unit/test_conditional_breakpoint-03.js
@@ -37,17 +37,17 @@ function test_simple_breakpoint() {
     gThreadClient.addOneTimeListener("paused", function(event, packet) {
       // Check the return value.
       Assert.equal(packet.why.type, "breakpointConditionThrown");
       Assert.equal(packet.frame.where.line, 3);
 
       // Remove the breakpoint.
       gThreadClient.removeBreakpoint(location);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
 
     // Continue until the breakpoint is hit.
     gThreadClient.resume();
   });
 
--- a/devtools/server/tests/unit/test_frameactor-01.js
+++ b/devtools/server/tests/unit/test_frameactor-01.js
@@ -29,17 +29,17 @@ function run_test() {
   do_test_pending();
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     Assert.ok(!!packet.frame);
     Assert.ok(!!packet.frame.actor);
     Assert.equal(packet.frame.displayName, "stopMe");
-    gThreadClient.resume(function() {
+    gThreadClient.resume().then(function() {
       finishClient(gClient);
     });
   });
 
   gDebuggee.eval("(" + function() {
     function stopMe() {
       debugger;
     }
--- a/devtools/server/tests/unit/test_frameactor-02.js
+++ b/devtools/server/tests/unit/test_frameactor-02.js
@@ -29,17 +29,17 @@ function run_test() {
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet1) {
     gThreadClient.addOneTimeListener("paused", function(event, packet2) {
       Assert.equal(packet1.frame.actor, packet2.frame.actor);
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
     gThreadClient.resume();
   });
 
   gDebuggee.eval("(" + function() {
     function stopMe() {
--- a/devtools/server/tests/unit/test_frameactor-03.js
+++ b/devtools/server/tests/unit/test_frameactor-03.js
@@ -31,17 +31,17 @@ function run_test() {
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet1) {
     gThreadClient.addOneTimeListener("paused", function(event, packet2) {
       const poppedFrames = packet2.poppedFrames;
       Assert.equal(typeof (poppedFrames), typeof ([]));
       Assert.ok(poppedFrames.includes(packet1.frame.actor));
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
     gThreadClient.resume();
   });
 
   gDebuggee.eval("(" + function() {
     function stopMe() {
--- a/devtools/server/tests/unit/test_frameactor-04.js
+++ b/devtools/server/tests/unit/test_frameactor-04.js
@@ -37,28 +37,28 @@ var frameFixtures = [
 
   // Anonymous function call in our eval...
   { type: "call", displayName: undefined },
 
   // The eval itself.
   { type: "eval", displayName: "(eval)" },
 ];
 
-function test_frame_packet() {
-  gThreadClient.getFrames(0, 1000, function(response) {
-    for (let i = 0; i < response.frames.length; i++) {
-      const expected = frameFixtures[i];
-      const actual = response.frames[i];
+async function test_frame_packet() {
+  const response = await gThreadClient.getFrames(0, 1000);
+  for (let i = 0; i < response.frames.length; i++) {
+    const expected = frameFixtures[i];
+    const actual = response.frames[i];
 
-      Assert.equal(expected.displayname, actual.displayname, "Frame displayname");
-      Assert.equal(expected.type, actual.type, "Frame displayname");
-    }
+    Assert.equal(expected.displayname, actual.displayname, "Frame displayname");
+    Assert.equal(expected.type, actual.type, "Frame displayname");
+  }
 
-    gThreadClient.resume(() => finishClient(gClient));
-  });
+  await gThreadClient.resume();
+  await finishClient(gClient);
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     test_frame_packet();
   });
 
   gDebuggee.eval("(" + function() {
--- a/devtools/server/tests/unit/test_frameactor-05.js
+++ b/devtools/server/tests/unit/test_frameactor-05.js
@@ -28,31 +28,31 @@ function run_test() {
                              test_pause_frame();
                            });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getFrames(0, null, function(frameResponse) {
+    gThreadClient.getFrames(0, null).then(function(frameResponse) {
       Assert.equal(frameResponse.frames.length, 5);
       // Now wait for the next pause, after which the three
       // youngest actors should be popped..
       const expectPopped = frameResponse.frames.slice(0, 3).map(frame => frame.actor);
       expectPopped.sort();
 
       gThreadClient.addOneTimeListener("paused", function(event, pausePacket) {
         const popped = pausePacket.poppedFrames.sort();
         Assert.equal(popped.length, 3);
         for (let i = 0; i < 3; i++) {
           Assert.equal(expectPopped[i], popped[i]);
         }
 
-        gThreadClient.resume(() => finishClient(gClient));
+        gThreadClient.resume().then(() => finishClient(gClient));
       });
       gThreadClient.resume();
     });
   });
 
   gDebuggee.eval("(" + function() {
     function depth3() {
       debugger;
--- a/devtools/server/tests/unit/test_frameactor_wasm-01.js
+++ b/devtools/server/tests/unit/test_frameactor_wasm-01.js
@@ -39,17 +39,17 @@ function run_test() {
         });
       });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getFrames(0, null, async function(frameResponse) {
+    gThreadClient.getFrames(0, null).then(async function(frameResponse) {
       Assert.equal(frameResponse.frames.length, 4);
 
       const wasmFrame = frameResponse.frames[1];
       Assert.equal(wasmFrame.type, "wasmcall");
       Assert.equal(wasmFrame.this, undefined);
 
       const location = wasmFrame.where;
       const source = await getSourceById(gThreadClient, location.actor);
--- a/devtools/server/tests/unit/test_framearguments-01.js
+++ b/devtools/server/tests/unit/test_framearguments-01.js
@@ -37,17 +37,17 @@ function test_pause_frame() {
     Assert.equal(args[1], true);
     Assert.equal(args[2], "nasu");
     Assert.equal(args[3].type, "null");
     Assert.equal(args[4].type, "undefined");
     Assert.equal(args[5].type, "object");
     Assert.equal(args[5].class, "Object");
     Assert.ok(!!args[5].actor);
 
-    gThreadClient.resume(function() {
+    gThreadClient.resume().then(function() {
       finishClient(gClient);
     });
   });
 
   gDebuggee.eval("(" + function() {
     function stopMe(number, bool, string, null_, undef, object) {
       debugger;
     }
--- a/devtools/server/tests/unit/test_framebindings-01.js
+++ b/devtools/server/tests/unit/test_framebindings-01.js
@@ -61,17 +61,17 @@ function test_pause_frame() {
       Assert.equal(response.ownProperties.a.value, "a");
 
       Assert.equal(response.ownProperties.b.configurable, true);
       Assert.equal(response.ownProperties.b.enumerable, true);
       Assert.equal(response.ownProperties.b.writable, true);
       Assert.equal(response.ownProperties.b.value.type, "undefined");
       Assert.equal(false, "class" in response.ownProperties.b.value);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number, bool, string, null_, undef, object) {
--- a/devtools/server/tests/unit/test_framebindings-02.js
+++ b/devtools/server/tests/unit/test_framebindings-02.js
@@ -47,17 +47,17 @@ function test_pause_frame() {
     parentEnv = parentEnv.parent.parent;
     Assert.notEqual(parentEnv, undefined);
     const objClient = gThreadClient.pauseGrip(parentEnv.object);
     objClient.getPrototypeAndProperties(function(response) {
       Assert.equal(response.ownProperties.Object.value.type, "object");
       Assert.equal(response.ownProperties.Object.value.class, "Function");
       Assert.ok(!!response.ownProperties.Object.value.actor);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number, bool, string, null_, undef, object) {
--- a/devtools/server/tests/unit/test_framebindings-03.js
+++ b/devtools/server/tests/unit/test_framebindings-03.js
@@ -52,17 +52,17 @@ function test_pause_frame() {
 
     const objClient = gThreadClient.pauseGrip(env.object);
     objClient.getPrototypeAndProperties(function(response) {
       Assert.equal(response.ownProperties.PI.value, Math.PI);
       Assert.equal(response.ownProperties.cos.value.type, "object");
       Assert.equal(response.ownProperties.cos.value.class, "Function");
       Assert.ok(!!response.ownProperties.cos.value.actor);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
     function stopMe(number) {
--- a/devtools/server/tests/unit/test_framebindings-04.js
+++ b/devtools/server/tests/unit/test_framebindings-04.js
@@ -63,17 +63,17 @@ function test_pause_frame() {
         Assert.equal(args.length, 1);
         Assert.equal(args[0].number.value, 10);
         Assert.equal(vars.r.value, 10);
         Assert.equal(vars.a.value, Math.PI * 100);
         Assert.equal(vars.arguments.value.class, "Arguments");
         Assert.ok(!!vars.arguments.value.actor);
         Assert.equal(vars.foo.value, 2 * Math.PI);
 
-        gThreadClient.resume(function() {
+        gThreadClient.resume().then(function() {
           finishClient(gClient);
         });
       });
     });
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
--- a/devtools/server/tests/unit/test_framebindings-05.js
+++ b/devtools/server/tests/unit/test_framebindings-05.js
@@ -51,17 +51,17 @@ function test_pause_frame() {
       const parentClient = gThreadClient.pauseGrip(parentEnv.object);
       parentClient.getPrototypeAndProperties(function(response) {
         Assert.equal(response.ownProperties.a.value, Math.PI * 100);
         Assert.equal(response.ownProperties.r.value, 10);
         Assert.equal(response.ownProperties.Object.value.type, "object");
         Assert.equal(response.ownProperties.Object.value.class, "Function");
         Assert.ok(!!response.ownProperties.Object.value.actor);
 
-        gThreadClient.resume(function() {
+        gThreadClient.resume().then(function() {
           finishClient(gClient);
         });
       });
     });
   });
 
   gDebuggee.eval("var a, r = 10;\n" +
                  "with (Math) {\n" +
--- a/devtools/server/tests/unit/test_framebindings-06.js
+++ b/devtools/server/tests/unit/test_framebindings-06.js
@@ -43,17 +43,17 @@ function test_banana_environment() {
       equal(parent.function.name, "banana2");
       parent = parent.parent;
       equal(parent.type, "block");
       ok("banana2" in parent.bindings.variables);
       parent = parent.parent;
       equal(parent.type, "function");
       equal(parent.function.name, "banana");
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
 
   gDebuggee.eval("function banana(x) {\n" +
                  "  return function banana2(y) {\n" +
                  "    return function banana3(z) {\n" +
                  "      debugger;\n" +
--- a/devtools/server/tests/unit/test_framebindings-07.js
+++ b/devtools/server/tests/unit/test_framebindings-07.js
@@ -48,17 +48,17 @@ function test_banana_environment() {
 
       const parentClient = new EnvironmentClient(gThreadClient, parent);
       parentClient.getBindings(response => {
         Assert.equal(response.bindings.variables.banana3.value.class, "Function");
 
         const grandpaClient = new EnvironmentClient(gThreadClient, grandpa);
         grandpaClient.getBindings(response => {
           Assert.equal(response.bindings.arguments[0].y.value, "y");
-          gThreadClient.resume(() => finishClient(gClient));
+          gThreadClient.resume().then(() => finishClient(gClient));
         });
       });
     });
   });
 
   gDebuggee.eval("function banana(x) {\n" +
                  "  return function banana2(y) {\n" +
                  "    return function banana3(z) {\n" +
--- a/devtools/server/tests/unit/test_functiongrips-01.js
+++ b/devtools/server/tests/unit/test_functiongrips-01.js
@@ -37,17 +37,17 @@ function test_named_function() {
     Assert.equal(args[0].name, "stopMe");
     Assert.equal(args[0].displayName, "stopMe");
 
     const objClient = gThreadClient.pauseGrip(args[0]);
     objClient.getParameterNames(function(response) {
       Assert.equal(response.parameterNames.length, 1);
       Assert.equal(response.parameterNames[0], "arg1");
 
-      gThreadClient.resume(test_inferred_name_function);
+      gThreadClient.resume().then(test_inferred_name_function);
     });
   });
 
   gDebuggee.eval("stopMe(stopMe)");
 }
 
 function test_inferred_name_function() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
@@ -60,17 +60,17 @@ function test_inferred_name_function() {
 
     const objClient = gThreadClient.pauseGrip(args[0]);
     objClient.getParameterNames(function(response) {
       Assert.equal(response.parameterNames.length, 3);
       Assert.equal(response.parameterNames[0], "foo");
       Assert.equal(response.parameterNames[1], "bar");
       Assert.equal(response.parameterNames[2], "baz");
 
-      gThreadClient.resume(test_anonymous_function);
+      gThreadClient.resume().then(test_anonymous_function);
     });
   });
 
   gDebuggee.eval("var o = { m: function(foo, bar, baz) { } }; stopMe(o.m)");
 }
 
 function test_anonymous_function() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
@@ -83,17 +83,17 @@ function test_anonymous_function() {
 
     const objClient = gThreadClient.pauseGrip(args[0]);
     objClient.getParameterNames(function(response) {
       Assert.equal(response.parameterNames.length, 3);
       Assert.equal(response.parameterNames[0], "foo");
       Assert.equal(response.parameterNames[1], "bar");
       Assert.equal(response.parameterNames[2], "baz");
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   gDebuggee.eval("stopMe(function(foo, bar, baz) { })");
 }
 
--- a/devtools/server/tests/unit/test_interrupt.js
+++ b/devtools/server/tests/unit/test_interrupt.js
@@ -1,17 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-shadow */
 
 "use strict";
 
 add_task(threadClientTest(async ({ threadClient, debuggee, client, targetFront }) => {
-  return new Promise(resolve => {
-    threadClient.interrupt(function(response) {
-      Assert.equal(threadClient.paused, true);
-      threadClient.resume(function() {
-        Assert.equal(threadClient.paused, false);
-        resolve();
-      });
-    });
-  });
+  await threadClient.interrupt();
+  Assert.equal(threadClient.paused, true);
+  await threadClient.resume();
+  Assert.equal(threadClient.paused, false);
 }));
--- a/devtools/server/tests/unit/test_listsources-01.js
+++ b/devtools/server/tests/unit/test_listsources-01.js
@@ -32,26 +32,26 @@ function run_test() {
                              test_simple_listsources();
                            });
   });
   do_test_pending();
 }
 
 function test_simple_listsources() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getSources(function(response) {
+    gThreadClient.getSources().then(function(response) {
       Assert.ok(response.sources.some(function(s) {
         return s.url && s.url.match(/test_listsources-01.js/);
       }));
 
       Assert.ok(gNumTimesSourcesSent <= 1,
                 "Should only send one sources request at most, even though we"
                 + " might have had to send one to determine feature support.");
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   /* eslint-disable */
   Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
                   "debugger;\n" +   // line0 + 1
--- a/devtools/server/tests/unit/test_listsources-02.js
+++ b/devtools/server/tests/unit/test_listsources-02.js
@@ -30,17 +30,17 @@ function run_test() {
                              gThreadClient = threadClient;
                              test_listing_zero_sources();
                            });
   });
   do_test_pending();
 }
 
 function test_listing_zero_sources() {
-  gThreadClient.getSources(function(packet) {
+  gThreadClient.getSources().then(function(packet) {
     Assert.ok(!packet.error);
     Assert.ok(!!packet.sources);
     Assert.equal(packet.sources.length, 0);
 
     Assert.ok(gNumTimesSourcesSent <= 1,
               "Should only send one sources request at most, even though we"
               + " might have had to send one to determine feature support.");
 
--- a/devtools/server/tests/unit/test_listsources-03.js
+++ b/devtools/server/tests/unit/test_listsources-03.js
@@ -26,26 +26,26 @@ function run_test() {
                              test_simple_listsources();
                            });
   });
   do_test_pending();
 }
 
 function test_simple_listsources() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getSources(function(response) {
+    gThreadClient.getSources().then(function(response) {
       Assert.ok(
         !response.error,
         "There shouldn't be an error fetching large amounts of sources.");
 
       Assert.ok(response.sources.some(function(s) {
         return s.url.match(/foo-999.js$/);
       }));
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   for (let i = 0; i < 1000; i++) {
     Cu.evalInSandbox("function foo###() {return ###;}".replace(/###/g, i),
                      gDebuggee,
--- a/devtools/server/tests/unit/test_longstringgrips-01.js
+++ b/devtools/server/tests/unit/test_longstringgrips-01.js
@@ -56,23 +56,23 @@ function test_longstring_grip() {
       Assert.equal(grip.initial,
                    longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH));
 
       const longStringFront = createLongStringFront(gClient, grip);
       longStringFront.substring(22, 28).then(function(response) {
         try {
           Assert.equal(response, "monkey");
         } finally {
-          gThreadClient.resume(function() {
+          gThreadClient.resume().then(function() {
             finishClient(gClient);
           });
         }
       });
     } catch (error) {
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
         do_throw(error);
       });
     }
   });
 
   gDebuggee.eval('stopMe("' + longString + '")');
 }
--- a/devtools/server/tests/unit/test_nesting-03.js
+++ b/devtools/server/tests/unit/test_nesting-03.js
@@ -31,24 +31,28 @@ function start_second_connection() {
     attachTestThread(gClient2, "test-nesting1",
                      function(response, targetFront, threadClient) {
                        gThreadClient2 = threadClient;
                        test_nesting();
                      });
   });
 }
 
-function test_nesting() {
-  gThreadClient1.resume(response => {
-    Assert.equal(response.error, "wrongOrder");
-    gThreadClient2.resume(response => {
-      Assert.ok(!response.error);
-      Assert.equal(response.from, gThreadClient2.actor);
+async function test_nesting() {
+  try {
+    await gThreadClient1.resume();
+  } catch (e) {
+    Assert.equal(e.error, "wrongOrder");
+  }
+  try {
+    await gThreadClient2.resume();
+  } catch (e) {
+    Assert.ok(!e.error);
+    Assert.equal(e.from, gThreadClient2.actor);
+  }
 
-      gThreadClient1.resume(response => {
-        Assert.ok(!response.error);
-        Assert.equal(response.from, gThreadClient1.actor);
+  gThreadClient1.resume().then(response => {
+    Assert.ok(!response.error);
+    Assert.equal(response.from, gThreadClient1.actor);
 
-        gClient1.close(() => finishClient(gClient2));
-      });
-    });
+    gClient1.close(() => finishClient(gClient2));
   });
 }
--- a/devtools/server/tests/unit/test_objectgrips-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-01.js
@@ -17,17 +17,17 @@ add_task(threadClientTest(async ({ threa
 
       const objClient = threadClient.pauseGrip(args[0]);
       objClient.getOwnPropertyNames(function(response) {
         Assert.equal(response.ownPropertyNames.length, 3);
         Assert.equal(response.ownPropertyNames[0], "a");
         Assert.equal(response.ownPropertyNames[1], "b");
         Assert.equal(response.ownPropertyNames[2], "c");
 
-        threadClient.resume(resolve);
+        threadClient.resume().then(resolve);
       });
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     debuggee.eval("stopMe({ a: 1, b: true, c: 'foo' })");
   });
--- a/devtools/server/tests/unit/test_objectgrips-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-02.js
@@ -21,17 +21,17 @@ add_task(threadClientTest(async ({ threa
         Assert.ok(response.prototype != undefined);
 
         const protoClient = threadClient.pauseGrip(response.prototype);
         protoClient.getOwnPropertyNames(function(response) {
           Assert.equal(response.ownPropertyNames.length, 2);
           Assert.equal(response.ownPropertyNames[0], "b");
           Assert.equal(response.ownPropertyNames[1], "c");
 
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         });
       });
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     debuggee.eval(function Constr() {
--- a/devtools/server/tests/unit/test_objectgrips-03.js
+++ b/devtools/server/tests/unit/test_objectgrips-03.js
@@ -31,17 +31,17 @@ add_task(threadClientTest(async ({ threa
 
           objClient.getProperty("a", function(response) {
             Assert.equal(response.descriptor.configurable, true);
             Assert.equal(response.descriptor.enumerable, true);
             Assert.equal(response.descriptor.get.type, "object");
             Assert.equal(response.descriptor.get.class, "Function");
             Assert.equal(response.descriptor.set.type, "undefined");
 
-            threadClient.resume(resolve);
+            threadClient.resume().then(resolve);
           });
         });
       });
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
--- a/devtools/server/tests/unit/test_objectgrips-04.js
+++ b/devtools/server/tests/unit/test_objectgrips-04.js
@@ -35,17 +35,17 @@ add_task(threadClientTest(async ({ threa
         Assert.equal(response.ownProperties.a.set.type, "undefined");
 
         Assert.ok(response.prototype != undefined);
 
         const protoClient = threadClient.pauseGrip(response.prototype);
         protoClient.getOwnPropertyNames(function(response) {
           Assert.ok(response.ownPropertyNames.toString != undefined);
 
-          threadClient.resume(resolve);
+          threadClient.resume().then(resolve);
         });
       });
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     debuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
--- a/devtools/server/tests/unit/test_objectgrips-05.js
+++ b/devtools/server/tests/unit/test_objectgrips-05.js
@@ -23,17 +23,17 @@ add_task(threadClientTest(async ({ threa
       Assert.ok(obj1Client.isFrozen);
 
       const obj2 = packet.frame.arguments[1];
       Assert.ok(!obj2.frozen);
 
       const obj2Client = threadClient.pauseGrip(obj2);
       Assert.ok(!obj2Client.isFrozen);
 
-      threadClient.resume(resolve);
+      threadClient.resume().then(resolve);
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     /* eslint-disable no-undef */
     debuggee.eval("(" + function() {
       const obj1 = {};
--- a/devtools/server/tests/unit/test_objectgrips-06.js
+++ b/devtools/server/tests/unit/test_objectgrips-06.js
@@ -23,17 +23,17 @@ add_task(threadClientTest(async ({ threa
       Assert.ok(obj1Client.isSealed);
 
       const obj2 = packet.frame.arguments[1];
       Assert.ok(!obj2.sealed);
 
       const obj2Client = threadClient.pauseGrip(obj2);
       Assert.ok(!obj2Client.isSealed);
 
-      threadClient.resume(resolve);
+      threadClient.resume().then(resolve);
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     /* eslint-disable no-undef */
     debuggee.eval("(" + function() {
       const obj1 = {};
--- a/devtools/server/tests/unit/test_objectgrips-07.js
+++ b/devtools/server/tests/unit/test_objectgrips-07.js
@@ -27,17 +27,17 @@ add_task(threadClientTest(async ({ threa
       Assert.ok(!sClient.isExtensible);
 
       Assert.ok(!ne.extensible);
       Assert.ok(!neClient.isExtensible);
 
       Assert.ok(e.extensible);
       Assert.ok(eClient.isExtensible);
 
-      threadClient.resume(resolve);
+      threadClient.resume().then(resolve);
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     /* eslint-disable no-undef */
     debuggee.eval("(" + function() {
       const f = {};
--- a/devtools/server/tests/unit/test_objectgrips-08.js
+++ b/devtools/server/tests/unit/test_objectgrips-08.js
@@ -26,17 +26,17 @@ add_task(threadClientTest(async ({ threa
 
         if (bigIntEnabled) {
           const {e, f, g} = response.ownProperties;
           testPropertyType(e, "BigInt");
           testPropertyType(f, "BigInt");
           testPropertyType(g, "BigInt");
         }
 
-        threadClient.resume(resolve);
+        threadClient.resume().then(resolve);
       });
     });
 
     debuggee.eval(function stopMe(arg1) {
       debugger;
     }.toString());
     debuggee.eval(`stopMe({
       a: Infinity,
--- a/devtools/server/tests/unit/test_objectgrips-10.js
+++ b/devtools/server/tests/unit/test_objectgrips-10.js
@@ -52,17 +52,17 @@ function test_object_grip() {
         Assert.equal(response.scope.bindings.arguments[0].name.value, "Bob");
 
         getAgeClient.getScope(response => {
           Assert.equal(response.scope.bindings.arguments[1].age.value, 58);
 
           getFooClient.getScope(response => {
             Assert.equal(response.scope.bindings.variables.foo.value, 10);
 
-            gThreadClient.resume(() => finishClient(gClient));
+            gThreadClient.resume().then(() => finishClient(gClient));
           });
         });
       });
     });
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
--- a/devtools/server/tests/unit/test_objectgrips-11.js
+++ b/devtools/server/tests/unit/test_objectgrips-11.js
@@ -42,17 +42,17 @@ function test_object_grip() {
       const opn = response.ownPropertyNames;
       Assert.equal(opn.length, 4);
       opn.sort();
       Assert.equal(opn[0], "columnNumber");
       Assert.equal(opn[1], "fileName");
       Assert.equal(opn[2], "lineNumber");
       Assert.equal(opn[3], "message");
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   gDebuggee.eval("stopMe(new TypeError('error message text'))");
 }
 
--- a/devtools/server/tests/unit/test_objectgrips-12.js
+++ b/devtools/server/tests/unit/test_objectgrips-12.js
@@ -152,17 +152,17 @@ function test_display_string() {
 
     (function loop() {
       const objClient = gThreadClient.pauseGrip(args.pop());
       objClient.getDisplayString(function({ displayString }) {
         Assert.equal(displayString, testCases.pop().output);
         if (args.length) {
           loop();
         } else {
-          gThreadClient.resume(function() {
+          gThreadClient.resume().then(function() {
             finishClient(gClient);
           });
         }
       });
     })();
   });
 
   const inputs = testCases.map(({ input }) => input).join(",");
--- a/devtools/server/tests/unit/test_objectgrips-13.js
+++ b/devtools/server/tests/unit/test_objectgrips-13.js
@@ -57,11 +57,11 @@ function test_definition_site(func, obj)
     test_bad_definition_site(obj);
   });
 }
 
 function test_bad_definition_site(obj) {
   try {
     obj._client.request("definitionSite", () => Assert.ok(false));
   } catch (e) {
-    gThreadClient.resume(() => finishClient(gClient));
+    gThreadClient.resume().then(() => finishClient(gClient));
   }
 }
--- a/devtools/server/tests/unit/test_pause_exceptions-01.js
+++ b/devtools/server/tests/unit/test_pause_exceptions-01.js
@@ -33,17 +33,17 @@ function run_test() {
   do_test_pending();
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     gThreadClient.addOneTimeListener("paused", function(event, packet) {
       Assert.equal(packet.why.type, "exception");
       Assert.equal(packet.why.exception, 42);
-      gThreadClient.resume(() => finishClient(gClient));
+      gThreadClient.resume().then(() => finishClient(gClient));
     });
 
     gThreadClient.pauseOnExceptions(true, false);
     gThreadClient.resume();
   });
 
   /* eslint-disable */
   gDebuggee.eval("(" + function () {
--- a/devtools/server/tests/unit/test_pause_exceptions-02.js
+++ b/devtools/server/tests/unit/test_pause_exceptions-02.js
@@ -28,21 +28,21 @@ function run_test() {
                              gThreadClient = threadClient;
                              test_pause_frame();
                            });
   });
   do_test_pending();
 }
 
 function test_pause_frame() {
-  gThreadClient.pauseOnExceptions(true, false, function() {
+  gThreadClient.pauseOnExceptions(true, false).then(function() {
     gThreadClient.addOneTimeListener("paused", function(event, packet) {
       Assert.equal(packet.why.type, "exception");
       Assert.equal(packet.why.exception, 42);
-      gThreadClient.resume(() => finishClient(gClient));
+      gThreadClient.resume().then(() => finishClient(gClient));
     });
 
     /* eslint-disable */
     gDebuggee.eval("(" + function () {   // 1
       function stopMe() {                // 2
         throw 42;                        // 3
       }                                  // 4
       try {                              // 5
--- a/devtools/server/tests/unit/test_pauselifetime-01.js
+++ b/devtools/server/tests/unit/test_pauselifetime-01.js
@@ -34,17 +34,17 @@ function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     const pauseActor = packet.actor;
 
     // Make a bogus request to the pause-lifetime actor.  Should get
     // unrecognized-packet-type (and not no-such-actor).
     gClient.request({ to: pauseActor, type: "bogusRequest" }, function(response) {
       Assert.equal(response.error, "unrecognizedPacketType");
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         // Now that we've resumed, should get no-such-actor for the
         // same request.
         gClient.request({ to: pauseActor, type: "bogusRequest" }, function(response) {
           Assert.equal(response.error, "noSuchActor");
           finishClient(gClient);
         });
       });
     });
--- a/devtools/server/tests/unit/test_pauselifetime-02.js
+++ b/devtools/server/tests/unit/test_pauselifetime-02.js
@@ -37,17 +37,17 @@ function test_pause_frame() {
     Assert.equal(args[0].class, "Object");
     Assert.ok(!!objActor);
 
     // Make a bogus request to the grip actor.  Should get
     // unrecognized-packet-type (and not no-such-actor).
     gClient.request({ to: objActor, type: "bogusRequest" }, function(response) {
       Assert.equal(response.error, "unrecognizedPacketType");
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         // Now that we've resumed, should get no-such-actor for the
         // same request.
         gClient.request({ to: objActor, type: "bogusRequest" }, function(response) {
           Assert.equal(response.error, "noSuchActor");
           finishClient(gClient);
         });
       });
     });
--- a/devtools/server/tests/unit/test_pauselifetime-03.js
+++ b/devtools/server/tests/unit/test_pauselifetime-03.js
@@ -41,17 +41,17 @@ function test_pause_frame() {
     Assert.ok(objClient.valid);
 
     // Make a bogus request to the grip actor.  Should get
     // unrecognized-packet-type (and not no-such-actor).
     gClient.request({ to: objActor, type: "bogusRequest" }, function(response) {
       Assert.equal(response.error, "unrecognizedPacketType");
       Assert.ok(objClient.valid);
 
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         // Now that we've resumed, should get no-such-actor for the
         // same request.
         gClient.request({ to: objActor, type: "bogusRequest" }, function(response) {
           Assert.ok(!objClient.valid);
           Assert.equal(response.error, "noSuchActor");
           finishClient(gClient);
         });
       });
--- a/devtools/server/tests/unit/test_pauselifetime-04.js
+++ b/devtools/server/tests/unit/test_pauselifetime-04.js
@@ -30,20 +30,20 @@ function run_test() {
   do_test_pending();
 }
 
 function test_pause_frame() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
     const args = packet.frame.arguments;
     const objActor1 = args[0].actor;
 
-    gThreadClient.getFrames(0, 1, function(response) {
+    gThreadClient.getFrames(0, 1).then(function(response) {
       const frame = response.frames[0];
       Assert.equal(objActor1, frame.arguments[0].actor);
-      gThreadClient.resume(function() {
+      gThreadClient.resume().then(function() {
         finishClient(gClient);
       });
     });
   });
 
   gDebuggee.eval("(" + function() {
     function stopMe(obj) {
       debugger;
--- a/devtools/server/tests/unit/test_source-01.js
+++ b/devtools/server/tests/unit/test_source-01.js
@@ -38,17 +38,17 @@ function run_test() {
 
 const SOURCE_URL = "http://example.com/foobar.js";
 const SOURCE_CONTENT = "stopMe()";
 
 function test_source() {
   DebuggerServer.LONG_STRING_LENGTH = 200;
 
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getSources(function(response) {
+    gThreadClient.getSources().then(function(response) {
       Assert.ok(!!response);
       Assert.ok(!!response.sources);
 
       const source = response.sources.filter(function(s) {
         return s.url === SOURCE_URL;
       })[0];
 
       Assert.ok(!!source);
@@ -58,17 +58,17 @@ function test_source() {
         Assert.ok(!!response);
         Assert.ok(!!response.contentType);
         Assert.ok(response.contentType.includes("javascript"));
 
         Assert.ok(!!response.source);
         Assert.equal(SOURCE_CONTENT,
                      response.source);
 
-        gThreadClient.resume(function() {
+        gThreadClient.resume().then(function() {
           finishClient(gClient);
         });
       });
     });
   });
 
   Cu.evalInSandbox(
     SOURCE_CONTENT,
--- a/devtools/server/tests/unit/test_source-02.js
+++ b/devtools/server/tests/unit/test_source-02.js
@@ -43,17 +43,17 @@ const SOURCE_CONTENT = `
     debugger;
   }
 `;
 
 function test_source() {
   DebuggerServer.LONG_STRING_LENGTH = 200;
 
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getSources(async function(response) {
+    gThreadClient.getSources().then(async function(response) {
       Assert.ok(!!response);
       Assert.ok(!!response.sources);
 
       const source = response.sources.filter(function(s) {
         return s.url === SOURCE_URL;
       })[0];
 
       Assert.ok(!!source);
--- a/devtools/server/tests/unit/test_stepping-05.js
+++ b/devtools/server/tests/unit/test_stepping-05.js
@@ -36,33 +36,32 @@ add_task(threadClientTest(async ({ threa
 
   const step3 = await stepIn(client, threadClient);
   equal(step3.type, "paused");
   equal(step3.frame.where.line, 4);
   equal(step3.why.type, "resumeLimit");
   equal(debuggee.a, 1);
   equal(debuggee.b, 2);
 
-  await new Promise(resolve => {
-    threadClient.stepIn(() => {
-      threadClient.addOneTimeListener("paused", (event, packet) => {
-        equal(packet.type, "paused");
-        // Before fixing bug 785689, the type was resumeLimit.
-        equal(packet.why.type, "debuggerStatement");
-        resolve();
-      });
-      debuggee.eval("debugger;");
+  await new Promise(async resolve => {
+    await threadClient.stepIn();
+    threadClient.addOneTimeListener("paused", (event, packet) => {
+      equal(packet.type, "paused");
+      // Before fixing bug 785689, the type was resumeLimit.
+      equal(packet.why.type, "debuggerStatement");
+      resolve();
     });
+    debuggee.eval("debugger;");
   });
 }));
 
 function evaluateTestCode(debuggee) {
   /* eslint-disable */
   Cu.evalInSandbox(
-    `                                   // 1                       
+    `                                   // 1
     debugger;                           // 2
     var a = 1;                          // 3
     var b = 2;`,                        // 4
     debuggee,
     "1.8",
     "test_stepping-05-test-code.js",
     1
   );
--- a/devtools/server/tests/unit/test_threadlifetime-01.js
+++ b/devtools/server/tests/unit/test_threadlifetime-01.js
@@ -40,17 +40,17 @@ function test_thread_lifetime() {
       Assert.equal(response.error, undefined);
       gThreadClient.addOneTimeListener("paused", function(event, packet) {
         // Verify that the promoted actor is returned again.
         Assert.equal(pauseGrip.actor, packet.frame.arguments[0].actor);
         // Now that we've resumed, should get unrecognizePacketType for the
         // promoted grip.
         gClient.request({to: pauseGrip.actor, type: "bogusRequest"}, function(response) {
           Assert.equal(response.error, "unrecognizedPacketType");
-          gThreadClient.resume(function() {
+          gThreadClient.resume().then(function() {
             finishClient(gClient);
           });
         });
       });
       gThreadClient.resume();
     });
   });
 
--- a/devtools/server/tests/unit/test_threadlifetime-02.js
+++ b/devtools/server/tests/unit/test_threadlifetime-02.js
@@ -41,17 +41,17 @@ function test_thread_lifetime() {
       gThreadClient.addOneTimeListener("paused", function(event, packet) {
         // Verify that the promoted actor is returned again.
         Assert.equal(pauseGrip.actor, packet.frame.arguments[0].actor);
         // Now that we've resumed, release the thread-lifetime grip.
         gClient.release(pauseGrip.actor, function(response) {
           gClient.request(
             {to: pauseGrip.actor, type: "bogusRequest"}, function(response) {
               Assert.equal(response.error, "noSuchActor");
-              gThreadClient.resume(function(response) {
+              gThreadClient.resume().then(function() {
                 finishClient(gClient);
               });
             });
         });
       });
       gThreadClient.resume();
     });
   });
--- a/devtools/server/tests/unit/test_threadlifetime-04.js
+++ b/devtools/server/tests/unit/test_threadlifetime-04.js
@@ -38,17 +38,17 @@ function test_thread_lifetime() {
     gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function(response) {
       // Successful promotion won't return an error.
       Assert.equal(response.error, undefined);
 
       const threadGrip1 = response.from;
 
       gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function(response) {
         Assert.equal(threadGrip1, response.from);
-        gThreadClient.resume(function() {
+        gThreadClient.resume().then(function() {
           finishClient(gClient);
         });
       });
     });
   });
 
   gDebuggee.eval("(" + function() {
     function stopMe(arg1) {
--- a/devtools/server/tests/unit/test_wasm_source-01.js
+++ b/devtools/server/tests/unit/test_wasm_source-01.js
@@ -33,34 +33,33 @@ function run_test() {
 
     attachTestTabAndResume(
       gClient, "test-wasm-source",
       function(response, targetFront, threadClient) {
         gThreadClient = threadClient;
         gThreadClient.reconfigure({
           observeAsmJS: true,
           wasmBinarySource: true,
-        }, function(response) {
-          Assert.equal(!!response.error, false);
+        }).then(function() {
           test_source();
         });
       });
   });
   do_test_pending();
 }
 
 const EXPECTED_CONTENT = String.fromCharCode(
   0, 97, 115, 109, 1, 0, 0, 0, 1, 132, 128, 128, 128, 0, 1, 96, 0, 0, 3, 130,
   128, 128, 128, 0, 1, 0, 6, 129, 128, 128, 128, 0, 0, 7, 133, 128, 128, 128,
   0, 1, 1, 102, 0, 0, 10, 136, 128, 128, 128, 0, 1, 130, 128, 128, 128, 0, 0,
   11);
 
 function test_source() {
   gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    gThreadClient.getSources(function(response) {
+    gThreadClient.getSources().then(function(response) {
       Assert.ok(!!response);
       Assert.ok(!!response.sources);
 
       const source = response.sources.filter(function(s) {
         return s.introductionType === "wasm";
       })[0];
 
       Assert.ok(!!source);
@@ -72,17 +71,17 @@ function test_source() {
         Assert.ok(response.contentType.includes("wasm"));
 
         const sourceContent = response.source;
         Assert.ok(!!sourceContent);
         Assert.equal(typeof sourceContent, "object");
         Assert.ok("binary" in sourceContent);
         Assert.equal(EXPECTED_CONTENT, sourceContent.binary);
 
-        gThreadClient.resume(function() {
+        gThreadClient.resume().then(function() {
           finishClient(gClient);
         });
       });
     });
   });
 
   /* eslint-disable comma-spacing, max-len */
   gDebuggee.eval("(" + function() {
--- a/devtools/server/tests/unit/test_xpcshell_debugging.js
+++ b/devtools/server/tests/unit/test_xpcshell_debugging.js
@@ -31,25 +31,25 @@ add_task(async function() {
   const onResumed = new Promise(resolve => {
     threadClient.addOneTimeListener("paused", (event, packet) => {
       equal(packet.why.type, "breakpoint",
           "yay - hit the breakpoint at the first line in our script");
       // Resume again - next stop should be our "debugger" statement.
       threadClient.addOneTimeListener("paused", (event, packet) => {
         equal(packet.why.type, "debuggerStatement",
               "yay - hit the 'debugger' statement in our script");
-        threadClient.resume(resolve);
+        threadClient.resume().then(resolve);
       });
       threadClient.resume();
     });
   });
 
   // tell the thread to do the initial resume.  This would cause the
   // xpcshell test harness to resume and load the file under test.
-  threadClient.resume(() => {
+  threadClient.resume().then(() => {
     // should have been told to resume the test itself.
     ok(testResumed);
     // Now load our test script.
     load(testFile.path);
     // and our "paused" listener above should get hit.
   });
 
   await onResumed;
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -61,18 +61,16 @@ ThreadClient.prototype = {
    * @param [optional] object limit
    *        An object with a type property set to the appropriate limit (next,
    *        step, or finish) per the remote debugging protocol specification.
    *        Use null to specify no limit.
    * @param bool aRewind
    *        Whether execution should rewind until the limit is reached, rather
    *        than proceeding forwards. This parameter has no effect if the
    *        server does not support rewinding.
-   * @param function onResponse
-   *        Called with the response packet.
    */
   _doResume: DebuggerClient.requester({
     type: "resume",
     resumeLimit: arg(0),
     rewind: arg(1),
   }, {
     before: function(packet) {
       this._assertPaused("resume");
@@ -100,182 +98,140 @@ ThreadClient.prototype = {
     },
   }),
 
   /**
    * Reconfigure the thread actor.
    *
    * @param object options
    *        A dictionary object of the new options to use in the thread actor.
-   * @param function onResponse
-   *        Called with the response packet.
    */
   reconfigure: DebuggerClient.requester({
     type: "reconfigure",
     options: arg(0),
   }),
 
   /**
    * Resume a paused thread.
    */
-  resume: function(onResponse) {
-    return this._doResume(null, false, onResponse);
+  resume: function() {
+    return this._doResume(null, false);
   },
 
   /**
    * Resume then pause without stepping.
    *
-   * @param function onResponse
-   *        Called with the response packet.
    */
-  resumeThenPause: function(onResponse) {
-    return this._doResume({ type: "break" }, false, onResponse);
+  resumeThenPause: function() {
+    return this._doResume({ type: "break" }, false);
   },
 
   /**
    * Rewind a thread until a breakpoint is hit.
-   *
-   * @param function aOnResponse
-   *        Called with the response packet.
    */
-  rewind: function(onResponse) {
-    this._doResume(null, true, onResponse);
+  rewind: function() {
+    return this._doResume(null, true);
   },
 
   /**
    * Step over a function call.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
-  stepOver: function(onResponse) {
-    return this._doResume({ type: "next" }, false, onResponse);
+  stepOver: function() {
+    return this._doResume({ type: "next" }, false);
   },
 
   /**
    * Step into a function call.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
-  stepIn: function(onResponse) {
-    return this._doResume({ type: "step" }, false, onResponse);
+  stepIn: function() {
+    return this._doResume({ type: "step" }, false);
   },
 
   /**
    * Step out of a function call.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
-  stepOut: function(onResponse) {
-    return this._doResume({ type: "finish" }, false, onResponse);
+  stepOut: function() {
+    return this._doResume({ type: "finish" }, false);
   },
 
   /**
    * Rewind step over a function call.
-   *
-   * @param function aOnResponse
-   *        Called with the response packet.
    */
-  reverseStepOver: function(onResponse) {
-    return this._doResume({ type: "next" }, true, onResponse);
+  reverseStepOver: function() {
+    return this._doResume({ type: "next" }, true);
   },
 
   /**
    * Rewind step into a function call.
-   *
-   * @param function aOnResponse
-   *        Called with the response packet.
    */
-  reverseStepIn: function(onResponse) {
-    return this._doResume({ type: "step" }, true, onResponse);
+  reverseStepIn: function() {
+    return this._doResume({ type: "step" }, true);
   },
 
   /**
    * Rewind step out of a function call.
-   *
-   * @param function aOnResponse
-   *        Called with the response packet.
    */
-  reverseStepOut: function(onResponse) {
-    return this._doResume({ type: "finish" }, true, onResponse);
+  reverseStepOut: function() {
+    return this._doResume({ type: "finish" }, true);
   },
 
   /**
    * Immediately interrupt a running thread.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
-  interrupt: function(onResponse) {
-    return this._doInterrupt(null, onResponse);
+  interrupt: function() {
+    return this._doInterrupt(null);
   },
 
   /**
    * Pause execution right before the next JavaScript bytecode is executed.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
-  breakOnNext: function(onResponse) {
-    return this._doInterrupt("onNext", onResponse);
+  breakOnNext: function() {
+    return this._doInterrupt("onNext");
   },
 
   /**
    * Warp through time to an execution point in the past or future.
    *
    * @param object aTarget
    *        Description of the warp destination.
-   * @param function aOnResponse
-   *        Called with the response packet.
    */
-  timeWarp: function(target, onResponse) {
+  timeWarp: function(target) {
     const warp = () => {
-      this._doResume({ type: "warp", target }, true, onResponse);
+      this._doResume({ type: "warp", target }, true);
     };
     if (this.paused) {
-      warp();
-    } else {
-      this.interrupt(warp);
+      return warp();
     }
+    return this.interrupt().then(warp);
   },
 
   /**
    * Interrupt a running thread.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
   _doInterrupt: DebuggerClient.requester({
     type: "interrupt",
     when: arg(0),
   }),
 
   /**
    * Enable or disable pausing when an exception is thrown.
    *
    * @param boolean pauseOnExceptions
    *        Enables pausing if true, disables otherwise.
    * @param boolean ignoreCaughtExceptions
    *        Whether to ignore caught exceptions
-   * @param function onResponse
-   *        Called with the response packet.
    */
   pauseOnExceptions: DebuggerClient.requester({
     type: "pauseOnExceptions",
     pauseOnExceptions: arg(0),
     ignoreCaughtExceptions: arg(1),
   }),
 
   /**
    * Detach from the thread actor.
-   *
-   * @param function onResponse
-   *        Called with the response packet.
    */
   detach: DebuggerClient.requester({
     type: "detach",
   }, {
     after: function(response) {
       this.client.unregisterClient(this);
       return response;
     },
@@ -289,35 +245,30 @@ ThreadClient.prototype = {
    */
   threadGrips: DebuggerClient.requester({
     type: "threadGrips",
     actors: arg(0),
   }),
 
   /**
    * Request the loaded sources for the current thread.
-   *
-   * @param onResponse Function
-   *        Called with the thread's response.
    */
   getSources: DebuggerClient.requester({
     type: "sources",
   }),
 
   /**
    * Request frames from the callstack for the current thread.
    *
    * @param start integer
    *        The number of the youngest stack frame to return (the youngest
    *        frame is 0).
    * @param count integer
    *        The maximum number of frames to return, or null to return all
    *        frames.
-   * @param onResponse function
-   *        Called with the thread's response.
    */
   getFrames: DebuggerClient.requester({
     type: "frames",
     start: arg(0),
     count: arg(1),
   }),
 
   /**
--- a/devtools/shared/specs/source.js
+++ b/devtools/shared/specs/source.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const {Arg, RetVal, generateActorSpec, types} = require("devtools/shared/protocol");
+const { Arg, RetVal, generateActorSpec, types } = require("devtools/shared/protocol");
 
 const longstringType = types.getType("longstring");
 const arraybufferType = types.getType("arraybuffer");
 // The sourcedata type needs some custom marshalling, because it is sometimes
 // returned as an arraybuffer and sometimes as a longstring.
 types.addType("sourcedata", {
   write: (value, context, detail) => {
     if (value.typeName === "arraybuffer") {
@@ -60,16 +60,22 @@ const sourceSpec = generateActorSpec({
     getBreakpointPositionsCompressed: {
       request: {
         query: Arg(0, "nullable:breakpointquery"),
       },
       response: {
         positions: RetVal("json"),
       },
     },
+    getBreakableLines: {
+      request: {},
+      response: {
+        lines: RetVal("json"),
+      },
+    },
     onSource: {
       // we are sending the type "source" to be compatible
       // with FF67 and older
       request: { type: "source" },
       response: RetVal("source.onsource"),
     },
     setPausePoints: {
       request: {
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -141,17 +141,21 @@ already_AddRefed<BrowsingContext> Browsi
 
 /* static */
 already_AddRefed<BrowsingContext> BrowsingContext::CreateFromIPC(
     BrowsingContext::IPCInitializer&& aInit, BrowsingContextGroup* aGroup,
     ContentParent* aOriginProcess) {
   MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess());
   MOZ_DIAGNOSTIC_ASSERT(aGroup);
 
-  uint64_t originId = aOriginProcess ? aOriginProcess->ChildID() : 0;
+  uint64_t originId = 0;
+  if (aOriginProcess) {
+    originId = aOriginProcess->ChildID();
+    aGroup->EnsureSubscribed(aOriginProcess);
+  }
 
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")",
            aInit.mId, originId));
 
   RefPtr<BrowsingContext> parent = aInit.GetParent();
 
   RefPtr<BrowsingContext> context;
--- a/dom/browser-element/mochitest/browserElement_SendEvent.js
+++ b/dom/browser-element/mochitest/browserElement_SendEvent.js
@@ -20,16 +20,18 @@ function runTest() {
   // iframe.getBoundingClientRect();
   // to refresh offsets and then calling
   // var remoteTab = SpecialPowers.wrap(iframe)
   //                .frameLoader.remoteTab;
   // and calling remoteTab.getChildProcessOffset(offsetX, offsetY) if
   // remoteTab was not null, but remoteTab was always null.
 
   iframe.addEventListener("mozbrowserloadend", function onloadend(e) {
+    // Ensure we lay out the iframe before sending mouse events.
+    iframe.getBoundingClientRect();
     iframe.sendMouseEvent("mousedown", x, y, 0, 1, 0);
   });
 
   iframe.addEventListener("mozbrowserlocationchange", function onlocchange(e) {
     var a = document.createElement("a");
     a.href = e.detail.url;
 
     switch (a.hash) {
--- a/dom/browser-element/mochitest/file_browserElement_CookiesNotThirdParty.html
+++ b/dom/browser-element/mochitest/file_browserElement_CookiesNotThirdParty.html
@@ -1,20 +1,20 @@
 <html>
 <body>
 file_browserElement_CookiesNotThirdParty.html
 
 <script type='text/javascript'>
 if (location.search != "?step=2") {
   // Step 1: Set a cookie.
-  document.cookie = "file_browserElement_CookiesNotThirdParty";
+  document.cookie = "file_browserElement_CookiesNotThirdParty=1";
   alert("next");
 } else {
   // Step 2: Read the cookie.
-  if (document.cookie == "file_browserElement_CookiesNotThirdParty") {
+  if (document.cookie == "file_browserElement_CookiesNotThirdParty=1") {
     alert("success: got the correct cookie");
   } else {
     alert('failure: got unexpected cookie: "' + document.cookie + '"');
   }
 
   alert("finish");
 }
 </script>
--- a/dom/chrome-webidl/ChannelWrapper.webidl
+++ b/dom/chrome-webidl/ChannelWrapper.webidl
@@ -457,16 +457,22 @@ dictionary MozRequestFilter {
    */
   sequence<MozContentPolicyType>? types = null;
 
   /**
    * If present, the request only matches if its finalURI matches the given
    * match pattern set.
    */
   MatchPatternSet? urls = null;
+
+  /**
+   * If present, the request only matches if the loadInfo privateBrowsingId matches
+   * against the given incognito value.
+   */
+  boolean? incognito = null;
 };
 
 dictionary MozRequestMatchOptions {
   /**
    * True if we're matching for the proxy portion of a proxied request.
    */
   boolean isProxy = false;
 };
--- a/dom/file/ipc/IPCBlobInputStreamChild.cpp
+++ b/dom/file/ipc/IPCBlobInputStreamChild.cpp
@@ -170,59 +170,75 @@ void IPCBlobInputStreamChild::ActorDestr
   bool migrating = false;
 
   {
     MutexAutoLock lock(mMutex);
     migrating = mState == eActiveMigrating;
     mState = migrating ? eInactiveMigrating : eInactive;
   }
 
-  if (migrating) {
-    // We were waiting for this! Now we can migrate the actor in the correct
-    // thread.
-    RefPtr<IPCBlobInputStreamThread> thread =
-        IPCBlobInputStreamThread::GetOrCreate();
-    MOZ_ASSERT(thread, "We cannot continue without DOMFile thread.");
-
-    ResetManager();
-    thread->MigrateActor(this);
+  if (!migrating) {
+    // Let's cleanup the workerRef and the pending operation queue.
+    Shutdown();
     return;
   }
-
-  // Let's cleanup the workerRef and the pending operation queue.
-  Shutdown();
 }
 
 IPCBlobInputStreamChild::ActorState IPCBlobInputStreamChild::State() {
   MutexAutoLock lock(mMutex);
   return mState;
 }
 
 already_AddRefed<IPCBlobInputStream> IPCBlobInputStreamChild::CreateStream() {
   bool shouldMigrate = false;
 
-  RefPtr<IPCBlobInputStream> stream = new IPCBlobInputStream(this);
+  RefPtr<IPCBlobInputStream> stream;
 
   {
     MutexAutoLock lock(mMutex);
 
     if (mState == eInactive) {
       return nullptr;
     }
 
     // The stream is active but maybe it is not running in the DOM-File thread.
     // We should migrate it there.
     if (mState == eActive &&
         !IPCBlobInputStreamThread::IsOnFileEventTarget(mOwningEventTarget)) {
       MOZ_ASSERT(mStreams.IsEmpty());
+
       shouldMigrate = true;
       mState = eActiveMigrating;
-    }
+
+      RefPtr<IPCBlobInputStreamThread> thread =
+          IPCBlobInputStreamThread::GetOrCreate();
+      MOZ_ASSERT(thread, "We cannot continue without DOMFile thread.");
+
+      // Create a new actor object to connect to the target thread.
+      RefPtr<IPCBlobInputStreamChild> newActor =
+          new IPCBlobInputStreamChild(mID, mSize);
+      {
+        MutexAutoLock newActorLock(newActor->mMutex);
 
-    mStreams.AppendElement(stream);
+        // Move over our local state onto the new actor object.
+        newActor->mWorkerRef = mWorkerRef;
+        newActor->mState = eInactiveMigrating;
+        newActor->mPendingOperations.SwapElements(mPendingOperations);
+
+        // Create the actual stream object.
+        stream = new IPCBlobInputStream(newActor);
+        newActor->mStreams.AppendElement(stream);
+      }
+
+      // Perform the actual migration.
+      thread->MigrateActor(newActor);
+    } else {
+      stream = new IPCBlobInputStream(this);
+      mStreams.AppendElement(stream);
+    }
   }
 
   // Send__delete__ will call ActorDestroy(). mMutex cannot be locked at this
   // time.
   if (shouldMigrate) {
     Send__delete__(this);
   }
 
--- a/dom/file/ipc/IPCBlobInputStreamParent.cpp
+++ b/dom/file/ipc/IPCBlobInputStreamParent.cpp
@@ -74,17 +74,17 @@ void IPCBlobInputStreamParent::ActorDest
   RefPtr<IPCBlobInputStreamParentCallback> callback;
   mCallback.swap(callback);
 
   RefPtr<IPCBlobInputStreamStorage> storage = IPCBlobInputStreamStorage::Get();
 
   if (mMigrating) {
     if (callback && storage) {
       // We need to assign this callback to the next parent.
-      IPCBlobInputStreamStorage::Get()->StoreCallback(mID, callback);
+      storage->StoreCallback(mID, callback);
     }
     return;
   }
 
   if (storage) {
     storage->ForgetStream(mID);
   }
 
--- a/dom/file/ipc/IPCBlobUtils.h
+++ b/dom/file/ipc/IPCBlobUtils.h
@@ -153,31 +153,31 @@
  *    they are used on special I/O threads).
  *
  * In order to avoid this, IPCBlobInputStreamChild are 'migrated' to a DOM-File
  * thread. This is done in this way:
  *
  * 1. If IPCBlobInputStreamChild actor is not already owned by DOM-File thread,
  *    it calls Send__delete__ in order to inform the parent side that we don't
  *    need this IPC channel on the current thread.
+ * 2. A new IPCBlobInputStreamChild is created. IPCBlobInputStreamThread is
+ *    used to assign this actor to the DOM-File thread.
+ *    IPCBlobInputStreamThread::GetOrCreate() creates the DOM-File thread if it
+ *    doesn't exist yet. Pending operations and IPCBlobInputStreams are moved
+ *    onto the new actor.
  * 3. IPCBlobInputStreamParent::Recv__delete__ is called on the parent side and
  *    the parent actor is deleted. Doing this we don't remove the UUID from
  *    IPCBlobInputStreamStorage.
- * 4. When IPCBlobInputStreamChild::ActorDestroy() is called, we are sure that
- *    the IPC channel is completely released. IPCBlobInputStreamThread is be
- *    used to assign IPCBlobInputStreamChild actor to the DOM-File thread.
- *    IPCBlobInputStreamThread::GetOrCreate() creates the DOM-File thread if it
- *    doesn't exist yet and it initializes PBackground on it if needed.
- * 5. IPCBlobInputStreamChild is reused on the DOM-File thread for the creation
- *    of a new IPCBlobInputStreamParent actor on the parent side. Doing this,
- *    IPCBlobInputStreamChild will now be owned by the DOM-File thread.
- * 6. When the new IPCBlobInputStreamParent actor is created, it will receive
+ * 4. The IPCBlobInputStream constructor is sent with the new
+ *    IPCBlobInputStreamChild actor, with the DOM-File thread's PBackground as
+ *    its manager.
+ * 5. When the new IPCBlobInputStreamParent actor is created, it will receive
  *    the same UUID of the previous parent actor. The nsIInputStream will be
  *    retrieved from IPCBlobInputStreamStorage.
- * 7. In order to avoid leaks, IPCBlobInputStreamStorage will monitor child
+ * 6. In order to avoid leaks, IPCBlobInputStreamStorage will monitor child
  *    processes and in case one of them dies, it will release the
  *    nsIInputStream objects belonging to that process.
  *
  * If any API wants to retrieve a 'real inputStream when the migration is in
  * progress, that operation is stored in a pending queue and processed at the
  * end of the migration.
  *
  * IPCBlob and nsIAsyncInputStream
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3550,17 +3550,18 @@ mozilla::ipc::IPCResult ContentChild::Re
     const FileDescriptor& aFile) {
   recordreplay::parent::SaveRecording(aFile);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect(
     const uint32_t& aRegistrarId, nsIURI* aURI, const uint32_t& aNewLoadFlags,
     const Maybe<LoadInfoArgs>& aLoadInfo, const uint64_t& aChannelId,
-    nsIURI* aOriginalURI, const uint64_t& aIdentifier) {
+    nsIURI* aOriginalURI, const uint64_t& aIdentifier,
+    const uint32_t& aRedirectMode) {
   nsCOMPtr<nsILoadInfo> loadInfo;
   nsresult rv =
       mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfo, getter_AddRefs(loadInfo));
   if (NS_FAILED(rv)) {
     MOZ_DIAGNOSTIC_ASSERT(false, "LoadInfoArgsToLoadInfo failed");
     return IPC_OK();
   }
 
@@ -3589,16 +3590,21 @@ mozilla::ipc::IPCResult ContentChild::Re
     return IPC_OK();
   }
 
   rv = httpChild->SetOriginalURI(aOriginalURI);
   if (NS_FAILED(rv)) {
     return IPC_OK();
   }
 
+  rv = httpChild->SetRedirectMode(aRedirectMode);
+  if (NS_FAILED(rv)) {
+    return IPC_OK();
+  }
+
   // connect parent.
   rv = httpChild->ConnectParent(aRegistrarId);  // creates parent channel
   if (NS_FAILED(rv)) {
     return IPC_OK();
   }
 
   nsCOMPtr<nsIChildProcessChannelListener> processListener =
       do_GetService("@mozilla.org/network/childProcessChannelListener;1");
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -662,17 +662,18 @@ class ContentChild final : public PConte
 
   bool DeallocPClientOpenWindowOpChild(PClientOpenWindowOpChild* aActor);
 
   mozilla::ipc::IPCResult RecvSaveRecording(const FileDescriptor& aFile);
 
   mozilla::ipc::IPCResult RecvCrossProcessRedirect(
       const uint32_t& aRegistrarId, nsIURI* aURI, const uint32_t& aNewLoadFlags,
       const Maybe<LoadInfoArgs>& aLoadInfoForwarder, const uint64_t& aChannelId,
-      nsIURI* aOriginalURI, const uint64_t& aIdentifier);
+      nsIURI* aOriginalURI, const uint64_t& aIdentifier,
+      const uint32_t& aRedirectMode);
 
   mozilla::ipc::IPCResult RecvStartDelayedAutoplayMediaComponents(
       BrowsingContext* aContext);
 
 #ifdef NIGHTLY_BUILD
   // Fetch the current number of pending input events.
   //
   // NOTE: This method performs an atomic read, and is safe to call from all
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -786,17 +786,18 @@ child:
     // represented by registrarId.
     // This is on PContent not PNecko, as PNecko may not be initialized yet.
     async CrossProcessRedirect(uint32_t aRegistrarId,
                                nsIURI aURI,
                                uint32_t aNewLoadFlags,
                                LoadInfoArgs? aLoadInfo,
                                uint64_t aChannelId,
                                nsIURI aOriginalURI,
-                               uint64_t aIdentifier);
+                               uint64_t aIdentifier,
+                               uint32_t aRedirectMode);
 
     /**
     * This method is used to notifty content process to start delayed autoplay
     * media via browsing context.
     */
     async StartDelayedAutoplayMediaComponents(BrowsingContext aContext);
 
     // Begin subscribing to a new BrowsingContextGroup, sending down the current
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -295,16 +295,19 @@ class MediaData {
   bool mKeyframe;
 
   media::TimeUnit GetEndTime() const { return mTime + mDuration; }
 
   // Return true if the adjusted time is valid. Caller should handle error when
   // the result is invalid.
   virtual bool AdjustForStartTime(const media::TimeUnit& aStartTime) {
     mTime -= aStartTime;
+    if (mTime.IsNegative()) {
+      NS_WARNING("Negative start time after time-adjustment!");
+    }
     return mTime.IsValid();
   }
 
   template <typename ReturnType>
   const ReturnType* As() const {
     MOZ_ASSERT(this->mType == ReturnType::sType);
     return static_cast<const ReturnType*>(this);
   }
--- a/dom/media/ipc/RemoteDecoderModule.cpp
+++ b/dom/media/ipc/RemoteDecoderModule.cpp
@@ -22,16 +22,19 @@
 
 namespace mozilla {
 
 using base::Thread;
 using dom::ContentChild;
 using namespace ipc;
 using namespace layers;
 
+RemoteDecoderModule::RemoteDecoderModule()
+    : mManagerThread(RemoteDecoderManagerChild::GetManagerThread()) {}
+
 bool RemoteDecoderModule::SupportsMimeType(
     const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
   bool supports = false;
 
 #ifdef MOZ_AV1
   if (StaticPrefs::MediaAv1Enabled()) {
     supports |= AOMDecoder::IsAV1(aMimeType);
   }
@@ -41,74 +44,101 @@ bool RemoteDecoderModule::SupportsMimeTy
   }
 
   MOZ_LOG(
       sPDMLog, LogLevel::Debug,
       ("Sandbox decoder %s requested type", supports ? "supports" : "rejects"));
   return supports;
 }
 
+void RemoteDecoderModule::LaunchRDDProcessIfNeeded() {
+  if (!XRE_IsContentProcess()) {
+    return;
+  }
+
+  // We have a couple possible states here.  We are in a content process
+  // and:
+  // 1) the RDD process has never been launched.  RDD should be launched
+  //    and the IPC connections setup.
+  // 2) the RDD process has been launched, but this particular content
+  //    process has not setup (or has lost) its IPC connection.
+  // In the code below, we assume we need to launch the RDD process and
+  // setup the IPC connections.  However, if the manager thread for
+  // RemoteDecoderManagerChild is available we do a quick check to see
+  // if we can send (meaning the IPC channel is open).  If we can send,
+  // then no work is necessary.  If we can't send, then we call
+  // LaunchRDDProcess which will launch RDD if necessary, and setup the
+  // IPC connections between *this* content process and the RDD process.
+  bool needsLaunch = true;
+  if (mManagerThread) {
+    RefPtr<Runnable> task = NS_NewRunnableFunction(
+        "RemoteDecoderModule::LaunchRDDProcessIfNeeded-CheckSend", [&]() {
+          if (RemoteDecoderManagerChild::GetSingleton()) {
+            needsLaunch = !RemoteDecoderManagerChild::GetSingleton()->CanSend();
+          }
+        });
+    SyncRunnable::DispatchToThread(mManagerThread, task);
+  }
+
+  if (needsLaunch) {
+    ContentChild::GetSingleton()->LaunchRDDProcess();
+    mManagerThread = RemoteDecoderManagerChild::GetManagerThread();
+  }
+}
+
 already_AddRefed<MediaDataDecoder> RemoteDecoderModule::CreateAudioDecoder(
     const CreateDecoderParams& aParams) {
-  if (XRE_IsContentProcess()) {
-    ContentChild* contentChild = ContentChild::GetSingleton();
-    contentChild->LaunchRDDProcess();
-  }
+  LaunchRDDProcessIfNeeded();
 
-  if (!RemoteDecoderManagerChild::GetManagerThread()) {
+  if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteAudioDecoderChild> child = new RemoteAudioDecoderChild();
   RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, RemoteDecoderManagerChild::GetManagerThread(),
+      child, mManagerThread,
       RemoteDecoderManagerChild::GetManagerAbstractThread());
 
   MediaResult result(NS_OK);
   RefPtr<Runnable> task = NS_NewRunnableFunction(
       "RemoteDecoderModule::CreateAudioDecoder", [&, child]() {
         result = child->InitIPDL(aParams.AudioConfig(), aParams.mOptions);
       });
-  SyncRunnable::DispatchToThread(RemoteDecoderManagerChild::GetManagerThread(),
-                                 task);
+  SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
   return object.forget();
 }
 
 already_AddRefed<MediaDataDecoder> RemoteDecoderModule::CreateVideoDecoder(
     const CreateDecoderParams& aParams) {
-  if (XRE_IsContentProcess()) {
-    ContentChild* contentChild = ContentChild::GetSingleton();
-    contentChild->LaunchRDDProcess();
-  }
+  LaunchRDDProcessIfNeeded();
 
-  if (!RemoteDecoderManagerChild::GetManagerThread()) {
+  if (!mManagerThread) {
     return nullptr;
   }
 
   RefPtr<RemoteVideoDecoderChild> child = new RemoteVideoDecoderChild();
   RefPtr<RemoteMediaDataDecoder> object = new RemoteMediaDataDecoder(
-      child, RemoteDecoderManagerChild::GetManagerThread(),
+      child, mManagerThread,
       RemoteDecoderManagerChild::GetManagerAbstractThread());
 
   MediaResult result(NS_OK);
   RefPtr<Runnable> task = NS_NewRunnableFunction(
       "RemoteDecoderModule::CreateVideoDecoder", [&, child]() {
         result = child->InitIPDL(aParams.VideoConfig(), aParams.mRate.mValue,
                                  aParams.mOptions);
       });
-  SyncRunnable::DispatchToThread(RemoteDecoderManagerChild::GetManagerThread(),
-                                 task);
+  SyncRunnable::DispatchToThread(mManagerThread, task);
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
 
--- a/dom/media/ipc/RemoteDecoderModule.h
+++ b/dom/media/ipc/RemoteDecoderModule.h
@@ -10,23 +10,29 @@
 namespace mozilla {
 
 // A PDM implementation that creates a RemoteMediaDataDecoder (a
 // MediaDataDecoder) that proxies to a RemoteVideoDecoderChild.
 // A decoder child will talk to its respective decoder parent
 // (RemoteVideoDecoderParent) on the RDD process.
 class RemoteDecoderModule : public PlatformDecoderModule {
  public:
-  RemoteDecoderModule() = default;
+  RemoteDecoderModule();
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
       const CreateDecoderParams& aParams) override;
 
   already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
       const CreateDecoderParams& aParams) override;
+
+ protected:
+  void LaunchRDDProcessIfNeeded();
+
+ private:
+  RefPtr<nsIThread> mManagerThread;
 };
 
 }  // namespace mozilla
 
 #endif  // include_dom_media_ipc_RemoteDecoderModule_h
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -168,24 +168,29 @@ class RemoteVideoDecoder : public Remote
       mSeekTarget.reset();
       mLatestOutputTime.reset();
       return RemoteDataDecoder::ProcessFlush();
     });
   }
 
   RefPtr<MediaDataDecoder::DecodePromise> Decode(
       MediaRawData* aSample) override {
-    const VideoInfo* config =
-        aSample->mTrackInfo ? aSample->mTrackInfo->GetAsVideoInfo() : &mConfig;
-    MOZ_ASSERT(config);
+    RefPtr<RemoteVideoDecoder> self = this;
+    RefPtr<MediaRawData> sample = aSample;
+    return InvokeAsync(mTaskQueue, __func__, [self, sample]() {
+      const VideoInfo* config = sample->mTrackInfo
+                                    ? sample->mTrackInfo->GetAsVideoInfo()
+                                    : &self->mConfig;
+      MOZ_ASSERT(config);
 
-    InputInfo info(aSample->mDuration.ToMicroseconds(), config->mImage,
-                   config->mDisplay);
-    mInputInfos.Insert(aSample->mTime.ToMicroseconds(), info);
-    return RemoteDataDecoder::Decode(aSample);
+      InputInfo info(sample->mDuration.ToMicroseconds(), config->mImage,
+                     config->mDisplay);
+      self->mInputInfos.Insert(sample->mTime.ToMicroseconds(), info);
+      return self->RemoteDataDecoder::ProcessDecode(sample);
+    });
   }
 
   bool SupportDecoderRecycling() const override {
     return mIsCodecSupportAdaptivePlayback;
   }
 
   void SetSeekThreshold(const TimeUnit& aTime) override {
     RefPtr<RemoteVideoDecoder> self = this;
@@ -359,16 +364,36 @@ class RemoteAudioDecoder : public Remote
     if (mJavaDecoder == nullptr) {
       return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                           __func__);
     }
 
     return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
   }
 
+  RefPtr<FlushPromise> Flush() override {
+    RefPtr<RemoteAudioDecoder> self = this;
+    return InvokeAsync(mTaskQueue, __func__, [self]() {
+      self->mFirstDemuxedSampleTime.reset();
+      return self->RemoteDataDecoder::ProcessFlush();
+    });
+  }
+
+  RefPtr<DecodePromise> Decode(MediaRawData* aSample) override {
+    RefPtr<RemoteAudioDecoder> self = this;
+    RefPtr<MediaRawData> sample = aSample;
+    return InvokeAsync(mTaskQueue, __func__, [self, sample]() {
+      if (!self->mFirstDemuxedSampleTime) {
+        MOZ_ASSERT(sample->mTime.IsValid());
+        self->mFirstDemuxedSampleTime.emplace(sample->mTime);
+      }
+      return self->RemoteDataDecoder::ProcessDecode(sample);
+    });
+  }
+
  private:
   class CallbacksSupport final : public JavaCallbacksSupport {
    public:
     explicit CallbacksSupport(RemoteAudioDecoder* aDecoder)
         : mDecoder(aDecoder) {}
 
     void HandleInput(int64_t aTimestamp, bool aProcessed) override {
       mDecoder->UpdateInputStatus(aTimestamp, aProcessed);
@@ -403,16 +428,31 @@ class RemoteAudioDecoder : public Remote
     void HandleError(const MediaResult& aError) override {
       mDecoder->Error(aError);
     }
 
    private:
     RemoteAudioDecoder* mDecoder;
   };
 
+  bool IsSampleTimeSmallerThanFirstDemuxedSampleTime(int64_t aTime) const {
+    return mFirstDemuxedSampleTime->ToMicroseconds() > aTime;
+  }
+
+  bool ShouldDiscardSample() const {
+    AssertOnTaskQueue();
+    // HandleOutput() runs on Android binder thread pool and could be preempted
+    // by RemoteDateDecoder task queue. That means ProcessOutput() could be
+    // scheduled after ProcessShutdown() or ProcessFlush(). We won't need the
+    // sample which is returned after calling Shutdown() and Flush(). We can
+    // check mFirstDemuxedSampleTime to know whether the Flush() has been
+    // called, becasue it would be reset in Flush().
+    return GetState() == State::SHUTDOWN || !mFirstDemuxedSampleTime;
+  }
+
   // Param and LocalRef are only valid for the duration of a JNI method call.
   // Use GlobalRef as the parameter type to keep the Java object referenced
   // until running.
   void ProcessOutput(Sample::GlobalRef&& aSample,
                      SampleBuffer::GlobalRef&& aBuffer) {
     if (!mTaskQueue->IsCurrentThreadIn()) {
       nsresult rv = mTaskQueue->Dispatch(
           NewRunnableMethod<Sample::GlobalRef&&, SampleBuffer::GlobalRef&&>(
@@ -421,17 +461,17 @@ class RemoteAudioDecoder : public Remote
               std::move(aBuffer)));
       MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
       Unused << rv;
       return;
     }
 
     AssertOnTaskQueue();
 
-    if (GetState() == State::SHUTDOWN || !aBuffer->IsValid()) {
+    if (ShouldDiscardSample() || !aBuffer->IsValid()) {
       aSample->Dispose();
       return;
     }
 
     RenderOrReleaseOutput autoRelease(mJavaDecoder, aSample);
 
     BufferInfo::LocalRef info = aSample->Info();
     MOZ_ASSERT(info);
@@ -443,17 +483,18 @@ class RemoteAudioDecoder : public Remote
     ok &= NS_SUCCEEDED(info->Offset(&offset));
 
     int64_t presentationTimeUs;
     ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs));
 
     int32_t size;
     ok &= NS_SUCCEEDED(info->Size(&size));
 
-    if (!ok) {
+    if (!ok ||
+        IsSampleTimeSmallerThanFirstDemuxedSampleTime(presentationTimeUs)) {
       Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
       return;
     }
 
     if (size > 0) {
 #ifdef MOZ_SAMPLE_TYPE_S16
       const int32_t numSamples = size / 2;
 #else
@@ -495,16 +536,17 @@ class RemoteAudioDecoder : public Remote
     AssertOnTaskQueue();
 
     mOutputChannels = aChannels;
     mOutputSampleRate = aSampleRate;
   }
 
   int32_t mOutputChannels;
   int32_t mOutputSampleRate;
+  Maybe<TimeUnit> mFirstDemuxedSampleTime;
 };
 
 already_AddRefed<MediaDataDecoder> RemoteDataDecoder::CreateAudioDecoder(
     const CreateDecoderParams& aParams, const nsString& aDrmStubId,
     CDMProxy* aProxy) {
   const AudioInfo& config = aParams.AudioConfig();
   MediaFormat::LocalRef format;
   NS_ENSURE_SUCCESS(
@@ -677,33 +719,36 @@ static CryptoInfo::LocalRef GetCryptoInf
   cryptoInfo->Set(numSubSamples, numBytesOfPlainData, numBytesOfEncryptedData,
                   keyId, iv, MediaCodec::CRYPTO_MODE_AES_CTR);
 
   return cryptoInfo;
 }
 
 RefPtr<MediaDataDecoder::DecodePromise> RemoteDataDecoder::Decode(
     MediaRawData* aSample) {
-  MOZ_ASSERT(aSample != nullptr);
-
   RefPtr<RemoteDataDecoder> self = this;
   RefPtr<MediaRawData> sample = aSample;
-  return InvokeAsync(mTaskQueue, __func__, [self, sample]() {
-    jni::ByteBuffer::LocalRef bytes = jni::ByteBuffer::New(
-        const_cast<uint8_t*>(sample->Data()), sample->Size());
+  return InvokeAsync(mTaskQueue, __func__,
+                     [self, sample]() { return self->ProcessDecode(sample); });
+}
 
-    self->SetState(State::DRAINABLE);
-    self->mInputBufferInfo->Set(0, sample->Size(),
-                                sample->mTime.ToMicroseconds(), 0);
-    return self->mJavaDecoder->Input(bytes, self->mInputBufferInfo,
-                                     GetCryptoInfoFromSample(sample))
-               ? self->mDecodePromise.Ensure(__func__)
-               : DecodePromise::CreateAndReject(
-                     MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
-  });
+RefPtr<MediaDataDecoder::DecodePromise> RemoteDataDecoder::ProcessDecode(
+    MediaRawData* aSample) {
+  AssertOnTaskQueue();
+  MOZ_ASSERT(aSample != nullptr);
+  jni::ByteBuffer::LocalRef bytes = jni::ByteBuffer::New(
+      const_cast<uint8_t*>(aSample->Data()), aSample->Size());
+
+  SetState(State::DRAINABLE);
+  mInputBufferInfo->Set(0, aSample->Size(), aSample->mTime.ToMicroseconds(), 0);
+  return mJavaDecoder->Input(bytes, mInputBufferInfo,
+                             GetCryptoInfoFromSample(aSample))
+             ? mDecodePromise.Ensure(__func__)
+             : DecodePromise::CreateAndReject(
+                   MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
 }
 
 void RemoteDataDecoder::UpdatePendingInputStatus(PendingOp aOp) {
   AssertOnTaskQueue();
   switch (aOp) {
     case PendingOp::INCREASE:
       mNumPendingInputs++;
       break;
--- a/dom/media/platforms/android/RemoteDataDecoder.h
+++ b/dom/media/platforms/android/RemoteDataDecoder.h
@@ -38,30 +38,33 @@ class RemoteDataDecoder : public MediaDa
  protected:
   virtual ~RemoteDataDecoder() {}
   RemoteDataDecoder(MediaData::Type aType, const nsACString& aMimeType,
                     java::sdk::MediaFormat::Param aFormat,
                     const nsString& aDrmStubId, TaskQueue* aTaskQueue);
 
   // Methods only called on mTaskQueue.
   RefPtr<FlushPromise> ProcessFlush();
+  RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample);
   RefPtr<ShutdownPromise> ProcessShutdown();
   void UpdateInputStatus(int64_t aTimestamp, bool aProcessed);
   void UpdateOutputStatus(RefPtr<MediaData>&& aSample);
   void ReturnDecodedData();
   void DrainComplete();
   void Error(const MediaResult& aError);
-  void AssertOnTaskQueue() { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); }
+  void AssertOnTaskQueue() const {
+    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  }
 
   enum class State { DRAINED, DRAINABLE, DRAINING, SHUTDOWN };
   void SetState(State aState) {
     AssertOnTaskQueue();
     mState = aState;
   }
-  State GetState() {
+  State GetState() const {
     AssertOnTaskQueue();
     return mState;
   }
 
   // Whether the sample will be used.
   virtual bool IsUsefulData(const RefPtr<MediaData>& aSample) { return true; }
 
   MediaData::Type mType;
--- a/dom/media/test/test_streams_element_capture.html
+++ b/dom/media/test/test_streams_element_capture.html
@@ -108,16 +108,18 @@ function startTest(test, token) {
     { "set": [
       ["privacy.reduceTimerPrecision", false],
       // This test exhibits bug 1543980 with RDD enabled.
       ["media.rdd-process.enabled", false],
     ]});
   let tests = gPlayTests;
   // Filter out bug1377278.webm due to bug 1541401.
   tests = tests.filter(t => !t.name.includes("1377278"));
-  // Filter out ambisonics.mp4 due to bug 1546655.
+  // Filter out these files due to bug 1546655.
   tests = tests.filter(t => !t.name.includes("ambisonics"));
+  tests = tests.filter(t => !t.name.includes("flac-s24"));
+  tests = tests.filter(t => !t.name.includes("opus-mapping2"));
   manager.runTests(tests, startTest);
 })();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/notification/moz.build
+++ b/dom/notification/moz.build
@@ -3,17 +3,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/.
 
 with Files("**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 EXTRA_JS_MODULES += [
-    'NotificationDB.jsm',
     'NotificationStorage.jsm',
 ]
 
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
 EXPORTS.mozilla.dom += [
@@ -37,8 +36,17 @@ LOCAL_INCLUDES += [
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
 
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
+
+if CONFIG['MOZ_NEW_NOTIFICATION_STORE']:
+    EXTRA_JS_MODULES += [
+        'new/NotificationDB.jsm',
+    ]
+else:
+    EXTRA_JS_MODULES += [
+        'old/NotificationDB.jsm',
+    ]
rename from dom/notification/NotificationDB.jsm
rename to dom/notification/new/NotificationDB.jsm
copy from dom/notification/NotificationDB.jsm
copy to dom/notification/old/NotificationDB.jsm
--- a/dom/notification/NotificationDB.jsm
+++ b/dom/notification/old/NotificationDB.jsm
@@ -4,51 +4,45 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [];
 
 const DEBUG = false;
 function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }
 
-ChromeUtils.defineModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "KeyValueService", "resource://gre/modules/kvstore.jsm");
-ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+ChromeUtils.defineModuleGetter(this, "Services",
+                               "resource://gre/modules/Services.jsm");
+
+const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
+const NOTIFICATION_STORE_PATH =
+        OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
 
 const kMessages = [
   "Notification:Save",
   "Notification:Delete",
   "Notification:GetAll",
 ];
 
-// Given its origin and ID, produce the key that uniquely identifies
-// a notification.
-function makeKey(origin, id) {
-  return origin.concat("\t", id);
-}
-
 var NotificationDB = {
 
   // Ensure we won't call init() while xpcom-shutdown is performed
   _shutdownInProgress: false,
 
-  // A handle to the kvstore, retrieved lazily when we load the data.
-  _store: null,
-
-  // A promise that resolves once the store has been loaded.
-  // The promise doesn't resolve to a value; it merely captures the state
-  // of the load via its resolution.
-  _loadPromise: null,
-
   init() {
     if (this._shutdownInProgress) {
       return;
     }
 
+    this.notifications = {};
+    this.byTag = {};
+    this.loaded = false;
+
     this.tasks = []; // read/write operation queue
     this.runningTask = null;
 
     Services.obs.addObserver(this, "xpcom-shutdown");
     this.registerListeners();
   },
 
   registerListeners() {
@@ -86,77 +80,78 @@ var NotificationDB = {
         if (DEBUG) debug("Origin " + origin + " is not linked to an app manifest, deleting.");
         delete notifications[origin];
       }
     }
 
     return notifications;
   },
 
-  async maybeMigrateData() {
-    // We avoid using OS.File until we know we're going to migrate data
-    // to avoid the performance cost of loading that module.
-    const oldStore = FileUtils.getFile("ProfD", ["notificationstore.json"]);
-
-    if (!oldStore.exists()) {
-      if (DEBUG) { debug("Old store doesn't exist; not migrating data."); }
-      return;
-    }
+  // Attempt to read notification file, if it's not there we will create it.
+  load() {
+    var promise = OS.File.read(NOTIFICATION_STORE_PATH, { encoding: "utf-8"});
+    return promise.then(
+      data => {
+        if (data.length > 0) {
+          // Preprocessing phase intends to cleanly separate any migration-related
+          // tasks.
+          this.notifications = this.filterNonAppNotifications(JSON.parse(data));
+        }
 
-    let data;
-    try {
-      data = await OS.File.read(oldStore.path, { encoding: "utf-8"});
-    } catch (ex) {
-      // If read failed, we assume we have no notifications to migrate.
-      if (DEBUG) { debug("Failed to read old store; not migrating data."); }
-      return;
-    } finally {
-      // Finally, delete the old file so we don't try to migrate it again.
-      await OS.File.remove(oldStore.path);
-    }
+        // populate the list of notifications by tag
+        if (this.notifications) {
+          for (var origin in this.notifications) {
+            this.byTag[origin] = {};
+            for (var id in this.notifications[origin]) {
+              var curNotification = this.notifications[origin][id];
+              if (curNotification.tag) {
+                this.byTag[origin][curNotification.tag] = curNotification;
+              }
+            }
+          }
+        }
 
-    if (data.length > 0) {
-      // Preprocessing phase intends to cleanly separate any migration-related
-      // tasks.
-      //
-      // NB: This code existed before we migrated the data to a kvstore,
-      // and the "migration-related tasks" it references are from an earlier
-      // migration.  We used to do it every time we read the JSON file;
-      // now we do it once, when migrating the JSON file to the kvstore.
-      const notifications = this.filterNonAppNotifications(JSON.parse(data));
+        this.loaded = true;
+      },
 
-      // Copy the data from the JSON file to the kvstore.
-      // TODO: use a transaction to improve the performance of these operations
-      // once the kvstore API supports it (bug 1515096).
-      for (const origin in notifications) {
-        for (const id in notifications[origin]) {
-          await this._store.put(makeKey(origin, id),
-            JSON.stringify(notifications[origin][id]));
-        }
+      // If read failed, we assume we have no notifications to load.
+      reason => {
+        this.loaded = true;
+        return this.createStore();
       }
-    }
+    );
   },
 
-  // Attempt to read notification file, if it's not there we will create it.
-  async load() {
-    // Get and cache a handle to the kvstore.
-    const dir = FileUtils.getDir("ProfD", ["notificationstore"], true);
-    this._store = await KeyValueService.getOrCreate(dir.path, "notifications");
+  // Creates the notification directory.
+  createStore() {
+    var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
+      ignoreExisting: true,
+    });
+    return promise.then(
+      this.createFile.bind(this)
+    );
+  },
 
-    // Migrate data from the old JSON file to the new kvstore if the old file
-    // is present in the user's profile directory.
-    await this.maybeMigrateData();
+  // Creates the notification file once the directory is created.
+  createFile() {
+    return OS.File.writeAtomic(NOTIFICATION_STORE_PATH, "");
+  },
+
+  // Save current notifications to the file.
+  save() {
+    var data = JSON.stringify(this.notifications);
+    return OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data, { encoding: "utf-8"});
   },
 
   // Helper function: promise will be resolved once file exists and/or is loaded.
   ensureLoaded() {
-    if (!this._loadPromise) {
-      this._loadPromise = this.load();
+    if (!this.loaded) {
+      return this.load();
     }
-    return this._loadPromise;
+      return Promise.resolve();
   },
 
   receiveMessage(message) {
     if (DEBUG) { debug("Received message:" + message.name); }
 
     // sendAsyncMessage can fail if the child process exits during a
     // notification storage operation, so always wrap it in a try/catch.
     function returnMessage(name, data) {
@@ -217,17 +212,21 @@ var NotificationDB = {
 
   // We need to make sure any read/write operations are atomic,
   // so use a queue to run each operation sequentially.
   queueTask(operation, data) {
     if (DEBUG) { debug("Queueing task: " + operation); }
 
     var defer = {};
 
-    this.tasks.push({ operation, data, defer });
+    this.tasks.push({
+      operation,
+      data,
+      defer,
+    });
 
     var promise = new Promise(function(resolve, reject) {
       defer.resolve = resolve;
       defer.reject = reject;
     });
 
     // Only run immediately if we aren't currently running another task.
     if (!this.runningTask) {
@@ -255,19 +254,21 @@ var NotificationDB = {
         case "getall":
           return this.taskGetAll(task.data);
 
         case "save":
           return this.taskSave(task.data);
 
         case "delete":
           return this.taskDelete(task.data);
+
+        default:
+          return Promise.reject(
+            new Error(`Found a task with unknown operation ${task.operation}`));
       }
-
-      throw new Error(`Unknown task operation: ${task.operation}`);
     })
     .then(payload => {
       if (DEBUG) {
         debug("Finishing task: " + this.runningTask.operation);
       }
       this.runningTask.defer.resolve(payload);
     })
     .catch(err => {
@@ -276,60 +277,76 @@ var NotificationDB = {
       }
       this.runningTask.defer.reject(err);
     })
     .then(() => {
       this.runNextTask();
     });
   },
 
-  enumerate(origin) {
-    // The "from" and "to" key parameters to nsIKeyValueStore.enumerate()
-    // are inclusive and exclusive, respectively, and keys are tuples
-    // of origin and ID joined by a tab (\t), which is character code 9;
-    // so enumerating ["origin", "origin\n"), where the line feed (\n)
-    // is character code 10, enumerates all pairs with the given origin.
-    return this._store.enumerate(origin, `${origin}\n`);
-  },
-
-  async taskGetAll(data) {
+  taskGetAll(data) {
     if (DEBUG) { debug("Task, getting all"); }
     var origin = data.origin;
     var notifications = [];
-
-    for (const {value} of await this.enumerate(origin)) {
-      notifications.push(JSON.parse(value));
+    // Grab only the notifications for specified origin.
+    if (this.notifications[origin]) {
+      if (data.tag) {
+        let n;
+        if ((n = this.byTag[origin][data.tag])) {
+          notifications.push(n);
+        }
+      } else {
+        for (var i in this.notifications[origin]) {
+          notifications.push(this.notifications[origin][i]);
+        }
+      }
     }
-
-    if (data.tag) {
-      notifications = notifications.filter(n => n.tag === data.tag);
-    }
-
-    return notifications;
+    return Promise.resolve(notifications);
   },
 
-  async taskSave(data) {
+  taskSave(data) {
     if (DEBUG) { debug("Task, saving"); }
     var origin = data.origin;
     var notification = data.notification;
+    if (!this.notifications[origin]) {
+      this.notifications[origin] = {};
+      this.byTag[origin] = {};
+    }
 
     // We might have existing notification with this tag,
     // if so we need to remove it before saving the new one.
     if (notification.tag) {
-      for (const {key, value} of await this.enumerate(origin)) {
-        const oldNotification = JSON.parse(value);
-        if (oldNotification.tag === notification.tag) {
-          await this._store.delete(key);
-        }
+      var oldNotification = this.byTag[origin][notification.tag];
+      if (oldNotification) {
+        delete this.notifications[origin][oldNotification.id];
       }
+      this.byTag[origin][notification.tag] = notification;
     }
 
-    await this._store.put(makeKey(origin, notification.id),
-      JSON.stringify(notification));
+    this.notifications[origin][notification.id] = notification;
+    return this.save();
   },
 
-  async taskDelete(data) {
+  taskDelete(data) {
     if (DEBUG) { debug("Task, deleting"); }
-    await this._store.delete(makeKey(data.origin, data.id));
+    var origin = data.origin;
+    var id = data.id;
+    if (!this.notifications[origin]) {
+      if (DEBUG) { debug("No notifications found for origin: " + origin); }
+      return Promise.resolve();
+    }
+
+    // Make sure we can find the notification to delete.
+    var oldNotification = this.notifications[origin][id];
+    if (!oldNotification) {
+      if (DEBUG) { debug("No notification found with id: " + id); }
+      return Promise.resolve();
+    }
+
+    if (oldNotification.tag) {
+      delete this.byTag[origin][oldNotification.tag];
+    }
+    delete this.notifications[origin][id];
+    return this.save();
   },
 };
 
 NotificationDB.init();
--- a/dom/notification/test/unit/test_notificationdb_migration.js
+++ b/dom/notification/test/unit/test_notificationdb_migration.js
@@ -1,87 +1,95 @@
 "use strict";
 
+const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 const fooNotification =
   getNotificationObject("foo", "a4f1d54a-98b7-4231-9120-5afc26545bad", null, true);
 const barNotification =
   getNotificationObject("bar", "a4f1d54a-98b7-4231-9120-5afc26545bad", "baz", true);
 const msg = "Notification:GetAll";
 const msgReply = "Notification:GetAll:Return:OK";
 
 do_get_profile();
 const OLD_STORE_PATH =
     OS.Path.join(OS.Constants.Path.profileDir, "notificationstore.json");
 
 let nextRequestID = 0;
 
-async function createOldDatastore() {
+// Create the old datastore and populate it with data before we initialize
+// the notification database so it has data to migrate.  This is a setup step,
+// not a test, but it seems like we need to do it in a test function
+// rather than in run_test() because the test runner doesn't handle async steps
+// in run_test().
+add_task({
+  skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
+}, async function test_create_old_datastore() {
   const notifications = {
     [fooNotification.origin]: {
       [fooNotification.id]: fooNotification,
     },
     [barNotification.origin]: {
       [barNotification.id]: barNotification,
     },
   };
 
   await OS.File.writeAtomic(OLD_STORE_PATH, JSON.stringify(notifications));
-}
 
-// Create the old datastore and populate it with data before we initialize
-// the notification database so it has data to migrate.  This is a setup step,
-// not a test, but it seems like we need to do it in a test function
-// rather than in run_test() because the test runner doesn't handle async steps
-// in run_test().
-add_task(async function test_create_old_datastore() {
-  await createOldDatastore();
   startNotificationDB();
 });
 
-add_test(function test_get_system_notification() {
+add_test({
+  skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
+}, function test_get_system_notification() {
   const requestID = nextRequestID++;
   const msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
     Assert.equal(0, message.data.notifications.length);
   };
 
   addAndSend(msg, msgReply, msgHandler, {
     origin: systemNotification.origin,
     requestID,
   });
 });
 
-add_test(function test_get_foo_notification() {
+add_test({
+  skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
+}, function test_get_foo_notification() {
   const requestID = nextRequestID++;
   const msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
     Assert.equal(1, message.data.notifications.length);
     Assert.deepEqual(fooNotification, message.data.notifications[0],
       "Notification data migrated");
   };
 
   addAndSend(msg, msgReply, msgHandler, {
     origin: fooNotification.origin,
     requestID,
   });
 });
 
-add_test(function test_get_bar_notification() {
+add_test({
+  skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
+}, function test_get_bar_notification() {
   const requestID = nextRequestID++;
   const msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
     Assert.equal(1, message.data.notifications.length);
     Assert.deepEqual(barNotification, message.data.notifications[0],
       "Notification data migrated");
   };
 
   addAndSend(msg, msgReply, msgHandler, {
     origin: barNotification.origin,
     requestID,
   });
 });
 
-add_task(async function test_old_datastore_deleted() {
+add_task({
+  skip_if: () => !AppConstants.MOZ_NEW_NOTIFICATION_STORE,
+}, async function test_old_datastore_deleted() {
     Assert.ok(!await OS.File.exists(OLD_STORE_PATH),
       "old datastore no longer exists");
 });
--- a/dom/serviceworkers/test/browser.ini
+++ b/dom/serviceworkers/test/browser.ini
@@ -9,26 +9,29 @@ support-files =
   download_canceled/sw_download_canceled.js
   fetch.js
   file_userContextId_openWindow.js
   force_refresh_browser_worker.js
   empty.html
   empty_with_utils.html
   empty.js
   page_post_controlled.html
+  redirect.sjs
   storage_recovery_worker.sjs
   utils.js
 
 [browser_antitracking.js]
 [browser_antitracking_subiframes.js]
 [browser_devtools_serviceworker_interception.js]
 skip-if = serviceworker_e10s
 [browser_force_refresh.js]
 [browser_download.js]
 [browser_download_canceled.js]
 skip-if = verify
+[browser_navigation_process_swap.js]
+skip-if = !e10s || verify # Bug 1548643
 [browser_storage_permission.js]
 skip-if = (verify && debug && (os == 'win' || os == 'mac'))
 [browser_storage_recovery.js]
 skip-if = serviceworker_e10s
 [browser_unregister_with_containers.js]
 [browser_userContextId_openWindow.js]
 skip-if = !e10s || serviceworker_e10s
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/test/browser_navigation_process_swap.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test tests a navigation request to a Service Worker-controlled origin &
+ * scope that results in a cross-origin redirect to a
+ * non-Service Worker-controlled scope which additionally participates in
+ * cross-process redirect.
+ *
+ * On success, the test will not crash.
+ */
+
+const ORIGIN = 'http://mochi.test:8888';
+const TEST_ROOT = getRootDirectory(gTestPath)
+  .replace('chrome://mochitests/content', ORIGIN);
+
+const SW_REGISTER_PAGE_URL = `${TEST_ROOT}empty_with_utils.html`;
+const SW_SCRIPT_URL = `${TEST_ROOT}empty.js`;
+
+const FILE_URL = (() => {
+  // Get the file as an nsIFile.
+  const file = getChromeDir(getResolvedURI(gTestPath));
+  file.append('empty.html');
+
+  // Convert the nsIFile to an nsIURI to access the path.
+  return Services.io.newFileURI(file).spec;
+})();
+
+const CROSS_ORIGIN = 'http://example.com';
+const CROSS_ORIGIN_URL = SW_REGISTER_PAGE_URL.replace(ORIGIN, CROSS_ORIGIN);
+const CROSS_ORIGIN_REDIRECT_URL =
+  `${TEST_ROOT}redirect.sjs?${CROSS_ORIGIN_URL}`;
+
+async function loadURI(aXULBrowser, aURI) {
+  const browserLoadedPromise = BrowserTestUtils.browserLoaded(aXULBrowser);
+  await BrowserTestUtils.loadURI(aXULBrowser, aURI);
+
+  return browserLoadedPromise;
+}
+
+async function runTest() {
+  // Step 1: register a Service Worker under `ORIGIN` so that all subsequent
+  // requests to `ORIGIN` will be marked as controlled.
+  await SpecialPowers.pushPrefEnv({
+    'set': [
+      ['dom.serviceWorkers.enabled', true],
+      ['dom.serviceWorkers.exemptFromPerDomainMax', true],
+      ['dom.serviceWorkers.testing.enabled', true],
+      ['devtools.console.stdout.content', true],
+    ],
+  });
+
+  info(`Loading tab with page ${SW_REGISTER_PAGE_URL}`);
+  const tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    opening: SW_REGISTER_PAGE_URL,
+  });
+  info(`Loaded page ${SW_REGISTER_PAGE_URL}`);
+
+  info(`Registering Service Worker ${SW_SCRIPT_URL}`);
+  await ContentTask.spawn(
+    tab.linkedBrowser,
+    { scriptURL: SW_SCRIPT_URL },
+    async ({ scriptURL }) =>
+      await content.wrappedJSObject.registerAndWaitForActive(scriptURL),
+  );
+  info(`Registered and activated Service Worker ${SW_SCRIPT_URL}`);
+
+  // Step 2: open a page over file:// and navigate to trigger a process swap
+  // for the response.
+  info(`Loading ${FILE_URL}`)
+  await loadURI(tab.linkedBrowser, FILE_URL);
+
+  Assert.equal(tab.linkedBrowser.remoteType, E10SUtils.FILE_REMOTE_TYPE,
+               `${FILE_URL} should load in a file process`);
+
+  info(`Dynamically creating ${FILE_URL}'s link`);
+  await ContentTask.spawn(
+    tab.linkedBrowser,
+    { href: CROSS_ORIGIN_REDIRECT_URL },
+    ({ href }) => {
+      const { document } = content;
+      const link = document.createElement('a');
+      link.href = href;
+      link.id = 'link';
+      link.appendChild(document.createTextNode(href));
+      document.body.appendChild(link);
+    },
+  );
+
+  const redirectPromise = BrowserTestUtils.waitForLocationChange(
+    gBrowser, CROSS_ORIGIN_URL);
+
+  info('Starting navigation')
+  await BrowserTestUtils.synthesizeMouseAtCenter('#link', {},
+                                                 tab.linkedBrowser);
+
+  info(`Waiting for location to change to ${CROSS_ORIGIN_URL}`);
+  await redirectPromise;
+
+  info('Waiting for the browser to stop')
+  await BrowserTestUtils.browserStopped(tab.linkedBrowser);
+
+  Assert.equal(tab.linkedBrowser.remoteType, E10SUtils.WEB_REMOTE_TYPE,
+               `${CROSS_ORIGIN_URL} should load in a web-content process`);
+
+  // Step 3: cleanup.
+  info('Loading initial page to unregister all Service Workers');
+  await loadURI(tab.linkedBrowser, SW_REGISTER_PAGE_URL);
+
+  info('Unregistering all Service Workers');
+  await ContentTask.spawn(
+    tab.linkedBrowser,
+    null,
+    async () => await content.wrappedJSObject.unregisterAll(),
+  )
+
+  info('Closing tab');
+  BrowserTestUtils.removeTab(tab);
+}
+
+add_task(runTest);
--- a/dom/serviceworkers/test/test_serviceworker_interfaces.js
+++ b/dom/serviceworkers/test/test_serviceworker_interfaces.js
@@ -22,19 +22,19 @@
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     "Array",
     "ArrayBuffer",
     {name: "Atomics", disabled: true},
     "Boolean",
-    {name: "BigInt", nightly: true},
-    {name: "BigInt64Array", nightly: true},
-    {name: "BigUint64Array", nightly: true},
+    "BigInt",
+    "BigInt64Array",
+    "BigUint64Array",
     {name: "ByteLengthQueuingStrategy", optional: true},
     {name: "CountQueuingStrategy", optional: true},
     "DataView",
     "Date",
     "Error",
     "EvalError",
     "Float32Array",
     "Float64Array",
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -39,19 +39,19 @@ const isFennec = isAndroid && SpecialPow
 
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     {name: "Array", insecureContext: true},
     {name: "ArrayBuffer", insecureContext: true},
     {name: "Atomics", insecureContext: true, disabled: true},
-    {name: "BigInt", insecureContext: true, nightly: true},
-    {name: "BigInt64Array", insecureContext: true, nightly: true},
-    {name: "BigUint64Array", insecureContext: true, nightly: true},
+    {name: "BigInt", insecureContext: true},
+    {name: "BigInt64Array", insecureContext: true},
+    {name: "BigUint64Array", insecureContext: true},
     {name: "Boolean", insecureContext: true},
     {name: "ByteLengthQueuingStrategy", insecureContext: true},
     {name: "CountQueuingStrategy", insecureContext: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
--- a/dom/tests/mochitest/general/test_offsets.js
+++ b/dom/tests/mochitest/general/test_offsets.js
@@ -4,17 +4,17 @@ function testElements(baseid, callback)
 {
   scrollbarWidth = scrollbarHeight = gcs($("scrollbox-test"), "width");
 
   var elements = $(baseid).getElementsByTagName("*");
   for (var t = 0; t < elements.length; t++) {
     var element = elements[t];
 
     // Ignore presentational content inside menus
-    if (element.closest("menu") && element.closest("[aria-hidden=true]")) {
+    if (element.closest("menu, menuitem") && element.closest("[aria-hidden=true]")) {
       continue;
     }
 
     // Ignore content inside a <button>  This can be removed if/when
     // button switches to use shadow DOM.
     let buttonParent = element.closest("button");
     if (buttonParent && buttonParent !== element) {
       continue;
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -27,19 +27,19 @@
 
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     {name: "Array", insecureContext: true},
     {name: "ArrayBuffer", insecureContext: true},
     {name: "Atomics", insecureContext: true, disabled: true},
-    {name: "BigInt", insecureContext: true, nightly: true},
-    {name: "BigInt64Array", insecureContext: true, nightly: true},
-    {name: "BigUint64Array", insecureContext: true, nightly: true},
+    {name: "BigInt", insecureContext: true},
+    {name: "BigInt64Array", insecureContext: true},
+    {name: "BigUint64Array", insecureContext: true},
     {name: "Boolean", insecureContext: true},
     {name: "ByteLengthQueuingStrategy", insecureContext: true},
     {name: "CountQueuingStrategy", insecureContext: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
--- a/dom/worklet/WorkletThread.cpp
+++ b/dom/worklet/WorkletThread.cpp
@@ -225,17 +225,17 @@ WorkletThread::~WorkletThread() {
   // This should be set during the termination step.
   MOZ_ASSERT(mExitLoop);
 }
 
 // static
 already_AddRefed<WorkletThread> WorkletThread::Create(
     WorkletImpl* aWorkletImpl) {
   RefPtr<WorkletThread> thread = new WorkletThread(aWorkletImpl);
-  if (NS_WARN_IF(NS_FAILED(thread->Init()))) {
+  if (NS_WARN_IF(NS_FAILED(thread->Init(NS_LITERAL_CSTRING("DOM Worklet"))))) {
     return nullptr;
   }
 
   RefPtr<PrimaryRunnable> runnable = new PrimaryRunnable(thread);
   if (NS_WARN_IF(NS_FAILED(thread->DispatchRunnable(runnable.forget())))) {
     return nullptr;
   }
 
--- a/dom/xul/XULPersist.cpp
+++ b/dom/xul/XULPersist.cpp
@@ -1,16 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #include "XULPersist.h"
-#include "mozilla/XULStore.h"
+
+#ifdef MOZ_NEW_XULSTORE
+#  include "mozilla/XULStore.h"
+#else
+#  include "nsIXULStore.h"
+#endif
 
 namespace mozilla {
 namespace dom {
 
 static bool ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute) {
   if (aElement->IsXULElement(nsGkAtoms::window)) {
     // This is not an element of the top document, its owner is
     // not an nsXULWindow. Persist it.
@@ -74,94 +79,142 @@ void XULPersist::Persist(Element* aEleme
   if (!mDocument) {
     return;
   }
   // For non-chrome documents, persistance is simply broken
   if (!nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal())) {
     return;
   }
 
+#ifndef MOZ_NEW_XULSTORE
+  if (!mLocalStore) {
+    mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+    if (NS_WARN_IF(!mLocalStore)) {
+      return;
+    }
+  }
+#endif
+
   nsAutoString id;
 
   aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
   nsAtomString attrstr(aAttribute);
 
   nsAutoString valuestr;
   aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
 
   nsAutoCString utf8uri;
   nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
   NS_ConvertUTF8toUTF16 uri(utf8uri);
 
   bool hasAttr;
+#ifdef MOZ_NEW_XULSTORE
   rv = XULStore::HasValue(uri, id, attrstr, hasAttr);
+#else
+  rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
+#endif
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   if (hasAttr && valuestr.IsEmpty()) {
+#ifdef MOZ_NEW_XULSTORE
     rv = XULStore::RemoveValue(uri, id, attrstr);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value removed");
+#else
+    mLocalStore->RemoveValue(uri, id, attrstr);
+#endif
     return;
   }
 
   // Persisting attributes to top level windows is handled by nsXULWindow.
   if (aElement->IsXULElement(nsGkAtoms::window)) {
     if (nsCOMPtr<nsIXULWindow> win =
             mDocument->GetXULWindowIfToplevelChrome()) {
       return;
     }
   }
 
+#ifdef MOZ_NEW_XULSTORE
   rv = XULStore::SetValue(uri, id, attrstr, valuestr);
+#else
+  mLocalStore->SetValue(uri, id, attrstr, valuestr);
+#endif
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "value set");
 }
 
 nsresult XULPersist::ApplyPersistentAttributes() {
   if (!mDocument) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   // For non-chrome documents, persistance is simply broken
   if (!nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Add all of the 'persisted' attributes into the content
   // model.
+#ifndef MOZ_NEW_XULSTORE
+  if (!mLocalStore) {
+    mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+    if (NS_WARN_IF(!mLocalStore)) {
+      return NS_ERROR_NOT_INITIALIZED;
+    }
+  }
+#endif
+
   ApplyPersistentAttributesInternal();
 
   return NS_OK;
 }
 
 nsresult XULPersist::ApplyPersistentAttributesInternal() {
   nsCOMArray<Element> elements;
 
   nsAutoCString utf8uri;
   nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   NS_ConvertUTF8toUTF16 uri(utf8uri);
 
   // Get a list of element IDs for which persisted values are available
+#ifdef MOZ_NEW_XULSTORE
   UniquePtr<XULStoreIterator> ids;
   rv = XULStore::GetIDs(uri, ids);
+#else
+  nsCOMPtr<nsIStringEnumerator> ids;
+  rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
+#endif
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+#ifdef MOZ_NEW_XULSTORE
   while (ids->HasMore()) {
     nsAutoString id;
     rv = ids->GetNext(&id);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+#else
+  while (1) {
+    bool hasmore = false;
+    ids->HasMore(&hasmore);
+    if (!hasmore) {
+      break;
+    }
+
+    nsAutoString id;
+    ids->GetNext(id);
+#endif
 
     // We want to hold strong refs to the elements while applying
     // persistent attributes, just in case.
     const nsTArray<Element*>* allElements = mDocument->GetAllElementsForId(id);
     if (!allElements) {
       continue;
     }
     elements.Clear();
@@ -184,31 +237,51 @@ nsresult XULPersist::ApplyPersistentAttr
   nsAutoCString utf8uri;
   nsresult rv = mDocument->GetDocumentURI()->GetSpec(utf8uri);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   NS_ConvertUTF8toUTF16 uri(utf8uri);
 
   // Get a list of attributes for which persisted values are available
+#ifdef MOZ_NEW_XULSTORE
   UniquePtr<XULStoreIterator> attrs;
   rv = XULStore::GetAttrs(uri, aID, attrs);
+#else
+  nsCOMPtr<nsIStringEnumerator> attrs;
+  rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
+#endif
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+#ifdef MOZ_NEW_XULSTORE
   while (attrs->HasMore()) {
     nsAutoString attrstr;
     rv = attrs->GetNext(&attrstr);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsAutoString value;
     rv = XULStore::GetValue(uri, aID, attrstr, value);
+#else
+  while (1) {
+    bool hasmore = PR_FALSE;
+    attrs->HasMore(&hasmore);
+    if (!hasmore) {
+      break;
+    }
+
+    nsAutoString attrstr;
+    attrs->GetNext(attrstr);
+
+    nsAutoString value;
+    rv = mLocalStore->GetValue(uri, aID, attrstr, value);
+#endif
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     RefPtr<nsAtom> attr = NS_Atomize(attrstr);
     if (NS_WARN_IF(!attr)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
--- a/dom/xul/XULPersist.h
+++ b/dom/xul/XULPersist.h
@@ -2,16 +2,20 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_XULPersist_h
 #define mozilla_dom_XULPersist_h
 
+#ifndef MOZ_NEW_XULSTORE
+class nsIXULStore;
+#endif
+
 namespace mozilla {
 namespace dom {
 
 class XULPersist final : public nsStubDocumentObserver {
  public:
   NS_DECL_ISUPPORTS
 
   explicit XULPersist(Document* aDocument);
@@ -26,16 +30,20 @@ class XULPersist final : public nsStubDo
 
  private:
   ~XULPersist();
   nsresult ApplyPersistentAttributes();
   nsresult ApplyPersistentAttributesInternal();
   nsresult ApplyPersistentAttributesToElements(const nsAString& aID,
                                                nsCOMArray<Element>& aElements);
 
+#ifndef MOZ_NEW_XULSTORE
+  nsCOMPtr<nsIXULStore> mLocalStore;
+#endif
+
   // A weak pointer to our document. Nulled out by DropDocumentReference.
   Document* MOZ_NON_OWNING_REF mDocument;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_XULPersist_h
--- a/extensions/permissions/nsPermissionManager.cpp
+++ b/extensions/permissions/nsPermissionManager.cpp
@@ -1017,18 +1017,20 @@ nsresult nsPermissionManager::OpenDataba
 
 nsresult nsPermissionManager::InitDB(bool aRemoveFile) {
   nsCOMPtr<nsIFile> permissionsFile;
   nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR,
                                        getter_AddRefs(permissionsFile));
   if (NS_FAILED(rv)) {
     rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                 getter_AddRefs(permissionsFile));
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_UNEXPECTED;
+    }
   }
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
 
   rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(PERMISSIONS_FILE_NAME));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aRemoveFile) {
     bool exists = false;
     rv = permissionsFile->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -518,21 +518,21 @@ class Compositor : public TextureSourceP
 
   // A stale Compositor has no CompositorBridgeParent; it will not process
   // frames and should not be used.
   void SetInvalid();
   bool IsValid() const override;
   CompositorBridgeParent* GetCompositorBridgeParent() const { return mParent; }
 
   /**
-   * Request the compositor to record its frames.
+   * Request the compositor to allow recording its frames.
    *
-   * For all compositors except |BasicCompositor|, this is a noop.
+   * This is a noop on |CompositorOGL|.
    */
-  virtual void RequestRecordFrames(bool aWillRecord) {}
+  virtual void RequestAllowFrameRecording(bool aWillRecord) {}
 
   /**
    * Record the current frame for readback by the |CompositionRecorder|.
    *
    * If this compositor does not support this feature, a null pointer is
    * returned instead.
    */
   already_AddRefed<RecordedFrame> RecordFrame(const TimeStamp& aTimeStamp);
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -135,16 +135,28 @@ static void NotifySubdocumentInvalidatio
         if (container) {
           nsIntRegion region =
               container->GetLocalVisibleRegion().ToUnknownRegion();
           aCallback(container, &region);
         }
       });
 }
 
+static void SetChildrenChangedRecursive(Layer* aLayer) {
+  ForEachNode<ForwardIterator>(
+      aLayer,
+      [](Layer* layer) {
+        ContainerLayer* container = layer->AsContainerLayer();
+        if (container) {
+          container->SetChildrenChanged(true);
+          container->SetInvalidCompositeRect(nullptr);
+        }
+      });
+}
+
 struct LayerPropertiesBase : public LayerProperties {
   explicit LayerPropertiesBase(Layer* aLayer)
       : mLayer(aLayer),
         mMaskLayer(nullptr),
         mVisibleRegion(mLayer->GetLocalVisibleRegion().ToUnknownRegion()),
         mPostXScale(aLayer->GetPostXScale()),
         mPostYScale(aLayer->GetPostYScale()),
         mOpacity(aLayer->GetLocalOpacity()),
@@ -442,20 +454,24 @@ struct ContainerLayerProperties : public
             // We've already seen this child in mChildren (which means it must
             // have been reordered) and invalidated its old area. We need to
             // invalidate its new area too:
             invalidateChildsCurrentArea = true;
           }
         } else {
           // |child| is new
           invalidateChildsCurrentArea = true;
+          SetChildrenChangedRecursive(child);
         }
       } else {
         // |child| is new, or was reordered to a higher index
         invalidateChildsCurrentArea = true;
+        if (!oldIndexMap.Contains(child)) {
+          SetChildrenChangedRecursive(child);
+        }
       }
       if (invalidateChildsCurrentArea) {
         LTI_DUMP(child->GetLocalVisibleRegion().ToUnknownRegion(),
                  "invalidateChildsCurrentArea");
         AddTransformedRegion(result,
                              child->GetLocalVisibleRegion().ToUnknownRegion(),
                              GetTransformForInvalidation(child));
         if (aCallback) {
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -242,24 +242,24 @@ AsyncPanZoomController* Layer::GetAsyncP
 #endif
   return mApzcs[aIndex];
 }
 
 void Layer::ScrollMetadataChanged() {
   mApzcs.SetLength(GetScrollMetadataCount());
 }
 
-void Layer::ApplyPendingUpdatesToSubtree() {
+std::unordered_set<ScrollableLayerGuid::ViewID>
+Layer::ApplyPendingUpdatesToSubtree() {
   ForEachNode<ForwardIterator>(this, [](Layer* layer) {
     layer->ApplyPendingUpdatesForThisTransaction();
   });
-
   // Once we're done recursing through the whole tree, clear the pending
   // updates from the manager.
-  Manager()->ClearPendingScrollInfoUpdate();
+  return Manager()->ClearPendingScrollInfoUpdate();
 }
 
 bool Layer::IsOpaqueForVisibility() {
   return GetEffectiveOpacity() == 1.0f &&
          GetEffectiveMixBlendMode() == CompositionOp::OP_OVER;
 }
 
 bool Layer::CanUseOpaqueSurface() {
@@ -616,17 +616,16 @@ void Layer::ApplyPendingUpdatesForThisTr
 
   for (size_t i = 0; i < mScrollMetadata.Length(); i++) {
     FrameMetrics& fm = mScrollMetadata[i].GetMetrics();
     ScrollableLayerGuid::ViewID scrollId = fm.GetScrollId();
     Maybe<ScrollUpdateInfo> update =
         Manager()->GetPendingScrollInfoUpdate(scrollId);
     if (update) {
       fm.UpdatePendingScrollInfo(update.value());
-      nsLayoutUtils::NotifyPaintSkipTransaction(scrollId);
       Mutated();
     }
   }
 }
 
 float Layer::GetLocalOpacity() {
   float opacity = mSimpleAttrs.GetOpacity();
   if (HostLayer* shadow = AsHostLayer()) opacity = shadow->GetShadowOpacity();
@@ -2259,20 +2258,27 @@ Maybe<ScrollUpdateInfo> LayerManager::Ge
   MOZ_ASSERT(GetBackendType() != LayersBackend::LAYERS_WR);
   auto it = mPendingScrollUpdates[wr::RenderRoot::Default].find(aScrollId);
   if (it != mPendingScrollUpdates[wr::RenderRoot::Default].end()) {
     return Some(it->second);
   }
   return Nothing();
 }
 
-void LayerManager::ClearPendingScrollInfoUpdate() {
+std::unordered_set<ScrollableLayerGuid::ViewID>
+LayerManager::ClearPendingScrollInfoUpdate() {
+  std::unordered_set<ScrollableLayerGuid::ViewID> scrollIds;
   for (auto renderRoot : wr::kRenderRoots) {
-    mPendingScrollUpdates[renderRoot].clear();
+    auto& updates = mPendingScrollUpdates[renderRoot];
+    for (const auto& update : updates) {
+      scrollIds.insert(update.first);
+    }
+    updates.clear();
   }
+  return scrollIds;
 }
 
 void PrintInfo(std::stringstream& aStream, HostLayer* aLayerComposite) {
   if (!aLayerComposite) {
     return;
   }
   if (const Maybe<ParentLayerIntRect>& clipRect =
           aLayerComposite->GetShadowClipRect()) {
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -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/. */
 
 #ifndef GFX_LAYERS_H
 #define GFX_LAYERS_H
 
 #include <map>
+#include <unordered_set>
 #include <stdint.h>        // for uint32_t, uint64_t, uint8_t
 #include <stdio.h>         // for FILE
 #include <sys/types.h>     // for int32_t
 #include "FrameMetrics.h"  // for FrameMetrics
 #include "Units.h"         // for LayerMargin, LayerPoint, ParentLayerIntRect
 #include "gfxContext.h"
 #include "gfxTypes.h"
 #include "gfxPoint.h"  // for gfxPoint
@@ -789,17 +790,18 @@ class LayerManager : public FrameRecorde
    * per-scrollid basis. This is used for empty transactions that push over
    * scroll position updates to the APZ code.
    */
   virtual bool SetPendingScrollUpdateForNextTransaction(
       ScrollableLayerGuid::ViewID aScrollId,
       const ScrollUpdateInfo& aUpdateInfo, wr::RenderRoot aRenderRoot);
   Maybe<ScrollUpdateInfo> GetPendingScrollInfoUpdate(
       ScrollableLayerGuid::ViewID aScrollId);
-  void ClearPendingScrollInfoUpdate();
+  std::unordered_set<ScrollableLayerGuid::ViewID>
+  ClearPendingScrollInfoUpdate();
 
  protected:
   wr::RenderRootArray<ScrollUpdatesMap> mPendingScrollUpdates;
 };
 
 /**
  * A Layer represents anything that can be rendered onto a destination
  * surface.
@@ -1478,18 +1480,21 @@ class Layer {
    */
   float GetLocalOpacity();
 
   /**
    * DRAWING PHASE ONLY
    *
    * Apply pending changes to layers before drawing them, if those
    * pending changes haven't been overridden by later changes.
+   *
+   * Returns a list of scroll ids which had pending updates.
    */
-  void ApplyPendingUpdatesToSubtree();
+  std::unordered_set<ScrollableLayerGuid::ViewID>
+  ApplyPendingUpdatesToSubtree();
 
   /**
    * DRAWING PHASE ONLY
    *
    * Write layer-subtype-specific attributes into aAttrs.  Used to
    * synchronize layer attributes to their shadows'.
    */
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) {}
--- a/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html
+++ b/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html
@@ -5,21 +5,16 @@
   <meta name="viewport" content="width=device-width; initial-scale=1.0">
   <title>Test that scrollBy() doesn't scroll more than it should</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
 function* test(testDriver) {
-  if (getPlatform() == "android") {
-    ok(true, "Skipping test on Android (bug 1547435)");
-    return;
-  }
-
   const maxSteps = 20;
   let scrollPerStep = 40;
   for (let step = 0; step < maxSteps; step++) {
     window.scrollBy(0, scrollPerStep);
     window.requestAnimationFrame(testDriver);
     yield;
   }
   is(window.scrollY, maxSteps * scrollPerStep, "Scrolled by the expected amount");
--- a/gfx/layers/basic/BasicCompositor.h
+++ b/gfx/layers/basic/BasicCompositor.h
@@ -140,17 +140,17 @@ class BasicCompositor : public Composito
   }
 
   gfx::DrawTarget* GetDrawTarget() { return mDrawTarget; }
 
   bool IsPendingComposite() override { return mIsPendingEndRemoteDrawing; }
 
   void FinishPendingComposite() override;
 
-  virtual void RequestRecordFrames(bool aWillRecord) override {
+  virtual void RequestAllowFrameRecording(bool aWillRecord) override {
     mRecordFrames = aWillRecord;
   }
 
  private:
   template <typename Geometry>
   void DrawGeometry(const Geometry& aGeometry, const gfx::Rect& aRect,
                     const gfx::IntRect& aClipRect,
                     const EffectChain& aEffectChain, gfx::Float aOpacity,
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -36,16 +36,17 @@
 #include "mozilla/gfx/Matrix.h"     // for Matrix
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/Rect.h"            // for IntRect, Rect
 #include "mozilla/layers/LayersTypes.h"  // for BufferMode::BUFFER_NONE, etc
 #include "mozilla/mozalloc.h"            // for operator new
 #include "nsCOMPtr.h"                    // for already_AddRefed
 #include "nsDebug.h"                     // for NS_ASSERTION, etc
 #include "nsISupportsImpl.h"             // for gfxContext::Release, etc
+#include "nsLayoutUtils.h"               // for nsLayoutUtils
 #include "nsPoint.h"                     // for nsIntPoint
 #include "nsRect.h"                      // for mozilla::gfx::IntRect
 #include "nsRegion.h"                    // for nsIntRegion, etc
 #include "nsTArray.h"                    // for AutoTArray
 #include "TreeTraversal.h"               // for ForEachNode
 
 class nsIWidget;
 
@@ -559,21 +560,23 @@ bool BasicLayerManager::EndTransactionIn
   mPhase = PHASE_DRAWING;
 
   SetCompositionTime(TimeStamp::Now());
 
   RenderTraceLayers(mRoot, "FF00");
 
   mTransactionIncomplete = false;
 
+  std::unordered_set<ScrollableLayerGuid::ViewID> scrollIdsUpdated;
+
   if (mRoot) {
     if (aFlags & END_NO_COMPOSITE) {
       // Apply pending tree updates before recomputing effective
       // properties.
-      mRoot->ApplyPendingUpdatesToSubtree();
+      scrollIdsUpdated = mRoot->ApplyPendingUpdatesToSubtree();
     }
 
     // Need to do this before we call ApplyDoubleBuffering,
     // which depends on correct effective transforms
     if (mTarget) {
       mSnapEffectiveTransforms =
           !mTarget->GetDrawTarget()->GetUserData(&sDisablePixelSnapping);
     } else {
@@ -619,16 +622,24 @@ bool BasicLayerManager::EndTransactionIn
       // Clear out target if we have a complete transaction.
       mTarget = nullptr;
     }
   }
 
   if (mRoot) {
     mAnimationReadyTime = TimeStamp::Now();
     mRoot->StartPendingAnimations(mAnimationReadyTime);
+
+    // Once we're sure we're not going to fall back to a full paint,
+    // notify the scroll frames which had pending updates.
+    if (!mTransactionIncomplete) {
+      for (ScrollableLayerGuid::ViewID scrollId : scrollIdsUpdated) {
+        nsLayoutUtils::NotifyPaintSkipTransaction(scrollId);
+      }
+    }
   }
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   Log();
   MOZ_LAYERS_LOG(("]----- EndTransaction"));
 #endif
 
   // Go back to the construction phase if the transaction isn't complete.
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -296,17 +296,17 @@ bool ClientLayerManager::EndTransactionI
 
   ClientLayer* root = ClientLayer::ToClientLayer(GetRoot());
 
   mTransactionIncomplete = false;
   mQueuedAsyncPaints = false;
 
   // Apply pending tree updates before recomputing effective
   // properties.
-  GetRoot()->ApplyPendingUpdatesToSubtree();
+  auto scrollIdsUpdated = GetRoot()->ApplyPendingUpdatesToSubtree();
 
   mPaintedLayerCallback = aCallback;
   mPaintedLayerCallbackData = aCallbackData;
 
   GetRoot()->ComputeEffectiveTransforms(Matrix4x4());
 
   // Skip the painting if the device is in device-reset status.
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
@@ -316,16 +316,24 @@ bool ClientLayerManager::EndTransactionI
       mLastPaintTime = TimeStamp::Now() - start;
     } else {
       root->RenderLayer();
     }
   } else {
     gfxCriticalNote << "LayerManager::EndTransaction skip RenderLayer().";
   }
 
+  // Once we're sure we're not going to fall back to a full paint,
+  // notify the scroll frames which had pending updates.
+  if (!mTransactionIncomplete) {
+    for (ScrollableLayerGuid::ViewID scrollId : scrollIdsUpdated) {
+      nsLayoutUtils::NotifyPaintSkipTransaction(scrollId);
+    }
+  }
+
   if (!mRepeatTransaction && !GetRoot()->GetInvalidRegion().IsEmpty()) {
     GetRoot()->Mutated();
   }
 
   if (!mIsRepeatTransaction) {
     mAnimationReadyTime = TimeStamp::Now();
     GetRoot()->StartPendingAnimations(mAnimationReadyTime);
   }
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -452,16 +452,21 @@ bool ClientTiledPaintedLayer::RenderLowP
 void ClientTiledPaintedLayer::EndPaint() {
   mPaintData.mLastScrollOffset = mPaintData.mScrollOffset;
   mPaintData.mPaintFinished = true;
   mPaintData.mFirstPaint = false;
   TILING_LOG("TILING %p: Paint finished\n", this);
 }
 
 void ClientTiledPaintedLayer::RenderLayer() {
+  if (!ClientManager()->IsRepeatTransaction()) {
+    // Only paint the mask layers on the first transaction.
+    RenderMaskLayers(this);
+  }
+
   LayerManager::DrawPaintedLayerCallback callback =
       ClientManager()->GetPaintedLayerCallback();
   void* data = ClientManager()->GetPaintedLayerCallbackData();
 
   IntSize layerSize = mVisibleRegion.GetBounds().ToUnknownRect().Size();
   IntSize tileSize = gfx::gfxVars::TileSize();
   bool isHalfTileWidthOrHeight = layerSize.width <= tileSize.width / 2 ||
                                  layerSize.height <= tileSize.height / 2;
@@ -535,19 +540,16 @@ void ClientTiledPaintedLayer::RenderLaye
   }
 
   if (!callback) {
     ClientManager()->SetTransactionIncomplete();
     return;
   }
 
   if (!ClientManager()->IsRepeatTransaction()) {
-    // Only paint the mask layers on the first transaction.
-    RenderMaskLayers(this);
-
     // For more complex cases we need to calculate a bunch of metrics before we
     // can do the paint.
     BeginPaint();
     if (mPaintData.mPaintFinished) {
       return;
     }
 
     // Make sure that tiles that fall outside of the visible region or outside
--- a/gfx/layers/composite/ColorLayerComposite.cpp
+++ b/gfx/layers/composite/ColorLayerComposite.cpp
@@ -17,29 +17,42 @@
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 void ColorLayerComposite::RenderLayer(const gfx::IntRect& aClipRect,
                                       const Maybe<gfx::Polygon>& aGeometry) {
-  Rect rect(GetBounds());
-
   const Matrix4x4& transform = GetEffectiveTransform();
 
-  RenderWithAllMasks(this, mCompositor, aClipRect,
-                     [&](EffectChain& effectChain, const IntRect& clipRect) {
-                       GenEffectChain(effectChain);
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+  // On desktop we want to draw a single rectangle to avoid possible
+  // seams if we're resampling. On mobile we'd prefer to use the accurate
+  // region for better performance.
+  LayerIntRegion drawRegion = GetLocalVisibleRegion();
+#else
+  LayerIntRegion drawRegion = ViewAs<LayerPixel>(GetBounds());
+#endif
 
-                       mCompositor->DrawGeometry(rect, clipRect, effectChain,
-                                                 GetEffectiveOpacity(),
-                                                 transform, aGeometry);
-                     });
+  for (auto iter = drawRegion.RectIter(); !iter.Done(); iter.Next()) {
+    const LayerIntRect& rect = iter.Get();
+    Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height());
+
+    RenderWithAllMasks(this, mCompositor, aClipRect,
+                       [&](EffectChain& effectChain, const IntRect& clipRect) {
+                         GenEffectChain(effectChain);
 
+                         mCompositor->DrawGeometry(
+                             graphicsRect, clipRect, effectChain,
+                             GetEffectiveOpacity(), transform, aGeometry);
+                       });
+  }
+
+  Rect rect(GetBounds());
   mCompositor->DrawDiagnostics(DiagnosticFlags::COLOR, rect, aClipRect,
                                transform);
 }
 
 void ColorLayerComposite::GenEffectChain(EffectChain& aEffect) {
   aEffect.mLayerRef = this;
   aEffect.mPrimaryEffect = new EffectSolidColor(GetColor());
 }
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -891,17 +891,17 @@ void LayerManagerComposite::Render(const
                                    const nsIntRegion& aOpaqueRegion) {
   AUTO_PROFILER_LABEL("LayerManagerComposite::Render", GRAPHICS);
 
   if (mDestroyed || !mCompositor || mCompositor->IsDestroyed()) {
     NS_WARNING("Call on destroyed layer manager");
     return;
   }
 
-  mCompositor->RequestRecordFrames(!!mCompositionRecorder);
+  mCompositor->RequestAllowFrameRecording(!!mCompositionRecorder);
 
   ClearLayerFlags(mRoot);
 
   // At this time, it doesn't really matter if these preferences change
   // during the execution of the function; we should be safe in all
   // permutations. However, may as well just get the values onces and
   // then use them, just in case the consistency becomes important in
   // the future.
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -444,22 +444,27 @@ CompositorD3D11::CreateRenderTargetFromS
 
   RefPtr<CompositingRenderTargetD3D11> rt =
       new CompositingRenderTargetD3D11(texture, aRect.TopLeft());
   rt->SetSize(aRect.Size());
 
   return rt.forget();
 }
 
+bool CompositorD3D11::ShouldAllowFrameRecording() const {
+#ifdef MOZ_GECKO_PROFILER
+  return mAllowFrameRecording || profiler_feature_active(ProfilerFeature::Screenshots);
+#else
+  return mAllowFrameRecording;
+#endif
+}
+
 already_AddRefed<CompositingRenderTarget>
 CompositorD3D11::GetWindowRenderTarget() const {
-#ifndef MOZ_GECKO_PROFILER
-  return nullptr;
-#else
-  if (!profiler_feature_active(ProfilerFeature::Screenshots)) {
+  if (!ShouldAllowFrameRecording()) {
     return nullptr;
   }
 
   if (!mDefaultRT) {
     return nullptr;
   }
 
   const IntSize size = mDefaultRT->GetSize();
@@ -490,17 +495,16 @@ CompositorD3D11::GetWindowRenderTarget()
   }
 
   const RefPtr<ID3D11Texture2D> sourceTexture = mDefaultRT->GetD3D11Texture();
   mContext->CopyResource(rtTexture, sourceTexture);
 
   return RefPtr<CompositingRenderTarget>(
              static_cast<CompositingRenderTarget*>(mWindowRTCopy))
       .forget();
-#endif
 }
 
 bool CompositorD3D11::ReadbackRenderTarget(CompositingRenderTarget* aSource,
                                            AsyncReadbackBuffer* aDest) {
   RefPtr<CompositingRenderTargetD3D11> srcTexture =
       static_cast<CompositingRenderTargetD3D11*>(aSource);
   RefPtr<AsyncReadbackBufferD3D11> destBuffer =
       static_cast<AsyncReadbackBufferD3D11*>(aDest);
--- a/gfx/layers/d3d11/CompositorD3D11.h
+++ b/gfx/layers/d3d11/CompositorD3D11.h
@@ -134,16 +134,20 @@ class CompositorD3D11 : public Composito
 
   // For TextureSourceProvider.
   ID3D11Device* GetD3D11Device() const override { return mDevice; }
 
   ID3D11Device* GetDevice() { return mDevice; }
 
   ID3D11DeviceContext* GetDC() { return mContext; }
 
+  virtual void RequestAllowFrameRecording(bool aWillRecord) override {
+    mAllowFrameRecording = aWillRecord;
+  }
+
  private:
   enum Severity {
     Recoverable,
     DebugAssert,
     Critical,
   };
 
   void HandleError(HRESULT hr, Severity aSeverity = DebugAssert);
@@ -204,16 +208,29 @@ class CompositorD3D11 : public Composito
 
   ID3D11VertexShader* GetVSForGeometry(const gfx::Rect& aRect,
                                        const bool aUseBlendShader,
                                        const MaskType aMaskType);
 
   template <typename VertexType>
   void SetVertexBuffer(ID3D11Buffer* aBuffer);
 
+  /**
+   * Whether or not the recorder should be recording frames.
+   *
+   * When this returns true, the CompositorD3D11 will allocate and return window
+   * render targets from |GetWindowRenderTarget|, which otherwise returns
+   * nullptr.
+   *
+   * This will be true when either we are recording a profile with screenshots
+   * enabled or the |LayerManagerComposite| has requested us to record frames
+   * for the |CompositionRecorder|.
+   */
+  bool ShouldAllowFrameRecording() const;
+
   RefPtr<ID3D11DeviceContext> mContext;
   RefPtr<ID3D11Device> mDevice;
   RefPtr<IDXGISwapChain> mSwapChain;
   RefPtr<CompositingRenderTargetD3D11> mDefaultRT;
   RefPtr<CompositingRenderTargetD3D11> mCurrentRT;
   mutable RefPtr<CompositingRenderTargetD3D11> mWindowRTCopy;
 
   RefPtr<ID3D11Query> mQuery;
@@ -235,16 +252,17 @@ class CompositorD3D11 : public Composito
 
   gfx::IntRegion mFrontBufferInvalid;
   gfx::IntRegion mBackBufferInvalid;
   // This is the clip rect applied to the default DrawTarget (i.e. the window)
   gfx::IntRect mCurrentClip;
 
   bool mVerifyBuffersFailed;
   bool mUseMutexOnPresent;
+  bool mAllowFrameRecording;
 };
 
 namespace TexSlot {
 static const int RGB = 0;
 static const int Y = 1;
 static const int Cb = 2;
 static const int Cr = 3;
 static const int RGBWhite = 4;
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -364,17 +364,20 @@ void WebRenderLayerManager::EndTransacti
       scrollData.SetPaintSequenceNumber(mPaintSequenceNumber);
     }
   }
   mIsFirstPaint = false;
 
   // Since we're sending a full mScrollData that will include the new scroll
   // offsets, and we can throw away the pending scroll updates we had kept for
   // an empty transaction.
-  ClearPendingScrollInfoUpdate();
+  auto scrollIdsUpdated = ClearPendingScrollInfoUpdate();
+  for (ScrollableLayerGuid::ViewID update : scrollIdsUpdated) {
+    nsLayoutUtils::NotifyPaintSkipTransaction(update);
+  }
 
   mLatestTransactionId =
       mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true);
 
   // Get the time of when the refresh driver start its tick (if available),
   // otherwise use the time of when LayerManager::BeginTransaction was called.
   TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart();
   if (!refreshStart) {
--- a/gfx/wr/ci-scripts/linux-debug-tests.sh
+++ b/gfx/wr/ci-scripts/linux-debug-tests.sh
@@ -23,15 +23,16 @@ pushd webrender
 cargo build ${CARGOFLAGS} --no-default-features
 cargo build ${CARGOFLAGS} --no-default-features --features capture
 cargo build ${CARGOFLAGS} --features capture,profiler
 cargo build ${CARGOFLAGS} --features replay
 popd
 
 pushd wrench
 cargo build ${CARGOFLAGS} --features env_logger
+OPTIMIZED=0 python script/headless.py reftest
 popd
 
 pushd examples
 cargo build ${CARGOFLAGS}
 popd
 
 cargo test ${CARGOFLAGS} --all
--- a/gfx/wr/wrench/Cargo.toml
+++ b/gfx/wr/wrench/Cargo.toml
@@ -44,21 +44,20 @@ mozangle = {version = "0.1.5", features 
 
 [target.'cfg(all(unix, not(target_os = "android")))'.dependencies]
 font-loader = "0.7"
 
 # Configuration information used when building wrench as an APK.
 [package.metadata.android]
 package_name = "org.mozilla.wrench"
 label = "Wrench"
-assets = "reftests"
 android_version = 28
 target_sdk_version = 18
 min_sdk_version = 18
 fullscreen = true
-build_targets = [ "armv7-linux-androideabi" ]
+build_targets = [ "armv7-linux-androideabi", "i686-linux-android" ]
 opengles_version_major = 3
 opengles_version_minor = 0
 [package.metadata.android.application_attributes]
 "android:hardwareAccelerated" = "true"
 [package.metadata.android.activity_attributes]
 "android:screenOrientation" = "unspecified"
 "android:uiOptions" = "none"
--- a/gfx/wr/wrench/android.txt
+++ b/gfx/wr/wrench/android.txt
@@ -1,51 +1,58 @@
 Running Wrench on Android devices.
 ==================================
 
 Setting up the environment:
 ---------------------------
 
 Follow the steps at https://github.com/tomaka/android-rs-glue#setting-up-your-environment, with exceptions:
-    - Instead of downloading the NDK and SDK, reference the ones downloaded by Gecko in ~/.mozbuild/
+    - No need to download the Android NDK and SDK, we will use the ones downloaded by Gecko in ~/.mozbuild/
 
-    - Don't install cargo-apk (currently published version has a bug related to SDK versions, hopefully 0.4.2+ will be published soon).
-      Instead, build it from source:
-        git clone https://github.com/tomaka/android-rs-glue
-        cd android-rs-glue/cargo-apk
-        cargo build
+    - Install both the i686-linux-android and armv7-linux-androideabi rust
+      targets, as the APK will include native libraries with both architectures.
+
+    - Don't install currently published version of cargo-apk (it has a bug related to SDK versions).
+      Instead, install the patched version like so:
+        cargo install --git https://github.com/staktrace/android-rs-glue --branch mozilla_master cargo-apk
 
     - Consider adding ~/.mozbuild/android-sdk-linux/platform-tools to your path, for the adb commands below.
 
 Compiling and running:
 ----------------------
 
     Compile wrench:
         cd wrench
-        <cargo-apk dir>/target/cargo-apk build
+        export ANDROID_HOME=$HOME/.mozbuild/android-sdk-linux # exact path may vary
+        export NDK_HOME=$HOME/.mozbuild/android-ndk-r17b      # exact path may vary
+        export CARGO_APK_GRADLE_COMMAND=$PWD/../../../gradlew
+        cargo-apk build
 
     Install the APK:
         adb install -r ../target/android-artifacts/app/build/outputs/apk/app-debug.apk
 
     Set command line arguments:
         adb shell
-        echo "load reftests/aa/rounded-rects.yaml" >/sdcard/wrench_args
+        mkdir /sdcard/wrench
+        echo "load reftests/aa/rounded-rects.yaml" >/sdcard/wrench/args
         exit
 
+    Push reftests (if you need these files for what you're doing):
+        adb push reftests /sdcard/wrench/
+
     Run the application:
         adb shell am start -n org.mozilla.wrench/rust.wrench.MainActivity
             (or click the icon in the launcher)
 
 Release mode:
 -------------
 
-    Building in release does work, but you need to sign and align the APK manually. We will automate this in future. For now,
-
-    Generate a signing key (once):
-        keytool -genkey -v -keystore wrench-release-key.keystore -alias wrench_key -keyalg RSA -keysize 2048 -validity 10000
+    Building in release does work as well. Use the following steps to compile wrench:
+        cd wrench
+        export ANDROID_HOME=$HOME/.mozbuild/android-sdk-linux # exact path may vary
+        export NDK_HOME=$HOME/.mozbuild/android-ndk           # exact path may vary
+        export CARGO_APK_GRADLE_COMMAND=$PWD/../../../gradlew
+        cargo-apk build --release
+        ../../../mobile/android/debug_sign_tool.py ../target/android-artifacts/app/build/outputs/apk/app-release-unsigned.apk
 
-    Sign the APK:
-        jarsigner -verbose -keystore wrench-release-key.keystore <apk path>/app-release-unsigned.apk wrench_key
-
-    Align the APK:
-        zipalign -f -v 4 <apk path>/app-release-unsigned.apk <apk path>/app-release.apk
-
-    Now the signed APK can be installed - you will probably need to uninstall the debug APK if you have that installed first.
+    Now the APK at ../target/android-artifacts/app/build/outputs/apk/app-release-unsigned.apk
+    should be signed and installable (you may need to uninstall the debug APK first if you
+    have that installed).
--- a/gfx/wr/wrench/src/main.rs
+++ b/gfx/wr/wrench/src/main.rs
@@ -373,17 +373,21 @@ fn rawtest(mut wrench: Wrench, window: &
 
 fn reftest<'a>(
     mut wrench: Wrench,
     window: &mut WindowWrapper,
     subargs: &clap::ArgMatches<'a>,
     rx: Receiver<NotifierEvent>
 ) -> usize {
     let dim = window.get_inner_size();
-    let base_manifest = Path::new("reftests/reftest.list");
+    let base_manifest = if cfg!(target_os = "android") {
+        Path::new("/sdcard/wrench/reftests/reftest.list")
+    } else {
+        Path::new("reftests/reftest.list")
+    };
     let specific_reftest = subargs.value_of("REFTEST").map(|x| Path::new(x));
     let mut reftest_options = ReftestOptions::default();
     if let Some(allow_max_diff) = subargs.value_of("fuzz_tolerance") {
         reftest_options.allow_max_difference = allow_max_diff.parse().unwrap_or(1);
         reftest_options.allow_num_differences = dim.width as usize * dim.height as usize;
     }
     let num_failures = ReftestHarness::new(&mut wrench, window, &rx)
         .run(base_manifest, specific_reftest, &reftest_options);
@@ -395,21 +399,25 @@ fn main() {
     #[cfg(feature = "env_logger")]
     env_logger::init();
 
     let args_yaml = load_yaml!("args.yaml");
     let clap = clap::App::from_yaml(args_yaml)
         .setting(clap::AppSettings::ArgRequiredElseHelp);
 
     // On android devices, attempt to read command line arguments
-    // from a text file located at /sdcard/wrench_args.
+    // from a text file located at /sdcard/wrench/args.
     let args = if cfg!(target_os = "android") {
+        // get full backtraces by default because it's hard to request
+        // externally on android
+        std::env::set_var("RUST_BACKTRACE", "full");
+
         let mut args = vec!["wrench".to_string()];
 
-        if let Ok(wrench_args) = fs::read_to_string("/sdcard/wrench_args") {
+        if let Ok(wrench_args) = fs::read_to_string("/sdcard/wrench/args") {
             for arg in wrench_args.split_whitespace() {
                 args.push(arg.to_string());
             }
         }
 
         clap.get_matches_from(&args)
     } else {
         clap.get_matches()
--- a/gfx/wr/wrench/src/reftest.rs
+++ b/gfx/wr/wrench/src/reftest.rs
@@ -23,18 +23,24 @@ use yaml_frame_reader::YamlFrameReader;
 
 
 #[cfg(target_os = "windows")]
 const PLATFORM: &str = "win";
 #[cfg(target_os = "linux")]
 const PLATFORM: &str = "linux";
 #[cfg(target_os = "macos")]
 const PLATFORM: &str = "mac";
-#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
+#[cfg(target_os = "android")]
+const PLATFORM: &str = "android";
+#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "android")))]
 const PLATFORM: &str = "other";
+#[cfg(debug)]
+const MODE: &str = "debug";
+#[cfg(not(debug))]
+const MODE: &str = "release";
 
 const OPTION_DISABLE_SUBPX: &str = "disable-subpixel";
 const OPTION_DISABLE_AA: &str = "disable-aa";
 const OPTION_DISABLE_DUAL_SOURCE_BLENDING: &str = "disable-dual-source-blending";
 const OPTION_ALLOW_MIPMAPS: &str = "allow-mipmaps";
 
 pub struct ReftestOptions {
     // These override values that are lower.
@@ -234,16 +240,24 @@ impl ReftestManifest {
                         let include = dir.join(tokens[1]);
 
                         reftests.append(
                             &mut ReftestManifest::new(include.as_path(), options).reftests,
                         );
 
                         break;
                     }
+                    platform if platform.starts_with("skip_on") => {
+                        // e.g. skip_on(android,debug) will skip only when
+                        // running on a debug android build.
+                        let (_, args, _) = parse_function(platform);
+                        if args.iter().all(|arg| arg == &PLATFORM || arg == &MODE) {
+                            break;
+                        }
+                    }
                     platform if platform.starts_with("platform") => {
                         let (_, args, _) = parse_function(platform);
                         if !args.iter().any(|arg| arg == &PLATFORM) {
                             // Skip due to platform not matching
                             break;
                         }
                     }
                     function if function.starts_with("zoom") => {
--- a/hal/windows/WindowsProcessPriority.cpp
+++ b/hal/windows/WindowsProcessPriority.cpp
@@ -15,16 +15,22 @@ namespace hal_impl {
 bool SetProcessPrioritySupported() { return true; }
 
 void SetProcessPriority(int aPid, ProcessPriority aPriority) {
   HAL_LOG("WindowsProcessPriority - SetProcessPriority(%d, %s)\n", aPid,
           ProcessPriorityToString(aPriority));
 
   nsAutoHandle processHandle(
       ::OpenProcess(PROCESS_SET_INFORMATION, FALSE, aPid));
+#ifdef DEBUG
+  if (!processHandle) {
+    printf_stderr("::OpenProcess() failed with error %#08x\n",
+                  ::GetLastError());
+  }
+#endif  // DEBUG
   MOZ_ASSERT(processHandle);
   if (processHandle) {
     DWORD priority = NORMAL_PRIORITY_CLASS;
     if (aPriority == PROCESS_PRIORITY_BACKGROUND) {
       priority = IDLE_PRIORITY_CLASS;
     } else if (aPriority == PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) {
       priority = BELOW_NORMAL_PRIORITY_CLASS;
     }
--- a/image/ImageCacheKey.cpp
+++ b/image/ImageCacheKey.cpp
@@ -8,20 +8,22 @@
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Move.h"
 #include "mozilla/Unused.h"
 #include "nsContentUtils.h"
 #include "nsICookieService.h"
 #include "nsLayoutUtils.h"
 #include "nsString.h"
 #include "mozilla/AntiTrackingCommon.h"
+#include "mozilla/HashFunctions.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/Document.h"
+#include "nsHashKeys.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 namespace image {
 
@@ -38,48 +40,57 @@ static Maybe<uint64_t> BlobSerial(nsIURI
   return Nothing();
 }
 
 ImageCacheKey::ImageCacheKey(nsIURI* aURI, const OriginAttributes& aAttrs,
                              Document* aDocument)
     : mURI(aURI),
       mOriginAttributes(aAttrs),
       mControlledDocument(GetSpecialCaseDocumentToken(aDocument, aURI)),
+      mTopLevelBaseDomain(GetTopLevelBaseDomain(aDocument, aURI)),
       mIsChrome(false) {
   if (SchemeIs("blob")) {
     mBlobSerial = BlobSerial(mURI);
   } else if (SchemeIs("chrome")) {
     mIsChrome = true;
   }
 }
 
 ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther)
     : mURI(aOther.mURI),
       mBlobSerial(aOther.mBlobSerial),
       mBlobRef(aOther.mBlobRef),
       mOriginAttributes(aOther.mOriginAttributes),
       mControlledDocument(aOther.mControlledDocument),
+      mTopLevelBaseDomain(aOther.mTopLevelBaseDomain),
       mHash(aOther.mHash),
       mIsChrome(aOther.mIsChrome) {}
 
 ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther)
     : mURI(std::move(aOther.mURI)),
       mBlobSerial(std::move(aOther.mBlobSerial)),
       mBlobRef(std::move(aOther.mBlobRef)),
       mOriginAttributes(aOther.mOriginAttributes),
       mControlledDocument(aOther.mControlledDocument),
+      mTopLevelBaseDomain(aOther.mTopLevelBaseDomain),
       mHash(aOther.mHash),
       mIsChrome(aOther.mIsChrome) {}
 
 bool ImageCacheKey::operator==(const ImageCacheKey& aOther) const {
   // Don't share the image cache between a controlled document and anything
   // else.
   if (mControlledDocument != aOther.mControlledDocument) {
     return false;
   }
+  // Don't share the image cache between two top-level documents of different
+  // base domains.
+  if (!mTopLevelBaseDomain.Equals(aOther.mTopLevelBaseDomain,
+                                  nsCaseInsensitiveCStringComparator())) {
+    return false;
+  }
   // The origin attributes always have to match.
   if (mOriginAttributes != aOther.mOriginAttributes) {
     return false;
   }
   if (mBlobSerial || aOther.mBlobSerial) {
     if (mBlobSerial && mBlobRef.IsEmpty()) {
       EnsureBlobRef();
     }
@@ -122,17 +133,18 @@ void ImageCacheKey::EnsureHash() const {
     }
     hash = HashGeneric(*mBlobSerial, HashString(mBlobRef));
   } else {
     nsAutoCString spec;
     Unused << mURI->GetSpec(spec);
     hash = HashString(spec);
   }
 
-  hash = AddToHash(hash, HashString(suffix), HashString(ptr));
+  hash = AddToHash(hash, HashString(suffix), HashString(mTopLevelBaseDomain),
+                   HashString(ptr));
   mHash.emplace(hash);
 }
 
 bool ImageCacheKey::SchemeIs(const char* aScheme) {
   bool matches = false;
   return NS_SUCCEEDED(mURI->SchemeIs(aScheme, &matches)) && matches;
 }
 
@@ -148,33 +160,50 @@ void* ImageCacheKey::GetSpecialCaseDocum
 
   // For controlled documents, we cast the pointer into a void* to avoid
   // dereferencing it (since we only use it for comparisons).
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (swm && aDocument->GetController().isSome()) {
     return aDocument;
   }
 
+  return nullptr;
+}
+
+/* static */
+nsCString ImageCacheKey::GetTopLevelBaseDomain(Document* aDocument,
+                                               nsIURI* aURI) {
+  if (!aDocument || !aDocument->GetInnerWindow()) {
+    return EmptyCString();
+  }
+
   // If the window is 3rd party resource, let's see if first-party storage
   // access is granted for this image.
   if (nsContentUtils::IsThirdPartyTrackingResourceWindow(
           aDocument->GetInnerWindow())) {
     return nsContentUtils::StorageDisabledByAntiTracking(aDocument, aURI)
-               ? aDocument
-               : nullptr;
+               ? aDocument->GetBaseDomain()
+               : EmptyCString();
   }
 
   // Another scenario is if this image is a 3rd party resource loaded by a
   // first party context. In this case, we should check if the nsIChannel has
   // been marked as tracking resource, but we don't have the channel yet at
   // this point.  The best approach here is to be conservative: if we are sure
-  // that the permission is granted, let's return a nullptr. Otherwise, let's
-  // make a unique image cache.
+  // that the permission is granted, let's return 0. Otherwise, let's make a
+  // unique image cache per the top-level document eTLD+1.
   if (!AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(
           aDocument->GetInnerWindow(), aURI)) {
-    return aDocument;
+    nsPIDOMWindowOuter* top = aDocument->GetInnerWindow()->GetScriptableTop();
+    nsPIDOMWindowInner* topInner = top->GetCurrentInnerWindow();
+    if (!topInner) {
+      return aDocument
+          ->GetBaseDomain();  // because we don't have anything better!
+    }
+    return topInner->GetExtantDoc() ? topInner->GetExtantDoc()->GetBaseDomain()
+                                    : EmptyCString();
   }
 
-  return nullptr;
+  return EmptyCString();
 }
 
 }  // namespace image
 }  // namespace mozilla
--- a/image/ImageCacheKey.h
+++ b/image/ImageCacheKey.h
@@ -56,29 +56,35 @@ class ImageCacheKey final {
 
   /// A token indicating which service worker controlled document this entry
   /// belongs to, if any.
   void* ControlledDocument() const { return mControlledDocument; }
 
  private:
   bool SchemeIs(const char* aScheme);
 
-  // For ServiceWorker and for anti-tracking we need to use the document as
+  // For ServiceWorker we need to use the document as
   // token for the key. All those exceptions are handled by this method.
   static void* GetSpecialCaseDocumentToken(dom::Document* aDocument,
                                            nsIURI* aURI);
 
+  // For anti-tracking we need to use the top-level document's base domain for
+  // the key. This is handled by this method.
+  static nsCString GetTopLevelBaseDomain(dom::Document* aDocument,
+                                         nsIURI* aURI);
+
   void EnsureHash() const;
   void EnsureBlobRef() const;
 
   nsCOMPtr<nsIURI> mURI;
   Maybe<uint64_t> mBlobSerial;
   mutable nsCString mBlobRef;
   OriginAttributes mOriginAttributes;
   void* mControlledDocument;
+  nsCString mTopLevelBaseDomain;
   mutable Maybe<PLDHashNumber> mHash;
   bool mIsChrome;
 };
 
 }  // namespace image
 }  // namespace mozilla
 
 #endif  // mozilla_image_src_ImageCacheKey_h
--- a/js/public/AllocPolicy.h
+++ b/js/public/AllocPolicy.h
@@ -20,49 +20,67 @@
 extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx);
 
 namespace js {
 
 enum class AllocFunction { Malloc, Calloc, Realloc };
 
 /* Base class allocation policies providing allocation methods. */
 class AllocPolicyBase {
-  const arena_id_t& arenaId_;
-
- protected:
-  arena_id_t getArenaId() { return arenaId_; }
-
  public:
-  explicit AllocPolicyBase(const arena_id_t& arenaId = js::MallocArena)
-      : arenaId_(arenaId) {}
+  template <typename T>
+  T* maybe_pod_arena_malloc(arena_id_t arenaId, size_t numElems) {
+    return js_pod_arena_malloc<T>(arenaId, numElems);
+  }
+  template <typename T>
+  T* maybe_pod_arena_calloc(arena_id_t arenaId, size_t numElems) {
+    return js_pod_arena_calloc<T>(arenaId, numElems);
+  }
+  template <typename T>
+  T* maybe_pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, size_t newSize) {
+    return js_pod_arena_realloc<T>(arenaId, p, oldSize, newSize);
+  }
+  template <typename T>
+  T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) {
+    return maybe_pod_arena_malloc<T>(arenaId, numElems);
+  }
+  template <typename T>
+  T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) {
+    return maybe_pod_arena_calloc<T>(arenaId, numElems);
+  }
+  template <typename T>
+  T* pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, size_t newSize) {
+    return maybe_pod_arena_realloc<T>(arenaId, p, oldSize, newSize);
+  }
 
   template <typename T>
   T* maybe_pod_malloc(size_t numElems) {
-    return js_pod_arena_malloc<T>(getArenaId(), numElems);
+    return maybe_pod_arena_malloc<T>(js::MallocArena, numElems);
   }
   template <typename T>
   T* maybe_pod_calloc(size_t numElems) {
-    return js_pod_arena_calloc<T>(getArenaId(), numElems);
+    return maybe_pod_arena_calloc<T>(js::MallocArena, numElems);
   }
   template <typename T>
   T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
-    return js_pod_arena_realloc<T>(getArenaId(), p, oldSize, newSize);
+    return maybe_pod_arena_realloc<T>(js::MallocArena, p, oldSize, newSize);
   }
   template <typename T>
   T* pod_malloc(size_t numElems) {
-    return maybe_pod_malloc<T>(numElems);
+    return pod_arena_malloc<T>(js::MallocArena, numElems);
   }
   template <typename T>
   T* pod_calloc(size_t numElems) {
-    return maybe_pod_calloc<T>(numElems);
+    return pod_arena_calloc<T>(js::MallocArena, numElems);
   }
   template <typename T>
   T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
-    return maybe_pod_realloc<T>(p, oldSize, newSize);
+    return pod_arena_realloc<T>(js::MallocArena, p, oldSize, newSize);
   }
+
   template <typename T>
   void free_(T* p, size_t numElems = 0) {
     js_free(p);
   }
 };
 
 /* Policy for using system memory functions and doing no error reporting. */
 class SystemAllocPolicy : public AllocPolicyBase {
@@ -84,62 +102,77 @@ MOZ_COLD JS_FRIEND_API void ReportOutOfM
  */
 class TempAllocPolicy : public AllocPolicyBase {
   JSContext* const cx_;
 
   /*
    * Non-inline helper to call JSRuntime::onOutOfMemory with minimal
    * code bloat.
    */
-  JS_FRIEND_API void* onOutOfMemory(AllocFunction allocFunc, size_t nbytes,
-                                    void* reallocPtr = nullptr);
+  JS_FRIEND_API void* onOutOfMemory(arena_id_t arenaId, AllocFunction allocFunc,
+                                    size_t nbytes, void* reallocPtr = nullptr);
 
   template <typename T>
-  T* onOutOfMemoryTyped(AllocFunction allocFunc, size_t numElems,
-                        void* reallocPtr = nullptr) {
+  T* onOutOfMemoryTyped(arena_id_t arenaId, AllocFunction allocFunc,
+                        size_t numElems, void* reallocPtr = nullptr) {
     size_t bytes;
     if (MOZ_UNLIKELY(!CalculateAllocSize<T>(numElems, &bytes))) {
       return nullptr;
     }
-    return static_cast<T*>(onOutOfMemory(allocFunc, bytes, reallocPtr));
+    return static_cast<T*>(
+        onOutOfMemory(arenaId, allocFunc, bytes, reallocPtr));
   }
 
  public:
-  MOZ_IMPLICIT TempAllocPolicy(JSContext* cx,
-                               const arena_id_t& arenaId = js::MallocArena)
-      : AllocPolicyBase(arenaId), cx_(cx) {}
+  MOZ_IMPLICIT TempAllocPolicy(JSContext* cx) : cx_(cx) {}
 
   template <typename T>
-  T* pod_malloc(size_t numElems) {
-    T* p = this->maybe_pod_malloc<T>(numElems);
+  T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) {
+    T* p = this->maybe_pod_arena_malloc<T>(arenaId, numElems);
     if (MOZ_UNLIKELY(!p)) {
-      p = onOutOfMemoryTyped<T>(AllocFunction::Malloc, numElems);
+      p = onOutOfMemoryTyped<T>(arenaId, AllocFunction::Malloc, numElems);
     }
     return p;
   }
 
   template <typename T>
-  T* pod_calloc(size_t numElems) {
-    T* p = this->maybe_pod_calloc<T>(numElems);
+  T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) {
+    T* p = this->maybe_pod_arena_calloc<T>(arenaId, numElems);
     if (MOZ_UNLIKELY(!p)) {
-      p = onOutOfMemoryTyped<T>(AllocFunction::Calloc, numElems);
+      p = onOutOfMemoryTyped<T>(arenaId, AllocFunction::Calloc, numElems);
     }
     return p;
   }
 
   template <typename T>
-  T* pod_realloc(T* prior, size_t oldSize, size_t newSize) {
-    T* p2 = this->maybe_pod_realloc<T>(prior, oldSize, newSize);
+  T* pod_arena_realloc(arena_id_t arenaId, T* prior, size_t oldSize, size_t newSize) {
+    T* p2 = this->maybe_pod_arena_realloc<T>(arenaId, prior, oldSize, newSize);
     if (MOZ_UNLIKELY(!p2)) {
-      p2 = onOutOfMemoryTyped<T>(AllocFunction::Realloc, newSize, prior);
+      p2 = onOutOfMemoryTyped<T>(arenaId, AllocFunction::Realloc, newSize,
+                                 prior);
     }
     return p2;
   }
 
   template <typename T>
+  T* pod_malloc(size_t numElems) {
+    return pod_arena_malloc<T>(js::MallocArena, numElems);
+  }
+
+  template <typename T>
+  T* pod_calloc(size_t numElems) {
+    return pod_arena_calloc<T>(js::MallocArena, numElems);
+  }
+
+  template <typename T>
+  T* pod_realloc(T* prior, size_t oldSize, size_t newSize) {
+    return pod_arena_realloc<T>(js::MallocArena, prior, oldSize, newSize);
+  }
+
+  template <typename T>
   void free_(T* p, size_t numElems = 0) {
     js_free(p);
   }
 
   JS_FRIEND_API void reportAllocOverflow() const;
 
   bool checkSimulatedOOM() const {
     if (js::oom::ShouldFailWithOOM()) {
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -146,16 +146,78 @@ struct ArrayOps {
 
 template <>
 JS::Result<> ArrayOps<uint32_t>::storeResult(JSContext* cx, uint32_t v,
                                              MutableHandleValue result) {
   result.setNumber(v);
   return Ok();
 }
 
+template <>
+struct ArrayOps<int64_t> {
+  static JS::Result<int64_t> convertValue(JSContext* cx, HandleValue v) {
+    BigInt* bi = ToBigInt(cx, v);
+    if (!bi) {
+      return cx->alreadyReportedError();
+    }
+    return BigInt::toInt64(bi);
+  }
+
+  static JS::Result<int64_t> convertValue(JSContext* cx, HandleValue v,
+                                           MutableHandleValue result) {
+    BigInt* bi = ToBigInt(cx, v);
+    if (!bi) {
+      return cx->alreadyReportedError();
+    }
+    result.setBigInt(bi);
+    return BigInt::toInt64(bi);
+  }
+
+  static JS::Result<> storeResult(JSContext* cx, int64_t v,
+                                  MutableHandleValue result) {
+    BigInt* bi = BigInt::createFromInt64(cx, v);
+    if (!bi) {
+      return cx->alreadyReportedError();
+    }
+    result.setBigInt(bi);
+    return Ok();
+  }
+};
+
+template <>
+struct ArrayOps<uint64_t> {
+  static JS::Result<uint64_t> convertValue(JSContext* cx, HandleValue v) {
+    BigInt* bi = ToBigInt(cx, v);
+    if (!bi) {
+      return cx->alreadyReportedError();
+    }
+    return BigInt::toUint64(bi);
+  }
+
+  static JS::Result<uint64_t> convertValue(JSContext* cx, HandleValue v,
+                                            MutableHandleValue result) {
+    BigInt* bi = ToBigInt(cx, v);
+    if (!bi) {
+      return cx->alreadyReportedError();
+    }
+    result.setBigInt(bi);
+    return BigInt::toUint64(bi);
+  }
+
+  static JS::Result<> storeResult(JSContext* cx, uint64_t v,
+                                  MutableHandleValue result) {
+    BigInt* bi = BigInt::createFromUint64(cx, v);
+    if (!bi) {
+      return cx->alreadyReportedError();
+    }
+    result.setBigInt(bi);
+    return Ok();
+  }
+};
+
 template <template <typename> class F, typename... Args>
 bool perform(JSContext* cx, HandleValue objv, HandleValue idxv, Args... args) {
   Rooted<TypedArrayObject*> view(cx, nullptr);
   if (!GetSharedTypedArray(cx, objv, &view)) {
     return false;
   }
   uint32_t offset;
   if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
@@ -173,19 +235,21 @@ bool perform(JSContext* cx, HandleValue 
       return F<uint16_t>::run(cx, viewData.cast<uint16_t*>() + offset, args...);
     case Scalar::Int32:
       return F<int32_t>::run(cx, viewData.cast<int32_t*>() + offset, args...);
     case Scalar::Uint32:
       return F<uint32_t>::run(cx, viewData.cast<uint32_t*>() + offset, args...);
     case Scalar::Float32:
     case Scalar::Float64:
     case Scalar::Uint8Clamped:
+      return ReportBadArrayType(cx);
     case Scalar::BigInt64:
+      return F<int64_t>::run(cx, viewData.cast<int64_t*>() + offset, args...);
     case Scalar::BigUint64:
-      return ReportBadArrayType(cx);
+      return F<uint64_t>::run(cx, viewData.cast<uint64_t*>() + offset, args...);
     case Scalar::MaxTypedArrayViewType:
     case Scalar::Int64:
       break;
   }
   MOZ_CRASH("Unsupported TypedArray type");
 }
 
 template <typename T>
@@ -300,70 +364,71 @@ static bool AtomicsBinop(JSContext* cx, 
   static uint16_t operate(SharedMem<uint16_t*> addr, uint16_t v) { \
     return NAME(addr, v);                                          \
   }                                                                \
   static int32_t operate(SharedMem<int32_t*> addr, int32_t v) {    \
     return NAME(addr, v);                                          \
   }                                                                \
   static uint32_t operate(SharedMem<uint32_t*> addr, uint32_t v) { \
     return NAME(addr, v);                                          \
+  }                                                                \
+  static int64_t operate(SharedMem<int64_t*> addr, int64_t v) {    \
+    return NAME(addr, v);                                          \
+  }                                                                \
+  static uint64_t operate(SharedMem<uint64_t*> addr, uint64_t v) { \
+    return NAME(addr, v);                                          \
   }
 
 class PerformAdd {
  public:
   INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
-  static int32_t perform(int32_t x, int32_t y) { return x + y; }
 };
 
 bool js::atomics_add(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return AtomicsBinop<PerformAdd>(cx, args.get(0), args.get(1), args.get(2),
                                   args.rval());
 }
 
 class PerformSub {
  public:
   INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
-  static int32_t perform(int32_t x, int32_t y) { return x - y; }
 };
 
 bool js::atomics_sub(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return AtomicsBinop<PerformSub>(cx, args.get(0), args.get(1), args.get(2),
                                   args.rval());
 }
 
 class PerformAnd {
  public:
   INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
-  static int32_t perform(int32_t x, int32_t y) { return x & y; }
 };
 
 bool js::atomics_and(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return AtomicsBinop<PerformAnd>(cx, args.get(0), args.get(1), args.get(2),
                                   args.rval());
 }
 
 class PerformOr {
  public:
   INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
-  static int32_t perform(int32_t x, int32_t y) { return x | y; }
 };
 
 bool js::atomics_or(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return AtomicsBinop<PerformOr>(cx, args.get(0), args.get(1), args.get(2),
                                  args.rval());
 }
 
 class PerformXor {
  public:
   INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
-  static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
 };
 
 bool js::atomics_xor(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return AtomicsBinop<PerformXor>(cx, args.get(0), args.get(1), args.get(2),
                                   args.rval());
 }
 
@@ -489,39 +554,20 @@ FutexThread::WaitResult js::atomics_wait
 }
 
 FutexThread::WaitResult js::atomics_wait_impl(
     JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
     int64_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
   return AtomicsWait(cx, sarb, byteOffset, value, timeout);
 }
 
-bool js::atomics_wait(JSContext* cx, unsigned argc, Value* vp) {
-  CallArgs args = CallArgsFromVp(argc, vp);
-  HandleValue objv = args.get(0);
-  HandleValue idxv = args.get(1);
-  HandleValue valv = args.get(2);
-  HandleValue timeoutv = args.get(3);
-  MutableHandleValue r = args.rval();
-
-  Rooted<TypedArrayObject*> view(cx, nullptr);
-  if (!GetSharedTypedArray(cx, objv, &view)) {
-    return false;
-  }
-  if (view->type() != Scalar::Int32) {
-    return ReportBadArrayType(cx);
-  }
-  uint32_t offset;
-  if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
-    return false;
-  }
-  int32_t value;
-  if (!ToInt32(cx, valv, &value)) {
-    return false;
-  }
+template <typename T>
+static bool DoAtomicsWait(JSContext* cx, Handle<TypedArrayObject*> view,
+                          uint32_t offset, T value, HandleValue timeoutv,
+                          MutableHandleValue r) {
   mozilla::Maybe<mozilla::TimeDuration> timeout;
   if (!timeoutv.isUndefined()) {
     double timeout_ms;
     if (!ToNumber(cx, timeoutv, &timeout_ms)) {
       return false;
     }
     if (!mozilla::IsNaN(timeout_ms)) {
       if (timeout_ms < 0) {
@@ -532,17 +578,17 @@ bool js::atomics_wait(JSContext* cx, uns
       }
     }
   }
 
   Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
   // The computation will not overflow because range checks have been
   // performed.
   uint32_t byteOffset =
-      offset * sizeof(int32_t) +
+      offset * sizeof(T) +
       (view->dataPointerShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
        sab->dataPointerShared().unwrap(/* arithmetic */));
 
   switch (atomics_wait_impl(cx, sab->rawBufferObject(), byteOffset, value,
                             timeout)) {
     case FutexThread::WaitResult::NotEqual:
       r.setString(cx->names().futexNotEqual);
       return true;
@@ -554,16 +600,54 @@ bool js::atomics_wait(JSContext* cx, uns
       return true;
     case FutexThread::WaitResult::Error:
       return false;
     default:
       MOZ_CRASH("Should not happen");
   }
 }
 
+bool js::atomics_wait(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  HandleValue objv = args.get(0);
+  HandleValue idxv = args.get(1);
+  HandleValue valv = args.get(2);
+  HandleValue timeoutv = args.get(3);
+  MutableHandleValue r = args.rval();
+
+  Rooted<TypedArrayObject*> view(cx, nullptr);
+  if (!GetSharedTypedArray(cx, objv, &view)) {
+    return false;
+  }
+
+  if (view->type() != Scalar::Int32 && view->type() != Scalar::BigInt64) {
+    return ReportBadArrayType(cx);
+  }
+
+  uint32_t offset;
+  if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
+    return false;
+  }
+
+  if (view->type() == Scalar::Int32) {
+    int32_t value;
+    if (!ToInt32(cx, valv, &value)) {
+      return false;
+    }
+    return DoAtomicsWait(cx, view, offset, value, timeoutv, r);
+  }
+
+  MOZ_ASSERT(view->type() == Scalar::BigInt64);
+  RootedBigInt valbi(cx, ToBigInt(cx, valv));
+  if (!valbi) {
+    return false;
+  }
+  return DoAtomicsWait(cx, view, offset, BigInt::toInt64(valbi), timeoutv, r);
+}
+
 int64_t js::atomics_notify_impl(SharedArrayRawBuffer* sarb, uint32_t byteOffset,
                                 int64_t count) {
   // Validation should ensure this does not happen.
   MOZ_ASSERT(sarb, "notify is only applicable to shared memory");
 
   AutoLockFutexAPI lock;
 
   int64_t woken = 0;
@@ -600,19 +684,21 @@ bool js::atomics_notify(JSContext* cx, u
   HandleValue idxv = args.get(1);
   HandleValue countv = args.get(2);
   MutableHandleValue r = args.rval();
 
   Rooted<TypedArrayObject*> view(cx, nullptr);
   if (!GetSharedTypedArray(cx, objv, &view)) {
     return false;
   }
-  if (view->type() != Scalar::Int32) {
+  if (view->type() != Scalar::Int32 && view->type() != Scalar::BigInt64) {
     return ReportBadArrayType(cx);
   }
+  uint32_t elementSize =
+      view->type() == Scalar::Int32 ? sizeof(int32_t) : sizeof(int64_t);
   uint32_t offset;
   if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
     return false;
   }
   int64_t count;
   if (countv.isUndefined()) {
     count = -1;
   } else {
@@ -625,17 +711,17 @@ bool js::atomics_notify(JSContext* cx, u
     }
     count = dcount > INT64_MAX ? -1 : int64_t(dcount);
   }
 
   Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
   // The computation will not overflow because range checks have been
   // performed.
   uint32_t byteOffset =
-      offset * sizeof(int32_t) +
+      offset * elementSize +
       (view->dataPointerShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
        sab->dataPointerShared().unwrap(/* arithmetic */));
 
   r.setNumber(
       double(atomics_notify_impl(sab->rawBufferObject(), byteOffset, count)));
 
   return true;
 }
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -234,75 +234,16 @@ skip script test262/built-ins/TypedArray
 skip script test262/built-ins/TypedArrayConstructors/internals/HasProperty/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/key-is-not-canonical-index.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/key-is-not-integer.js
 skip script test262/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1531647
-skip script test262/built-ins/Atomics/store/bigint/bad-range.js
-skip script test262/built-ins/Atomics/store/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/store/bigint/good-views.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-add.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-or.js
-skip script test262/built-ins/Atomics/wait/bigint/bad-range.js
-skip script test262/built-ins/Atomics/wait/bigint/non-shared-bufferdata-throws.js
-skip script test262/built-ins/Atomics/wait/bigint/nan-for-timeout.js
-skip script test262/built-ins/Atomics/wait/bigint/waiterlist-block-indexedposition-wake.js
-skip script test262/built-ins/Atomics/wait/bigint/negative-index-throws.js
-skip script test262/built-ins/Atomics/wait/bigint/value-not-equal.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-and.js
-skip script test262/built-ins/Atomics/wait/bigint/negative-timeout.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-sub.js
-skip script test262/built-ins/Atomics/wait/bigint/negative-timeout-agent.js
-skip script test262/built-ins/Atomics/wait/bigint/waiterlist-order-of-operations-is-fifo.js
-skip script test262/built-ins/Atomics/wait/bigint/null-bufferdata-throws.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-exchange.js
-skip script test262/built-ins/Atomics/wait/bigint/out-of-range-index-throws.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-store.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-compareExchange.js
-skip script test262/built-ins/Atomics/wait/bigint/false-for-timeout.js
-skip script test262/built-ins/Atomics/wait/bigint/false-for-timeout-agent.js
-skip script test262/built-ins/Atomics/wait/bigint/was-woken-before-timeout.js
-skip script test262/built-ins/Atomics/wait/bigint/non-bigint64-typedarray-throws.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-no-operation.js
-skip script test262/built-ins/Atomics/wait/bigint/no-spurious-wakeup-on-xor.js
-skip script test262/built-ins/Atomics/add/bigint/bad-range.js
-skip script test262/built-ins/Atomics/add/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/add/bigint/good-views.js
-skip script test262/built-ins/Atomics/xor/bigint/bad-range.js
-skip script test262/built-ins/Atomics/xor/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/xor/bigint/good-views.js
-skip script test262/built-ins/Atomics/notify/bigint/bad-range.js
-skip script test262/built-ins/Atomics/notify/bigint/non-shared-bufferdata-throws.js
-skip script test262/built-ins/Atomics/notify/bigint/notify-all-on-loc.js
-skip script test262/built-ins/Atomics/notify/bigint/null-bufferdata-throws.js
-skip script test262/built-ins/Atomics/notify/bigint/non-bigint64-typedarray-throws.js
-skip script test262/built-ins/Atomics/load/bigint/bad-range.js
-skip script test262/built-ins/Atomics/load/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/load/bigint/good-views.js
-skip script test262/built-ins/Atomics/isLockFree/bigint/expected-return-value.js
-skip script test262/built-ins/Atomics/and/bigint/bad-range.js
-skip script test262/built-ins/Atomics/and/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/and/bigint/good-views.js
-skip script test262/built-ins/Atomics/or/bigint/bad-range.js
-skip script test262/built-ins/Atomics/or/bigint/good-views.js
-skip script test262/built-ins/Atomics/or/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/compareExchange/bigint/bad-range.js
-skip script test262/built-ins/Atomics/compareExchange/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/compareExchange/bigint/good-views.js
-skip script test262/built-ins/Atomics/exchange/bigint/bad-range.js
-skip script test262/built-ins/Atomics/exchange/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/exchange/bigint/good-views.js
-skip script test262/built-ins/Atomics/sub/bigint/bad-range.js
-skip script test262/built-ins/Atomics/sub/bigint/nonshared-int-views.js
-skip script test262/built-ins/Atomics/sub/bigint/good-views.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317405
 skip script test262/language/computed-property-names/class/static/method-number.js
 skip script test262/language/computed-property-names/class/static/method-string.js
 skip script test262/language/computed-property-names/class/static/method-symbol.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1286997
 # Bug 1286997 probably doesn't cover all spec violations.
 skip script test262/language/expressions/assignment/S11.13.1_A5_T5.js
--- a/js/src/util/AllocPolicy.cpp
+++ b/js/src/util/AllocPolicy.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "js/AllocPolicy.h"
 
 #include "vm/JSContext.h"
 
 using namespace js;
 
-void* TempAllocPolicy::onOutOfMemory(AllocFunction allocFunc, size_t nbytes,
+void* TempAllocPolicy::onOutOfMemory(arena_id_t arenaId,
+                                     AllocFunction allocFunc, size_t nbytes,
                                      void* reallocPtr) {
-  return cx_->onOutOfMemory(allocFunc, this->getArenaId(), nbytes, reallocPtr);
+  return cx_->onOutOfMemory(allocFunc, arenaId, nbytes, reallocPtr);
 }
 
 void TempAllocPolicy::reportAllocOverflow() const {
   ReportAllocationOverflow(cx_);
 }
--- a/js/src/util/StringBuffer.cpp
+++ b/js/src/util/StringBuffer.cpp
@@ -13,17 +13,17 @@
 #include "vm/StringType-inl.h"
 
 using namespace js;
 
 template <typename CharT, class Buffer>
 static CharT* ExtractWellSized(Buffer& cb) {
   size_t capacity = cb.capacity();
   size_t length = cb.length();
-  TempAllocPolicy allocPolicy = cb.allocPolicy();
+  StringBufferAllocPolicy allocPolicy = cb.allocPolicy();
 
   CharT* buf = cb.extractOrCopyRawBuffer();
   if (!buf) {
     return nullptr;
   }
 
   /* For medium/big buffers, avoid wasting more than 1/4 of the memory. */
   MOZ_ASSERT(capacity >= length);
@@ -45,17 +45,17 @@ char16_t* StringBuffer::stealChars() {
   }
 
   return ExtractWellSized<char16_t>(twoByteChars());
 }
 
 bool StringBuffer::inflateChars() {
   MOZ_ASSERT(isLatin1());
 
-  TwoByteCharBuffer twoByte(TempAllocPolicy{cx_, arenaId_});
+  TwoByteCharBuffer twoByte(StringBufferAllocPolicy{cx_, arenaId_});
 
   /*
    * Note: we don't use Vector::capacity() because it always returns a
    * value >= sInlineCapacity. Since Latin1CharBuffer::sInlineCapacity >
    * TwoByteCharBuffer::sInlineCapacitychars, we'd always malloc here.
    */
   size_t capacity = Max(reserved_, latin1Chars().length());
   if (!twoByte.reserve(capacity)) {
--- a/js/src/util/StringBuffer.h
+++ b/js/src/util/StringBuffer.h
@@ -11,31 +11,72 @@
 #include "mozilla/MaybeOneOf.h"
 #include "mozilla/Utf8.h"
 
 #include "js/Vector.h"
 #include "vm/JSContext.h"
 
 namespace js {
 
+class StringBufferAllocPolicy {
+  TempAllocPolicy impl_;
+
+  const arena_id_t& arenaId_;
+
+ public:
+  StringBufferAllocPolicy(JSContext* cx, const arena_id_t& arenaId)
+      : impl_(cx), arenaId_(arenaId) {}
+
+  template <typename T>
+  T* maybe_pod_malloc(size_t numElems) {
+    return impl_.maybe_pod_arena_malloc<T>(arenaId_, numElems);
+  }
+  template <typename T>
+  T* maybe_pod_calloc(size_t numElems) {
+    return impl_.maybe_pod_arena_calloc<T>(arenaId_, numElems);
+  }
+  template <typename T>
+  T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
+    return impl_.maybe_pod_arena_realloc<T>(arenaId_, p, oldSize, newSize);
+  }
+  template <typename T>
+  T* pod_malloc(size_t numElems) {
+    return impl_.pod_arena_malloc<T>(arenaId_, numElems);
+  }
+  template <typename T>
+  T* pod_calloc(size_t numElems) {
+    return impl_.pod_arena_calloc<T>(arenaId_, numElems);
+  }
+  template <typename T>
+  T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
+    return impl_.pod_arena_realloc<T>(arenaId_, p, oldSize, newSize);
+  }
+  template <typename T>
+  void free_(T* p, size_t numElems = 0) {
+    impl_.free_(p, numElems);
+  }
+  void reportAllocOverflow() const { impl_.reportAllocOverflow(); }
+  bool checkSimulatedOOM() const { return impl_.checkSimulatedOOM(); }
+};
+
 /*
  * String builder that eagerly checks for over-allocation past the maximum
  * string length.
  *
  * Any operation which would exceed the maximum string length causes an
  * exception report on the context and results in a failed return value.
  *
  * Well-sized extractions (which waste no more than 1/4 of their char
  * buffer space) are guaranteed for strings built by this interface.
  * See |extractWellSized|.
  */
 class StringBuffer {
  protected:
   template <typename CharT>
-  using BufferType = Vector<CharT, 64 / sizeof(CharT)>;
+  using BufferType = Vector<CharT, 64 / sizeof(CharT), StringBufferAllocPolicy>;
 
   /*
    * The Vector's buffer may be either stolen or copied, so we need to use
    * TempAllocPolicy and account for the memory manually when stealing.
    */
   using Latin1CharBuffer = BufferType<Latin1Char>;
   using TwoByteCharBuffer = BufferType<char16_t>;
 
@@ -96,17 +137,17 @@ class StringBuffer {
 
   template <typename CharT>
   JSFlatString* finishStringInternal(JSContext* cx);
 
  public:
   explicit StringBuffer(JSContext* cx,
                         const arena_id_t& arenaId = js::MallocArena)
       : cx_(cx), arenaId_(arenaId), reserved_(0) {
-    cb.construct<Latin1CharBuffer>(TempAllocPolicy{cx_, arenaId_});
+    cb.construct<Latin1CharBuffer>(StringBufferAllocPolicy{cx_, arenaId_});
   }
 
   void clear() {
     if (isLatin1()) {
       latin1Chars().clear();
     } else {
       twoByteChars().clear();
     }
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -665,17 +665,21 @@ bool XPCJSContext::InterruptCallback(JSC
 
   // Check if we're waiting to cancel JS when going back/forward in a tab.
   if (sTabIdToCancelContentJS) {
     if (nsIBrowserChild* browserChild = win->GetBrowserChild()) {
       uint64_t tabId;
       browserChild->GetTabId(&tabId);
       if (sTabIdToCancelContentJS == tabId) {
         // Don't add this page to the BF cache, since we're cancelling its JS.
-        win->GetExtantDoc()->GetTopLevelContentDocument()->DisallowBFCaching();
+        if (Document* doc = win->GetExtantDoc()) {
+          if (Document* topLevelDoc = doc->GetTopLevelContentDocument()) {
+            topLevelDoc->DisallowBFCaching();
+          }
+        }
         sTabIdToCancelContentJS = 0;
         return false;
       }
     }
   }
 
   // If there's no limit, or we're within the limit, let it go.
   if (limit == 0 || duration.ToSeconds() < limit / 2.0) {
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -287,23 +287,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   gConstructorProperties['WeakSet'] =
     constructorProps([]);
 
   gPrototypeProperties['DataView'] =
     ["constructor", "buffer", "byteLength", "byteOffset", Symbol.toStringTag,
      "getInt8", "getUint8", "getInt16", "getUint16",
      "getInt32", "getUint32", "getFloat32", "getFloat64",
      "setInt8", "setUint8", "setInt16", "setUint16",
-     "setInt32", "setUint32", "setFloat32", "setFloat64"];
-  if (isNightlyBuild) {
-    for (const p of ["getBigInt64", "getBigUint64",
-                     "setBigInt64", "setBigUint64"]) {
-      gPrototypeProperties['DataView'].push(p)
-    }
-  }
+     "setInt32", "setUint32", "setFloat32", "setFloat64",
+     "getBigInt64", "getBigUint64", "setBigInt64", "setBigUint64"]
   gConstructorProperties['DataView'] = constructorProps([]);
 
   // Sort an array that may contain symbols as well as strings.
   function sortProperties(arr) {
     function sortKey(prop) {
       return typeof prop + ":" + prop.toString();
     }
     arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1);
@@ -1084,40 +1079,30 @@ for (var prop of props) {
                    "setInt32", "setUint32", "setFloat32", "setFloat64"];
 
       for (const f of get) {
         let x = t[f](0);
         is(x, 0, `${f} is 0 for "${constructor}"`);
         is(typeof x, 'number', `typeof ${f} is number for "${constructor}"`);
       }
 
-      if (isNightlyBuild) {
-        for (const f of ["getBigInt64", "getBigUint64"]) {
-          let x = t[f](0);
-          is(x, BigInt(0), `${f} is 0n for "${constructor}"`);
-          is(typeof x, 'bigint', `typeof ${f} is bigint for "${constructor}"`);
-        }
-      } else {
-        for (const f of ["getBigInt64", "getBigUint64",
-                         "setBigInt64", "setBugUint64"]) {
-          is(t[f], undefined, `${constructor} has no "${f}"`);
-          ok(!(f in t), `no "${f}" in ${constructor}`);
-        }
+      for (const f of ["getBigInt64", "getBigUint64"]) {
+        let x = t[f](0);
+        is(x, BigInt(0), `${f} is 0n for "${constructor}"`);
+        is(typeof x, 'bigint', `typeof ${f} is bigint for "${constructor}"`);
       }
 
       for (let i = 0; i < set.length; i++) {
         t[set[i]](0, 13);
         is(t[get[i]](0), 13, `${get[i]}(0) afer ${set[i]}(0, 13) is 13 for "${constructor}"`);
       }
 
-      if (isNightlyBuild) {
-        for (const k of ["BigInt64", "BigUint64"]) {
-          t["set" + k](0, BigInt(13));
-          is(t["get" + k](0), BigInt(13), `get${k}(0) afer set${k}(0, 13n) is 13n for "${constructor}"`);
-        }
+      for (const k of ["BigInt64", "BigUint64"]) {
+        t["set" + k](0, BigInt(13));
+        is(t["get" + k](0), BigInt(13), `get${k}(0) afer set${k}(0, 13n) is 13n for "${constructor}"`);
       }
     }
   }
 
   function testNumber() {
     // We don't actually support Xrays to Number yet.  This is testing
     // that case.  If we add such support, we might have to start
     // using a different non-Xrayed class here, if we can find one.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/opacity-keep-intermediate-surface-too-long-ref.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8">
+<title>Clicking the canvas should turn it green (and shift it slighly)</title>
+
+<style>
+
+canvas {
+  border: 10px solid black;
+}
+
+.opacity {
+  opacity: 0.8;
+}
+
+</style>
+
+</head><body><div style="transform: translateX(1px)">
+  <div class="wrapper" style="transform: translateX(1px);">
+    <div class="opacity">
+      <div class="border">
+        <canvas id="canvas" width="200" height="200"></canvas>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script>
+
+var canvas = document.getElementById('canvas');
+canvas.width = 200;
+canvas.height = 200;
+var ctx = canvas.getContext('2d');
+ctx.fillStyle = 'lime';
+ctx.fillRect(0, 0, 200, 200);
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/layers/opacity-keep-intermediate-surface-too-long.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait"><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8">
+<title>Clicking the canvas should turn it green (and shift it slighly)</title>
+
+<style>
+
+canvas {
+  border: 10px solid black;
+}
+
+.opacity {
+  opacity: 0.8;
+}
+
+</style>
+
+</head><body><div style="transform: translateX(1px)"><!-- create reference frame -->
+  <div class="wrapper"><!-- this starts out without a transform but later gets transformed -->
+    <div class="opacity"><!-- this creates a ContainerLayer with an intermediate surface for group opacity -->
+      <div class="border"><!-- this adds another visual element into the group opacity -->
+        <canvas id="canvas" width="200" height="200"></canvas><!-- this causes all ancestor effects to become active ContainerLayers -->
+      </div>
+    </div>
+  </div>
+</div>
+
+<script>
+
+var canvas = document.getElementById('canvas');
+var wrapper = document.querySelector('.wrapper');
+canvas.width = 200;
+canvas.height = 200;
+var ctx = canvas.getContext('2d');
+ctx.fillStyle = 'red';
+ctx.fillRect(0, 0, 200, 200);
+
+function doTest() {
+  ctx.fillStyle = 'lime';
+  ctx.fillRect(0, 0, 200, 200);
+  wrapper.style.transform = 'translateX(1px)';
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest);
+
+</script>
+</body></html>
--- a/layout/reftests/layers/reftest.list
+++ b/layout/reftests/layers/reftest.list
@@ -35,8 +35,10 @@ skip-if(!asyncPan) == fixed-pos-scrolled
 #   Direct2D 1.1 works (as a proxy for Windows 7 SP1 + Platform Update or higher), OR
 #   The GPU process has been forced on.
 # If these conditions are met, but the GPU process is not on, these tests will turn on
 # and compare false.
 skip-if(!browserIsRemote||!d2d||gpuProcess) == data:text/plain,FAIL about:blank
 skip-if(!gpuProcessForceEnabled||gpuProcess) == data:text/plain,FAIL about:blank
 
 fuzzy-if(webrender,0-1,0-8033) == opacity-background-1.html opacity-background-1-ref.html
+
+== opacity-keep-intermediate-surface-too-long.html opacity-keep-intermediate-surface-too-long-ref.html
--- a/layout/reftests/xul/reftest.list
+++ b/layout/reftests/xul/reftest.list
@@ -1,14 +1,14 @@
 == css-flex-1.xul css-flex-1-ref.html
 
 == menuitem-key.xul menuitem-key-ref.xul
 # these random-if(Android) are due to differences between Android Native & Xul, see bug 732569
-random-if(Android) == menulist-shrinkwrap-1.xul menulist-shrinkwrap-1-ref.xul
-random-if(Android) == menulist-shrinkwrap-2.xul menulist-shrinkwrap-2-ref.xul
+random-if(Android) == chrome://reftest/content/xul/menulist-shrinkwrap-1.xul chrome://reftest/content/xul/menulist-shrinkwrap-1-ref.xul
+random-if(Android) == chrome://reftest/content/xul/menulist-shrinkwrap-2.xul chrome://reftest/content/xul/menulist-shrinkwrap-2-ref.xul
 == textbox-overflow-1.xul textbox-overflow-1-ref.xul # for bug 749658
 # accesskeys are not normally displayed on Mac, so set a pref to enable them
 pref(ui.key.menuAccessKey,18) == chrome://reftest/content/xul/accesskey.xul accesskey-ref.xul
 pref(layout.css.xul-tree-pseudos.content.enabled,true) fuzzy-if(xulRuntime.widgetToolkit=="gtk3",0-1,0-11) == tree-row-outline-1.xul tree-row-outline-1-ref.xul # win8: bug 1254832
 skip-if(!cocoaWidget) fails-if(webrender&&cocoaWidget) == chrome://reftest/content/xul/mac-tab-toolbar.xul chrome://reftest/content/xul/mac-tab-toolbar-ref.xul
 pref(layout.css.xul-tree-pseudos.content.enabled,true) != tree-row-outline-1.xul tree-row-outline-1-notref.xul
 == text-crop.xul text-crop-ref.xul
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == text-small-caps-1.xul text-small-caps-1-ref.xul
--- a/layout/style/res/plaintext.css
+++ b/layout/style/res/plaintext.css
@@ -7,8 +7,20 @@ pre {
   word-wrap: break-word;
   -moz-control-character-visibility: visible;
 }
 
 /* Make text go with the rules of dir=auto, but allow it to be overriden if 'Switch Text Direction' is triggered */
 html:not([dir]) pre { /* Not a UA sheet, so doesn't use :-moz-has-dir-attr */
   unicode-bidi: plaintext;
 }
+
+/* NOTE(emilio): For some reason some pages, mainly bing.com, load a bunch of
+ * scripts in zero-size <object> elements, see bug 1548449.
+ *
+ * Line-breaking such documents is useless and pretty expensive, so only render
+ * them if there's a viewport. Sigh.
+ */
+@media (width: 0) or (height: 0) {
+  :root {
+    display: none;
+  }
+}
--- a/layout/style/test/test_garbage_at_end_of_declarations.html
+++ b/layout/style/test/test_garbage_at_end_of_declarations.html
@@ -124,16 +124,17 @@ function test_property(property)
     test_value(info.initial_values[idx]);
   for (idx in info.other_values)
     test_value(info.other_values[idx]);
 }
 
 // To avoid triggering the slow script dialog, we have to test one
 // property at a time.
 SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
 var props = [];
 for (var prop in gCSSProperties)
   props.push(prop);
 props = props.reverse();
 function do_one() {
   if (props.length == 0) {
     SimpleTest.finish();
     return;
--- a/layout/style/test/test_property_syntax_errors.html
+++ b/layout/style/test/test_property_syntax_errors.html
@@ -14,18 +14,18 @@
 <div id="content" style="display: none">
 
 <div id="testnode"></div>
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-SimpleTest.requestLongerTimeout(2);
 SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(4);
 
 function check_not_accepted(decl, property, info, badval)
 {
   decl.setProperty(property, badval, "");
 
   is(decl.getPropertyValue(property), "",
      "invalid value '" + badval + "' not accepted for '" + property +
      "' property");
--- a/memory/build/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -482,19 +482,20 @@ DEFINE_GLOBAL(size_t) gChunkNumPages = k
 // Number of pages necessary for a chunk header.
 DEFINE_GLOBAL(size_t)
 gChunkHeaderNumPages =
     ((sizeof(arena_chunk_t) + sizeof(arena_chunk_map_t) * (gChunkNumPages - 1) +
       gPageSizeMask) &
      ~gPageSizeMask) >>
     gPageSize2Pow;
 
-// Max size class for arenas.
+// One chunk, minus the header, minus a guard page
 DEFINE_GLOBAL(size_t)
-gMaxLargeClass = kChunkSize - (gChunkHeaderNumPages << gPageSize2Pow);
+gMaxLargeClass =
+    kChunkSize - gPageSize - (gChunkHeaderNumPages << gPageSize2Pow);
 
 // Various sanity checks that regard configuration.
 GLOBAL_ASSERT(1ULL << gPageSize2Pow == gPageSize,
               "Page size is not a power of two");
 GLOBAL_ASSERT(kQuantum >= sizeof(void*));
 GLOBAL_ASSERT(kQuantum <= gPageSize);
 GLOBAL_ASSERT(kChunkSize >= gPageSize);
 GLOBAL_ASSERT(kQuantum * 4 <= kChunkSize);
@@ -2279,25 +2280,31 @@ void arena_t::InitChunk(arena_chunk_t* a
   arena_run_t* run = (arena_run_t*)(uintptr_t(aChunk) +
                                     (gChunkHeaderNumPages << gPageSize2Pow));
 #endif
 
   for (i = 0; i < gChunkHeaderNumPages; i++) {
     aChunk->map[i].bits = 0;
   }
   aChunk->map[i].bits = gMaxLargeClass | flags;
-  for (i++; i < gChunkNumPages - 1; i++) {
+  for (i++; i < gChunkNumPages - 2; i++) {
     aChunk->map[i].bits = flags;
   }
-  aChunk->map[gChunkNumPages - 1].bits = gMaxLargeClass | flags;
+  aChunk->map[gChunkNumPages - 2].bits = gMaxLargeClass | flags;
+  // Mark the guard page as decommited.
+  aChunk->map[gChunkNumPages - 1].bits = CHUNK_MAP_DECOMMITTED;
 
 #ifdef MALLOC_DECOMMIT
   // Start out decommitted, in order to force a closer correspondence
   // between dirty pages and committed untouched pages.
-  pages_decommit(run, gMaxLargeClass);
+  pages_decommit(run, gMaxLargeClass + gPageSize);
+#else
+  // Only decommit the last page as a guard.
+  pages_decommit((void*)(uintptr_t(aChunk) + kChunkSize - gPageSize),
+                 gPageSize);
 #endif
   mStats.committed += gChunkHeaderNumPages;
 
   // Insert the run into the tree of available runs.
   mRunsAvail.Insert(&aChunk->map[gChunkHeaderNumPages]);
 
 #ifdef MALLOC_DOUBLE_PURGE
   new (&aChunk->chunks_madvised_elem) DoublyLinkedListElement<arena_chunk_t>();
@@ -2393,18 +2400,20 @@ void arena_t::Purge(bool aAll) {
   // number of system calls, even if a chunk has only been partially
   // purged.
   while (mNumDirty > (dirty_max >> 1)) {
 #ifdef MALLOC_DOUBLE_PURGE
     bool madvised = false;
 #endif
     chunk = mChunksDirty.Last();
     MOZ_DIAGNOSTIC_ASSERT(chunk);
-
-    for (i = gChunkNumPages - 1; chunk->ndirty > 0; i--) {
+    // Last page is DECOMMITTED as a guard page.
+    MOZ_ASSERT((chunk->map[gChunkNumPages - 1].bits & CHUNK_MAP_DECOMMITTED) !=
+               0);
+    for (i = gChunkNumPages - 2; chunk->ndirty > 0; i--) {
       MOZ_DIAGNOSTIC_ASSERT(i >= gChunkHeaderNumPages);
 
       if (chunk->map[i].bits & CHUNK_MAP_DIRTY) {
 #ifdef MALLOC_DECOMMIT
         const size_t free_operation = CHUNK_MAP_DECOMMITTED;
 #else
         const size_t free_operation = CHUNK_MAP_MADVISED;
 #endif
@@ -2465,17 +2474,17 @@ void arena_t::Purge(bool aAll) {
 
 void arena_t::DallocRun(arena_run_t* aRun, bool aDirty) {
   arena_chunk_t* chunk;
   size_t size, run_ind, run_pages;
 
   chunk = GetChunkForPtr(aRun);
   run_ind = (size_t)((uintptr_t(aRun) - uintptr_t(chunk)) >> gPageSize2Pow);
   MOZ_DIAGNOSTIC_ASSERT(run_ind >= gChunkHeaderNumPages);
-  MOZ_DIAGNOSTIC_ASSERT(run_ind < gChunkNumPages);
+  MOZ_RELEASE_ASSERT(run_ind < gChunkNumPages - 1);
   if ((chunk->map[run_ind].bits & CHUNK_MAP_LARGE) != 0) {
     size = chunk->map[run_ind].bits & ~gPageSizeMask;
   } else {
     size = aRun->mBin->mRunSize;
   }
   run_pages = (size >> gPageSize2Pow);
 
   // Mark pages as unallocated in the chunk map.
@@ -2500,17 +2509,17 @@ void arena_t::DallocRun(arena_run_t* aRu
       chunk->map[run_ind + i].bits &= ~(CHUNK_MAP_LARGE | CHUNK_MAP_ALLOCATED);
     }
   }
   chunk->map[run_ind].bits = size | (chunk->map[run_ind].bits & gPageSizeMask);
   chunk->map[run_ind + run_pages - 1].bits =
       size | (chunk->map[run_ind + run_pages - 1].bits & gPageSizeMask);
 
   // Try to coalesce forward.
-  if (run_ind + run_pages < gChunkNumPages &&
+  if (run_ind + run_pages < gChunkNumPages - 1 &&
       (chunk->map[run_ind + run_pages].bits & CHUNK_MAP_ALLOCATED) == 0) {
     size_t nrun_size = chunk->map[run_ind + run_pages].bits & ~gPageSizeMask;
 
     // Remove successor from tree of available runs; the coalesced run is
     // inserted later.
     mRunsAvail.Remove(&chunk->map[run_ind + run_pages]);
 
     size += nrun_size;
@@ -3274,16 +3283,18 @@ static inline void arena_dalloc(void* aP
   auto arena = chunk->arena;
   MOZ_ASSERT(arena);
   MOZ_DIAGNOSTIC_ASSERT(arena->mMagic == ARENA_MAGIC);
   MOZ_RELEASE_ASSERT(!aArena || arena == aArena);
 
   MutexAutoLock lock(arena->mLock);
   size_t pageind = aOffset >> gPageSize2Pow;
   arena_chunk_map_t* mapelm = &chunk->map[pageind];
+  MOZ_RELEASE_ASSERT((mapelm->bits & CHUNK_MAP_DECOMMITTED) == 0,
+                     "Freeing in decommitted page.");
   MOZ_RELEASE_ASSERT((mapelm->bits & CHUNK_MAP_ALLOCATED) != 0, "Double-free?");
   if ((mapelm->bits & CHUNK_MAP_LARGE) == 0) {
     // Small allocation.
     arena->DallocSmall(chunk, aPtr, mapelm);
   } else {
     // Large allocation.
     arena->DallocLarge(chunk, aPtr);
   }
@@ -3320,17 +3331,17 @@ bool arena_t::RallocGrowLarge(arena_chun
   size_t npages = aOldSize >> gPageSize2Pow;
 
   MutexAutoLock lock(mLock);
   MOZ_DIAGNOSTIC_ASSERT(aOldSize ==
                         (aChunk->map[pageind].bits & ~gPageSizeMask));
 
   // Try to extend the run.
   MOZ_ASSERT(aSize > aOldSize);
-  if (pageind + npages < gChunkNumPages &&
+  if (pageind + npages < gChunkNumPages - 1 &&
       (aChunk->map[pageind + npages].bits & CHUNK_MAP_ALLOCATED) == 0 &&
       (aChunk->map[pageind + npages].bits & ~gPageSizeMask) >=
           aSize - aOldSize) {
     // The next run is available and sufficiently large.  Split the
     // following run, then merge the first part with the existing
     // allocation.
     if (!SplitRun((arena_run_t*)(uintptr_t(aChunk) +
                                  ((pageind + npages) << gPageSize2Pow)),
--- a/memory/gtest/TestJemalloc.cpp
+++ b/memory/gtest/TestJemalloc.cpp
@@ -224,17 +224,17 @@ TEST(Jemalloc, PtrInfo)
   // Static memory.
   jemalloc_ptr_info(&gStaticVar, &info);
   ASSERT_TRUE(InfoEq(info, TagUnknown, nullptr, 0U));
 
   // Chunk header.
   UniquePtr<int> p = MakeUnique<int>();
   size_t chunksizeMask = stats.chunksize - 1;
   char* chunk = (char*)(uintptr_t(p.get()) & ~chunksizeMask);
-  size_t chunkHeaderSize = stats.chunksize - stats.large_max;
+  size_t chunkHeaderSize = stats.chunksize - stats.large_max - stats.page_size;
   for (size_t i = 0; i < chunkHeaderSize; i += 64) {
     jemalloc_ptr_info(&chunk[i], &info);
     ASSERT_TRUE(InfoEq(info, TagUnknown, nullptr, 0U));
   }
 
   // Run header.
   size_t page_sizeMask = stats.page_size - 1;
   char* run = (char*)(uintptr_t(p.get()) & ~page_sizeMask);
@@ -616,9 +616,57 @@ TEST(Jemalloc, JunkPoison)
   moz_arena_free(buf_arena, junk_buf);
   // Until Bug 1364359 is fixed it is unsafe to call moz_dispose_arena.
   // moz_dispose_arena(buf_arena);
 
 #  ifdef HAS_GDB_SLEEP_DURATION
   _gdb_sleep_duration = old_gdb_sleep_duration;
 #  endif
 }
-#endif
+
+TEST(Jemalloc, GuardRegion) {
+  jemalloc_stats_t stats;
+  jemalloc_stats(&stats);
+
+#  ifdef HAS_GDB_SLEEP_DURATION
+  // Avoid death tests adding some unnecessary (long) delays.
+  unsigned int old_gdb_sleep_duration = _gdb_sleep_duration;
+  _gdb_sleep_duration = 0;
+#  endif
+
+  arena_id_t arena = moz_create_arena();
+  ASSERT_TRUE(arena != 0);
+
+  // Do enough large allocations to fill a chunk, and then one additional one,
+  // and check that the guard page is still present after the one-but-last
+  // allocation, i.e. that we didn't allocate the guard.
+  Vector<void*> ptr_list;
+  for (size_t cnt = 0; cnt < stats.large_max / stats.page_size; cnt++) {
+    void* ptr = moz_arena_malloc(arena, stats.page_size);
+    ASSERT_TRUE(ptr != nullptr);
+    ASSERT_TRUE(ptr_list.append(ptr));
+  }
+
+  void* last_ptr_in_chunk = ptr_list[ptr_list.length() - 1];
+  void* extra_ptr = moz_arena_malloc(arena, stats.page_size);
+  void* guard_page = (void*)ALIGNMENT_CEILING(
+    (uintptr_t)last_ptr_in_chunk + stats.page_size, stats.page_size);
+  jemalloc_ptr_info_t info;
+  jemalloc_ptr_info(guard_page, &info);
+  ASSERT_TRUE(jemalloc_ptr_is_freed_page(&info));
+  ASSERT_TRUE(info.tag == TagFreedPageDecommitted);
+
+  ASSERT_DEATH_WRAP(*(char*)guard_page = 0, "");
+
+  for (void* ptr : ptr_list) {
+    moz_arena_free(arena, ptr);
+  }
+  moz_arena_free(arena, extra_ptr);
+
+  // Until Bug 1364359 is fixed it is unsafe to call moz_dispose_arena.
+  // moz_dispose_arena(arena);
+
+#  ifdef HAS_GDB_SLEEP_DURATION
+  _gdb_sleep_duration = old_gdb_sleep_duration;
+#  endif
+}
+
+#endif
\ No newline at end of file
--- a/mobile/android/chrome/geckoview/GeckoViewSelectionActionChild.js
+++ b/mobile/android/chrome/geckoview/GeckoViewSelectionActionChild.js
@@ -29,17 +29,17 @@ class GeckoViewSelectionActionChild exte
       id: "org.mozilla.geckoview.COPY",
       predicate: e => !e.collapsed && !this._isPasswordField(e),
       perform: _ => docShell.doCommand("cmd_copy"),
     }, {
       id: "org.mozilla.geckoview.PASTE",
       predicate: e => e.selectionEditable &&
                       Services.clipboard.hasDataMatchingFlavors(
                           ["text/unicode"], 1, Ci.nsIClipboard.kGlobalClipboard),
-      perform: _ => docShell.doCommand("cmd_paste"),
+      perform: _ => this._performPaste(),
     }, {
       id: "org.mozilla.geckoview.DELETE",
       predicate: e => !e.collapsed && e.selectionEditable,
       perform: _ => docShell.doCommand("cmd_delete"),
     }, {
       id: "org.mozilla.geckoview.COLLAPSE_TO_START",
       predicate: e => !e.collapsed && e.selectionEditable,
       perform: e => docShell.doCommand("cmd_moveLeft"),
@@ -53,16 +53,21 @@ class GeckoViewSelectionActionChild exte
       perform: e => docShell.doCommand("cmd_selectNone"),
     }, {
       id: "org.mozilla.geckoview.SELECT_ALL",
       predicate: e => e.reason !== "longpressonemptycontent",
       perform: e => docShell.doCommand("cmd_selectAll"),
     }];
   }
 
+  _performPaste() {
+    this.handleEvent({type: "pagehide"});
+    docShell.doCommand("cmd_paste");
+  }
+
   _isPasswordField(aEvent) {
     if (!aEvent.selectionEditable) {
       return false;
     }
 
     const win = aEvent.target.defaultView;
     const focus = aEvent.target.activeElement;
     return win && win.HTMLInputElement &&
@@ -185,18 +190,16 @@ class GeckoViewSelectionActionChild exte
         // Don't call again if we're already active and things haven't changed.
         return;
       }
 
       msg.seqNo = ++this._seqNo;
       this._isActive = true;
       this._previousMessage = JSON.stringify(msg);
 
-      // This event goes to GeckoViewSelectionAction.jsm, where the data is
-      // further transformed and then sent to GeckoSession.
       this.eventDispatcher.sendRequest(msg, {
         onSuccess: response => {
           if (response.seqNo !== this._seqNo) {
             // Stale action.
             warn `Stale response ${response.id}`;
             return;
           }
           let action = actions.find(action => action.id === response.id);
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1391,28 +1391,22 @@ VARCACHE_PREF(
 
 // Streams API
 VARCACHE_PREF(
   "javascript.options.streams",
    javascript_options_streams,
   RelaxedAtomicBool, false
 )
 
-#ifdef NIGHTLY_BUILD
-# define PREF_VALUE true
-#else
-# define PREF_VALUE false
-#endif
 // BigInt API
 VARCACHE_PREF(
   "javascript.options.bigint",
    javascript_options_bigint,
-  RelaxedAtomicBool, PREF_VALUE
+  RelaxedAtomicBool, true
 )
-#undef PREF_VALUE
 
 VARCACHE_PREF(
   "javascript.options.experimental.fields",
    javascript_options_experimental_fields,
   RelaxedAtomicBool, false
 )
 
 VARCACHE_PREF(
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1555,23 +1555,16 @@ pref("javascript.options.spectre.object_
 pref("javascript.options.spectre.string_mitigations", true);
 pref("javascript.options.spectre.value_masking", true);
 pref("javascript.options.spectre.jit_to_C++_calls", true);
 #endif
 
 // Streams API
 pref("javascript.options.streams", true);
 
-// BigInt API
-#ifdef NIGHTLY_BUILD
-pref("javascript.options.bigint", true);
-#else
-pref("javascript.options.bigint", false);
-#endif
-
 pref("javascript.options.experimental.fields", false);
 
 pref("javascript.options.experimental.await_fix", false);
 
 // Dynamic module import.
 pref("javascript.options.dynamicImport", true);
 
 // advanced prefs
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -76,17 +76,21 @@
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/net/HttpBaseChannel.h"
 #include "nsIScriptError.h"
 #include "nsISiteSecurityService.h"
 #include "nsHttpHandler.h"
 #include "nsNSSComponent.h"
 #include "nsIRedirectHistoryEntry.h"
-#include "nsICertStorage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsICertStorage.h"
+#else
+#  include "nsICertBlocklist.h"
+#endif
 #include "nsICertOverrideService.h"
 #include "nsQueryObject.h"
 #include "mozIThirdPartyUtil.h"
 #include "../mime/nsMIMEHeaderParamImpl.h"
 #include "nsStandardURL.h"
 #include "nsChromeProtocolHandler.h"
 #include "nsJSProtocolHandler.h"
 #include "nsDataHandler.h"
@@ -2560,17 +2564,21 @@ nsresult NS_GetFilenameFromDisposition(n
 }
 
 void net_EnsurePSMInit() {
   nsresult rv;
   nsCOMPtr<nsISupports> psm = do_GetService(PSM_COMPONENT_CONTRACTID, &rv);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   nsCOMPtr<nsISupports> sss = do_GetService(NS_SSSERVICE_CONTRACTID);
+#ifdef MOZ_NEW_CERT_STORAGE
   nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTSTORAGE_CONTRACTID);
+#else
+  nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
+#endif
   nsCOMPtr<nsISupports> cos = do_GetService(NS_CERTOVERRIDE_CONTRACTID);
 }
 
 bool NS_IsAboutBlank(nsIURI* uri) {
   // GetSpec can be expensive for some URIs, so check the scheme first.
   bool isAbout = false;
   if (NS_FAILED(uri->SchemeIs("about", &isAbout)) || !isAbout) {
     return false;
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -3246,17 +3246,18 @@ bool nsCookieService::CanSetCookie(nsIUR
   aCookieAttributes.expiryTime = INT64_MAX;
 
   // aCookieHeader is an in/out param to point to the next cookie, if
   // there is one. Save the present value for logging purposes
   nsDependentCString savedCookieHeader(aCookieHeader);
 
   // newCookie says whether there are multiple cookies in the header;
   // so we can handle them separately.
-  bool newCookie = ParseAttributes(aCookieHeader, aCookieAttributes);
+  bool discard = false;
+  bool newCookie = ParseAttributes(aCookieHeader, aCookieAttributes, discard);
 
   // Collect telemetry on how often secure cookies are set from non-secure
   // origins, and vice-versa.
   //
   // 0 = nonsecure and "http:"
   // 1 = nonsecure and "https:"
   // 2 = secure and "http:"
   // 3 = secure and "https:"
@@ -3392,17 +3393,17 @@ bool nsCookieService::CanSetCookie(nsIUR
       if (NS_FAILED(rv) || isThirdParty) {
         COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader,
                           "failed the samesite tests");
         return newCookie;
       }
     }
   }
 
-  aSetCookie = true;
+  aSetCookie = !discard;
   return newCookie;
 }
 
 // processes a single cookie, and returns true if there are more cookies
 // to be processed
 bool nsCookieService::SetCookieInternal(nsIURI* aHostURI,
                                         const mozilla::net::nsCookieKey& aKey,
                                         bool aRequireHostMatch,
@@ -3793,17 +3794,18 @@ bool nsCookieService::GetTokenValue(nsAC
   }
   return false;
 }
 
 // Parses attributes from cookie header. expires/max-age attributes aren't
 // folded into the cookie struct here, because we don't know which one to use
 // until we've parsed the header.
 bool nsCookieService::ParseAttributes(nsDependentCString& aCookieHeader,
-                                      nsCookieAttributes& aCookieAttributes) {
+                                      nsCookieAttributes& aCookieAttributes,
+                                      bool& aDiscard) {
   static const char kPath[] = "path";
   static const char kDomain[] = "domain";
   static const char kExpires[] = "expires";
   static const char kMaxage[] = "max-age";
   static const char kSecure[] = "secure";
   static const char kHttpOnly[] = "httponly";
   static const char kSameSite[] = "samesite";
   static const char kSameSiteLax[] = "lax";
@@ -3813,39 +3815,45 @@ bool nsCookieService::ParseAttributes(ns
   nsACString::const_char_iterator cookieStart, cookieEnd;
   aCookieHeader.BeginReading(cookieStart);
   aCookieHeader.EndReading(cookieEnd);
 
   aCookieAttributes.isSecure = false;
   aCookieAttributes.isHttpOnly = false;
   aCookieAttributes.sameSite = nsICookie2::SAMESITE_UNSET;
 
+  aDiscard = false;
+
   nsDependentCSubstring tokenString(cookieStart, cookieStart);
   nsDependentCSubstring tokenValue(cookieStart, cookieStart);
   bool newCookie, equalsFound;
 
   // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings.
   // if we find multiple cookies, return for processing
   // note: if there's no '=', we assume token is <VALUE>. this is required by
   //       some sites (see bug 169091).
   // XXX fix the parser to parse according to <VALUE> grammar for this case
   newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
                             equalsFound);
   if (equalsFound) {
     aCookieAttributes.name = tokenString;
     aCookieAttributes.value = tokenValue;
   } else {
-    aCookieAttributes.value = tokenString;
+    aDiscard = true;
   }
 
   // extract remaining attributes
   while (cookieStart != cookieEnd && !newCookie) {
     newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue,
                               equalsFound);
 
+    if (aDiscard) {
+      continue;
+    }
+
     if (!tokenValue.IsEmpty()) {
       tokenValue.BeginReading(tempBegin);
       tokenValue.EndReading(tempEnd);
     }
 
     // decide which attribute we have, and copy the string
     if (tokenString.LowerCaseEqualsLiteral(kPath))
       aCookieAttributes.path = tokenValue;
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -337,17 +337,18 @@ class nsCookieService final : public nsI
   void UpdateCookieInList(nsCookie* aCookie, int64_t aLastAccessed,
                           mozIStorageBindingParamsArray* aParamsArray);
   static bool GetTokenValue(nsACString::const_char_iterator& aIter,
                             nsACString::const_char_iterator& aEndIter,
                             nsDependentCSubstring& aTokenString,
                             nsDependentCSubstring& aTokenValue,
                             bool& aEqualsFound);
   static bool ParseAttributes(nsDependentCString& aCookieHeader,
-                              nsCookieAttributes& aCookie);
+                              nsCookieAttributes& aCookieAttributes,
+                              bool& aDiscard);
   bool RequireThirdPartyCheck();
   static bool CheckDomain(nsCookieAttributes& aCookie, nsIURI* aHostURI,
                           const nsCString& aBaseDomain, bool aRequireHostMatch);
   static bool CheckPath(nsCookieAttributes& aCookie, nsIURI* aHostURI);
   static bool CheckPrefixes(nsCookieAttributes& aCookie, bool aSecureRequest);
   static bool GetExpiry(nsCookieAttributes& aCookie, int64_t aServerTime,
                         int64_t aCurrentTime, bool aFromHttp);
   void RemoveAllFromMemory();
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -187,20 +187,27 @@ nsresult HttpChannelParentListener::Trig
         channel->GetURI(getter_AddRefs(uri));
 
         nsCOMPtr<nsIURI> originalURI;
         channel->GetOriginalURI(getter_AddRefs(originalURI));
 
         uint64_t channelId;
         MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&channelId));
 
+        uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
+        nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+            do_QueryInterface(channel);
+        if (internalChannel) {
+          MOZ_ALWAYS_SUCCEEDS(internalChannel->GetRedirectMode(&redirectMode));
+        }
+
         dom::BrowserParent* browserParent = dom::BrowserParent::GetFrom(tp);
         auto result = browserParent->Manager()->SendCrossProcessRedirect(
             self->mRedirectChannelId, uri, newLoadFlags, loadInfoArgs,
-            channelId, originalURI, aIdentifier);
+            channelId, originalURI, aIdentifier, redirectMode);
 
         MOZ_ASSERT(result, "SendCrossProcessRedirect failed");
 
         return result ? NS_OK : NS_ERROR_UNEXPECTED;
       },
       [httpChannel](nsresult aStatus) {
         MOZ_ASSERT(NS_FAILED(aStatus), "Status should be error");
         httpChannel->OnRedirectVerifyCallback(aStatus);
--- a/netwerk/test/TestCookie.cpp
+++ b/netwerk/test/TestCookie.cpp
@@ -615,32 +615,32 @@ TEST(TestCookie, TestCookieMain)
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
   EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
   SetACookie(cookieService, "http://parser.test/", nullptr,
              "test=\"fubar! = foo;bar\\\";\" parser; domain=.parser.test; "
              "max-age=6\nfive; max-age=2.63,",
              nullptr);
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
   EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, R"(test="fubar! = foo)"));
-  EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "five"));
+  EXPECT_TRUE(CheckResult(cookie.get(), MUST_NOT_CONTAIN, "five"));
   SetACookie(cookieService, "http://parser.test/", nullptr,
              "test=kill; domain=.parser.test; max-age=0 \n five; max-age=0",
              nullptr);
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
   EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
 
   // test the handling of VALUE-only cookies (see bug 169091),
   // i.e. "six" should assume an empty NAME, which allows other VALUE-only
   // cookies to overwrite it
   SetACookie(cookieService, "http://parser.test/", nullptr, "six", nullptr);
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
-  EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "six"));
+  EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
   SetACookie(cookieService, "http://parser.test/", nullptr, "seven", nullptr);
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
-  EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "seven"));
+  EXPECT_TRUE(CheckResult(cookie.get(), MUST_BE_NULL));
   SetACookie(cookieService, "http://parser.test/", nullptr, " =eight", nullptr);
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
   EXPECT_TRUE(CheckResult(cookie.get(), MUST_EQUAL, "eight"));
   SetACookie(cookieService, "http://parser.test/", nullptr, "test=six",
              nullptr);
   GetACookie(cookieService, "http://parser.test/", nullptr, cookie);
   EXPECT_TRUE(CheckResult(cookie.get(), MUST_CONTAIN, "test=six"));
 
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -3489,18 +3489,21 @@ class StaticAnalysis(MachCommandBase):
                     diff_command = ["diff", "-u", original_path, target_file]
                     try:
                         output = check_output(diff_command)
                     except CalledProcessError as e:
                         # diff -u returns 0 when no change
                         # here, we expect changes. if we are here, this means that
                         # there is a diff to show
                         if e.output:
-                            # Replace the temp path by its original path to display a valid patch
-                            patches[original_path] = e.output.replace(target_file, original_path)
+                            # Replace the temp path by the path relative to the repository to display a valid patch
+                            relative_path = os.path.relpath(original_path, self.topsrcdir)
+                            patch = e.output.replace(target_file, relative_path)
+                            patch = patch.replace(original_path, relative_path)
+                            patches[original_path] = patch
 
             if output_format == 'json':
                 output = json.dumps(patches, indent=4)
             else:
                 # Display all the patches at once
                 output = '\n'.join(patches.values())
 
             # Output to specified file or stdout
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -9,17 +9,19 @@
 #include <stdint.h>
 
 #include "ExtendedValidation.h"
 #include "NSSErrorsService.h"
 #include "OCSPVerificationTrustDomain.h"
 #include "PublicKeyPinningService.h"
 #include "cert.h"
 #include "certdb.h"
-#include "cert_storage/src/cert_storage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "cert_storage/src/cert_storage.h"
+#endif
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Services.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsCRTGlue.h"
@@ -83,26 +85,31 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
       mDistrustedCAPolicy(distrustedCAPolicy),
       mSawDistrustedCAByPolicyError(false),
       mOriginAttributes(originAttributes),
       mThirdPartyRootInputs(thirdPartyRootInputs),
       mThirdPartyIntermediateInputs(thirdPartyIntermediateInputs),
       mBuiltChain(builtChain),
       mPinningTelemetryInfo(pinningTelemetryInfo),
       mHostname(hostname),
+#ifdef MOZ_NEW_CERT_STORAGE
       mCertStorage(do_GetService(NS_CERT_STORAGE_CID)),
+#else
+      mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)),
+#endif
       mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED),
       mSCTListFromCertificate(),
       mSCTListFromOCSPStapling() {}
 
 Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
                                         IssuerChecker& checker, Time) {
   Vector<Input> rootCandidates;
   Vector<Input> intermediateCandidates;
 
+#ifdef MOZ_NEW_CERT_STORAGE
   if (!mCertStorage) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
   nsTArray<uint8_t> subject;
   if (!subject.AppendElements(encodedIssuerName.UnsafeGetData(),
                               encodedIssuerName.GetLength())) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
@@ -117,16 +124,17 @@ Result NSSCertDBTrustDomain::FindIssuer(
     if (rv != Success) {
       continue;  // probably too big
     }
     // Currently we're only expecting intermediate certificates in cert storage.
     if (!intermediateCandidates.append(certDER)) {
       return Result::FATAL_ERROR_NO_MEMORY;
     }
   }
+#endif
 
   SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName);
 
   // NSS seems not to differentiate between "no potential issuers found" and
   // "there was an error trying to retrieve the potential issuers." We assume
   // there was no error if CERT_CreateSubjectCertList returns nullptr.
   UniqueCERTCertList candidates(CERT_CreateSubjectCertList(
       nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameItem, 0, false));
@@ -211,44 +219,69 @@ Result NSSCertDBTrustDomain::GetCertTrus
   SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
   UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
       CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
   if (!candidateCert) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
 
   // Check the certificate against the OneCRL cert blocklist
+#ifdef MOZ_NEW_CERT_STORAGE
   if (!mCertStorage) {
+#else
+  if (!mCertBlocklist) {
+#endif
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // The certificate blocklist currently only applies to TLS server
   // certificates.
   if (mCertDBTrustType == trustSSL) {
+#ifdef MOZ_NEW_CERT_STORAGE
     int16_t revocationState;
 
     nsTArray<uint8_t> issuerBytes;
     nsTArray<uint8_t> serialBytes;
     nsTArray<uint8_t> subjectBytes;
     nsTArray<uint8_t> pubKeyBytes;
 
     nsresult nsrv = BuildRevocationCheckArrays(
         candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
+#else
+    bool isCertRevoked;
+
+    nsAutoCString encIssuer;
+    nsAutoCString encSerial;
+    nsAutoCString encSubject;
+    nsAutoCString encPubKey;
+
+    nsresult nsrv = BuildRevocationCheckStrings(
+        candidateCert.get(), encIssuer, encSerial, encSubject, encPubKey);
+#endif
 
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
+#ifdef MOZ_NEW_CERT_STORAGE
     nsrv = mCertStorage->GetRevocationState(
         issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
+#else
+    nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
+                                         encPubKey, &isCertRevoked);
+#endif
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
+#ifdef MOZ_NEW_CERT_STORAGE
     if (revocationState == nsICertStorage::STATE_ENFORCE) {
+#else
+    if (isCertRevoked) {
+#endif
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
               ("NSSCertDBTrustDomain: certificate is in blocklist"));
       return Result::ERROR_REVOKED_CERTIFICATE;
     }
   }
 
   // This may be a third-party root.
   for (const auto& thirdPartyRootInput : mThirdPartyRootInputs) {
@@ -492,17 +525,21 @@ Result NSSCertDBTrustDomain::CheckRevoca
   }
   // At this point, if and only if cachedErrorResult is Success, there was no
   // cached response.
   MOZ_ASSERT((!cachedResponsePresent && cachedResponseResult == Success) ||
              (cachedResponsePresent && cachedResponseResult != Success));
 
   // If we have a fresh OneCRL Blocklist we can skip OCSP for CA certs
   bool blocklistIsFresh;
+#ifdef MOZ_NEW_CERT_STORAGE
   nsresult nsrv = mCertStorage->IsBlocklistFresh(&blocklistIsFresh);
+#else
+  nsresult nsrv = mCertBlocklist->IsBlocklistFresh(&blocklistIsFresh);
+#endif
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // TODO: We still need to handle the fallback for invalid stapled responses.
   // But, if/when we disable OCSP fetching by default, it would be ambiguous
   // whether security.OCSP.enable==0 means "I want the default" or "I really
   // never want you to ever fetch OCSP."
@@ -1268,16 +1305,17 @@ nsresult DefaultServerNicknameForCert(co
     if (!conflict) {
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
+#ifdef MOZ_NEW_CERT_STORAGE
 nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
                                     /*out*/ nsTArray<uint8_t>& issuerBytes,
                                     /*out*/ nsTArray<uint8_t>& serialBytes,
                                     /*out*/ nsTArray<uint8_t>& subjectBytes,
                                     /*out*/ nsTArray<uint8_t>& pubKeyBytes) {
   issuerBytes.Clear();
   if (!issuerBytes.AppendElements(
           BitwiseCast<char*, uint8_t*>(cert->derIssuer.data),
@@ -1299,16 +1337,55 @@ nsresult BuildRevocationCheckArrays(cons
   pubKeyBytes.Clear();
   if (!pubKeyBytes.AppendElements(
           BitwiseCast<char*, uint8_t*>(cert->derPublicKey.data),
           cert->derPublicKey.len)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
+#else
+nsresult BuildRevocationCheckStrings(const CERTCertificate* cert,
+                                     /*out*/ nsCString& encIssuer,
+                                     /*out*/ nsCString& encSerial,
+                                     /*out*/ nsCString& encSubject,
+                                     /*out*/ nsCString& encPubKey) {
+  // Convert issuer, serial, subject and pubKey data to Base64 encoded DER
+  nsDependentCSubstring issuerString(
+      BitwiseCast<char*, uint8_t*>(cert->derIssuer.data), cert->derIssuer.len);
+  nsDependentCSubstring serialString(
+      BitwiseCast<char*, uint8_t*>(cert->serialNumber.data),
+      cert->serialNumber.len);
+  nsDependentCSubstring subjectString(
+      BitwiseCast<char*, uint8_t*>(cert->derSubject.data),
+      cert->derSubject.len);
+  nsDependentCSubstring pubKeyString(
+      BitwiseCast<char*, uint8_t*>(cert->derPublicKey.data),
+      cert->derPublicKey.len);
+
+  nsresult rv = Base64Encode(issuerString, encIssuer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(serialString, encSerial);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(subjectString, encSubject);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(pubKeyString, encPubKey);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+#endif
 
 /**
  * Given a list of certificates representing a verified certificate path from an
  * end-entity certificate to a trust anchor, imports the intermediate
  * certificates into the permanent certificate database. This is an attempt to
  * cope with misconfigured servers that don't include the appropriate
  * intermediate certificates in the TLS handshake.
  *
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -6,17 +6,21 @@
 
 #ifndef NSSCertDBTrustDomain_h
 #define NSSCertDBTrustDomain_h
 
 #include "CertVerifier.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/TimeStamp.h"
-#include "nsICertStorage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsICertStorage.h"
+#else
+#  include "nsICertBlocklist.h"
+#endif
 #include "nsString.h"
 #include "mozpkix/pkixtypes.h"
 #include "secmodt.h"
 
 namespace mozilla {
 namespace psm {
 
 enum class ValidityCheckingMode {
@@ -54,16 +58,17 @@ void DisableMD5();
  */
 bool LoadLoadableRoots(const nsCString& dir);
 
 void UnloadLoadableRoots();
 
 nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,
                                       /*out*/ nsCString& nickname);
 
+#ifdef MOZ_NEW_CERT_STORAGE
 /**
  * Build nsTArray<uint8_t>s out of the issuer, serial, subject and public key
  * data from the supplied certificate for use in revocation checks.
  *
  * @param cert
  *        The CERTCertificate* from which to extract the data.
  * @param out encIssuer
  *        The array to populate with issuer data.
@@ -77,16 +82,41 @@ nsresult DefaultServerNicknameForCert(co
  *        NS_OK, unless there's a memory allocation problem, in which case
  *        NS_ERROR_OUT_OF_MEMORY.
  */
 nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
                                     /*out*/ nsTArray<uint8_t>& issuerBytes,
                                     /*out*/ nsTArray<uint8_t>& serialBytes,
                                     /*out*/ nsTArray<uint8_t>& subjectBytes,
                                     /*out*/ nsTArray<uint8_t>& pubKeyBytes);
+#else
+/**
+ * Build strings of base64 encoded issuer, serial, subject and public key data
+ * from the supplied certificate for use in revocation checks.
+ *
+ * @param cert
+ *        The CERTCertificate* from which to extract the data.
+ * @param out encIssuer
+ *        The string to populate with base64 encoded issuer data.
+ * @param out encSerial
+ *        The string to populate with base64 encoded serial number data.
+ * @param out encSubject
+ *        The string to populate with base64 encoded subject data.
+ * @param out encPubKey
+ *        The string to populate with base64 encoded public key data.
+ * @return
+ *        NS_OK, unless there's a Base64 encoding problem, in which case
+ *        NS_ERROR_FAILURE.
+ */
+nsresult BuildRevocationCheckStrings(const CERTCertificate* cert,
+                                     /*out*/ nsCString& encIssuer,
+                                     /*out*/ nsCString& encSerial,
+                                     /*out*/ nsCString& encSubject,
+                                     /*out*/ nsCString& encPubKey);
+#endif
 
 void SaveIntermediateCerts(const UniqueCERTCertList& certList);
 
 class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain {
  public:
   typedef mozilla::pkix::Result Result;
 
   enum OCSPFetching {
@@ -227,17 +257,21 @@ class NSSCertDBTrustDomain : public mozi
   bool mSawDistrustedCAByPolicyError;
   const OriginAttributes& mOriginAttributes;
   const Vector<mozilla::pkix::Input>& mThirdPartyRootInputs;  // non-owning
   const Vector<mozilla::pkix::Input>&
       mThirdPartyIntermediateInputs;  // non-owning
   UniqueCERTCertList& mBuiltChain;    // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname;  // non-owning - only used for pinning checks
+#ifdef MOZ_NEW_CERT_STORAGE
   nsCOMPtr<nsICertStorage> mCertStorage;
+#else
+  nsCOMPtr<nsICertBlocklist> mCertBlocklist;
+#endif
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
 };
 
 }  // namespace psm
 }  // namespace mozilla
--- a/security/manager/ssl/CSTrustDomain.cpp
+++ b/security/manager/ssl/CSTrustDomain.cpp
@@ -1,19 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "cert_storage/src/cert_storage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "cert_storage/src/cert_storage.h"
+#endif
 #include "CSTrustDomain.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Preferences.h"
-#include "nsDirectoryServiceUtils.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsDirectoryServiceUtils.h"
+#endif
 #include "nsNSSCertificate.h"
 #include "nsNSSComponent.h"
 #include "NSSCertDBTrustDomain.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "mozpkix/pkixnss.h"
 
 using namespace mozilla::pkix;
@@ -21,17 +25,21 @@ using namespace mozilla::pkix;
 namespace mozilla {
 namespace psm {
 
 static LazyLogModule gTrustDomainPRLog("CSTrustDomain");
 #define CSTrust_LOG(args) MOZ_LOG(gTrustDomainPRLog, LogLevel::Debug, args)
 
 CSTrustDomain::CSTrustDomain(UniqueCERTCertList& certChain)
     : mCertChain(certChain),
+#ifdef MOZ_NEW_CERT_STORAGE
       mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {}
+#else
+      mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) {}
+#endif
 
 Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
                                    const CertPolicyId& policy,
                                    Input candidateCertDER,
                                    /*out*/ TrustLevel& trustLevel) {
   MOZ_ASSERT(policy.IsAnyPolicy());
   if (!policy.IsAnyPolicy()) {
     return Result::FATAL_ERROR_INVALID_ARGS;
@@ -39,35 +47,55 @@ Result CSTrustDomain::GetCertTrust(EndEn
 
   SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
   UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
       CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
   if (!candidateCert) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
 
+#ifdef MOZ_NEW_CERT_STORAGE
   nsTArray<uint8_t> issuerBytes;
   nsTArray<uint8_t> serialBytes;
   nsTArray<uint8_t> subjectBytes;
   nsTArray<uint8_t> pubKeyBytes;
 
   nsresult nsrv = BuildRevocationCheckArrays(
       candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
+#else
+  nsAutoCString encIssuer;
+  nsAutoCString encSerial;
+  nsAutoCString encSubject;
+  nsAutoCString encPubKey;
+
+  nsresult nsrv = BuildRevocationCheckStrings(candidateCert.get(), encIssuer,
+                                              encSerial, encSubject, encPubKey);
+#endif
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
+#ifdef MOZ_NEW_CERT_STORAGE
   int16_t revocationState;
   nsrv = mCertBlocklist->GetRevocationState(
       issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
+#else
+  bool isCertRevoked;
+  nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
+                                       encPubKey, &isCertRevoked);
+#endif
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
+#ifdef MOZ_NEW_CERT_STORAGE
   if (revocationState == nsICertStorage::STATE_ENFORCE) {
+#else
+  if (isCertRevoked) {
+#endif
     CSTrust_LOG(("CSTrustDomain: certificate is revoked\n"));
     return Result::ERROR_REVOKED_CERTIFICATE;
   }
 
   // Is this cert our built-in content signing root?
   bool isRoot = false;
   nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
   if (!component) {
--- a/security/manager/ssl/CSTrustDomain.h
+++ b/security/manager/ssl/CSTrustDomain.h
@@ -6,17 +6,21 @@
 
 #ifndef CSTrustDomain_h
 #define CSTrustDomain_h
 
 #include "mozpkix/pkixtypes.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/UniquePtr.h"
 #include "nsDebug.h"
-#include "nsICertStorage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsICertStorage.h"
+#else
+#  include "nsICertBlocklist.h"
+#endif
 #include "nsIX509CertDB.h"
 #include "ScopedNSSTypes.h"
 
 namespace mozilla {
 namespace psm {
 
 class CSTrustDomain final : public mozilla::pkix::TrustDomain {
  public:
@@ -68,15 +72,19 @@ class CSTrustDomain final : public mozil
       mozilla::pkix::Input extensionData) override;
   virtual Result DigestBuf(mozilla::pkix::Input item,
                            mozilla::pkix::DigestAlgorithm digestAlg,
                            /*out*/ uint8_t* digestBuf,
                            size_t digestBufLen) override;
 
  private:
   /*out*/ UniqueCERTCertList& mCertChain;
+#ifdef MOZ_NEW_CERT_STORAGE
   nsCOMPtr<nsICertStorage> mCertBlocklist;
+#else
+  nsCOMPtr<nsICertBlocklist> mCertBlocklist;
+#endif
 };
 
 }  // namespace psm
 }  // namespace mozilla
 
 #endif  // CSTrustDomain_h
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/CertBlocklist.cpp
@@ -0,0 +1,627 @@
+/* -*- Mode: C++; tab-width: 2; 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 "CertBlocklist.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Casting.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDependentString.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIFileStreams.h"
+#include "nsILineInputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsIX509Cert.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPromiseFlatString.h"
+#include "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#include "mozpkix/Input.h"
+#include "prtime.h"
+
+NS_IMPL_ISUPPORTS(CertBlocklist, nsICertBlocklist)
+
+using namespace mozilla;
+using namespace mozilla::pkix;
+
+#define PREF_BACKGROUND_UPDATE_TIMER \
+  "app.update.lastUpdateTime.blocklist-background-update-timer"
+#define PREF_BLOCKLIST_ONECRL_CHECKED \
+  "services.settings.security.onecrl.checked"
+#define PREF_MAX_STALENESS_IN_SECONDS \
+  "security.onecrl.maximum_staleness_in_seconds"
+
+static LazyLogModule gCertBlockPRLog("CertBlock");
+
+uint32_t CertBlocklist::sLastBlocklistUpdate = 0U;
+uint32_t CertBlocklist::sMaxStaleness = 0U;
+
+CertBlocklistItem::CertBlocklistItem(const uint8_t* DNData, size_t DNLength,
+                                     const uint8_t* otherData,
+                                     size_t otherLength,
+                                     CertBlocklistItemMechanism itemMechanism)
+    : mIsCurrent(false), mItemMechanism(itemMechanism) {
+  mDNData = new uint8_t[DNLength];
+  memcpy(mDNData, DNData, DNLength);
+  mDNLength = DNLength;
+
+  mOtherData = new uint8_t[otherLength];
+  memcpy(mOtherData, otherData, otherLength);
+  mOtherLength = otherLength;
+}
+
+CertBlocklistItem::CertBlocklistItem(const CertBlocklistItem& aItem) {
+  mDNLength = aItem.mDNLength;
+  mDNData = new uint8_t[mDNLength];
+  memcpy(mDNData, aItem.mDNData, mDNLength);
+
+  mOtherLength = aItem.mOtherLength;
+  mOtherData = new uint8_t[mOtherLength];
+  memcpy(mOtherData, aItem.mOtherData, mOtherLength);
+
+  mItemMechanism = aItem.mItemMechanism;
+
+  mIsCurrent = aItem.mIsCurrent;
+}
+
+CertBlocklistItem::~CertBlocklistItem() {
+  delete[] mDNData;
+  delete[] mOtherData;
+}
+
+nsresult CertBlocklistItem::ToBase64(nsACString& b64DNOut,
+                                     nsACString& b64OtherOut) {
+  nsDependentCSubstring DNString(BitwiseCast<char*, uint8_t*>(mDNData),
+                                 mDNLength);
+  nsDependentCSubstring otherString(BitwiseCast<char*, uint8_t*>(mOtherData),
+                                    mOtherLength);
+  nsresult rv = Base64Encode(DNString, b64DNOut);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(otherString, b64OtherOut);
+  return rv;
+}
+
+bool CertBlocklistItem::operator==(const CertBlocklistItem& aItem) const {
+  if (aItem.mItemMechanism != mItemMechanism) {
+    return false;
+  }
+  if (aItem.mDNLength != mDNLength || aItem.mOtherLength != mOtherLength) {
+    return false;
+  }
+  return memcmp(aItem.mDNData, mDNData, mDNLength) == 0 &&
+         memcmp(aItem.mOtherData, mOtherData, mOtherLength) == 0;
+}
+
+uint32_t CertBlocklistItem::Hash() const {
+  uint32_t hash;
+  // there's no requirement for a serial to be as large as the size of the hash
+  // key; if it's smaller, fall back to the first octet (otherwise, the last
+  // four)
+  if (mItemMechanism == BlockByIssuerAndSerial &&
+      mOtherLength >= sizeof(hash)) {
+    memcpy(&hash, mOtherData + mOtherLength - sizeof(hash), sizeof(hash));
+  } else {
+    hash = *mOtherData;
+  }
+  return hash;
+}
+
+CertBlocklist::CertBlocklist()
+    : mMutex("CertBlocklist::mMutex"),
+      mModified(false),
+      mBackingFileIsInitialized(false),
+      mBackingFile(nullptr) {}
+
+CertBlocklist::~CertBlocklist() {
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_MAX_STALENESS_IN_SECONDS, this);
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_BLOCKLIST_ONECRL_CHECKED, this);
+}
+
+nsresult CertBlocklist::Init() {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, ("CertBlocklist::Init"));
+
+  // Init must be on main thread for getting the profile directory
+  if (!NS_IsMainThread()) {
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::Init - called off main thread"));
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+
+  // Register preference callbacks
+  nsresult rv = Preferences::RegisterCallbackAndCall(
+      CertBlocklist::PreferenceChanged, PREF_MAX_STALENESS_IN_SECONDS, this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Preferences::RegisterCallbackAndCall(
+      CertBlocklist::PreferenceChanged, PREF_BLOCKLIST_ONECRL_CHECKED, this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Get the profile directory
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                              getter_AddRefs(mBackingFile));
+  if (NS_FAILED(rv) || !mBackingFile) {
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::Init - couldn't get profile dir"));
+    // Since we're returning NS_OK here, set mBackingFile to a safe value.
+    // (We need initialization to succeed and CertBlocklist to be in a
+    // well-defined state if the profile directory doesn't exist.)
+    mBackingFile = nullptr;
+    return NS_OK;
+  }
+  rv = mBackingFile->Append(NS_LITERAL_STRING("revocations.txt"));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsAutoCString path;
+  rv = mBackingFile->GetPersistentDescriptor(path);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::Init certList path: %s", path.get()));
+
+  return NS_OK;
+}
+
+nsresult CertBlocklist::EnsureBackingFileInitialized(MutexAutoLock& lock) {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::EnsureBackingFileInitialized"));
+  if (mBackingFileIsInitialized || !mBackingFile) {
+    return NS_OK;
+  }
+
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::EnsureBackingFileInitialized - not initialized"));
+
+  bool exists = false;
+  nsresult rv = mBackingFile->Exists(&exists);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!exists) {
+    MOZ_LOG(
+        gCertBlockPRLog, LogLevel::Warning,
+        ("CertBlocklist::EnsureBackingFileInitialized no revocations file"));
+    return NS_OK;
+  }
+
+  // Load the revocations file into the cert blocklist
+  nsCOMPtr<nsIFileInputStream> fileStream(
+      do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = fileStream->Init(mBackingFile, -1, -1, false);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
+  nsAutoCString line;
+  nsAutoCString DN;
+  nsAutoCString other;
+  CertBlocklistItemMechanism mechanism;
+  // read in the revocations file. The file format is as follows: each line
+  // contains a comment, base64 encoded DER for a DN, base64 encoded DER for a
+  // serial number or a Base64 encoded SHA256 hash of a public key. Comment
+  // lines start with '#', serial number lines, ' ' (a space), public key hashes
+  // with '\t' (a tab) and anything else is assumed to be a DN.
+  bool more = true;
+  do {
+    rv = lineStream->ReadLine(line, &more);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+    // ignore comments and empty lines
+    if (line.IsEmpty() || line.First() == '#') {
+      continue;
+    }
+    if (line.First() != ' ' && line.First() != '\t') {
+      DN = line;
+      continue;
+    }
+    other = line;
+    if (line.First() == ' ') {
+      mechanism = BlockByIssuerAndSerial;
+    } else {
+      mechanism = BlockBySubjectAndPubKey;
+    }
+    other.Trim(" \t", true, false, false);
+    // Serial numbers and public key hashes 'belong' to the last DN line seen;
+    // if no DN has been seen, the serial number or public key hash is ignored.
+    if (DN.IsEmpty() || other.IsEmpty()) {
+      continue;
+    }
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::EnsureBackingFileInitialized adding: %s %s",
+             DN.get(), other.get()));
+
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::EnsureBackingFileInitialized - pre-decode"));
+
+    rv = AddRevokedCertInternal(DN, other, mechanism, CertOldFromLocalCache,
+                                lock);
+
+    if (NS_FAILED(rv)) {
+      // we warn here, rather than abandoning, since we need to
+      // ensure that as many items as possible are read
+      MOZ_LOG(
+          gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::EnsureBackingFileInitialized adding revoked cert "
+           "failed"));
+    }
+  } while (more);
+  mBackingFileIsInitialized = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CertBlocklist::RevokeCertBySubjectAndPubKey(const nsACString& aSubject,
+                                            const nsACString& aPubKeyHash) {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::RevokeCertBySubjectAndPubKey - subject is: %s and "
+           "pubKeyHash: %s",
+           PromiseFlatCString(aSubject).get(),
+           PromiseFlatCString(aPubKeyHash).get()));
+  MutexAutoLock lock(mMutex);
+
+  return AddRevokedCertInternal(aSubject, aPubKeyHash, BlockBySubjectAndPubKey,
+                                CertNewFromBlocklist, lock);
+}
+
+NS_IMETHODIMP
+CertBlocklist::RevokeCertByIssuerAndSerial(const nsACString& aIssuer,
+                                           const nsACString& aSerialNumber) {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::RevokeCertByIssuerAndSerial - issuer is: %s and "
+           "serial: %s",
+           PromiseFlatCString(aIssuer).get(),
+           PromiseFlatCString(aSerialNumber).get()));
+  MutexAutoLock lock(mMutex);
+
+  return AddRevokedCertInternal(aIssuer, aSerialNumber, BlockByIssuerAndSerial,
+                                CertNewFromBlocklist, lock);
+}
+
+nsresult CertBlocklist::AddRevokedCertInternal(
+    const nsACString& aEncodedDN, const nsACString& aEncodedOther,
+    CertBlocklistItemMechanism aMechanism, CertBlocklistItemState aItemState,
+    MutexAutoLock& /*proofOfLock*/) {
+  nsCString decodedDN;
+  nsCString decodedOther;
+
+  nsresult rv = Base64Decode(aEncodedDN, decodedDN);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Decode(aEncodedOther, decodedOther);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  CertBlocklistItem item(
+      BitwiseCast<const uint8_t*, const char*>(decodedDN.get()),
+      decodedDN.Length(),
+      BitwiseCast<const uint8_t*, const char*>(decodedOther.get()),
+      decodedOther.Length(), aMechanism);
+
+  if (aItemState == CertNewFromBlocklist) {
+    // We want SaveEntries to be a no-op if no new entries are added.
+    nsGenericHashKey<CertBlocklistItem>* entry = mBlocklist.GetEntry(item);
+    if (!entry) {
+      mModified = true;
+    } else {
+      // Ensure that any existing item is replaced by a fresh one so we can
+      // use mIsCurrent to decide which entries to write out.
+      mBlocklist.RemoveEntry(entry);
+    }
+    item.mIsCurrent = true;
+  }
+  mBlocklist.PutEntry(item);
+
+  return NS_OK;
+}
+
+// Write a line for a given string in the output stream
+nsresult WriteLine(nsIOutputStream* outputStream, const nsACString& string) {
+  nsAutoCString line(string);
+  line.Append('\n');
+
+  const char* data = line.get();
+  uint32_t length = line.Length();
+  nsresult rv = NS_OK;
+  while (NS_SUCCEEDED(rv) && length) {
+    uint32_t bytesWritten = 0;
+    rv = outputStream->Write(data, length, &bytesWritten);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    // if no data is written, something is wrong
+    if (!bytesWritten) {
+      return NS_ERROR_FAILURE;
+    }
+    length -= bytesWritten;
+    data += bytesWritten;
+  }
+  return rv;
+}
+
+// void saveEntries();
+// Store the blockist in a text file containing base64 encoded issuers and
+// serial numbers.
+//
+// Each item is stored on a separate line; each issuer is followed by its
+// revoked serial numbers, indented by one space.
+//
+// lines starting with a # character are ignored
+NS_IMETHODIMP
+CertBlocklist::SaveEntries() {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::SaveEntries - not initialized"));
+  MutexAutoLock lock(mMutex);
+  if (!mModified) {
+    return NS_OK;
+  }
+
+  nsresult rv = EnsureBackingFileInitialized(lock);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!mBackingFile) {
+    // We allow this to succeed with no profile directory for tests
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+            ("CertBlocklist::SaveEntries no file in profile to write to"));
+    return NS_OK;
+  }
+
+  // Data needed for writing blocklist items out to the revocations file
+  IssuerTable issuerTable;
+  BlocklistStringSet issuers;
+  nsCOMPtr<nsIOutputStream> outputStream;
+
+  rv = NS_NewAtomicFileOutputStream(getter_AddRefs(outputStream), mBackingFile,
+                                    -1, -1, 0);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = WriteLine(outputStream,
+                 NS_LITERAL_CSTRING("# Auto generated contents. Do not edit."));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Sort blocklist items into lists of serials for each issuer
+  for (auto iter = mBlocklist.Iter(); !iter.Done(); iter.Next()) {
+    CertBlocklistItem item = iter.Get()->GetKey();
+    if (!item.mIsCurrent) {
+      continue;
+    }
+
+    nsAutoCString encDN;
+    nsAutoCString encOther;
+
+    nsresult rv = item.ToBase64(encDN, encOther);
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+              ("CertBlocklist::SaveEntries writing revocation data failed"));
+      return NS_ERROR_FAILURE;
+    }
+
+    // If it's a subject / public key block, write it straight out
+    if (item.mItemMechanism == BlockBySubjectAndPubKey) {
+      WriteLine(outputStream, encDN);
+      WriteLine(outputStream, NS_LITERAL_CSTRING("\t") + encOther);
+      continue;
+    }
+
+    // Otherwise, we have to group entries by issuer
+    issuers.PutEntry(encDN);
+    BlocklistStringSet* issuerSet = issuerTable.Get(encDN);
+    if (!issuerSet) {
+      issuerSet = new BlocklistStringSet();
+      issuerTable.Put(encDN, issuerSet);
+    }
+    issuerSet->Put