Merge mozilla-central to inbound. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Fri, 12 Apr 2019 19:19:43 +0300
changeset 469279 b4501ced5619396b80cf5f55f4fbf1007afda71b
parent 469226 550e2e142615142d54c4758948a4e1a1923c2218 (current diff)
parent 469278 412447b6149e347e8745b1480537726b0c5b02a5 (diff)
child 469280 f612b35ee5148f6b2517220285dae324ecda65f1
push id112776
push usershindli@mozilla.com
push dateFri, 12 Apr 2019 16:20:17 +0000
treeherdermozilla-inbound@b4501ced5619 [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
testing/web-platform/meta/html/interaction/focus/processing-model/preventScroll.html.ini
toolkit/crashreporter/google-breakpad/src/third_party/curl/curlbuild.h
toolkit/crashreporter/google-breakpad/src/third_party/curl/curlrules.h
toolkit/crashreporter/google-breakpad/src/third_party/curl/mprintf.h
toolkit/crashreporter/google-breakpad/src/third_party/curl/stdcheaders.h
toolkit/crashreporter/google-breakpad/src/third_party/curl/types.h
toolkit/crashreporter/google-breakpad/src/third_party/linux/include/gflags/gflags_completions.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1290,17 +1290,17 @@ pref("browser.library.activity-stream.en
 
 // The remote FxA root content URL for the Activity Stream firstrun page.
 pref("browser.newtabpage.activity-stream.fxaccounts.endpoint", "https://accounts.firefox.com/");
 
 // The pref that controls if the search shortcuts experiment is on
 pref("browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", true);
 
 // ASRouter provider configuration
-pref("browser.newtabpage.activity-stream.asrouter.providers.cfr", "{\"id\":\"cfr\",\"enabled\":true,\"type\":\"local\",\"localProvider\":\"CFRMessageProvider\",\"frequency\":{\"custom\":[{\"period\":\"daily\",\"cap\":1}]},\"categories\":[\"cfrAddons\",\"cfrFeatures\"]}");
+pref("browser.newtabpage.activity-stream.asrouter.providers.cfr", "{\"id\":\"cfr-remote\",\"enabled\":true,\"type\":\"remote-settings\",\"bucket\":\"cfr\",\"frequency\":{\"custom\":[{\"period\":\"daily\",\"cap\":1}]},\"categories\":[\"cfrAddons\",\"cfrFeatures\"],\"updateCycleInMs\":3600000}");
 pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "{\"id\":\"snippets\",\"enabled\":true,\"type\":\"remote\",\"url\":\"https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/\",\"updateCycleInMs\":14400000}");
 
 // The pref controls if search hand-off is enabled for Activity Stream.
 #ifdef NIGHTLY_BUILD
 pref("browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", true);
 #else
 pref("browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", false);
 #endif
--- a/browser/base/content/test/permissions/browser_autoplay_blocked.js
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.js
@@ -26,22 +26,22 @@ function autoplayBlockedIcon() {
                                 ".blocked-permission-icon.autoplay-media-icon");
 }
 
 function sleep(ms) {
   /* eslint-disable mozilla/no-arbitrary-setTimeout */
   return new Promise(resolve => setTimeout(resolve, ms));
 }
 
-async function blockedIconShown(browser) {
-  // May need to wait for `GloballyAutoplayBlocked` event before showing icon.
-  if (BrowserTestUtils.is_hidden(autoplayBlockedIcon())) {
-    await BrowserTestUtils.waitForEvent(browser, "GloballyAutoplayBlocked");
-  }
-  ok(!BrowserTestUtils.is_hidden(autoplayBlockedIcon()), "Blocked icon is shown");
+async function blockedIconShown() {
+  await TestUtils.waitForCondition(() => {
+    return BrowserTestUtils.is_visible(autoplayBlockedIcon());
+  });
+
+  ok(BrowserTestUtils.is_visible(autoplayBlockedIcon()), "Blocked icon is shown");
 }
 
 add_task(async function setup() {
   registerCleanupFunction(() => {
     Services.perms.removeAll();
     Services.prefs.clearUserPref(AUTOPLAY_PREF);
   });
 });
@@ -61,17 +61,17 @@ add_task(async function testMainViewVisi
   });
 
   Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
 
   await BrowserTestUtils.withNewTab(AUTOPLAY_PAGE, async function(browser) {
     let permissionsList = document.getElementById("identity-popup-permission-list");
     let emptyLabel = permissionsList.nextElementSibling.nextElementSibling;
 
-    await blockedIconShown(browser);
+    await blockedIconShown();
 
     await openIdentityPopup();
     ok(BrowserTestUtils.is_hidden(emptyLabel), "List of permissions is not empty");
     let labelText = SitePermissions.getPermissionLabel(AUTOPLAY_PERM);
     let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
     is(labels.length, 1, "One permission visible in main view");
     is(labels[0].textContent, labelText, "Correct value");
 
@@ -99,17 +99,17 @@ add_task(async function testMainViewVisi
 });
 
 add_task(async function testGloballyBlockedOnNewWindow() {
   Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
 
   let uri = Services.io.newURI(AUTOPLAY_PAGE);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri.spec);
-  await blockedIconShown(tab.linkedBrowser);
+  await blockedIconShown();
 
   Assert.deepEqual(SitePermissions.get(uri, AUTOPLAY_PERM, tab.linkedBrowser), {
     state: SitePermissions.BLOCK,
     scope: SitePermissions.SCOPE_PERSISTENT,
   });
 
   let promiseWin = BrowserTestUtils.waitForNewWindow();
   gBrowser.replaceTabWithWindow(tab);
@@ -125,41 +125,41 @@ add_task(async function testGloballyBloc
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function testBFCache() {
   Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
 
   await BrowserTestUtils.withNewTab("about:home", async function(browser) {
     await BrowserTestUtils.loadURI(browser, AUTOPLAY_PAGE);
-    await blockedIconShown(browser);
+    await blockedIconShown();
 
     gBrowser.goBack();
     await TestUtils.waitForCondition(() => {
       return BrowserTestUtils.is_hidden(autoplayBlockedIcon());
     });
 
     // Not sure why using `gBrowser.goForward()` doesn't trigger document's
     // visibility changes in some debug build on try server, which makes us not
     // to receive the blocked event.
     await ContentTask.spawn(browser, null, () => {
       content.history.forward();
     });
-    await blockedIconShown(browser);
+    await blockedIconShown();
   });
 
   Services.perms.removeAll();
 });
 
 add_task(async function testChangingBlockingSettingDuringNavigation() {
   Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
 
   await BrowserTestUtils.withNewTab("about:home", async function(browser) {
     await BrowserTestUtils.loadURI(browser, AUTOPLAY_PAGE);
-    await blockedIconShown(browser);
+    await blockedIconShown();
     Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.ALLOWED);
 
     gBrowser.goBack();
     await TestUtils.waitForCondition(() => {
       return BrowserTestUtils.is_hidden(autoplayBlockedIcon());
     });
 
     gBrowser.goForward();
@@ -175,21 +175,23 @@ add_task(async function testChangingBloc
   Services.perms.removeAll();
 });
 
 add_task(async function testSlowLoadingPage() {
   Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
 
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
   let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, SLOW_AUTOPLAY_PAGE);
+  await blockedIconShown();
+
   await BrowserTestUtils.switchTab(gBrowser, tab1);
   // Wait until the blocked icon is hidden by switching tabs
   await TestUtils.waitForCondition(() => {
     return BrowserTestUtils.is_hidden(autoplayBlockedIcon());
   });
   await BrowserTestUtils.switchTab(gBrowser, tab2);
-  await blockedIconShown(tab2.linkedBrowser);
+  await blockedIconShown();
 
   BrowserTestUtils.removeTab(tab1);
   BrowserTestUtils.removeTab(tab2);
 
   Services.perms.removeAll();
 });
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine.js
@@ -45,11 +45,9 @@ add_task(async function() {
 
   let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
   EventUtils.synthesizeMouseAtCenter(element, {}, window);
   await tabPromise;
 
   Assert.equal(gBrowser.selectedBrowser.currentURI.spec,
     "http://example.com/?q=open+a+search", "Should have loaded the correct page");
-
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
@@ -40,11 +40,9 @@ add_task(async function() {
   Assert.equal(result.image, ICON_URI, "Should have the correct image");
 
   let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   EventUtils.synthesizeKey("KEY_Enter");
   await tabPromise;
 
   Assert.equal(gBrowser.selectedBrowser.currentURI.spec,
     "http://example.com/?q=open+a+search", "Should have loaded the correct page");
-
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_autoFill_caretNotAtEnd.js
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_caretNotAtEnd.js
@@ -23,11 +23,10 @@ add_task(async function noAutofillWhenCa
   // Check the first result and input.
   let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
   Assert.ok(!result.autofill, "The first result should not be autofill");
 
   Assert.equal(gURLBar.value, "example");
   Assert.equal(gURLBar.selectionStart, 1);
   Assert.equal(gURLBar.selectionEnd, 1);
 
-  await UrlbarTestUtils.promisePopupClose(window);
   await PlacesUtils.history.clear();
 });
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
@@ -65,10 +65,9 @@ add_task(async function searchSuggestion
       // should update and have this test running on QuantumBar.
       let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, i);
       Assert.equal(element.label,
         suggestion + " browser_searchSuggestionEngine searchSuggestionEngine.xml Search",
         "Result label should be: <search term> <engine name> Search");
     }
   }
   Assert.ok(expectedSearches.length == 0);
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_autoselect.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_autoselect.js
@@ -96,13 +96,9 @@ add_task(async function() {
 
   info("Page Up will go up the list, but not wrap");
   EventUtils.synthesizeKey("KEY_PageUp");
   assertSelected(0);
 
   info("Page Up again will wrap around to the end of the list");
   EventUtils.synthesizeKey("KEY_PageUp");
   assertSelected(maxResults - 1);
-
-  await UrlbarTestUtils.promisePopupClose(window, () => {
-    EventUtils.synthesizeKey("KEY_Escape");
-  });
 });
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_no_title.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_no_title.js
@@ -17,11 +17,9 @@
     await PlacesUtils.history.clear();
     BrowserTestUtils.removeTab(tab);
   });
 
   await promiseAutocompleteResultPopup("bug1060642");
   let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
   Assert.equal(result.displayed.title, "bug1060642.example.com",
     "Result title should be as expected");
-
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_readline_navigation.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_readline_navigation.js
@@ -53,11 +53,9 @@ add_task(async function() {
 
   info("Ctrl-n to select the next item");
   EventUtils.synthesizeKey("n", {ctrlKey: true});
   assertSelected(1);
 
   info("Ctrl-p to select the previous item");
   EventUtils.synthesizeKey("p", {ctrlKey: true});
   assertSelected(0);
-
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_dropmarker.js
+++ b/browser/components/urlbar/tests/browser/browser_dropmarker.js
@@ -11,11 +11,10 @@ add_task(async function() {
         window.document.getAnonymousElementByAttribute(gURLBar.textbox, "anonid", "historydropmarker");
       EventUtils.synthesizeMouseAtCenter(historyDropMarker, {}, window);
     });
     let queryContext = await gURLBar.lastQueryContextPromise;
     is(queryContext.searchString, "",
        "Clicking the history dropmarker should initiate an empty search instead of searching for the loaded URL");
     is(gURLBar.value, "example.com",
        "Clicking the history dropmarker should not change the input value");
-    await UrlbarTestUtils.promisePopupClose(window);
   });
 });
--- a/browser/components/urlbar/tests/browser/browser_keyword_override.js
+++ b/browser/components/urlbar/tests/browser/browser_keyword_override.js
@@ -52,11 +52,9 @@
 
     // QuantumBar doesn't have separate boxes for items.
     let urlHbox = element._urlText.parentNode.parentNode;
     Assert.ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check");
     BrowserTestUtils.is_hidden(urlHbox, "URL element should be hidden");
   }
 
   EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
-
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_keyword_select_and_type.js
+++ b/browser/components/urlbar/tests/browser/browser_keyword_select_and_type.js
@@ -61,11 +61,10 @@ add_task(async function() {
     Assert.equal(result.url, "http://example.com/?q=ab",
       "Should have the correct url");
   } else {
     Assert.equal(result.url,
       PlacesUtils.mozActionURI("keyword", {url: "http://example.com/?q=ab", keyword: "keyword", input: "keyword ab"}),
       "Should have the correct url");
   }
 
-  await UrlbarTestUtils.promisePopupClose(window);
   gBrowser.removeTab(tab);
 });
--- a/browser/components/urlbar/tests/browser/browser_searchTelemetry.js
+++ b/browser/components/urlbar/tests/browser/browser_searchTelemetry.js
@@ -14,19 +14,16 @@ add_task(async function prepare() {
 
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
     await Services.search.setDefault(oldDefaultEngine);
 
     // Clicking urlbar results causes visits to their associated pages, so clear
     // that history now.
     await PlacesUtils.history.clear();
-
-    // Make sure the popup is closed for the next test.
-    await UrlbarTestUtils.promisePopupClose(window);
   });
 
   // Move the mouse away from the urlbar one-offs so that a one-off engine is
   // not inadvertently selected.
   await new Promise(resolve => {
     EventUtils.synthesizeNativeMouseMove(window.document.documentElement, 0, 0,
                                          resolve);
   });
--- a/browser/components/urlbar/tests/browser/browser_textruns.js
+++ b/browser/components/urlbar/tests/browser/browser_textruns.js
@@ -17,10 +17,9 @@
   });
 
   await promiseAutocompleteResultPopup("textruns");
   let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
   Assert.equal(result.displayed.title.length, UrlbarUtils.MAX_TEXT_LENGTH,
                "Result title should be limited");
   Assert.equal(result.displayed.url.length, UrlbarUtils.MAX_TEXT_LENGTH,
                "Result url should be limited");
-  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_urlbarOneOffs.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarOneOffs.js
@@ -12,17 +12,16 @@ add_task(async function init() {
 
   // Add a search suggestion engine and move it to the front so that it appears
   // as the first one-off.
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   await Services.search.moveEngine(engine, 0);
 
   registerCleanupFunction(async function() {
-    await hidePopup();
     await PlacesUtils.history.clear();
   });
 
   await PlacesUtils.history.clear();
 
   let visits = [];
   for (let i = 0; i < gMaxResults; i++) {
     visits.push({
--- a/browser/components/urlbar/tests/browser/browser_urlbarOneOffs_searchSuggestions.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarOneOffs_searchSuggestions.js
@@ -29,18 +29,16 @@ add_task(async function init() {
   let oldDefaultEngine = await Services.search.getDefault();
   await Services.search.moveEngine(engine2, 0);
   await Services.search.moveEngine(engine, 0);
   await Services.search.setDefault(engine);
   registerCleanupFunction(async function() {
     await Services.search.setDefault(oldDefaultEngine);
 
     await PlacesUtils.history.clear();
-    // Make sure the popup is closed for the next test.
-    await UrlbarTestUtils.promisePopupClose(window);
   });
 });
 
 async function withSecondSuggestion(testFn) {
   await BrowserTestUtils.withNewTab(gBrowser, async () => {
     let typedValue = "foo";
     await promiseAutocompleteResultPopup(typedValue, window, true);
     await promiseSuggestionsPresent();
--- a/browser/components/urlbar/tests/browser/browser_urlbarOneOffs_settings.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarOneOffs_settings.js
@@ -10,17 +10,16 @@
 
 let gMaxResults;
 
 add_task(async function init() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.urlbar.oneOffSearches", true]]});
   gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
 
   registerCleanupFunction(async function() {
-    await UrlbarTestUtils.promisePopupClose(window);
     await PlacesUtils.history.clear();
   });
 
   await PlacesUtils.history.clear();
 
   let visits = [];
   for (let i = 0; i < gMaxResults; i++) {
     visits.push({
--- a/browser/components/urlbar/tests/browser/browser_urlbarSearchFunction.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarSearchFunction.js
@@ -16,17 +16,16 @@ add_task(async function init() {
     if (which === undefined) {
       delete gURLBar._whichSearchSuggestionsNotification;
     } else {
       gURLBar._whichSearchSuggestionsNotification = which;
     }
     Services.prefs.clearUserPref("timesBeforeHidingSuggestionsHint");
 
     gURLBar.handleRevert();
-    await UrlbarTestUtils.promisePopupClose(window);
   });
 });
 
 
 // Calls search() with a normal, non-"@engine" search-string argument.
 add_task(async function basic() {
   let resetNotification = enableSearchSuggestionsNotification();
 
--- a/browser/components/urlbar/tests/browser/browser_urlbarSearchSuggestions.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarSearchSuggestions.js
@@ -21,18 +21,16 @@ add_task(async function prepare() {
   await Services.search.setDefault(engine);
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
     await Services.search.setDefault(oldDefaultEngine);
 
     // Clicking suggestions causes visits to search results pages, so clear that
     // history now.
     await PlacesUtils.history.clear();
-
-    await UrlbarTestUtils.promisePopupClose(window);
   });
 });
 
 add_task(async function clickSuggestion() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   gURLBar.focus();
   await promiseAutocompleteResultPopup("foo");
   let [idx, suggestion, engineName] = await promiseFirstSuggestion();
@@ -88,18 +86,16 @@ add_task(async function copySuggestionTe
   let [idx, suggestion] = await promiseFirstSuggestion();
   for (let i = 0; i < idx; ++i) {
     EventUtils.synthesizeKey("KEY_ArrowDown");
   }
   gURLBar.select();
   await new Promise((resolve, reject) => waitForClipboard(suggestion, function() {
     goDoCommand("cmd_copy");
   }, resolve, reject));
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape"));
 });
 
 async function getFirstSuggestion() {
   let matchCount = UrlbarTestUtils.getResultCount(window);
   for (let i = 0; i < matchCount; i++) {
     let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
     if (result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
         result.searchParams.suggestion) {
--- a/browser/components/urlbar/tests/browser/browser_urlbarStopSearchOnSelection.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarStopSearchOnSelection.js
@@ -27,18 +27,16 @@ add_task(async function init() {
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
   let oldDefaultEngine = await Services.search.getDefault();
   await Services.search.moveEngine(engine, 0);
   await Services.search.setDefault(engine);
   registerCleanupFunction(async () => {
     await Services.search.setDefault(oldDefaultEngine);
     await PlacesUtils.history.clear();
-    // Make sure the popup is closed for the next test.
-    await UrlbarTestUtils.promisePopupClose(window);
   });
 });
 
 add_task(async function mainTest() {
   // Trigger an initial search.  Restrict matches to search suggestions.
   await promiseAutocompleteResultPopup(`${UrlbarTokenizer.RESTRICT.SEARCH} test`, window);
   await promiseSuggestionsPresent("Waiting for initial suggestions");
 
--- a/browser/components/urlbar/tests/browser/browser_urlbarTokenAlias.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarTokenAlias.js
@@ -10,19 +10,16 @@ const ALIAS = "@test";
 add_task(async function init() {
   await Services.search.addEngineWithDetails("Test", {
     alias: ALIAS,
     template: "http://example.com/?search={searchTerms}",
   });
   registerCleanupFunction(async function() {
     let engine = Services.search.getEngineByName("Test");
     await Services.search.removeEngine(engine);
-    // Make sure the popup is closed for the next test.
-    gURLBar.handleRevert();
-    await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
   });
 
   // Search results aren't shown in quantumbar unless search suggestions are
   // enabled.
   await SpecialPowers.pushPrefEnv({
     set: [
       ["browser.urlbar.suggest.searches", true],
     ],
--- a/browser/components/urlbar/tests/browser/browser_urlbar_remove_match.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_remove_match.js
@@ -61,13 +61,11 @@ add_task(async function test_remove_book
     { shiftKey: true } : {};
   EventUtils.synthesizeKey("KEY_Delete", options);
 
   // We don't have an easy way of determining if the event was process or not,
   // so let any event queues clear before testing.
   await new Promise(resolve => setTimeout(resolve, 0));
   await PlacesTestUtils.promiseAsyncUpdates();
 
-  await UrlbarTestUtils.promisePopupClose(window);
-
   Assert.ok(await PlacesUtils.bookmarks.fetch({url: TEST_URL}),
     "Should still have the URL bookmarked.");
 });
--- a/browser/components/urlbar/tests/browser/head.js
+++ b/browser/components/urlbar/tests/browser/head.js
@@ -21,8 +21,14 @@ XPCOMUtils.defineLazyModuleGetters(this,
 });
 
 /* import-globals-from head-common.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/head-common.js",
   this);
 
 const {sinon} = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+
+registerCleanupFunction(async () => {
+  // Ensure the Urlbar popup is always closed at the end of a test, to save having
+  // to do it within each test.
+  await UrlbarTestUtils.promisePopupClose(window);
+});
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -33,23 +33,16 @@
     @media (-moz-windows-compositor) {
       #main-window[sizemode="maximized"] .titlebar-buttonbox {
         -moz-appearance: -moz-window-button-box-maximized;
       }
     }
   }
 }
 
-@media (-moz-windows-default-theme) {
-  .menu-accel,
-  .menu-iconic-accel {
-    color: graytext;
-  }
-}
-
 @media (-moz-windows-compositor) {
   @media not (-moz-os-version: windows-win7) {
     @media not (-moz-os-version: windows-win8) {
       @media (-moz-windows-default-theme) {
         :root:not(:-moz-lwtheme) {
           background-color: hsl(0, 0%, 78%);
         }
 
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -450,16 +450,47 @@ BasePrincipal::CloneStrippingUserContext
 
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return BasePrincipal::CreateCodebasePrincipal(uri, attrs);
 }
 
+already_AddRefed<BasePrincipal> BasePrincipal::CloneForcingFirstPartyDomain(
+    nsIURI* aURI) {
+  if (NS_WARN_IF(!IsCodebasePrincipal())) {
+    return nullptr;
+  }
+
+  OriginAttributes attrs = OriginAttributesRef();
+  // XXX this is slow. Maybe we should consider to make it faster.
+  attrs.SetFirstPartyDomain(false, aURI, true /* aForced */);
+
+  return CloneForcingOriginAttributes(attrs);
+}
+
+already_AddRefed<BasePrincipal> BasePrincipal::CloneForcingOriginAttributes(
+    const OriginAttributes& aOriginAttributes) {
+  if (NS_WARN_IF(!IsCodebasePrincipal())) {
+    return nullptr;
+  }
+
+  nsAutoCString originNoSuffix;
+  nsresult rv = GetOriginNoSuffix(originNoSuffix);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  nsIURI* uri = static_cast<ContentPrincipal*>(this)->mCodebase;
+  RefPtr<ContentPrincipal> copy = new ContentPrincipal();
+  rv = copy->Init(uri, aOriginAttributes, originNoSuffix);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  return copy.forget();
+}
+
 extensions::WebExtensionPolicy* BasePrincipal::ContentScriptAddonPolicy() {
   if (!Is<ExpandedPrincipal>()) {
     return nullptr;
   }
 
   auto expanded = As<ExpandedPrincipal>();
   for (auto& prin : expanded->AllowList()) {
     if (auto policy = BasePrincipal::Cast(prin)->AddonPolicy()) {
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -164,16 +164,21 @@ class BasePrincipal : public nsJSPrincip
     return mOriginAttributes.mInIsolatedMozBrowser;
   }
 
   PrincipalKind Kind() const { return mKind; }
 
   already_AddRefed<BasePrincipal>
   CloneStrippingUserContextIdAndFirstPartyDomain();
 
+  already_AddRefed<BasePrincipal> CloneForcingFirstPartyDomain(nsIURI* aURI);
+
+  already_AddRefed<BasePrincipal> CloneForcingOriginAttributes(
+      const OriginAttributes& aOriginAttributes);
+
   // If this is an add-on content script principal, returns its AddonPolicy.
   // Otherwise returns null.
   extensions::WebExtensionPolicy* ContentScriptAddonPolicy();
 
   // Helper to check whether this principal is associated with an addon that
   // allows unprivileged code to load aURI.  aExplicit == true will prevent
   // use of all_urls permission, requiring the domain in its permissions.
   bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
--- a/caps/OriginAttributes.cpp
+++ b/caps/OriginAttributes.cpp
@@ -33,21 +33,21 @@ void OriginAttributes::InitPrefs() {
         "privacy.firstparty.isolate.restrict_opener_access");
     Preferences::AddBoolVarCache(
         &sBlockPostMessageForFPI,
         "privacy.firstparty.isolate.block_post_message");
   }
 }
 
 void OriginAttributes::SetFirstPartyDomain(const bool aIsTopLevelDocument,
-                                           nsIURI* aURI) {
+                                           nsIURI* aURI, bool aForced) {
   bool isFirstPartyEnabled = IsFirstPartyEnabled();
 
-  // If the pref is off or this is not a top level load, bail out.
-  if (!isFirstPartyEnabled || !aIsTopLevelDocument) {
+  // If the prefs are off or this is not a top level load, bail out.
+  if ((!isFirstPartyEnabled || !aIsTopLevelDocument) && !aForced) {
     return;
   }
 
   nsCOMPtr<nsIEffectiveTLDService> tldService =
       do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
   MOZ_ASSERT(tldService);
   if (!tldService) {
     return;
--- a/caps/OriginAttributes.h
+++ b/caps/OriginAttributes.h
@@ -20,17 +20,18 @@ class OriginAttributes : public dom::Ori
   OriginAttributes(uint32_t aAppId, bool aInIsolatedMozBrowser) {
     mAppId = aAppId;
     mInIsolatedMozBrowser = aInIsolatedMozBrowser;
   }
 
   explicit OriginAttributes(const OriginAttributesDictionary& aOther)
       : OriginAttributesDictionary(aOther) {}
 
-  void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI);
+  void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI,
+                           bool aForced = false);
   void SetFirstPartyDomain(const bool aIsTopLevelDocument,
                            const nsACString& aDomain);
 
   enum {
     STRIP_FIRST_PARTY_DOMAIN = 0x01,
     STRIP_USER_CONTEXT_ID = 0x02,
   };
 
--- a/caps/nsIScriptSecurityManager.idl
+++ b/caps/nsIScriptSecurityManager.idl
@@ -192,24 +192,42 @@ interface nsIScriptSecurityManager : nsI
      * don't need reporting.
      * FromPrivateWindow indicates whether the error occurs in a private
      * window or not.
      */
     void checkSameOriginURI(in nsIURI aSourceURI,
                             in nsIURI aTargetURI,
                             in boolean reportError,
                             in boolean fromPrivateWindow);
+
     /**
      * Get the principal for the given channel.  This will typically be the
      * channel owner if there is one, and the codebase principal for the
      * channel's URI otherwise.  aChannel must not be null.
      */
     nsIPrincipal getChannelResultPrincipal(in nsIChannel aChannel);
 
     /**
+     * Get the storage principal for the given channel.  This is basically the
+     * same of getChannelResultPrincipal() execept for trackers, where we
+     * return a principal with a different OriginAttributes.
+     */
+    nsIPrincipal getChannelResultStoragePrincipal(in nsIChannel aChannel);
+
+    /**
+     * This method does getChannelResultPrincipal() +
+     * getChannelResultStoragePrincipal().
+     * This method is mainly done for Document::Reset(). There are no other
+     * reasons to use this method.
+     */
+    void getChannelResultPrincipals(in nsIChannel aChannel,
+                                    out nsIPrincipal aPrincipal,
+                                    out nsIPrincipal aStoragePrincipal);
+
+    /**
      * Temporary API until bug 1220687 is fixed.
      *
      * Returns the same value as getChannelResultPrincipal, but ignoring
      * sandboxing.  Specifically, if sandboxing would have prevented the
      * channel's triggering principal from being returned by
      * getChannelResultPrincipal, the triggering principal will be returned
      * by this method.
      *
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsScriptSecurityManager.h"
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/StoragePrincipalHelper.h"
 
 #include "xpcpublic.h"
 #include "XPCWrapper.h"
 #include "nsIInputStreamChannel.h"
 #include "nsILoadContext.h"
 #include "nsIServiceManager.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
@@ -240,16 +241,43 @@ nsScriptSecurityManager::GetChannelResul
 }
 
 nsresult nsScriptSecurityManager::GetChannelResultPrincipalIfNotSandboxed(
     nsIChannel* aChannel, nsIPrincipal** aPrincipal) {
   return GetChannelResultPrincipal(aChannel, aPrincipal,
                                    /*aIgnoreSandboxing*/ true);
 }
 
+NS_IMETHODIMP
+nsScriptSecurityManager::GetChannelResultStoragePrincipal(
+    nsIChannel* aChannel, nsIPrincipal** aPrincipal) {
+  nsCOMPtr<nsIPrincipal> principal;
+  nsresult rv = GetChannelResultPrincipal(aChannel, getter_AddRefs(principal),
+                                          /*aIgnoreSandboxing*/ false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return StoragePrincipalHelper::Create(aChannel, principal, aPrincipal);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetChannelResultPrincipals(
+    nsIChannel* aChannel, nsIPrincipal** aPrincipal,
+    nsIPrincipal** aStoragePrincipal) {
+  nsresult rv = GetChannelResultPrincipal(aChannel, aPrincipal,
+                                          /*aIgnoreSandboxing*/ false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return StoragePrincipalHelper::Create(aChannel, *aPrincipal,
+                                        aStoragePrincipal);
+}
+
 static void InheritAndSetCSPOnPrincipalIfNeeded(nsIChannel* aChannel,
                                                 nsIPrincipal* aPrincipal) {
   // loading a data: URI into an iframe, or loading frame[srcdoc] need
   // to inherit the CSP (see Bug 1073952, 1381761).
   MOZ_ASSERT(aChannel && aPrincipal, "need a valid channel and principal");
   if (!aChannel) {
     return;
   }
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -52,16 +52,17 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_debug-target-pane_collapsibilities_preference.js]
 [browser_aboutdebugging_debug-target-pane_empty.js]
 [browser_aboutdebugging_debug-target-pane_usb_runtime.js]
 [browser_aboutdebugging_devtools.js]
 [browser_aboutdebugging_devtoolstoolbox_contextmenu.js]
 [browser_aboutdebugging_devtoolstoolbox_contextmenu_markupview.js]
 [browser_aboutdebugging_devtoolstoolbox_focus.js]
 [browser_aboutdebugging_devtoolstoolbox_menubar.js]
+[browser_aboutdebugging_devtoolstoolbox_performance.js]
 [browser_aboutdebugging_devtoolstoolbox_reload.js]
 skip-if = verify # test loads the toolbox 2 times for each panel, might timeout or OOM
 [browser_aboutdebugging_devtoolstoolbox_shortcuts.js]
 skip-if = (os == "win" && ccov) # Bug 1521349
 [browser_aboutdebugging_devtoolstoolbox_target_destroyed.js]
 skip-if = debug || asan # This test leaks. See bug 1529005
 [browser_aboutdebugging_devtoolstoolbox_tooltip_markupview.js]
 [browser_aboutdebugging_navigate.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_performance.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test can take a long time to run on debug builds.
+requestLongerTimeout(2);
+
+/* import-globals-from helper-collapsibilities.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-collapsibilities.js", this);
+
+/**
+ * Check that graphs used by the old performance panel are correctly displayed.
+ */
+add_task(async function() {
+  info("Force all debug target panes to be expanded");
+  prepareCollapsibilitiesTest();
+
+  info("Force old performance panel");
+  await pushPref("devtools.performance.new-panel-enabled", false);
+
+  const { document, tab, window } = await openAboutDebugging();
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+  const { devtoolsTab, devtoolsWindow } =
+    await openAboutDevtoolsToolbox(document, tab, window);
+
+  info("Select performance panel");
+  const toolbox = getToolbox(devtoolsWindow);
+  await toolbox.selectTool("performance");
+
+  // Retrieve shared helpers for the old performance panel.
+  const { startRecording, stopRecording } =
+    require("devtools/client/performance/test/helpers/actions");
+  const performancePanel = toolbox.getCurrentPanel();
+  await startRecording(performancePanel);
+
+  const { idleWait } = require("devtools/client/performance/test/helpers/wait-utils");
+  await idleWait(100);
+
+  info("Stop recording");
+  await stopRecording(performancePanel);
+
+  info("Select the call tree");
+  const { EVENTS, DetailsView, JsCallTreeView } = performancePanel.panelWin;
+  const rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
+  await DetailsView.selectView("js-calltree");
+
+  info("Wait for the call tree to be rendered");
+  await rendered;
+
+  await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
+  await removeTab(tab);
+});
--- a/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.js
+++ b/devtools/client/debugger/src/components/PrimaryPanes/SourcesTree.js
@@ -307,25 +307,25 @@ class SourcesTree extends Component<Prop
     const { worker, workerCount } = this.props;
 
     if (!features.windowlessWorkers || workerCount == 0) {
       return null;
     }
 
     if (worker) {
       return (
-        <div className="node thread-header">
+        <div className="node thread-header" key="thread-header">
           <AccessibleImage className="worker" />
           <span className="label">{getDisplayName(worker)}</span>
         </div>
       );
     }
 
     return (
-      <div className="node thread-header">
+      <div className="node thread-header" key="thread-header">
         <AccessibleImage className={"file"} />
         <span className="label">{L10N.getStr("mainThread")}</span>
       </div>
     );
   }
 
   render() {
     const { worker } = this.props;
--- a/devtools/client/debugger/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
+++ b/devtools/client/debugger/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
@@ -2,16 +2,17 @@
 
 exports[`SourcesTree After changing expanded nodes Shows the tree with four.js, five.js and six.js expanded 1`] = `
 <div
   className="sources-pane thread"
   key="pane"
 >
   <div
     className="node thread-header"
+    key="thread-header"
   >
     <AccessibleImage
       className="file"
     />
     <span
       className="label"
     >
       Main Thread
@@ -50,16 +51,17 @@ exports[`SourcesTree After changing expa
 
 exports[`SourcesTree Should show a 'No Sources' message if there are no sources 1`] = `
 <div
   className="sources-pane thread"
   key="pane"
 >
   <div
     className="node thread-header"
+    key="thread-header"
   >
     <AccessibleImage
       className="file"
     />
     <span
       className="label"
     >
       Main Thread
@@ -76,16 +78,17 @@ exports[`SourcesTree Should show a 'No S
 
 exports[`SourcesTree Should show the tree with nothing expanded 1`] = `
 <div
   className="sources-pane thread"
   key="pane"
 >
   <div
     className="node thread-header"
+    key="thread-header"
   >
     <AccessibleImage
       className="file"
     />
     <span
       className="label"
     >
       Main Thread
@@ -117,16 +120,17 @@ exports[`SourcesTree Should show the tre
 
 exports[`SourcesTree When loading initial source Shows the tree with one.js, two.js and three.js expanded 1`] = `
 <div
   className="sources-pane thread"
   key="pane"
 >
   <div
     className="node thread-header"
+    key="thread-header"
   >
     <AccessibleImage
       className="file"
     />
     <span
       className="label"
     >
       Main Thread
@@ -165,16 +169,17 @@ exports[`SourcesTree When loading initia
 
 exports[`SourcesTree on receiving new props updates highlighted items updates highlightItems if selectedSource changes 1`] = `
 <div
   className="sources-pane thread"
   key="pane"
 >
   <div
     className="node thread-header"
+    key="thread-header"
   >
     <AccessibleImage
       className="file"
     />
     <span
       className="label"
     >
       Main Thread
--- a/devtools/client/debugger/src/components/SecondaryPanes/index.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/index.js
@@ -231,17 +231,17 @@ class SecondaryPanes extends Component<P
       !features.mapScopes ||
       !selectedFrame ||
       isGeneratedId(selectedFrame.location.sourceId)
     ) {
       return null;
     }
 
     return [
-      <div>
+      <div key="scopes-buttons">
         <label
           className="map-scopes-header"
           title={L10N.getStr("scopes.mapping.label")}
           onClick={e => e.stopPropagation()}
         >
           <input
             type="checkbox"
             checked={shouldMapScopes ? "checked" : ""}
--- a/devtools/client/performance/test/browser_perf-range-changed-render.js
+++ b/devtools/client/performance/test/browser_perf-range-changed-render.js
@@ -64,17 +64,21 @@ add_task(async function() {
   await rendered;
   ok(true, "Call tree rerenders after its corresponding pane is shown.");
 
   rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
   await DetailsView.selectView("waterfall");
   await rendered;
   ok(true, "Waterfall rerenders after its corresponding pane is shown.");
 
-  is(updatedWaterfall, 3, "WaterfallView rerendered 3 times.");
+  // The WaterfallView is rerendered on window resize. Loading the other graphs can
+  // trigger a window resize and increase the total number of rerenders.
+  // See Bug 1532993#c12.
+  ok((updatedWaterfall === 3) || (updatedWaterfall === 4),
+    "WaterfallView rerendered 3 or 4 times.");
   is(updatedCallTree, 2, "JsCallTreeView rerendered 2 times.");
   is(updatedFlameGraph, 2, "JsFlameGraphView rerendered 2 times.");
 
   WaterfallView.off(EVENTS.UI_WATERFALL_RENDERED, updateWaterfall);
   JsCallTreeView.off(EVENTS.UI_JS_CALL_TREE_RENDERED, updateCallTree);
   JsFlameGraphView.off(EVENTS.UI_JS_FLAMEGRAPH_RENDERED, updateFlameGraph);
 
   await teardownToolboxAndRemoveTab(panel);
--- a/devtools/client/shared/widgets/Graphs.js
+++ b/devtools/client/shared/widgets/Graphs.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
 const { getCurrentZoom } = require("devtools/shared/layout/utils");
+const { DOMHelpers } = require("resource://devtools/client/shared/DOMHelpers.jsm");
 
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/event-emitter");
 
 loader.lazyImporter(this, "DevToolsWorker",
   "resource://devtools/shared/worker/worker.js");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
@@ -1231,29 +1232,33 @@ AbstractCanvasGraph.prototype = {
  * @param Node parent
  *        The desired parent node for the iframe.
  * @param function callback
  *        Invoked once the content is loaded, with the iframe as an argument.
  */
 AbstractCanvasGraph.createIframe = function(url, parent, callback) {
   const iframe = parent.ownerDocument.createElementNS(HTML_NS, "iframe");
 
-  iframe.addEventListener("DOMContentLoaded", function() {
-    callback(iframe);
-  }, {once: true});
-
   // Setting 100% width on the frame and flex on the parent allows the graph
   // to properly shrink when the window is resized to be smaller.
   iframe.setAttribute("frameborder", "0");
   iframe.style.width = "100%";
   iframe.style.minWidth = "50px";
-  iframe.src = url;
 
   parent.style.display = "flex";
   parent.appendChild(iframe);
+
+  // Use DOMHelpers to wait for the frame load. DOMHelpers relies on chromeEventHandler
+  // so this will still work if DevTools are loaded in a content frame.
+  const domHelper = new DOMHelpers(iframe.contentWindow);
+  domHelper.onceDOMReady(function() {
+    callback(iframe);
+  });
+
+  iframe.src = url;
 };
 
 /**
  * Gets a striped pattern used as a background in selections and regions.
  *
  * @param object data
  *        The following properties are required:
  *          - ownerDocument: the nsIDocumentElement owning the canvas
--- a/devtools/client/themes/changes.css
+++ b/devtools/client/themes/changes.css
@@ -1,37 +1,29 @@
 /* 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/. */
 
- /* CSS Variables specific to the Changes panel that aren't defined by the themes */
- :root {
-   --changes-button-copy-all-background-color--hover: var(--grey-90-a05);
-   --changes-button-copy-all-background-color--active: var(--grey-90-a10);
-   --changes-button-copy-rule-background-color: var(--grey-90-a05);
-   --changes-button-copy-rule-background-color--active: var(--grey-90-a10);
-   --diff-add-background-color: #f1feec;
-   --diff-add-text-color: #54983f;
-   --diff-remove-background-color: #fbf2f5;
-   --diff-remove-text-color: #bf7173;
-   --diff-source-background: var(--theme-toolbar-background);
-   --diff-level: 0;
-   --diff-level-offset: 10px;
-   /*
+/* CSS Variables specific to the Changes panel that aren't defined by the themes */
+:root {
+  --diff-add-background-color: #f1feec;
+  --diff-add-text-color: #54983f;
+  --diff-remove-background-color: #fbf2f5;
+  --diff-remove-text-color: #bf7173;
+  --diff-source-background: var(--theme-toolbar-background);
+  --diff-level: 0;
+  --diff-level-offset: 10px;
+  /*
     Minimum padding so content on the first level (zero) isn't touching the edge. Added
     and removed lines will re-declare this to add extra padding to clear the +/- icons.
-   */
-   --diff-level-min-offset: 5px;
- }
+  */
+  --diff-level-min-offset: 5px;
+}
 
 :root.theme-dark {
-  --changes-button-copy-all-background-color--hover: var(--grey-10-a10);
-  --changes-button-copy-all-background-color--active: var(--grey-10-a15);
-  --changes-button-copy-rule-background-color: var(--grey-10-a10);
-  --changes-button-copy-rule-background-color--active: var(--grey-10-a15);
   --diff-add-background-color: rgba(18, 188, 0, 0.15);
   --diff-add-text-color: #12BC00;
   --diff-remove-background-color: rgba(255, 0, 57, 0.15);
   --diff-remove-text-color: #FF0039;
   --diff-source-background: #222225;
 }
 
 :root[dir="rtl"] {
@@ -82,63 +74,65 @@
   padding-right: 5px;
   padding-bottom: 3px;
   padding-left: calc(var(--diff-level-min-offset) +
                      var(--diff-level-offset) * var(--diff-level));
 }
 
 .changes__copy-all-changes-button {
   -moz-context-properties: fill;
-  background: url(chrome://devtools/skin/images/copy.svg) no-repeat;
+  /* Use the Firefox copy icon (16px) instead of the smaller DevTools one (12px) */
+  background: url(chrome://browser/skin/edit-copy.svg) no-repeat;
   background-position: 4px 3px;
   background-size: 16px;
   border: none;
   border-radius: 3px;
   color: var(--theme-body-color);
   fill: currentColor;
   margin: 2px 5px;
   padding: 4px 5px 4px 25px;
 }
 
-.changes__copy-all-changes-button:hover {
-  background-color: var(--changes-button-copy-all-background-color--hover);
+.changes__copy-all-changes-button:hover,
+.changes__copy-all-changes-button:focus {
+  background-color: var(--theme-button-background);
 }
 
 .changes__copy-all-changes-button:active {
-  background-color: var(--changes-button-copy-all-background-color--active);
+  background-color: var(--theme-button-active-background);
 }
 
 /* Hide the Copy Rule button by default. */
 .changes__copy-rule-button {
   /**
    * The rgba() format of the background colors makes the button see-through and it looks
    * bad when stacked on top of long selectors.
    *
    * To solve this and not change the color format which is inherited from the Photon
    * color guide in `client/themes/variables.css`, we use a blending trick to overlay the
    * rgba() color value onto a solid color used for the panel background (achieved with
    * a linear gradient with no transition). This keeps the rgba() color, but prevents the
    * see-through effect.
    */
   background-blend-mode: overlay;
-  background-color: var(--changes-button-copy-rule-background-color);
+  background-color: var(--theme-button-background);
   background-image:
     linear-gradient(var(--theme-sidebar-background), var(--theme-sidebar-background));
   border-radius: 8px;
   border: none;
   color: var(--theme-body-color);
   display: none;
   padding: 1px 7px;
   position: absolute;
   right: 5px;
   top: 2px;
 }
 
 .changes__copy-rule-button:active {
-  background-color: var(--changes-button-copy-rule-background-color--active);
+  background-color: var(--theme-button-active-background);
 }
 
 .changes__rule {
   position: relative;
 }
 
 .changes__selector {
   word-wrap: break-word;
@@ -146,16 +140,20 @@
 
 /* Show the Copy Rule button when hovering over the rule's selector elements */
 .changes__selector:hover + .changes__copy-rule-button,
 .changes__selector:hover + .changes__selector + .changes__copy-rule-button,
 .changes__copy-rule-button:hover {
   display: block;
 }
 
+.changes__declaration {
+  overflow-wrap: break-word;
+}
+
 .changes__declaration-name {
   margin-left: 10px;
 }
 
 .diff-add,
 .diff-remove {
   --diff-level-min-offset: 15px;
   position: relative;
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -562,16 +562,17 @@
   padding: 0;
   margin: -1px -3px -1px -1px;
 }
 
 .ruleview-property {
   border-left: 3px solid transparent;
   clear: both;
   padding-left: 28px;
+  overflow-wrap: break-word;
 }
 
 .ruleview-propertycontainer  > * {
   vertical-align: middle;
 }
 
 .ruleview-property.ruleview-changed,
 .ruleview-property[dirty] {
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -25,16 +25,20 @@
   --theme-toolbar-color: var(--grey-90);
   --theme-toolbar-selected-color: var(--blue-60);
   --theme-toolbar-highlighted-color: var(--green-60);
   --theme-toolbar-background-hover: rgba(221, 225, 228, 0.66);
   --theme-toolbar-background-alt: #f5f5f5;
   --theme-toolbar-hover: var(--grey-20);
   --theme-toolbar-hover-active: var(--grey-20);
 
+  /* Buttons */
+  --theme-button-background: rgba(12, 12, 13, 0.05);
+  --theme-button-active-background: rgba(12, 12, 13, 0.1);
+
   /* Selection */
   --theme-selection-background: var(--blue-55);
   --theme-selection-background-hover: #F0F9FE;
   --theme-selection-focus-background: var(--toolbarbutton-hover-background);
   --theme-selection-focus-color: var(--grey-70);
   --theme-selection-color: #ffffff;
 
   /* Border color that splits the toolbars/panels/headers. */
@@ -114,16 +118,20 @@
   --theme-toolbar-color: var(--grey-40);
   --theme-toolbar-selected-color: white;
   --theme-toolbar-highlighted-color: var(--green-50);
   --theme-toolbar-background-hover: #20232B;
   --theme-toolbar-background-alt: var(--grey-85);
   --theme-toolbar-hover: #252526;
   --theme-toolbar-hover-active: #252526;
 
+  /* Buttons */
+  --theme-button-background: rgba(249, 249, 250, 0.1);
+  --theme-button-active-background: rgba(249, 249, 250, 0.15);
+
   /* Selection */
   --theme-selection-background: #204E8A;
   --theme-selection-background-hover: #353B48;
   --theme-selection-focus-background: var(--grey-60);
   --theme-selection-focus-color: var(--grey-30);
   --theme-selection-color: #ffffff;
 
   /* Border color that splits the toolbars/panels/headers. */
@@ -240,25 +248,21 @@
 
   --yellow-50: #ffe900;
   --yellow-60: #d7b600;
   --yellow-65: #be9b00;
   --yellow-70: #a47f00;
   --yellow-80: #715100;
 
   --grey-10: #f9f9fa;
-  --grey-10-a10: rgba(249, 249, 250, 0.1);
-  --grey-10-a15: rgba(249, 249, 250, 0.15);
   --grey-20: #ededf0;
   --grey-25: #e0e0e2;
   --grey-30: #d7d7db;
   --grey-40: #b1b1b3;
   --grey-45: #939395;
   --grey-50: #737373;
   --grey-55: #5c5c5f;
   --grey-60: #4a4a4f;
   --grey-70: #38383d;
   --grey-80: #2a2a2e;
   --grey-85: #1b1b1d;
   --grey-90: #0c0c0d;
-  --grey-90-a05: rgba(12, 12, 13, 0.05);
-  --grey-90-a10: rgba(12, 12, 13, 0.1);
 }
--- a/dom/base/ContentBlockingLog.cpp
+++ b/dom/base/ContentBlockingLog.cpp
@@ -87,26 +87,53 @@ void ContentBlockingLog::ReportLog() {
     }
 
     for (const auto& logEntry : Reversed(originEntry.mData->mLogs)) {
       if (logEntry.mType !=
           nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
         continue;
       }
 
-      bool isBlocked = logEntry.mBlocked;
-      auto metricId = isBlocked
-                          ? Telemetry::OriginMetricID::ContentBlocking_Blocked
-                          : Telemetry::OriginMetricID::ContentBlocking_Exempt;
-      if (StaticPrefs::telemetry_origin_telemetry_test_mode_enabled()) {
-        metricId =
-            isBlocked
-                ? Telemetry::OriginMetricID::ContentBlocking_Blocked_TestOnly
-                : Telemetry::OriginMetricID::ContentBlocking_Exempt_TestOnly;
+      const bool isBlocked = logEntry.mBlocked;
+      Maybe<StorageAccessGrantedReason> reason = logEntry.mReason;
+      const bool testMode =
+          StaticPrefs::telemetry_origin_telemetry_test_mode_enabled();
+
+      using OriginMetricID = Telemetry::OriginMetricID;
+      OriginMetricID metricId =
+          testMode ? OriginMetricID::ContentBlocking_Blocked_TestOnly
+                   : OriginMetricID::ContentBlocking_Blocked;
+      if (!isBlocked) {
+        MOZ_ASSERT(reason.isSome());
+        switch (reason.value()) {
+          case StorageAccessGrantedReason::eStorageAccessAPI:
+            metricId =
+                testMode
+                    ? OriginMetricID::
+                          ContentBlocking_StorageAccessAPIExempt_TestOnly
+                    : OriginMetricID::ContentBlocking_StorageAccessAPIExempt;
+            break;
+          case StorageAccessGrantedReason::eOpenerAfterUserInteraction:
+            metricId =
+                testMode
+                    ? OriginMetricID::
+                          ContentBlocking_OpenerAfterUserInteractionExempt_TestOnly
+                    : OriginMetricID::
+                          ContentBlocking_OpenerAfterUserInteractionExempt;
+            break;
+          case StorageAccessGrantedReason::eOpener:
+            metricId =
+                testMode ? OriginMetricID::ContentBlocking_OpenerExempt_TestOnly
+                         : OriginMetricID::ContentBlocking_OpenerExempt;
+            break;
+          default:
+            MOZ_ASSERT_UNREACHABLE("Unknown StorageAccessGrantedReason");
+        }
       }
+
       Telemetry::RecordOrigin(metricId, originEntry.mOrigin);
       break;
     }
   }
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/ContentBlockingLog.h
+++ b/dom/base/ContentBlockingLog.h
@@ -2,33 +2,38 @@
 /* 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_ContentBlockingLog_h
 #define mozilla_dom_ContentBlockingLog_h
 
+#include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/JSONWriter.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
 #include "nsReadableUtils.h"
 #include "nsTArray.h"
 #include "nsWindowSizes.h"
 
 namespace mozilla {
 namespace dom {
 
 class ContentBlockingLog final {
+  typedef AntiTrackingCommon::StorageAccessGrantedReason
+      StorageAccessGrantedReason;
+
   struct LogEntry {
     uint32_t mType;
     uint32_t mRepeatCount;
     bool mBlocked;
+    Maybe<AntiTrackingCommon::StorageAccessGrantedReason> mReason;
   };
 
   struct OriginDataEntry {
     OriginDataEntry() : mHasTrackingContentLoaded(false) {}
 
     bool mHasTrackingContentLoaded;
     Maybe<bool> mHasCookiesLoaded;
     nsTArray<LogEntry> mLogs;
@@ -63,17 +68,28 @@ class ContentBlockingLog final {
       return aLeft.mOrigin.Equals(aRight);
     }
   };
 
  public:
   ContentBlockingLog() = default;
   ~ContentBlockingLog() = default;
 
-  void RecordLog(const nsACString& aOrigin, uint32_t aType, bool aBlocked) {
+  void RecordLog(
+      const nsACString& aOrigin, uint32_t aType, bool aBlocked,
+      const Maybe<AntiTrackingCommon::StorageAccessGrantedReason>& aReason) {
+    MOZ_ASSERT_IF(aBlocked, aReason.isNothing());
+    MOZ_ASSERT_IF(
+        aType != nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
+        aReason.isNothing());
+    MOZ_ASSERT_IF(
+        !aBlocked &&
+            aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
+        aReason.isSome());
+
     if (aOrigin.IsVoid()) {
       return;
     }
     auto index = mLog.IndexOf(aOrigin, 0, Comparator());
     if (index != OriginDataTable::NoIndex) {
       OriginEntry& entry = mLog[index];
       if (!entry.mData) {
         return;
@@ -91,26 +107,28 @@ class ContentBlockingLog final {
         }
         return;
       }
       if (!entry.mData->mLogs.IsEmpty()) {
         auto& last = entry.mData->mLogs.LastElement();
         if (last.mType == aType && last.mBlocked == aBlocked) {
           ++last.mRepeatCount;
           // Don't record recorded events.  This helps compress our log.
+          // We don't care about if the the reason is the same, just keep the
+          // first one.
           return;
         }
       }
       if (entry.mData->mLogs.Length() ==
           std::max(1u,
                    StaticPrefs::browser_contentblocking_originlog_length())) {
         // Cap the size at the maximum length adjustable by the pref
         entry.mData->mLogs.RemoveElementAt(0);
       }
-      entry.mData->mLogs.AppendElement(LogEntry{aType, 1u, aBlocked});
+      entry.mData->mLogs.AppendElement(LogEntry{aType, 1u, aBlocked, aReason});
       return;
     }
 
     // The entry has not been found.
 
     OriginEntry* entry = mLog.AppendElement();
     if (NS_WARN_IF(!entry || !entry->mData)) {
       return;
@@ -119,17 +137,17 @@ class ContentBlockingLog final {
     entry->mOrigin = aOrigin;
 
     if (aType == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
       entry->mData->mHasTrackingContentLoaded = aBlocked;
     } else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
       MOZ_ASSERT(entry->mData->mHasCookiesLoaded.isNothing());
       entry->mData->mHasCookiesLoaded.emplace(aBlocked);
     } else {
-      entry->mData->mLogs.AppendElement(LogEntry{aType, 1u, aBlocked});
+      entry->mData->mLogs.AppendElement(LogEntry{aType, 1u, aBlocked, aReason});
     }
   }
 
   void ReportLog();
 
   nsAutoCString Stringify() {
     nsAutoCString buffer;
 
@@ -162,16 +180,19 @@ class ContentBlockingLog final {
         w.EndArray();
       }
       for (const LogEntry& item : entry.mData->mLogs) {
         w.StartArrayElement(w.SingleLineStyle);
         {
           w.IntElement(item.mType);
           w.BoolElement(item.mBlocked);
           w.IntElement(item.mRepeatCount);
+          if (item.mReason.isSome()) {
+            w.IntElement(item.mReason.value());
+          }
         }
         w.EndArray();
       }
       w.EndArray();
     }
 
     w.End();
 
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1922,33 +1922,42 @@ bool Document::IsVisibleConsideringAnces
   } while ((parent = parent->GetParentDocument()));
 
   return true;
 }
 
 void Document::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
   nsCOMPtr<nsIURI> uri;
   nsCOMPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsIPrincipal> storagePrincipal;
   if (aChannel) {
     // Note: this code is duplicated in XULDocument::StartDocumentLoad and
-    // nsScriptSecurityManager::GetChannelResultPrincipal.
+    // nsScriptSecurityManager::GetChannelResultPrincipals.
     // Note: this should match nsDocShell::OnLoadingSite
     NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
 
     nsIScriptSecurityManager* securityManager =
         nsContentUtils::GetSecurityManager();
     if (securityManager) {
-      securityManager->GetChannelResultPrincipal(aChannel,
-                                                 getter_AddRefs(principal));
-    }
-  }
+      securityManager->GetChannelResultPrincipals(
+          aChannel, getter_AddRefs(principal),
+          getter_AddRefs(storagePrincipal));
+    }
+  }
+
+  bool equal = principal->Equals(storagePrincipal);
 
   principal = MaybeDowngradePrincipal(principal);
-
-  ResetToURI(uri, aLoadGroup, principal);
+  if (equal) {
+    storagePrincipal = principal;
+  } else {
+    storagePrincipal = MaybeDowngradePrincipal(storagePrincipal);
+  }
+
+  ResetToURI(uri, aLoadGroup, principal, storagePrincipal);
 
   // Note that, since mTiming does not change during a reset, the
   // navigationStart time remains unchanged and therefore any future new
   // timeline will have the same global clock time as the old one.
   mDocumentTimeline = nullptr;
 
   nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
   if (bag) {
@@ -2030,18 +2039,20 @@ void Document::DisconnectNodeTree() {
     }
     MOZ_ASSERT(!mCachedRootElement,
                "After removing all children, there should be no root elem");
   }
   mInUnlinkOrDeletion = oldVal;
 }
 
 void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                          nsIPrincipal* aPrincipal) {
+                          nsIPrincipal* aPrincipal,
+                          nsIPrincipal* aStoragePrincipal) {
   MOZ_ASSERT(aURI, "Null URI passed to ResetToURI");
+  MOZ_ASSERT(!!aPrincipal == !!aStoragePrincipal);
 
   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
           ("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()));
 
   mSecurityInfo = nullptr;
 
   nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
   if (!aLoadGroup || group != aLoadGroup) {
@@ -2061,17 +2072,17 @@ void Document::ResetToURI(nsIURI* aURI, 
 
   // Release the stylesheets list.
   mDOMStyleSheets = nullptr;
 
   // Release our principal after tearing down the document, rather than before.
   // This ensures that, during teardown, the document and the dying window
   // (which already nulled out its document pointer and cached the principal)
   // have matching principals.
-  SetPrincipal(nullptr);
+  SetPrincipals(nullptr, nullptr);
 
   // Clear the original URI so SetDocumentURI sets it.
   mOriginalURI = nullptr;
 
   SetDocumentURI(aURI);
   mChromeXHRDocURI = nullptr;
   // If mDocumentBaseURI is null, Document::GetBaseURI() returns
   // mDocumentURI.
@@ -2109,17 +2120,17 @@ void Document::ResetToURI(nsIURI* aURI, 
   mContentLanguage.Truncate();
   mBaseTarget.Truncate();
   mReferrer.Truncate();
 
   mXMLDeclarationBits = 0;
 
   // Now get our new principal
   if (aPrincipal) {
-    SetPrincipal(aPrincipal);
+    SetPrincipals(aPrincipal, aStoragePrincipal);
   } else {
     nsIScriptSecurityManager* securityManager =
         nsContentUtils::GetSecurityManager();
     if (securityManager) {
       nsCOMPtr<nsILoadContext> loadContext(mDocumentContainer);
 
       if (!loadContext && aLoadGroup) {
         nsCOMPtr<nsIInterfaceRequestor> cbs;
@@ -2129,17 +2140,17 @@ void Document::ResetToURI(nsIURI* aURI, 
 
       MOZ_ASSERT(loadContext,
                  "must have a load context or pass in an explicit principal");
 
       nsCOMPtr<nsIPrincipal> principal;
       nsresult rv = securityManager->GetLoadContextCodebasePrincipal(
           mDocumentURI, loadContext, getter_AddRefs(principal));
       if (NS_SUCCEEDED(rv)) {
-        SetPrincipal(principal);
+        SetPrincipals(principal, principal);
       }
     }
   }
 
   if (mFontFaceSet) {
     mFontFaceSet->RefreshStandardFontLoadPrincipal();
   }
 
@@ -2792,17 +2803,17 @@ nsresult Document::InitCSP(nsIChannel* a
   bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) &&
                               !(mSandboxFlags & SANDBOXED_ORIGIN);
 
   mSandboxFlags |= cspSandboxFlags;
 
   if (needNewNullPrincipal) {
     principal = NullPrincipal::CreateWithInheritedAttributes(principal);
     principal->SetCsp(csp);
-    SetPrincipal(principal);
+    SetPrincipals(principal, principal);
   }
 
   // ----- Enforce frame-ancestor policy on any applied policies
   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
   if (docShell) {
     bool safeAncestry = false;
 
     // PermitsAncestry sends violation reports when necessary
@@ -3016,26 +3027,29 @@ void Document::RemoveFromIdTable(Element
       !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
     IncrementExpandoGeneration(*this);
   }
   if (entry->IsEmpty()) {
     mIdentifierMap.RemoveEntry(entry);
   }
 }
 
-void Document::SetPrincipal(nsIPrincipal* aNewPrincipal) {
+void Document::SetPrincipals(nsIPrincipal* aNewPrincipal,
+                             nsIPrincipal* aNewStoragePrincipal) {
+  MOZ_ASSERT(!!aNewPrincipal == !!aNewStoragePrincipal);
   if (aNewPrincipal && mAllowDNSPrefetch && sDisablePrefetchHTTPSPref) {
     nsCOMPtr<nsIURI> uri;
     aNewPrincipal->GetURI(getter_AddRefs(uri));
     bool isHTTPS;
     if (!uri || NS_FAILED(uri->SchemeIs("https", &isHTTPS)) || isHTTPS) {
       mAllowDNSPrefetch = false;
     }
   }
   mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
+  mIntrinsicStoragePrincipal = aNewStoragePrincipal;
 
 #ifdef DEBUG
   // Validate that the docgroup is set correctly by calling its getter and
   // triggering its sanity check.
   //
   // If we're setting the principal to null, we don't want to perform the check,
   // as the document is entering an intermediate state where it does not have a
   // principal. It will be given another real principal shortly which we will
@@ -8192,31 +8206,32 @@ nsresult Document::CloneDocHelper(Docume
     nsCOMPtr<nsIURI> uri;
     if (channel) {
       NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
     } else {
       uri = Document::GetDocumentURI();
     }
     clone->mChannel = channel;
     if (uri) {
-      clone->ResetToURI(uri, loadGroup, NodePrincipal());
+      clone->ResetToURI(uri, loadGroup, NodePrincipal(),
+                        EffectiveStoragePrincipal());
     }
 
     clone->SetContainer(mDocumentContainer);
   }
 
   // Now ensure that our clone has the same URI, base URI, and principal as us.
   // We do this after the mCreatingStaticClone block above, because that block
   // can set the base URI to an incorrect value in cases when base URI
   // information came from the channel.  So we override explicitly, and do it
   // for all these properties, in case ResetToURI messes with any of the rest of
   // them.
   clone->SetDocumentURI(Document::GetDocumentURI());
   clone->SetChromeXHRDocURI(mChromeXHRDocURI);
-  clone->SetPrincipal(NodePrincipal());
+  clone->SetPrincipals(NodePrincipal(), EffectiveStoragePrincipal());
   clone->mDocumentBaseURI = mDocumentBaseURI;
   clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
 
   bool hasHadScriptObject = true;
   nsIScriptGlobalObject* scriptObject =
       GetScriptHandlingObject(hasHadScriptObject);
   NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
   if (mCreatingStaticClone) {
@@ -8825,18 +8840,19 @@ class nsAutoFocusEvent : public Runnable
       return NS_OK;
     }
 
     // Don't steal focus from the user.
     if (mTopWindow->GetFocusedElement()) {
       return NS_OK;
     }
 
-    mozilla::ErrorResult rv;
-    mElement->Focus(rv);
+    FocusOptions options;
+    ErrorResult rv;
+    mElement->Focus(options, rv);
     return rv.StealNSResult();
   }
 
  private:
   nsCOMPtr<Element> mElement;
   nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
 };
 
@@ -12828,10 +12844,28 @@ nsICookieSettings* Document::CookieSetti
   // we must have a nsCookieSettings. Let's create it.
   if (!mCookieSettings) {
     mCookieSettings = net::CookieSettings::Create();
   }
 
   return mCookieSettings;
 }
 
+nsIPrincipal* Document::EffectiveStoragePrincipal() const {
+  if (!StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
+    return NodePrincipal();
+  }
+
+  nsContentUtils::StorageAccess access =
+      nsContentUtils::StorageAllowedForDocument(this);
+
+  // Let's use the storage principal only if we need to partition the cookie
+  // jar. When the permission is granted, access will be different and the
+  // normal principal will be used.
+  if (access != nsContentUtils::StorageAccess::ePartitionedOrDeny) {
+    return NodePrincipal();
+  }
+
+  return mIntrinsicStoragePrincipal;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -518,19 +518,25 @@ class Document : public nsINode,
   }
   bool GetValueMissingState(const nsAString& aName) const final {
     return DocumentOrShadowRoot::GetValueMissingState(aName);
   }
   void SetValueMissingState(const nsAString& aName, bool aValue) final {
     return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
   }
 
+  nsIPrincipal* EffectiveStoragePrincipal() const;
+
   // nsIScriptObjectPrincipal
   nsIPrincipal* GetPrincipal() final { return NodePrincipal(); }
 
+  nsIPrincipal* GetEffectiveStoragePrincipal() final {
+    return EffectiveStoragePrincipal();
+  }
+
   // EventTarget
   void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
   EventListenerManager* GetOrCreateListenerManager() override;
   EventListenerManager* GetExistingListenerManager() const override;
 
   // This helper class must be set when we dispatch beforeunload and unload
   // events in order to avoid unterminate sync XHRs.
   class MOZ_RAII PageUnloadingEventTimeStamp {
@@ -720,20 +726,20 @@ class Document : public nsINode,
       return mUpgradeInsecurePreloads;
     }
     return mUpgradeInsecureRequests;
   }
 
   void SetReferrer(const nsACString& aReferrer) { mReferrer = aReferrer; }
 
   /**
-   * Set the principal responsible for this document.  Chances are,
-   * you do not want to be using this.
-   */
-  void SetPrincipal(nsIPrincipal* aPrincipal);
+   * Set the principals responsible for this document.  Chances are, you do not
+   * want to be using this.
+   */
+  void SetPrincipals(nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal);
 
   /**
    * Get the list of ancestor principals for a document.  This is the same as
    * the ancestor list for the document's docshell the last time SetContainer()
    * was called with a non-null argument. See the documentation for the
    * corresponding getter in docshell for how this list is determined.  We store
    * a copy of the list, because we may lose the ability to reach our docshell
    * before people stop asking us for this information.
@@ -1101,21 +1107,22 @@ class Document : public nsINode,
     RecordContentBlockingLog(aOriginBlocked,
                              nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL,
                              aHasAllCookiesBlocked);
   }
 
   /**
    * Set the tracking cookies blocked flag for this document.
    */
-  void SetHasTrackingCookiesBlocked(bool aHasTrackingCookiesBlocked,
-                                    const nsACString& aOriginBlocked) {
+  void SetHasTrackingCookiesBlocked(
+      bool aHasTrackingCookiesBlocked, const nsACString& aOriginBlocked,
+      const Maybe<AntiTrackingCommon::StorageAccessGrantedReason>& aReason) {
     RecordContentBlockingLog(
         aOriginBlocked, nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
-        aHasTrackingCookiesBlocked);
+        aHasTrackingCookiesBlocked, aReason);
   }
 
   /**
    * Set the third-party cookies blocked flag for this document.
    */
   void SetHasForeignCookiesBlocked(bool aHasForeignCookiesBlocked,
                                    const nsACString& aOriginBlocked) {
     RecordContentBlockingLog(
@@ -2081,22 +2088,23 @@ class Document : public nsINode,
   /**
    * Reset the document using the given channel and loadgroup.  This works
    * like ResetToURI, but also sets the document's channel to aChannel.
    * The principal of the document will be set from the channel.
    */
   virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup);
 
   /**
-   * Reset this document to aURI, aLoadGroup, and aPrincipal.  aURI must not be
-   * null.  If aPrincipal is null, a codebase principal based on aURI will be
-   * used.
+   * Reset this document to aURI, aLoadGroup, aPrincipal and aStoragePrincipal.
+   * aURI must not be null.  If aPrincipal is null, a codebase principal based
+   * on aURI will be used.
    */
   virtual void ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                          nsIPrincipal* aPrincipal);
+                          nsIPrincipal* aPrincipal,
+                          nsIPrincipal* aStoragePrincipal);
 
   /**
    * Set the container (docshell) for this document. Virtual so that
    * docshell can call it.
    */
   virtual void SetContainer(nsDocShell* aContainer);
 
   /**
@@ -3835,19 +3843,21 @@ class Document : public nsINode,
   void NotifyStyleSheetApplicableStateChanged();
   // Just like EnableStyleSheetsForSet, but doesn't check whether
   // aSheetSet is null and allows the caller to control whether to set
   // aSheetSet as the preferred set in the CSSLoader.
   void EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
                                        bool aUpdateCSSLoader);
 
  private:
-  void RecordContentBlockingLog(const nsACString& aOrigin, uint32_t aType,
-                                bool aBlocked) {
-    mContentBlockingLog.RecordLog(aOrigin, aType, aBlocked);
+  void RecordContentBlockingLog(
+      const nsACString& aOrigin, uint32_t aType, bool aBlocked,
+      const Maybe<AntiTrackingCommon::StorageAccessGrantedReason>& aReason =
+          Nothing()) {
+    mContentBlockingLog.RecordLog(aOrigin, aType, aBlocked, aReason);
   }
 
   mutable std::bitset<eDeprecatedOperationCount> mDeprecationWarnedAbout;
   mutable std::bitset<eDocumentWarningCount> mDocWarningWarnedAbout;
 
   // Lazy-initialization to have mDocGroup initialized in prior to the
   // SelectorCaches.
   UniquePtr<SelectorCache> mSelectorCache;
@@ -4729,16 +4739,19 @@ class Document : public nsINode,
   int32_t mGeneration;
 
   // Cached TabSizes values for the document.
   int32_t mCachedTabSizeGeneration;
   nsTabSizes mCachedTabSizes;
 
   bool mInRDMPane;
 
+  // The principal to use for the storage area of this document.
+  nsCOMPtr<nsIPrincipal> mIntrinsicStoragePrincipal;
+
  public:
   // Needs to be public because the bindings code pokes at it.
   js::ExpandoAndGeneration mExpandoAndGeneration;
 
   bool HasPendingInitialTranslation() { return mPendingInitialTranslation; }
 
   void TraceProtos(JSTracer* aTrc);
 };
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -333,25 +333,31 @@ int32_t Element::TabIndex() {
   const nsAttrValue* attrVal = mAttrs.GetAttr(nsGkAtoms::tabindex);
   if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
     return attrVal->GetIntegerValue();
   }
 
   return TabIndexDefault();
 }
 
-void Element::Focus(mozilla::ErrorResult& aError) {
+void Element::Focus(const FocusOptions& aOptions, ErrorResult& aError) {
   nsFocusManager* fm = nsFocusManager::GetFocusManager();
   // Also other browsers seem to have the hack to not re-focus (and flush) when
   // the element is already focused.
+  // Until https://github.com/whatwg/html/issues/4512 is clarified, we'll
+  // maintain interoperatibility by not re-focusing, independent of aOptions.
+  // I.e., `focus({ preventScroll: true})` followed by `focus( { preventScroll:
+  // false })` won't re-focus.
   if (fm) {
     if (fm->CanSkipFocus(this)) {
       fm->NeedsFlushBeforeEventHandling(this);
     } else {
-      aError = fm->SetFocus(this, nsIFocusManager::FLAG_BYELEMENTFOCUS);
+      aError = fm->SetFocus(
+          this, nsIFocusManager::FLAG_BYELEMENTFOCUS |
+                    nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions));
     }
   }
 }
 
 void Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError) {
   nsAutoString value;
   value.AppendInt(aTabIndex);
 
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -248,17 +248,17 @@ class Element : public FragmentOrElement
    *
    * @param aShadowRoot The ShadowRoot to be bound to this element.
    */
   void SetShadowRoot(ShadowRoot* aShadowRoot);
 
   /**
    * Make focus on this element.
    */
-  virtual void Focus(mozilla::ErrorResult& aError);
+  virtual void Focus(const FocusOptions& aOptions, ErrorResult& aError);
 
   /**
    * Show blur and clear focus.
    */
   virtual void Blur(mozilla::ErrorResult& aError);
 
   /**
    * The style state of this element. This is the real state of the element
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8220,17 +8220,17 @@ nsContentUtils::StorageAccess nsContentU
   }
 
   // No document? Let's return a generic rejected reason.
   return StorageAccess::eDeny;
 }
 
 // static, public
 nsContentUtils::StorageAccess nsContentUtils::StorageAllowedForDocument(
-    Document* aDoc) {
+    const Document* aDoc) {
   MOZ_ASSERT(aDoc);
 
   if (nsPIDOMWindowInner* inner = aDoc->GetInnerWindow()) {
     nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
     // Note that GetChannel() below may return null, but that's OK, since the
     // callee is able to deal with a null channel argument, and if passed null,
     // will only fail to notify the UI in case storage gets blocked.
     nsIChannel* channel = aDoc->GetChannel();
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2990,17 +2990,17 @@ class nsContentUtils {
    * Checks if storage for the given document is permitted by a combination of
    * the user's preferences, and whether the document's window is a third-party
    * iframe.
    *
    * Note, this may be used on documents during the loading process where
    * the window's extant document has not been set yet.  The code in
    * StorageAllowedForWindow(), however, will not work in these cases.
    */
-  static StorageAccess StorageAllowedForDocument(Document* aDoc);
+  static StorageAccess StorageAllowedForDocument(const Document* aDoc);
 
   /*
    * Checks if storage should be allowed for a new window with the given
    * principal, load URI, and parent.
    */
   static StorageAccess StorageAllowedForNewWindow(nsIPrincipal* aPrincipal,
                                                   nsIURI* aURI,
                                                   nsPIDOMWindowInner* aParent);
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -42,16 +42,17 @@
 #include "nsFrameLoader.h"
 #include "nsNumberControlFrame.h"
 #include "nsNetUtil.h"
 #include "nsRange.h"
 
 #include "mozilla/AccessibleCaretEventHub.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSlotElement.h"
 #include "mozilla/dom/BrowserBridgeChild.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
@@ -2110,19 +2111,16 @@ void nsFocusManager::FireFocusOrBlurEven
                             currentWindow, currentFocusedContent,
                             aRelatedTarget);
     }
   }
 }
 
 void nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
                                     nsIContent* aContent, uint32_t aFlags) {
-  MOZ_ASSERT(!(aFlags & FLAG_BYELEMENTFOCUS) ||
-                 !!(aFlags & FLAG_BYELEMENTFOCUS) == !(aFlags & FLAG_NOSCROLL),
-             "FLAG_BYELEMENTFOCUS shouldn't involve with FLAG_NOSCROLL");
   // if the noscroll flag isn't set, scroll the newly focused element into view
   if (!(aFlags & FLAG_NOSCROLL)) {
     uint32_t scrollFlags = nsIPresShell::SCROLL_OVERFLOW_HIDDEN;
     if (!(aFlags & FLAG_BYELEMENTFOCUS)) {
       scrollFlags |= nsIPresShell::SCROLL_IGNORE_SCROLL_MARGIN_AND_PADDING;
     }
     aPresShell->ScrollContentIntoView(
         aContent,
@@ -2859,16 +2857,21 @@ nsresult nsFocusManager::DetermineElemen
     // wrapped all the way around and didn't find anything to move the focus
     // to, so just break out
     if (startContent == originalStartContent) break;
   }
 
   return NS_OK;
 }
 
+uint32_t nsFocusManager::FocusOptionsToFocusManagerFlags(
+    const mozilla::dom::FocusOptions& aOptions) {
+  return aOptions.mPreventScroll ? nsIFocusManager::FLAG_NOSCROLL : 0;
+}
+
 static bool IsHostOrSlot(const nsIContent* aContent) {
   return aContent && (aContent->GetShadowRoot() ||
                       aContent->IsHTMLElement(nsGkAtoms::slot));
 }
 
 // Helper class to iterate contents in scope by traversing flattened tree
 // in tree order
 class MOZ_STACK_CLASS ScopedContentTraversal {
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -24,16 +24,17 @@
 
 class nsIContent;
 class nsIDocShellTreeItem;
 class nsPIDOMWindowOuter;
 
 namespace mozilla {
 namespace dom {
 class Element;
+struct FocusOptions;
 class TabParent;
 }  // namespace dom
 }  // namespace mozilla
 
 struct nsDelayedBlurOrFocusEvent;
 
 /**
  * The focus manager keeps track of where the focus is, that is, the node
@@ -162,16 +163,19 @@ class nsFocusManager final : public nsIF
    * navigation is not done to parent documents and iteration returns to the
    * beginning (or end) of the starting document.
    */
   nsresult DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
                                        nsIContent* aStart, int32_t aType,
                                        bool aNoParentTraversal,
                                        nsIContent** aNextContent);
 
+  static uint32_t FocusOptionsToFocusManagerFlags(
+      const mozilla::dom::FocusOptions& aOptions);
+
   /**
    * Returns the content node that focus will be redirected to if aContent was
    * focused. This is used for the special case of certain XUL elements such
    * as textboxes or input number which redirect focus to an anonymous child.
    *
    * aContent must be non-null.
    *
    * XXXndeakin this should be removed eventually but I want to do that as
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1123,16 +1123,17 @@ void nsGlobalWindowInner::FreeInnerObjec
 
 #if defined(MOZ_WIDGET_ANDROID)
   mOrientationChangeObserver = nullptr;
 #endif
 
   if (mDoc) {
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
+    mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
     mDocBaseURI = mDoc->GetDocBaseURI();
 
     while (mDoc->EventHandlingSuppressed()) {
       mDoc->UnsuppressEventHandlingAndFireEvents(false);
     }
 
     if (mObservingDidRefresh) {
@@ -1352,16 +1353,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharedWorkers)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTabChild)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
   for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
     cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
   }
 
@@ -1455,16 +1457,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
         ->Disconnect();
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache)
   }
   if (tmp->mIndexedDB) {
     tmp->mIndexedDB->DisconnectFromGlobal(tmp);
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTabChild)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)
 
@@ -2043,16 +2046,39 @@ nsIPrincipal* nsGlobalWindowInner::GetPr
 
   if (objPrincipal) {
     return objPrincipal->GetPrincipal();
   }
 
   return nullptr;
 }
 
+nsIPrincipal* nsGlobalWindowInner::GetEffectiveStoragePrincipal() {
+  if (mDoc) {
+    // If we have a document, get the principal from the document
+    return mDoc->EffectiveStoragePrincipal();
+  }
+
+  if (mDocumentStoragePrincipal) {
+    return mDocumentStoragePrincipal;
+  }
+
+  // If we don't have a storage principal and we don't have a document we ask
+  // the parent window for the storage principal.
+
+  nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+      do_QueryInterface(GetParentInternal());
+
+  if (objPrincipal) {
+    return objPrincipal->GetEffectiveStoragePrincipal();
+  }
+
+  return nullptr;
+}
+
 //*****************************************************************************
 // nsGlobalWindowInner::nsIDOMWindow
 //*****************************************************************************
 
 bool nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext) {
   mAudioContexts.AppendElement(aAudioContext);
 
   // Return true if the context should be muted and false if not.
@@ -2790,20 +2816,19 @@ bool nsGlobalWindowInner::MayResolve(jsi
     // We only resolve .controllers/.Controllers in release builds and on
     // non-chrome windows, but let's not worry about any of that stuff.
     return true;
   }
 
   return WebIDLGlobalNameHash::MayResolve(aId);
 }
 
-void nsGlobalWindowInner::GetOwnPropertyNames(JSContext* aCx,
-                                              JS::MutableHandleVector<jsid> aNames,
-                                              bool aEnumerableOnly,
-                                              ErrorResult& aRv) {
+void nsGlobalWindowInner::GetOwnPropertyNames(
+    JSContext* aCx, JS::MutableHandleVector<jsid> aNames, bool aEnumerableOnly,
+    ErrorResult& aRv) {
   if (aEnumerableOnly) {
     // The names we would return from here get defined on the window via one of
     // two codepaths.  The ones coming from the WebIDLGlobalNameHash will end up
     // in the DefineConstructor function in BindingUtils, which always defines
     // things as non-enumerable.  The ones coming from the script namespace
     // manager get defined by our resolve hook using FillPropertyDescriptor with
     // 0 for the property attributes, so non-enumerable as well.
     //
@@ -6867,16 +6892,19 @@ void nsGlobalWindowInner::StorageAccessG
 
     if (NextGenLocalStorageEnabled() && mListenerManager &&
         mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
       auto object = static_cast<LSObject*>(mLocalStorage.get());
 
       object->EnsureObserver();
     }
   }
+
+  // Reset the IndexedDB factory.
+  mIndexedDB = nullptr;
 }
 
 mozilla::dom::TabGroup* nsPIDOMWindowInner::TabGroup() {
   return nsGlobalWindowInner::Cast(this)->TabGroupInner();
 }
 
 /* static */
 already_AddRefed<nsGlobalWindowInner> nsGlobalWindowInner::Create(
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -255,16 +255,18 @@ class nsGlobalWindowInner final : public
 
   virtual nsIScriptContext* GetScriptContext() override;
 
   virtual bool IsBlackForCC(bool aTracingNeeded = true) override;
 
   // nsIScriptObjectPrincipal
   virtual nsIPrincipal* GetPrincipal() override;
 
+  virtual nsIPrincipal* GetEffectiveStoragePrincipal() override;
+
   // nsIDOMWindow
   NS_DECL_NSIDOMWINDOW
 
   // nsIDOMChromeWindow (only implemented on chrome windows)
   NS_DECL_NSIDOMCHROMEWINDOW
 
   void CaptureEvents();
   void ReleaseEvents();
@@ -1289,16 +1291,18 @@ class nsGlobalWindowInner final : public
   RefPtr<nsHistory> mHistory;
   RefPtr<mozilla::dom::CustomElementRegistry> mCustomElements;
 
   nsTObserverArray<RefPtr<mozilla::dom::SharedWorker>> mSharedWorkers;
 
   RefPtr<mozilla::dom::VisualViewport> mVisualViewport;
 
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+  nsCOMPtr<nsIPrincipal> mDocumentStoragePrincipal;
+
   // mTabChild is only ever populated in the content process.
   nsCOMPtr<nsITabChild> mTabChild;
 
   uint32_t mSuspendDepth;
   uint32_t mFreezeDepth;
 
 #ifdef DEBUG
   uint32_t mSerial;
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -471,18 +471,19 @@ class nsOuterWindowProxy : public MaybeC
    * enumerating.
    *
    * We have to override this because js::Wrapper overrides it, but we want
    * different behavior from js::Wrapper.
    *
    * "proxy" is the WindowProxy object involved.  It may not be same-compartment
    * with cx.
    */
-  bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
-                                    JS::MutableHandleVector<jsid> props) const override;
+  bool getOwnEnumerablePropertyKeys(
+      JSContext* cx, JS::Handle<JSObject*> proxy,
+      JS::MutableHandleVector<jsid> props) const override;
 
   /**
    * Hook used by SpiderMonkey to implement Object.prototype.toString.
    */
   const char* className(JSContext* cx,
                         JS::Handle<JSObject*> wrapper) const override;
 
   void finalize(JSFreeOp* fop, JSObject* proxy) const override;
@@ -728,19 +729,19 @@ bool nsOuterWindowProxy::definePropertyS
     return true;
   }
 #endif
 
   result.succeed();
   return true;
 }
 
-bool nsOuterWindowProxy::ownPropertyKeys(JSContext* cx,
-                                         JS::Handle<JSObject*> proxy,
-                                         JS::MutableHandleVector<jsid> props) const {
+bool nsOuterWindowProxy::ownPropertyKeys(
+    JSContext* cx, JS::Handle<JSObject*> proxy,
+    JS::MutableHandleVector<jsid> props) const {
   // Just our indexed stuff followed by our "normal" own property names.
   if (!AppendIndexedPropertyNames(proxy, props)) {
     return false;
   }
 
   if (IsPlatformObjectSameOrigin(cx, proxy)) {
     // When forwarding to js::Wrapper, we should just enter the Realm of proxy
     // for now.  That's what js::Wrapper expects, and since we're same-origin
@@ -933,17 +934,18 @@ bool nsOuterWindowProxy::set(JSContext* 
   }
 
   JS_MarkCrossZoneId(cx, id);
 
   return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result);
 }
 
 bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys(
-    JSContext* cx, JS::Handle<JSObject*> proxy, JS::MutableHandleVector<jsid> props) const {
+    JSContext* cx, JS::Handle<JSObject*> proxy,
+    JS::MutableHandleVector<jsid> props) const {
   // We could just stop overring getOwnEnumerablePropertyKeys and let our
   // superclasses deal (by falling back on the BaseProxyHandler implementation
   // that uses a combination of ownPropertyKeys and getOwnPropertyDescriptor to
   // only return the enumerable ones.  But maybe there's value in having
   // somewhat faster for-in iteration on Window objects...
 
   // Like ownPropertyKeys, our indexed stuff followed by our "normal" enumerable
   // own property names.
@@ -1396,16 +1398,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
 
   // Traverse stuff from nsPIDOMWindow
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerForInitialContentBrowser)
@@ -1422,16 +1425,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
 
   // Unlink stuff from nsPIDOMWindow
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerForInitialContentBrowser)
@@ -1889,16 +1893,18 @@ static nsresult CreateNativeGlobalForInn
   return NS_OK;
 }
 
 nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
                                              nsISupports* aState,
                                              bool aForceReuseInnerWindow) {
   MOZ_ASSERT(mDocumentPrincipal == nullptr,
              "mDocumentPrincipal prematurely set!");
+  MOZ_ASSERT(mDocumentStoragePrincipal == nullptr,
+             "mDocumentStoragePrincipal prematurely set!");
   MOZ_ASSERT(aDocument);
 
   // Bail out early if we're in process of closing down the window.
   NS_ENSURE_STATE(!mCleanedUp);
 
   NS_ASSERTION(!GetCurrentInnerWindow() ||
                    GetCurrentInnerWindow()->GetExtantDoc() == mDoc,
                "Uh, mDoc doesn't match the current inner window "
@@ -2441,16 +2447,17 @@ void nsGlobalWindowOuter::DetachFromDocS
 
   nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal();
 
   if (currentInner) {
     NS_ASSERTION(mDoc, "Must have doc!");
 
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
+    mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
 
     // Release our document reference
     DropOuterWindowDocs();
   }
 
   ClearControllers();
 
@@ -2756,16 +2763,39 @@ nsIPrincipal* nsGlobalWindowOuter::GetPr
 
   if (objPrincipal) {
     return objPrincipal->GetPrincipal();
   }
 
   return nullptr;
 }
 
+nsIPrincipal* nsGlobalWindowOuter::GetEffectiveStoragePrincipal() {
+  if (mDoc) {
+    // If we have a document, get the principal from the document
+    return mDoc->EffectiveStoragePrincipal();
+  }
+
+  if (mDocumentStoragePrincipal) {
+    return mDocumentStoragePrincipal;
+  }
+
+  // If we don't have a storage principal and we don't have a document we ask
+  // the parent window for the storage principal.
+
+  nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+      do_QueryInterface(GetParentInternal());
+
+  if (objPrincipal) {
+    return objPrincipal->GetEffectiveStoragePrincipal();
+  }
+
+  return nullptr;
+}
+
 //*****************************************************************************
 // nsGlobalWindowOuter::nsIDOMWindow
 //*****************************************************************************
 
 void nsPIDOMWindowOuter::SetInitialKeyboardIndicators(
     UIStateChangeType aShowAccelerators, UIStateChangeType aShowFocusRings) {
   MOZ_ASSERT(!GetCurrentInnerWindow());
 
@@ -5350,21 +5380,28 @@ void nsGlobalWindowOuter::FirePopupBlock
   RefPtr<PopupBlockedEvent> event = PopupBlockedEvent::Constructor(
       aDoc, NS_LITERAL_STRING("DOMPopupBlocked"), init);
 
   event->SetTrusted(true);
 
   aDoc->DispatchEvent(*event);
 }
 
-void nsGlobalWindowOuter::NotifyContentBlockingEvent(unsigned aEvent,
-                                                     nsIChannel* aChannel,
-                                                     bool aBlocked,
-                                                     nsIURI* aURIHint) {
+void nsGlobalWindowOuter::NotifyContentBlockingEvent(
+    unsigned aEvent, nsIChannel* aChannel, bool aBlocked, nsIURI* aURIHint,
+    const mozilla::Maybe<AntiTrackingCommon::StorageAccessGrantedReason>&
+        aReason) {
   MOZ_ASSERT(aURIHint);
+  MOZ_ASSERT_IF(aBlocked, aReason.isNothing());
+  MOZ_ASSERT_IF(aEvent != nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
+                aReason.isNothing());
+  MOZ_ASSERT_IF(
+      !aBlocked &&
+          aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
+      aReason.isSome());
 
   nsCOMPtr<nsIDocShell> docShell = GetDocShell();
   if (!docShell) {
     return;
   }
   nsCOMPtr<Document> doc = docShell->GetDocument();
   NS_ENSURE_TRUE_VOID(doc);
 
@@ -5376,17 +5413,17 @@ void nsGlobalWindowOuter::NotifyContentB
     Preferences::AddBoolVarCache(
         &gSyncContentBlockingNotifications,
         "dom.testing.sync-content-blocking-notifications", false);
     prefInitialized = true;
   }
 
   nsCOMPtr<nsIRunnable> func = NS_NewRunnableFunction(
       "NotifyContentBlockingEventDelayed",
-      [doc, docShell, uri, channel, aEvent, aBlocked]() {
+      [doc, docShell, uri, channel, aEvent, aBlocked, aReason]() {
         // This event might come after the user has navigated to another
         // page. To prevent showing the TrackingProtection UI on the wrong
         // page, we need to check that the loading URI for the channel is
         // the same as the URI currently loaded in the document.
         if (!SameLoadingURI(doc, channel) &&
             aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
           return;
         }
@@ -5443,17 +5480,17 @@ void nsGlobalWindowOuter::NotifyContentB
         } else if (aEvent == nsIWebProgressListener::
                                  STATE_COOKIES_BLOCKED_BY_PERMISSION) {
           doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
           if (!aBlocked) {
             unblocked = !doc->GetHasCookiesBlockedByPermission();
           }
         } else if (aEvent ==
                    nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
-          doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
+          doc->SetHasTrackingCookiesBlocked(aBlocked, origin, aReason);
           if (!aBlocked) {
             unblocked = !doc->GetHasTrackingCookiesBlocked();
           }
         } else if (aEvent ==
                    nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
           doc->SetHasAllCookiesBlocked(aBlocked, origin);
           if (!aBlocked) {
             unblocked = !doc->GetHasAllCookiesBlocked();
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -242,16 +242,18 @@ class nsGlobalWindowOuter final : public
 
   void PoisonOuterWindowProxy(JSObject* aObject);
 
   virtual bool IsBlackForCC(bool aTracingNeeded = true) override;
 
   // nsIScriptObjectPrincipal
   virtual nsIPrincipal* GetPrincipal() override;
 
+  virtual nsIPrincipal* GetEffectiveStoragePrincipal() override;
+
   // nsIDOMWindow
   NS_DECL_NSIDOMWINDOW
 
   // nsIDOMChromeWindow (only implemented on chrome windows)
   NS_DECL_NSIDOMCHROMEWINDOW
 
   mozilla::dom::ChromeMessageBroadcaster* GetMessageManager();
   mozilla::dom::ChromeMessageBroadcaster* GetGroupMessageManager(
@@ -450,19 +452,21 @@ class nsGlobalWindowOuter final : public
   bool HadOriginalOpener() const { return mHadOriginalOpener; }
 
   bool IsTopLevelWindow();
 
   virtual void FirePopupBlockedEvent(
       Document* aDoc, nsIURI* aPopupURI, const nsAString& aPopupWindowName,
       const nsAString& aPopupWindowFeatures) override;
 
-  virtual void NotifyContentBlockingEvent(unsigned aEvent, nsIChannel* aChannel,
-                                          bool aBlocked,
-                                          nsIURI* aURIHint) override;
+  virtual void NotifyContentBlockingEvent(
+      unsigned aEvent, nsIChannel* aChannel, bool aBlocked, nsIURI* aURIHint,
+      const mozilla::Maybe<
+          mozilla::AntiTrackingCommon::StorageAccessGrantedReason>& aReason)
+      override;
 
   void AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
 
   void AllowScriptsToClose() { mAllowScriptsToClose = true; }
 
   // Outer windows only.
   uint32_t GetAutoActivateVRDisplayID();
   // Outer windows only.
@@ -1091,16 +1095,17 @@ class nsGlobalWindowOuter final : public
 
   RefPtr<nsDOMWindowList> mFrames;
   RefPtr<nsDOMWindowUtils> mWindowUtils;
   nsString mStatus;
 
   RefPtr<mozilla::dom::Storage> mLocalStorage;
 
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+  nsCOMPtr<nsIPrincipal> mDocumentStoragePrincipal;
 
 #ifdef DEBUG
   uint32_t mSerial;
 
   bool mSetOpenerWindowCalled;
   nsCOMPtr<nsIURI> mLastOpenedURI;
 #endif
 
--- a/dom/base/nsIScriptObjectPrincipal.h
+++ b/dom/base/nsIScriptObjectPrincipal.h
@@ -21,14 +21,16 @@ class nsIPrincipal;
 /**
  * JS Object Principal information.
  */
 class nsIScriptObjectPrincipal : public nsISupports {
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTOBJECTPRINCIPAL_IID)
 
   virtual nsIPrincipal* GetPrincipal() = 0;
+
+  virtual nsIPrincipal* GetEffectiveStoragePrincipal() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptObjectPrincipal,
                               NS_ISCRIPTOBJECTPRINCIPAL_IID)
 
 #endif  // nsIScriptObjectPrincipal_h__
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -8,16 +8,18 @@
 #define nsPIDOMWindow_h__
 
 #include "nsIDOMWindow.h"
 #include "mozIDOMWindow.h"
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "mozilla/dom/EventTarget.h"
+#include "mozilla/AntiTrackingCommon.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/TaskCategory.h"
 #include "js/TypeDecls.h"
 #include "nsRefPtrHashtable.h"
 
 // Only fired for inner windows.
 #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
@@ -997,18 +999,21 @@ class nsPIDOMWindowOuter : public mozIDO
 
   /**
    * Fire a popup blocked event on the document.
    */
   virtual void FirePopupBlockedEvent(Document* aDoc, nsIURI* aPopupURI,
                                      const nsAString& aPopupWindowName,
                                      const nsAString& aPopupWindowFeatures) = 0;
 
-  virtual void NotifyContentBlockingEvent(unsigned aEvent, nsIChannel* aChannel,
-                                          bool aBlocked, nsIURI* aURIHint) = 0;
+  virtual void NotifyContentBlockingEvent(
+      unsigned aEvent, nsIChannel* aChannel, bool aBlocked, nsIURI* aURIHint,
+      const mozilla::Maybe<
+          mozilla::AntiTrackingCommon::StorageAccessGrantedReason>& aReason =
+          mozilla::Nothing()) = 0;
 
   // WebIDL-ish APIs
   void MarkUncollectableForCCGeneration(uint32_t aGeneration) {
     mMarkedCCGeneration = aGeneration;
   }
 
   uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
 
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -43,61 +43,63 @@ class BroadcastChannelMessage final : pu
   BroadcastChannelMessage() : StructuredCloneDataNoTransfers() {}
 
  private:
   ~BroadcastChannelMessage() {}
 };
 
 namespace {
 
-nsIPrincipal* GetPrincipalFromThreadSafeWorkerRef(
+nsIPrincipal* GetStoragePrincipalFromThreadSafeWorkerRef(
     ThreadSafeWorkerRef* aWorkerRef) {
-  nsIPrincipal* principal = aWorkerRef->Private()->GetPrincipal();
-  if (principal) {
-    return principal;
+  nsIPrincipal* storagePrincipal =
+      aWorkerRef->Private()->GetEffectiveStoragePrincipal();
+  if (storagePrincipal) {
+    return storagePrincipal;
   }
 
   // Walk up to our containing page
   WorkerPrivate* wp = aWorkerRef->Private();
   while (wp->GetParent()) {
     wp = wp->GetParent();
   }
 
-  return wp->GetPrincipal();
+  return wp->GetEffectiveStoragePrincipal();
 }
 
 class InitializeRunnable final : public WorkerMainThreadRunnable {
  public:
   InitializeRunnable(ThreadSafeWorkerRef* aWorkerRef, nsACString& aOrigin,
-                     PrincipalInfo& aPrincipalInfo, ErrorResult& aRv)
+                     PrincipalInfo& aStoragePrincipalInfo, ErrorResult& aRv)
       : WorkerMainThreadRunnable(
             aWorkerRef->Private(),
             NS_LITERAL_CSTRING("BroadcastChannel :: Initialize")),
         mWorkerRef(aWorkerRef),
         mOrigin(aOrigin),
-        mPrincipalInfo(aPrincipalInfo),
+        mStoragePrincipalInfo(aStoragePrincipalInfo),
         mRv(aRv) {
     MOZ_ASSERT(mWorkerRef);
   }
 
   bool MainThreadRun() override {
     MOZ_ASSERT(NS_IsMainThread());
 
-    nsIPrincipal* principal = GetPrincipalFromThreadSafeWorkerRef(mWorkerRef);
-    if (!principal) {
+    nsIPrincipal* storagePrincipal =
+        GetStoragePrincipalFromThreadSafeWorkerRef(mWorkerRef);
+    if (!storagePrincipal) {
       mRv.Throw(NS_ERROR_FAILURE);
       return true;
     }
 
-    mRv = PrincipalToPrincipalInfo(principal, &mPrincipalInfo);
+    mRv = PrincipalToPrincipalInfo(storagePrincipal, &mStoragePrincipalInfo);
     if (NS_WARN_IF(mRv.Failed())) {
       return true;
     }
 
-    mRv = principal->GetOrigin(mOrigin);
+    mRv = storagePrincipal->GetOrigin(mOrigin);
     if (NS_WARN_IF(mRv.Failed())) {
       return true;
     }
 
     // Walk up to our containing page
     WorkerPrivate* wp = mWorkerRef->Private();
     while (wp->GetParent()) {
       wp = wp->GetParent();
@@ -110,17 +112,17 @@ class InitializeRunnable final : public 
     }
 
     return true;
   }
 
  private:
   RefPtr<ThreadSafeWorkerRef> mWorkerRef;
   nsACString& mOrigin;
-  PrincipalInfo& mPrincipalInfo;
+  PrincipalInfo& mStoragePrincipalInfo;
   ErrorResult& mRv;
 };
 
 class CloseRunnable final : public nsIRunnable, public nsICancelableRunnable {
  public:
   NS_DECL_ISUPPORTS
 
   explicit CloseRunnable(BroadcastChannel* aBC) : mBC(aBC) { MOZ_ASSERT(mBC); }
@@ -222,55 +224,57 @@ already_AddRefed<BroadcastChannel> Broad
   if (NS_WARN_IF(!global)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<BroadcastChannel> bc = new BroadcastChannel(global, aChannel);
 
   nsAutoCString origin;
-  PrincipalInfo principalInfo;
+  PrincipalInfo storagePrincipalInfo;
+
+  nsContentUtils::StorageAccess storageAccess;
 
   if (NS_IsMainThread()) {
     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
     if (NS_WARN_IF(!window)) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
 
     nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
 
     if (!incumbent) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
 
-    nsIPrincipal* principal = incumbent->PrincipalOrNull();
-    if (!principal) {
+    nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(incumbent);
+    if (NS_WARN_IF(!sop)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsIPrincipal* storagePrincipal = sop->GetEffectiveStoragePrincipal();
+    if (!storagePrincipal) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
 
-    // We want to allow opaque origins.
-    if (!principal->GetIsNullPrincipal() &&
-        nsContentUtils::StorageAllowedForWindow(window) <=
-            nsContentUtils::StorageAccess::eDeny) {
-      aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-      return nullptr;
-    }
-
-    aRv = principal->GetOrigin(origin);
+    aRv = storagePrincipal->GetOrigin(origin);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
-    aRv = PrincipalToPrincipalInfo(principal, &principalInfo);
+    aRv = PrincipalToPrincipalInfo(storagePrincipal, &storagePrincipalInfo);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
+
+    storageAccess = nsContentUtils::StorageAllowedForWindow(window);
   } else {
     JSContext* cx = aGlobal.Context();
 
     WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
     MOZ_ASSERT(workerPrivate);
 
     RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
         workerPrivate, "BroadcastChannel", [bc]() { bc->Shutdown(); });
@@ -279,41 +283,45 @@ already_AddRefed<BroadcastChannel> Broad
     if (NS_WARN_IF(!workerRef)) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
 
     RefPtr<ThreadSafeWorkerRef> tsr = new ThreadSafeWorkerRef(workerRef);
 
     RefPtr<InitializeRunnable> runnable =
-        new InitializeRunnable(tsr, origin, principalInfo, aRv);
+        new InitializeRunnable(tsr, origin, storagePrincipalInfo, aRv);
     runnable->Dispatch(Canceling, aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
 
-    if (principalInfo.type() != PrincipalInfo::TNullPrincipalInfo &&
-        !workerPrivate->IsStorageAllowed()) {
-      aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-      return nullptr;
-    }
+    storageAccess = workerPrivate->StorageAccess();
+    bc->mWorkerRef = workerRef;
+  }
 
-    bc->mWorkerRef = std::move(workerRef);
+  // We want to allow opaque origins.
+  if (storagePrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo &&
+      (storageAccess == nsContentUtils::StorageAccess::eDeny ||
+       (storageAccess == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
+        !StaticPrefs::privacy_storagePrincipal_enabledForTrackers()))) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
   }
 
   // Register this component to PBackground.
   PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!actorChild)) {
     // Firefox is probably shutting down. Let's return a 'generic' error.
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor(
-      principalInfo, origin, nsString(aChannel));
+      storagePrincipalInfo, origin, nsString(aChannel));
 
   bc->mActor = static_cast<BroadcastChannelChild*>(actor);
   MOZ_ASSERT(bc->mActor);
 
   bc->mActor->SetParent(bc);
 
   return bc.forget();
 }
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -562,28 +562,30 @@ void CacheStorage::RunRequest(nsAutoPtr<
 
 OpenMode CacheStorage::GetOpenMode() const {
   return mNamespace == CHROME_ONLY_NAMESPACE ? OpenMode::Eager : OpenMode::Lazy;
 }
 
 bool CacheStorage::HasStorageAccess() const {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 
+  nsContentUtils::StorageAccess access;
+
   if (NS_IsMainThread()) {
     nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
     if (NS_WARN_IF(!window)) {
       return true;
     }
 
-    nsContentUtils::StorageAccess access =
-        nsContentUtils::StorageAllowedForWindow(window);
-    return access > nsContentUtils::StorageAccess::ePrivateBrowsing;
+    access = nsContentUtils::StorageAllowedForWindow(window);
+  } else {
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+
+    access = workerPrivate->StorageAccess();
   }
 
-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(workerPrivate);
-
-  return workerPrivate->IsStorageAllowed();
+  return access > nsContentUtils::StorageAccess::ePrivateBrowsing;
 }
 
 }  // namespace cache
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/clients/manager/ClientSource.cpp
+++ b/dom/clients/manager/ClientSource.cpp
@@ -201,17 +201,18 @@ void ClientSource::WorkerExecutionReady(
     return;
   }
 
   // A client without access to storage should never be controlled by
   // a service worker.  Check this here in case we were controlled before
   // execution ready.  We can't reliably determine what our storage policy
   // is before execution ready, unfortunately.
   if (mController.isSome()) {
-    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed() ||
+    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->StorageAccess() >
+                              nsContentUtils::StorageAccess::ePrivateBrowsing ||
                           StringBeginsWith(aWorkerPrivate->ScriptURL(),
                                            NS_LITERAL_STRING("blob:")));
   }
 
   // Its safe to store the WorkerPrivate* here because the ClientSource
   // is explicitly destroyed by WorkerPrivate before exiting its run loop.
   MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
   mOwner = AsVariant(aWorkerPrivate);
@@ -375,17 +376,18 @@ void ClientSource::SetController(
   // and about:blank windows.
   if (GetInnerWindow()) {
     MOZ_DIAGNOSTIC_ASSERT(
         Info().URL().LowerCaseEqualsLiteral("about:blank") ||
         StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
         nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
             nsContentUtils::StorageAccess::eAllow);
   } else if (GetWorkerPrivate()) {
-    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed() ||
+    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->StorageAccess() >
+                              nsContentUtils::StorageAccess::ePrivateBrowsing ||
                           StringBeginsWith(GetWorkerPrivate()->ScriptURL(),
                                            NS_LITERAL_STRING("blob:")));
   }
 
   if (mController.isSome() && mController.ref() == aServiceWorker) {
     return;
   }
 
@@ -430,17 +432,18 @@ RefPtr<ClientOpPromise> ClientSource::Co
     // Local URL windows and windows with access to storage can be controlled.
     controlAllowed =
         Info().URL().LowerCaseEqualsLiteral("about:blank") ||
         StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
         nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
             nsContentUtils::StorageAccess::eAllow;
   } else if (GetWorkerPrivate()) {
     // Local URL workers and workers with access to storage cna be controlled.
-    controlAllowed = GetWorkerPrivate()->IsStorageAllowed() ||
+    controlAllowed = GetWorkerPrivate()->StorageAccess() >
+                         nsContentUtils::StorageAccess::ePrivateBrowsing ||
                      StringBeginsWith(GetWorkerPrivate()->ScriptURL(),
                                       NS_LITERAL_STRING("blob:"));
   }
 
   if (NS_WARN_IF(!controlAllowed)) {
     return ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
                                             __func__);
   }
@@ -643,23 +646,17 @@ nsresult ClientSource::SnapshotState(Cli
     return NS_OK;
   }
 
   WorkerPrivate* workerPrivate = GetWorkerPrivate();
   if (!workerPrivate) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  // Workers only keep a boolean for storage access at the moment.
-  // Map this back to eAllow or eDeny for now.
-  nsContentUtils::StorageAccess storage =
-      workerPrivate->IsStorageAllowed() ? nsContentUtils::StorageAccess::eAllow
-                                        : nsContentUtils::StorageAccess::eDeny;
-
-  *aStateOut = ClientState(ClientWorkerState(storage));
+  *aStateOut = ClientState(ClientWorkerState(workerPrivate->StorageAccess()));
   return NS_OK;
 }
 
 nsISerialEventTarget* ClientSource::EventTarget() const { return mEventTarget; }
 
 void ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback,
                             const char* aName, uint32_t aFlags) {
   if (mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -34,16 +34,17 @@
 #include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
 #include "mozilla/dom/Promise.h"
 #include "nsNetUtil.h"
+#include "nsReadableUtils.h"
 
 #define MOZ_CALLS_ENABLED_PREF "dom.datatransfer.mozAtAPIs"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
 
@@ -615,17 +616,28 @@ bool DataTransfer::PrincipalMaySetData(c
     }
 
     if (aType.EqualsASCII(kFileMime) || aType.EqualsASCII(kFilePromiseMime)) {
       NS_WARNING(
           "Disallowing adding x-moz-file or x-moz-file-promize types to "
           "DataTransfer");
       return false;
     }
+
+    // Disallow content from creating x-moz-place flavors, so that it cannot
+    // create fake Places smart queries exposing user data, but give a free
+    // pass to WebExtensions.
+    auto principal = BasePrincipal::Cast(aPrincipal);
+    if (!principal->AddonPolicy() &&
+        StringBeginsWith(aType, NS_LITERAL_STRING("text/x-moz-place"))) {
+      NS_WARNING("Disallowing adding moz-place types to DataTransfer");
+      return false;
+    }
   }
+
   return true;
 }
 
 void DataTransfer::TypesListMayHaveChanged() {
   DataTransfer_Binding::ClearCachedTypesValue(this);
 }
 
 already_AddRefed<DataTransfer> DataTransfer::MozCloneForEvent(
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2936,42 +2936,43 @@ void HTMLInputElement::Blur(ErrorResult&
       dispatcher->RunDOMEventWhenSafe();
       return;
     }
   }
 
   nsGenericHTMLElement::Blur(aError);
 }
 
-void HTMLInputElement::Focus(ErrorResult& aError) {
+void HTMLInputElement::Focus(const FocusOptions& aOptions,
+                             ErrorResult& aError) {
   if (mType == NS_FORM_INPUT_NUMBER) {
     // Focus our anonymous text control, if we have one.
     nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
     if (numberControlFrame) {
       RefPtr<HTMLInputElement> textControl =
           numberControlFrame->GetAnonTextControl();
       if (textControl) {
-        textControl->Focus(aError);
+        textControl->Focus(aOptions, aError);
         return;
       }
     }
   }
 
   if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
       !IsExperimentalMobileType(mType)) {
     if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
       AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
           dateTimeBoxElement, NS_LITERAL_STRING("MozFocusInnerTextBox"),
           CanBubble::eNo, ChromeOnlyDispatch::eNo);
       dispatcher->RunDOMEventWhenSafe();
       return;
     }
   }
 
-  nsGenericHTMLElement::Focus(aError);
+  nsGenericHTMLElement::Focus(aOptions, aError);
 }
 
 #if !defined(ANDROID) && !defined(XP_MACOSX)
 bool HTMLInputElement::IsNodeApzAwareInternal() const {
   // Tell APZC we may handle mouse wheel event and do preventDefault when input
   // type is number.
   return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
          nsINode::IsNodeApzAwareInternal();
@@ -3911,18 +3912,20 @@ nsresult HTMLInputElement::PostHandleEve
                 if (container) {
                   nsAutoString name;
                   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
                   RefPtr<HTMLInputElement> selectedRadioButton;
                   container->GetNextRadioButton(
                       name, isMovingBack, this,
                       getter_AddRefs(selectedRadioButton));
                   if (selectedRadioButton) {
+                    FocusOptions options;
                     ErrorResult error;
-                    selectedRadioButton->Focus(error);
+
+                    selectedRadioButton->Focus(options, error);
                     rv = error.StealNSResult();
                     if (NS_SUCCEEDED(rv)) {
                       rv = DispatchSimulatedClick(selectedRadioButton,
                                                   aVisitor.mEvent->IsTrusted(),
                                                   aVisitor.mPresContext);
                       if (NS_SUCCEEDED(rv)) {
                         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
                       }
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -139,17 +139,18 @@ class HTMLInputElement final : public ns
   NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLInputElement, input)
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual int32_t TabIndexDefault() override;
   using nsGenericHTMLElement::Focus;
   virtual void Blur(ErrorResult& aError) override;
-  virtual void Focus(ErrorResult& aError) override;
+  virtual void Focus(const FocusOptions& aOptions,
+                     ErrorResult& aError) override;
 
   // nsINode
 #if !defined(ANDROID) && !defined(XP_MACOSX)
   virtual bool IsNodeApzAwareInternal() const override;
 #endif
 
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -45,23 +45,26 @@ HTMLFormElement* HTMLLabelElement::GetFo
   nsCOMPtr<nsIFormControl> formControl = do_QueryObject(control);
   if (!formControl) {
     return nullptr;
   }
 
   return static_cast<HTMLFormElement*>(formControl->GetFormElement());
 }
 
-void HTMLLabelElement::Focus(ErrorResult& aError) {
+void HTMLLabelElement::Focus(const FocusOptions& aOptions,
+                             ErrorResult& aError) {
   // retarget the focus method at the for content
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
     RefPtr<Element> elem = GetLabeledElement();
     if (elem) {
-      fm->SetFocus(elem, nsIFocusManager::FLAG_BYELEMENTFOCUS);
+      fm->SetFocus(
+          elem, nsIFocusManager::FLAG_BYELEMENTFOCUS |
+                    nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions));
     }
   }
 }
 
 nsresult HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
   if (mHandlingEvent ||
       (!(mouseEvent && mouseEvent->IsLeftClickEvent()) &&
--- a/dom/html/HTMLLabelElement.h
+++ b/dom/html/HTMLLabelElement.h
@@ -38,17 +38,18 @@ class HTMLLabelElement final : public ns
     GetHTMLAttr(nsGkAtoms::_for, aHtmlFor);
   }
   void SetHtmlFor(const nsAString& aHtmlFor) {
     SetHTMLAttr(nsGkAtoms::_for, aHtmlFor);
   }
   nsGenericHTMLElement* GetControl() const { return GetLabeledElement(); }
 
   using nsGenericHTMLElement::Focus;
-  virtual void Focus(mozilla::ErrorResult& aError) override;
+  virtual void Focus(const FocusOptions& aOptions,
+                     ErrorResult& aError) override;
 
   // nsIContent
   virtual nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
   virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
   nsGenericHTMLElement* GetLabeledElement() const;
--- a/dom/html/HTMLLegendElement.cpp
+++ b/dom/html/HTMLLegendElement.cpp
@@ -63,47 +63,51 @@ nsresult HTMLLegendElement::BindToTree(D
                                        nsIContent* aBindingParent) {
   return nsGenericHTMLElement::BindToTree(aDocument, aParent, aBindingParent);
 }
 
 void HTMLLegendElement::UnbindFromTree(bool aDeep, bool aNullParent) {
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
-void HTMLLegendElement::Focus(ErrorResult& aError) {
+void HTMLLegendElement::Focus(const FocusOptions& aOptions,
+                              ErrorResult& aError) {
   nsIFrame* frame = GetPrimaryFrame();
   if (!frame) {
     return;
   }
 
   int32_t tabIndex;
   if (frame->IsFocusable(&tabIndex, false)) {
-    nsGenericHTMLElement::Focus(aError);
+    nsGenericHTMLElement::Focus(aOptions, aError);
     return;
   }
 
   // If the legend isn't focusable, focus whatever is focusable following
   // the legend instead, bug 81481.
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (!fm) {
     return;
   }
 
   RefPtr<Element> result;
-  aError = fm->MoveFocus(nullptr, this, nsIFocusManager::MOVEFOCUS_FORWARD,
-                         nsIFocusManager::FLAG_NOPARENTFRAME |
-                             nsIFocusManager::FLAG_BYELEMENTFOCUS,
-                         getter_AddRefs(result));
+  aError = fm->MoveFocus(
+      nullptr, this, nsIFocusManager::MOVEFOCUS_FORWARD,
+      nsIFocusManager::FLAG_NOPARENTFRAME |
+          nsIFocusManager::FLAG_BYELEMENTFOCUS |
+          nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions),
+      getter_AddRefs(result));
 }
 
 bool HTMLLegendElement::PerformAccesskey(bool aKeyCausesActivation,
                                          bool aIsTrustedEvent) {
-  // just use the same behaviour as the focus method
+  FocusOptions options;
   ErrorResult rv;
-  Focus(rv);
+
+  Focus(options, rv);
   return NS_SUCCEEDED(rv.StealNSResult());
 }
 
 already_AddRefed<HTMLFormElement> HTMLLegendElement::GetForm() {
   Element* form = GetFormElement();
   MOZ_ASSERT_IF(form, form->IsHTMLElement(nsGkAtoms::form));
   RefPtr<HTMLFormElement> ret = static_cast<HTMLFormElement*>(form);
   return ret.forget();
--- a/dom/html/HTMLLegendElement.h
+++ b/dom/html/HTMLLegendElement.h
@@ -18,17 +18,18 @@ class HTMLLegendElement final : public n
  public:
   explicit HTMLLegendElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
       : nsGenericHTMLElement(std::move(aNodeInfo)) {}
 
   NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLLegendElement, legend)
 
   using nsGenericHTMLElement::Focus;
-  virtual void Focus(ErrorResult& aError) override;
+  virtual void Focus(const FocusOptions& aOptions,
+                     ErrorResult& aError) override;
 
   virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
 
   // nsIContent
   virtual nsresult BindToTree(Document* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent) override;
   virtual void UnbindFromTree(bool aDeep = true,
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -217,20 +217,21 @@ void nsHTMLDocument::Reset(nsIChannel* a
   Document::Reset(aChannel, aLoadGroup);
 
   if (aChannel) {
     aChannel->GetLoadFlags(&mLoadFlags);
   }
 }
 
 void nsHTMLDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                                nsIPrincipal* aPrincipal) {
+                                nsIPrincipal* aPrincipal,
+                                nsIPrincipal* aStoragePrincipal) {
   mLoadFlags = nsIRequest::LOAD_NORMAL;
 
-  Document::ResetToURI(aURI, aLoadGroup, aPrincipal);
+  Document::ResetToURI(aURI, aLoadGroup, aPrincipal, aStoragePrincipal);
 
   mImages = nullptr;
   mApplets = nullptr;
   mEmbeds = nullptr;
   mLinks = nullptr;
   mAnchors = nullptr;
   mScripts = nullptr;
 
@@ -1024,17 +1025,24 @@ void nsHTMLDocument::GetCookie(nsAString
 
   // If the document's sandboxed origin flag is set, access to read cookies
   // is prohibited.
   if (mSandboxFlags & SANDBOXED_ORIGIN) {
     rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
-  if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
+  nsContentUtils::StorageAccess storageAccess =
+      nsContentUtils::StorageAllowedForDocument(this);
+  if (storageAccess == nsContentUtils::StorageAccess::eDeny) {
+    return;
+  }
+
+  if (storageAccess == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
+      !StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
     return;
   }
 
   // If the document is a cookie-averse Document... return the empty string.
   if (IsCookieAverse()) {
     return;
   }
 
@@ -1077,17 +1085,24 @@ void nsHTMLDocument::SetCookie(const nsA
 
   // If the document's sandboxed origin flag is set, access to write cookies
   // is prohibited.
   if (mSandboxFlags & SANDBOXED_ORIGIN) {
     rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
-  if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
+  nsContentUtils::StorageAccess storageAccess =
+      nsContentUtils::StorageAllowedForDocument(this);
+  if (storageAccess == nsContentUtils::StorageAccess::eDeny) {
+    return;
+  }
+
+  if (storageAccess == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
+      !StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
     return;
   }
 
   // If the document is a cookie-averse Document... do nothing.
   if (IsCookieAverse()) {
     return;
   }
 
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -51,17 +51,18 @@ class nsHTMLDocument : public mozilla::d
   virtual nsresult Init() override;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLDocument, Document)
 
   // Document
   virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) override;
   virtual void ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                          nsIPrincipal* aPrincipal) override;
+                          nsIPrincipal* aPrincipal,
+                          nsIPrincipal* aStoragePrincipal) override;
 
   virtual nsresult StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
                                      nsILoadGroup* aLoadGroup,
                                      nsISupports* aContainer,
                                      nsIStreamListener** aDocListener,
                                      bool aReset = true,
                                      nsIContentSink* aSink = nullptr) override;
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -296,24 +296,29 @@ nsresult IDBFactory::AllowedForWindowInt
   }
 
   nsContentUtils::StorageAccess access =
       nsContentUtils::StorageAllowedForWindow(aWindow);
 
   // the factory callsite records whether the browser is in private browsing.
   // and thus we don't have to respect that setting here. IndexedDB has no
   // concept of session-local storage, and thus ignores it.
-  if (access <= nsContentUtils::StorageAccess::eDeny) {
+  if (access == nsContentUtils::StorageAccess::eDeny) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  if (access == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
+      !StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
   MOZ_ASSERT(sop);
 
-  nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
+  nsCOMPtr<nsIPrincipal> principal = sop->GetEffectiveStoragePrincipal();
   if (NS_WARN_IF(!principal)) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   if (nsContentUtils::IsSystemPrincipal(principal)) {
     principal.forget(aPrincipal);
     return NS_OK;
   }
--- a/dom/interfaces/base/nsIFocusManager.idl
+++ b/dom/interfaces/base/nsIFocusManager.idl
@@ -146,17 +146,18 @@ interface nsIFocusManager : nsISupports
   boolean elementIsFocusable(in Element aElement, in unsigned long aFlags);
 
   /*
    * Raise the window when switching focus
    */
   const unsigned long FLAG_RAISE = 1;
 
   /**
-   * Do not scroll the element to focus into view
+   * Do not scroll the element to focus into view. If FLAG_BYELEMENTFOCUS is
+   * set too, the latter is ignored.
    */
   const unsigned long FLAG_NOSCROLL = 2;
 
   /**
    * If attempting to change focus in a window that is not focused, do not
    * switch focus to that window. Instead, just update the focus within that
    * window and leave the application focus as is. This flag will have no
    * effect if a child window is focused and an attempt is made to adjust the
@@ -206,17 +207,17 @@ interface nsIFocusManager : nsISupports
   const unsigned long FLAG_BYTOUCH = 0x200000;
 
   /**
    * Focus is changing due to a Element.focus() call.
    * This is used to distinguish the Element.focus() call from other focus
    * functionalities since we need to factor scroll-margin and scroll-padding
    * values into the position where we scroll to the element by the
    * Element.focus() call.
-   * NOTE: This flag shouldn't be specified with FLAG_NOSCROLL.
+   * NOTE: This flag is ignored if FLAG_NOSCROLL is set too.
    */
   const unsigned long FLAG_BYELEMENTFOCUS = 0x400000;
 
   // these constants are used with the aType argument to MoveFocus
 
   /** move focus forward one element, used when pressing TAB */
   const unsigned long MOVEFOCUS_FORWARD = 1;
   /** move focus backward one element, used when pressing Shift+TAB */
--- a/dom/ipc/WindowGlobalChild.h
+++ b/dom/ipc/WindowGlobalChild.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_WindowGlobalChild_h
 #define mozilla_dom_WindowGlobalChild_h
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/PWindowGlobalChild.h"
+#include "nsRefPtrHashtable.h"
 #include "nsWrapperCache.h"
 
 class nsGlobalWindowInner;
 class nsDocShell;
 
 namespace mozilla {
 namespace dom {
 
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_WindowGlobalParent_h
 #define mozilla_dom_WindowGlobalParent_h
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/PWindowGlobalParent.h"
+#include "nsRefPtrHashtable.h"
 #include "nsWrapperCache.h"
 #include "nsISupports.h"
 
 class nsIPrincipal;
 class nsIURI;
 class nsFrameLoader;
 
 namespace mozilla {
--- a/dom/media/webspeech/recognition/SpeechGrammar.cpp
+++ b/dom/media/webspeech/recognition/SpeechGrammar.cpp
@@ -16,17 +16,17 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechGr
 NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechGrammar)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechGrammar)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 SpeechGrammar::SpeechGrammar(nsISupports* aParent) : mParent(aParent) {}
 
-SpeechGrammar::~SpeechGrammar() {}
+SpeechGrammar::~SpeechGrammar() = default;
 
 already_AddRefed<SpeechGrammar> SpeechGrammar::Constructor(
     const GlobalObject& aGlobal, ErrorResult& aRv) {
   RefPtr<SpeechGrammar> speechGrammar =
       new SpeechGrammar(aGlobal.GetAsSupports());
   return speechGrammar.forget();
 }
 
--- a/dom/media/webspeech/recognition/SpeechGrammarList.cpp
+++ b/dom/media/webspeech/recognition/SpeechGrammarList.cpp
@@ -19,17 +19,17 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechGr
 NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechGrammarList)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechGrammarList)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 SpeechGrammarList::SpeechGrammarList(nsISupports* aParent) : mParent(aParent) {}
 
-SpeechGrammarList::~SpeechGrammarList() {}
+SpeechGrammarList::~SpeechGrammarList() = default;
 
 already_AddRefed<SpeechGrammarList> SpeechGrammarList::Constructor(
     const GlobalObject& aGlobal, ErrorResult& aRv) {
   RefPtr<SpeechGrammarList> speechGrammarList =
       new SpeechGrammarList(aGlobal.GetAsSupports());
   return speechGrammarList.forget();
 }
 
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -98,18 +98,37 @@ already_AddRefed<nsISpeechRecognitionSer
   }
 
   nsresult rv;
   nsCOMPtr<nsISpeechRecognitionService> recognitionService;
   recognitionService = do_GetService(speechRecognitionServiceCID.get(), &rv);
   return recognitionService.forget();
 }
 
+class SpeechRecognitionShutdownBlocker : public media::ShutdownBlocker {
+ public:
+  explicit SpeechRecognitionShutdownBlocker(SpeechRecognition* aRecognition)
+      : media::ShutdownBlocker(NS_LITERAL_STRING("SpeechRecognition shutdown")),
+        mRecognition(aRecognition) {}
+
+  NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // AbortSilently will eventually clear the blocker.
+    mRecognition->Abort();
+    return NS_OK;
+  }
+
+ private:
+  const RefPtr<SpeechRecognition> mRecognition;
+};
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(SpeechRecognition, DOMEventTargetHelper,
-                                   mTrack, mSpeechGrammarList)
+                                   mStream, mTrack, mRecognitionService,
+                                   mSpeechGrammarList)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechRecognition)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(SpeechRecognition, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SpeechRecognition, DOMEventTargetHelper)
 
@@ -530,27 +549,43 @@ SpeechRecognition::StartRecording(RefPtr
   mTrack = aTrack;
 
   if (NS_WARN_IF(mTrack->Ended())) {
     return NS_ERROR_UNEXPECTED;
   }
   mSpeechListener = new SpeechTrackListener(this);
   mTrack->AddListener(mSpeechListener);
 
+  mShutdownBlocker = MakeAndAddRef<SpeechRecognitionShutdownBlocker>(this);
+  RefPtr<nsIAsyncShutdownClient> shutdown = media::GetShutdownBarrier();
+  shutdown->AddBlocker(mShutdownBlocker, NS_LITERAL_STRING(__FILE__), __LINE__,
+                       NS_LITERAL_STRING("SpeechRecognition shutdown"));
+
   mEndpointer.StartSession();
 
   return mSpeechDetectionTimer->Init(this, kSPEECH_DETECTION_TIMEOUT_MS,
                                      nsITimer::TYPE_ONE_SHOT);
 }
 
 NS_IMETHODIMP
 SpeechRecognition::StopRecording() {
-  // we only really need to remove the listener explicitly when testing,
-  // as our JS code still holds a reference to mTrack and only assigning
-  // it to nullptr isn't guaranteed to free the stream and the listener.
+  if (mShutdownBlocker) {
+    // Block shutdown until the speech track listener has been removed from the
+    // MSG, as it holds a reference to us, and we reference the world, which we
+    // don't want to leak.
+    mSpeechListener->mRemovedPromise->Then(
+        GetCurrentThreadSerialEventTarget(), __func__,
+        [blocker = std::move(mShutdownBlocker)] {
+          RefPtr<nsIAsyncShutdownClient> shutdown = media::GetShutdownBarrier();
+          nsresult rv = shutdown->RemoveBlocker(blocker);
+          MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+        });
+  }
+  MOZ_ASSERT(!mShutdownBlocker);
+
   mStream->UnregisterTrackListener(this);
   mTrack->RemoveListener(mSpeechListener);
   mStream = nullptr;
   mSpeechListener = nullptr;
   mTrack = nullptr;
 
   mEndpointer.EndSession();
   DispatchTrustedEvent(NS_LITERAL_STRING("audioend"));
--- a/dom/media/webspeech/recognition/SpeechRecognition.h
+++ b/dom/media/webspeech/recognition/SpeechRecognition.h
@@ -35,16 +35,17 @@ namespace mozilla {
 namespace dom {
 
 #define SPEECH_RECOGNITION_TEST_EVENT_REQUEST_TOPIC \
   "SpeechRecognitionTest:RequestEvent"
 #define SPEECH_RECOGNITION_TEST_END_TOPIC "SpeechRecognitionTest:End"
 
 class GlobalObject;
 class AudioStreamTrack;
+class SpeechRecognitionShutdownBlocker;
 class SpeechEvent;
 class SpeechTrackListener;
 
 LogModule* GetSpeechRecognitionLog();
 #define SR_LOG(...) \
   MOZ_LOG(GetSpeechRecognitionLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 class SpeechRecognition final : public DOMEventTargetHelper,
@@ -181,16 +182,17 @@ class SpeechRecognition final : public D
   void NotifyFinalResult(SpeechEvent* aEvent);
   void DoNothing(SpeechEvent* aEvent);
   void AbortSilently(SpeechEvent* aEvent);
   void AbortError(SpeechEvent* aEvent);
 
   RefPtr<DOMMediaStream> mStream;
   RefPtr<AudioStreamTrack> mTrack;
   RefPtr<SpeechTrackListener> mSpeechListener;
+  RefPtr<SpeechRecognitionShutdownBlocker> mShutdownBlocker;
   nsCOMPtr<nsISpeechRecognitionService> mRecognitionService;
 
   FSMState mCurrentState;
 
   Endpointer mEndpointer;
   uint32_t mEstimationSamples;
 
   uint32_t mAudioSamplesPerChunk;
@@ -230,17 +232,17 @@ class SpeechRecognition final : public D
   // while for a 0 value returning no SpeechRecognitionAlternative per result is
   // a conforming implementation.
   uint32_t mMaxAlternatives;
 
   void ProcessTestEventRequest(nsISupports* aSubject,
                                const nsAString& aEventName);
 
   const char* GetName(FSMState aId);
-  const char* GetName(SpeechEvent* aId);
+  const char* GetName(SpeechEvent* aEvent);
 };
 
 class SpeechEvent : public Runnable {
  public:
   SpeechEvent(SpeechRecognition* aRecognition,
               SpeechRecognition::EventType aType);
 
   ~SpeechEvent();
--- a/dom/media/webspeech/recognition/SpeechRecognitionAlternative.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognitionAlternative.cpp
@@ -20,17 +20,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 SpeechRecognitionAlternative::SpeechRecognitionAlternative(
     SpeechRecognition* aParent)
     : mConfidence(0), mParent(aParent) {}
 
-SpeechRecognitionAlternative::~SpeechRecognitionAlternative() {}
+SpeechRecognitionAlternative::~SpeechRecognitionAlternative() = default;
 
 JSObject* SpeechRecognitionAlternative::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return SpeechRecognitionAlternative_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 nsISupports* SpeechRecognitionAlternative::GetParentObject() const {
   return static_cast<DOMEventTargetHelper*>(mParent.get());
--- a/dom/media/webspeech/recognition/SpeechRecognitionResult.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognitionResult.cpp
@@ -18,17 +18,17 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechR
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechRecognitionResult)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 SpeechRecognitionResult::SpeechRecognitionResult(SpeechRecognition* aParent)
     : mParent(aParent) {}
 
-SpeechRecognitionResult::~SpeechRecognitionResult() {}
+SpeechRecognitionResult::~SpeechRecognitionResult() = default;
 
 JSObject* SpeechRecognitionResult::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return SpeechRecognitionResult_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 nsISupports* SpeechRecognitionResult::GetParentObject() const {
   return static_cast<DOMEventTargetHelper*>(mParent.get());
--- a/dom/media/webspeech/recognition/SpeechRecognitionResultList.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognitionResultList.cpp
@@ -21,17 +21,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 SpeechRecognitionResultList::SpeechRecognitionResultList(
     SpeechRecognition* aParent)
     : mParent(aParent) {}
 
-SpeechRecognitionResultList::~SpeechRecognitionResultList() {}
+SpeechRecognitionResultList::~SpeechRecognitionResultList() = default;
 
 nsISupports* SpeechRecognitionResultList::GetParentObject() const {
   return static_cast<DOMEventTargetHelper*>(mParent.get());
 }
 
 JSObject* SpeechRecognitionResultList::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return SpeechRecognitionResultList_Binding::Wrap(aCx, this, aGivenProto);
--- a/dom/media/webspeech/recognition/SpeechTrackListener.cpp
+++ b/dom/media/webspeech/recognition/SpeechTrackListener.cpp
@@ -8,21 +8,24 @@
 
 #include "SpeechRecognition.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 
 SpeechTrackListener::SpeechTrackListener(SpeechRecognition* aRecognition)
-    : mRecognition(aRecognition) {}
-
-SpeechTrackListener::~SpeechTrackListener() {
-  NS_ReleaseOnMainThreadSystemGroup("SpeechTrackListener::mRecognition",
-                                    mRecognition.forget());
+    : mRecognition(aRecognition),
+      mRemovedPromise(
+          mRemovedHolder.Ensure("SpeechTrackListener::mRemovedPromise")) {
+  MOZ_ASSERT(NS_IsMainThread());
+  mRemovedPromise->Then(GetCurrentThreadSerialEventTarget(), __func__,
+                        [self = RefPtr<SpeechTrackListener>(this), this] {
+                          mRecognition = nullptr;
+                        });
 }
 
 void SpeechTrackListener::NotifyQueuedChanges(
     MediaStreamGraph* aGraph, StreamTime aTrackOffset,
     const MediaSegment& aQueuedMedia) {
   AudioSegment* audio = const_cast<AudioSegment*>(
       static_cast<const AudioSegment*>(&aQueuedMedia));
 
@@ -74,10 +77,14 @@ void SpeechTrackListener::ConvertAndDisp
 
   mRecognition->FeedAudioData(samples.forget(), aDuration, this, aTrackRate);
 }
 
 void SpeechTrackListener::NotifyEnded() {
   // TODO dispatch SpeechEnd event so services can be informed
 }
 
+void SpeechTrackListener::NotifyRemoved() {
+  mRemovedHolder.ResolveIfExists(true, __func__);
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/webspeech/recognition/SpeechTrackListener.h
+++ b/dom/media/webspeech/recognition/SpeechTrackListener.h
@@ -5,39 +5,46 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_SpeechStreamListener_h
 #define mozilla_dom_SpeechStreamListener_h
 
 #include "MediaStreamGraph.h"
 #include "MediaStreamListener.h"
 #include "AudioSegment.h"
+#include "mozilla/MozPromise.h"
 
 namespace mozilla {
 
 class AudioSegment;
 
 namespace dom {
 
 class SpeechRecognition;
 
 class SpeechTrackListener : public MediaStreamTrackListener {
  public:
   explicit SpeechTrackListener(SpeechRecognition* aRecognition);
-  ~SpeechTrackListener();
+  ~SpeechTrackListener() = default;
 
   void NotifyQueuedChanges(MediaStreamGraph* aGraph, StreamTime aTrackOffset,
                            const MediaSegment& aQueuedMedia) override;
 
   void NotifyEnded() override;
 
+  void NotifyRemoved() override;
+
  private:
   template <typename SampleFormatType>
   void ConvertAndDispatchAudioChunk(int aDuration, float aVolume,
                                     SampleFormatType* aData,
                                     TrackRate aTrackRate);
   RefPtr<SpeechRecognition> mRecognition;
+  MozPromiseHolder<GenericNonExclusivePromise> mRemovedHolder;
+
+ public:
+  const RefPtr<GenericNonExclusivePromise> mRemovedPromise;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- a/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp
+++ b/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp
@@ -18,19 +18,19 @@
 
 namespace mozilla {
 
 using namespace dom;
 
 NS_IMPL_ISUPPORTS(FakeSpeechRecognitionService, nsISpeechRecognitionService,
                   nsIObserver)
 
-FakeSpeechRecognitionService::FakeSpeechRecognitionService() {}
+FakeSpeechRecognitionService::FakeSpeechRecognitionService() = default;
 
-FakeSpeechRecognitionService::~FakeSpeechRecognitionService() {}
+FakeSpeechRecognitionService::~FakeSpeechRecognitionService() = default;
 
 NS_IMETHODIMP
 FakeSpeechRecognitionService::Initialize(
     WeakPtr<SpeechRecognition> aSpeechRecognition) {
   mRecognition = aSpeechRecognition;
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->AddObserver(this, SPEECH_RECOGNITION_TEST_EVENT_REQUEST_TOPIC, false);
   obs->AddObserver(this, SPEECH_RECOGNITION_TEST_END_TOPIC, false);
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -1724,20 +1724,22 @@ nsresult ServiceWorkerPrivate::SpawnWork
   // bug 965637 is ever fixed this can be removed.
   info.mPrincipal =
       BasePrincipal::CreateCodebasePrincipal(uri, mInfo->GetOriginAttributes());
   if (NS_WARN_IF(!info.mPrincipal)) {
     return NS_ERROR_FAILURE;
   }
   info.mLoadingPrincipal = info.mPrincipal;
 
-  nsContentUtils::StorageAccess access =
+  // StoragePrincipal for ServiceWorkers is equal to mPrincipal because, at the
+  // moment, ServiceWorkers are not exposed in partitioned contexts.
+  info.mStoragePrincipal = info.mPrincipal;
+
+  info.mStorageAccess =
       nsContentUtils::StorageAllowedForServiceWorker(info.mPrincipal);
-  info.mStorageAllowed =
-      access > nsContentUtils::StorageAccess::ePrivateBrowsing;
 
   info.mCookieSettings = mozilla::net::CookieSettings::Create();
   MOZ_ASSERT(info.mCookieSettings);
 
   info.mOriginAttributes = mInfo->GetOriginAttributes();
 
   // Verify that we don't have any CSP on pristine principal.
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
@@ -1748,17 +1750,18 @@ nsresult ServiceWorkerPrivate::SpawnWork
 
   // Default CSP permissions for now.  These will be overrided if necessary
   // based on the script CSP headers during load in ScriptLoader.
   info.mEvalAllowed = true;
   info.mReportCSPViolations = false;
 
   WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mPrincipal);
 
-  rv = info.SetPrincipalOnMainThread(info.mPrincipal, info.mLoadGroup);
+  rv = info.SetPrincipalsOnMainThread(info.mPrincipal, info.mStoragePrincipal,
+                                      info.mLoadGroup);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   AutoJSAPI jsapi;
   jsapi.Init();
   ErrorResult error;
   NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());
--- a/dom/tests/mochitest/general/test_clipboard_disallowed.html
+++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html
@@ -35,16 +35,31 @@ function checkAllowed(event)
     clipboardData.setData("application/x-moz-file-promise", "Test");
   } catch(ex) {
     exception = ex;
   }
   is(String(exception).indexOf("SecurityError"), 0, "Cannot set file promise");
 
   exception = null;
   try {
+    clipboardData.setData("text/x-moz-place", "Test");
+  } catch(ex) {
+    exception = ex;
+  }
+  is(String(exception).indexOf("SecurityError"), 0, "Cannot set place");
+  exception = null;
+  try {
+    clipboardData.setData("text/x-moz-place-container", "Test");
+  } catch(ex) {
+    exception = ex;
+  }
+  is(String(exception).indexOf("SecurityError"), 0, "Cannot set place container");
+
+  exception = null;
+  try {
     clipboardData.setData("application/something", "This is data");
   } catch(ex) {
     exception = ex;
   }
   is(exception, null, "Can set custom data to a string");
   SimpleTest.finish();
 }
 
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -10,16 +10,17 @@
  * https://w3c.github.io/pointerlock/#extensions-to-the-document-interface
  * https://w3c.github.io/pointerlock/#extensions-to-the-documentorshadowroot-mixin
  * https://w3c.github.io/page-visibility/#extensions-to-the-document-interface
  * https://drafts.csswg.org/cssom/#extensions-to-the-document-interface
  * https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
  * https://wicg.github.io/feature-policy/#policy
  */
 
+interface Principal;
 interface WindowProxy;
 interface nsISupports;
 interface URI;
 interface nsIDocShell;
 interface nsILoadGroup;
 
 enum VisibilityState { "hidden", "visible" };
 
@@ -353,16 +354,20 @@ partial interface Document {
   [Func="IsChromeOrXBL"]
   Element? getBindingParent(Node node);
   [Throws, Func="IsChromeOrXBL", NeedsSubjectPrincipal]
   void loadBindingDocument(DOMString documentURL);
   // Creates a new XUL element regardless of the document's default type.
   [CEReactions, NewObject, Throws, Func="IsChromeOrXBL"]
   Element createXULElement(DOMString localName, optional (ElementCreationOptions or DOMString) options);
 
+  // The principal to use for the storage area of this document
+  [ChromeOnly]
+  readonly attribute Principal effectiveStoragePrincipal;
+
   // Touch bits
   // XXXbz I can't find the sane spec for this stuff, so just cribbing
   // from our xpidl for now.
   [NewObject, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   Touch createTouch(optional Window? view = null,
                     optional EventTarget? target = null,
                     optional long identifier = 0,
                     optional long pageX = 0,
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -150,16 +150,28 @@ interface Element : Node {
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToAncestor(Element ancestor);
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToParent();
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToViewport();
 };
 
+// https://html.spec.whatwg.org/#focus-management-apis
+dictionary FocusOptions {
+  boolean preventScroll = false;
+};
+
+// TODO(mbrodesser): once https://bugzilla.mozilla.org/show_bug.cgi?id=1414372
+// is fixed, mixin should be used.
+[NoInterfaceObject] interface HTMLOrSVGOrXULElementMixin {
+  [Throws]
+  void focus(optional FocusOptions options);
+};
+
 // http://dev.w3.org/csswg/cssom-view/
 enum ScrollLogicalPosition { "start", "center", "end", "nearest" };
 dictionary ScrollIntoViewOptions : ScrollOptions {
   ScrollLogicalPosition block = "start";
   ScrollLogicalPosition inline = "nearest";
 };
 
 // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface
--- a/dom/webidl/HTMLElement.webidl
+++ b/dom/webidl/HTMLElement.webidl
@@ -31,18 +31,16 @@ interface HTMLElement : Element {
   // user interaction
   [CEReactions, SetterThrows, Pure]
            attribute boolean hidden;
   [NeedsCallerType]
   void click();
   [CEReactions, SetterThrows, Pure]
            attribute long tabIndex;
   [Throws]
-  void focus();
-  [Throws]
   void blur();
   [CEReactions, SetterThrows, Pure]
            attribute DOMString accessKey;
   [Pure]
   readonly attribute DOMString accessKeyLabel;
   [CEReactions, SetterThrows, Pure]
            attribute boolean draggable;
   //[PutForwards=value] readonly attribute DOMTokenList dropzone;
@@ -88,13 +86,14 @@ interface TouchEventHandlers {
            attribute EventHandler ontouchend;
   [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchmove;
   [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchcancel;
 };
 
 HTMLElement implements GlobalEventHandlers;
+HTMLElement implements HTMLOrSVGOrXULElementMixin;
 HTMLElement implements DocumentAndElementEventHandlers;
 HTMLElement implements TouchEventHandlers;
 HTMLElement implements OnErrorEventHandlerForNodes;
 
 interface HTMLUnknownElement : HTMLElement {};
--- a/dom/webidl/SVGElement.webidl
+++ b/dom/webidl/SVGElement.webidl
@@ -19,16 +19,16 @@ interface SVGElement : Element {
   [PutForwards=cssText, Constant]
   readonly attribute CSSStyleDeclaration style;
 
   readonly attribute SVGSVGElement? ownerSVGElement;
   readonly attribute SVGElement? viewportElement;
 
   [SetterThrows, Pure]
         attribute long tabIndex;
-  [Throws] void focus();
   [Throws] void blur();
 };
 
 SVGElement implements GlobalEventHandlers;
+SVGElement implements HTMLOrSVGOrXULElementMixin;
 SVGElement implements DocumentAndElementEventHandlers;
 SVGElement implements TouchEventHandlers;
 SVGElement implements OnErrorEventHandlerForNodes;
--- a/dom/webidl/XULElement.webidl
+++ b/dom/webidl/XULElement.webidl
@@ -77,18 +77,16 @@ interface XULElement : Element {
   [Throws, ChromeOnly]
   readonly attribute XULControllers             controllers;
   [Throws]
   readonly attribute BoxObject?                 boxObject;
 
   [SetterThrows]
   attribute long tabIndex;
   [Throws]
-  void                      focus();
-  [Throws]
   void                      blur();
   [NeedsCallerType]
   void                      click();
   void                      doCommand();
 
   [Constant]
   readonly attribute CSSStyleDeclaration style;
 
@@ -97,10 +95,11 @@ interface XULElement : Element {
   boolean hasMenu();
 
   // If this is a menu-type element, opens or closes the menu
   // depending on the argument passed.
   void openMenu(boolean open);
 };
 
 XULElement implements GlobalEventHandlers;
+XULElement implements HTMLOrSVGOrXULElementMixin;
 XULElement implements TouchEventHandlers;
 XULElement implements OnErrorEventHandlerForNodes;
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1183,17 +1183,17 @@ class ScriptLoaderRunnable final : publi
       NS_ENSURE_TRUE(mWorkerPrivate->FinalChannelPrincipalIsValid(channel),
                      NS_ERROR_FAILURE);
 
       // However, we must still override the principal since the nsIPrincipal
       // URL may be different due to same-origin redirects.  Unfortunately this
       // URL must exactly match the final worker script URL in order to
       // properly set the referrer header on fetch/xhr requests.  If bug 1340694
       // is ever fixed this can be removed.
-      rv = mWorkerPrivate->SetPrincipalFromChannel(channel);
+      rv = mWorkerPrivate->SetPrincipalsFromChannel(channel);
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerPrivate->GetCSP();
       // We did inherit CSP in bug 1223647. If we do not already have a CSP, we
       // should get it from the HTTP headers on the worker script.
       if (StaticPrefs::security_csp_enable()) {
         if (!csp) {
           rv = mWorkerPrivate->SetCSPFromHeaderValues(tCspHeaderValue,
@@ -1291,18 +1291,21 @@ class ScriptLoaderRunnable final : publi
       nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
       MOZ_DIAGNOSTIC_ASSERT(loadGroup);
 
       // Override the principal on the WorkerPrivate.  This is only necessary
       // in order to get a principal with exactly the correct URL.  The fetch
       // referrer logic depends on the WorkerPrivate principal having a URL
       // that matches the worker script URL.  If bug 1340694 is ever fixed
       // this can be removed.
-      rv = mWorkerPrivate->SetPrincipalOnMainThread(responsePrincipal,
-                                                    loadGroup);
+      // XXX: force the storagePrincipal to be equal to the response one. This
+      // is OK for now because we don't want to expose storagePrincipal
+      // functionality in ServiceWorkers yet.
+      rv = mWorkerPrivate->SetPrincipalsOnMainThread(
+          responsePrincipal, responsePrincipal, loadGroup);
       MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
 
       rv = mWorkerPrivate->SetCSPFromHeaderValues(aCSPHeaderValue,
                                                   aCSPReportOnlyHeaderValue);
       MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
 
       mWorkerPrivate->SetReferrerPolicyFromHeaderValue(
           aReferrerPolicyHeaderValue);
@@ -1836,17 +1839,17 @@ class ChannelGetterRunnable final : publ
     mResult = workerinternals::ChannelFromScriptURLMainThread(
         mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url,
         clientInfo,
         // Nested workers are always dedicated.
         nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieSettings,
         getter_AddRefs(channel));
     NS_ENSURE_SUCCESS(mResult, true);
 
-    mResult = mLoadInfo.SetPrincipalFromChannel(channel);
+    mResult = mLoadInfo.SetPrincipalsFromChannel(channel);
     NS_ENSURE_SUCCESS(mResult, true);
 
     mLoadInfo.mChannel = channel.forget();
     return true;
   }
 
   nsresult GetResult() const { return mResult; }
 
--- a/dom/workers/WorkerLoadInfo.cpp
+++ b/dom/workers/WorkerLoadInfo.cpp
@@ -82,83 +82,98 @@ WorkerLoadInfoData::WorkerLoadInfoData()
     : mLoadFlags(nsIRequest::LOAD_NORMAL),
       mWindowID(UINT64_MAX),
       mReferrerPolicy(net::RP_Unset),
       mFromWindow(false),
       mEvalAllowed(false),
       mReportCSPViolations(false),
       mXHRParamsAllowed(false),
       mPrincipalIsSystem(false),
-      mStorageAllowed(false),
+      mStorageAccess(nsContentUtils::StorageAccess::eDeny),
       mFirstPartyStorageAccessGranted(false),
       mServiceWorkersTestingInWindow(false),
       mSecureContext(eNotSet) {}
 
-nsresult WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
-                                                  nsILoadGroup* aLoadGroup) {
+nsresult WorkerLoadInfo::SetPrincipalsOnMainThread(
+    nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
+    nsILoadGroup* aLoadGroup) {
   AssertIsOnMainThread();
   MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal));
 
   mPrincipal = aPrincipal;
+  mStoragePrincipal = aStoragePrincipal;
   mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
 
   nsresult rv = aPrincipal->GetCsp(getter_AddRefs(mCSP));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mCSP) {
     mCSP->GetAllowsEval(&mReportCSPViolations, &mEvalAllowed);
   } else {
     mEvalAllowed = true;
     mReportCSPViolations = false;
   }
 
   mLoadGroup = aLoadGroup;
 
   mPrincipalInfo = new PrincipalInfo();
+  mStoragePrincipalInfo = new PrincipalInfo();
   mOriginAttributes = nsContentUtils::GetOriginAttributes(aLoadGroup);
 
   rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (aPrincipal->Equals(aStoragePrincipal)) {
+    *mStoragePrincipalInfo = *mPrincipalInfo;
+  } else {
+    mStoragePrincipalInfo = new PrincipalInfo();
+    rv = PrincipalToPrincipalInfo(aStoragePrincipal, mStoragePrincipalInfo);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   rv = nsContentUtils::GetUTFOrigin(aPrincipal, mOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-nsresult WorkerLoadInfo::GetPrincipalAndLoadGroupFromChannel(
+nsresult WorkerLoadInfo::GetPrincipalsAndLoadGroupFromChannel(
     nsIChannel* aChannel, nsIPrincipal** aPrincipalOut,
-    nsILoadGroup** aLoadGroupOut) {
+    nsIPrincipal** aStoragePrincipalOut, nsILoadGroup** aLoadGroupOut) {
   AssertIsOnMainThread();
   MOZ_DIAGNOSTIC_ASSERT(aChannel);
   MOZ_DIAGNOSTIC_ASSERT(aPrincipalOut);
+  MOZ_DIAGNOSTIC_ASSERT(aStoragePrincipalOut);
   MOZ_DIAGNOSTIC_ASSERT(aLoadGroupOut);
 
   // Initial triggering principal should be set
   NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_DOM_INVALID_STATE_ERR);
 
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   MOZ_DIAGNOSTIC_ASSERT(ssm);
 
   nsCOMPtr<nsIPrincipal> channelPrincipal;
-  nsresult rv = ssm->GetChannelResultPrincipal(
-      aChannel, getter_AddRefs(channelPrincipal));
+  nsCOMPtr<nsIPrincipal> channelStoragePrincipal;
+  nsresult rv = ssm->GetChannelResultPrincipals(
+      aChannel, getter_AddRefs(channelPrincipal),
+      getter_AddRefs(channelStoragePrincipal));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Every time we call GetChannelResultPrincipal() it will return a different
   // null principal for a data URL.  We don't want to change the worker's
   // principal again, though.  Instead just keep the original null principal we
   // first got from the channel.
   //
   // Note, we don't do this by setting principalToInherit on the channel's
   // load info because we don't yet have the first null principal when we
   // create the channel.
   if (mPrincipal && mPrincipal->GetIsNullPrincipal() &&
       channelPrincipal->GetIsNullPrincipal()) {
     channelPrincipal = mPrincipal;
+    channelStoragePrincipal = mPrincipal;
   }
 
   nsCOMPtr<nsILoadGroup> channelLoadGroup;
   rv = aChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
   NS_ENSURE_SUCCESS(rv, rv);
   MOZ_ASSERT(channelLoadGroup);
 
   // If the loading principal is the system principal then the channel
@@ -181,51 +196,57 @@ nsresult WorkerLoadInfo::GetPrincipalAnd
       rv = NS_URIChainHasFlags(finalURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (isResource) {
         // Assign the system principal to the resource:// worker only if it
         // was loaded from code using the system principal.
         channelPrincipal = mLoadingPrincipal;
+        channelStoragePrincipal = mLoadingPrincipal;
       } else {
         return NS_ERROR_DOM_BAD_URI;
       }
     }
   }
 
   // The principal can change, but it should still match the original
   // load group's appId and browser element flag.
   MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
 
   channelPrincipal.forget(aPrincipalOut);
+  channelStoragePrincipal.forget(aStoragePrincipalOut);
   channelLoadGroup.forget(aLoadGroupOut);
 
   return NS_OK;
 }
 
-nsresult WorkerLoadInfo::SetPrincipalFromChannel(nsIChannel* aChannel) {
+nsresult WorkerLoadInfo::SetPrincipalsFromChannel(nsIChannel* aChannel) {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsIPrincipal> storagePrincipal;
   nsCOMPtr<nsILoadGroup> loadGroup;
-  nsresult rv = GetPrincipalAndLoadGroupFromChannel(
-      aChannel, getter_AddRefs(principal), getter_AddRefs(loadGroup));
+  nsresult rv = GetPrincipalsAndLoadGroupFromChannel(
+      aChannel, getter_AddRefs(principal), getter_AddRefs(storagePrincipal),
+      getter_AddRefs(loadGroup));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return SetPrincipalOnMainThread(principal, loadGroup);
+  return SetPrincipalsOnMainThread(principal, storagePrincipal, loadGroup);
 }
 
 bool WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsIPrincipal> storagePrincipal;
   nsCOMPtr<nsILoadGroup> loadGroup;
-  nsresult rv = GetPrincipalAndLoadGroupFromChannel(
-      aChannel, getter_AddRefs(principal), getter_AddRefs(loadGroup));
+  nsresult rv = GetPrincipalsAndLoadGroupFromChannel(
+      aChannel, getter_AddRefs(principal), getter_AddRefs(storagePrincipal),
+      getter_AddRefs(loadGroup));
   NS_ENSURE_SUCCESS(rv, false);
 
   // Verify that the channel is still a null principal.  We don't care
   // if these are the exact same null principal object, though.  From
   // the worker's perspective its the same effect.
   if (principal->GetIsNullPrincipal() && mPrincipal->GetIsNullPrincipal()) {
     return true;
   }
@@ -238,17 +259,20 @@ bool WorkerLoadInfo::FinalChannelPrincip
 
   return false;
 }
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 bool WorkerLoadInfo::PrincipalIsValid() const {
   return mPrincipal && mPrincipalInfo &&
          mPrincipalInfo->type() != PrincipalInfo::T__None &&
-         mPrincipalInfo->type() <= PrincipalInfo::T__Last;
+         mPrincipalInfo->type() <= PrincipalInfo::T__Last &&
+         mStoragePrincipal && mStoragePrincipalInfo &&
+         mStoragePrincipalInfo->type() != PrincipalInfo::T__None &&
+         mStoragePrincipalInfo->type() <= PrincipalInfo::T__Last;
 }
 
 bool WorkerLoadInfo::PrincipalURIMatchesScriptURL() {
   AssertIsOnMainThread();
 
   nsAutoCString scheme;
   nsresult rv = mBaseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, false);
@@ -307,24 +331,25 @@ bool WorkerLoadInfo::PrincipalURIMatches
 bool WorkerLoadInfo::ProxyReleaseMainThreadObjects(
     WorkerPrivate* aWorkerPrivate) {
   nsCOMPtr<nsILoadGroup> nullLoadGroup;
   return ProxyReleaseMainThreadObjects(aWorkerPrivate, nullLoadGroup);
 }
 
 bool WorkerLoadInfo::ProxyReleaseMainThreadObjects(
     WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsILoadGroup>& aLoadGroupToCancel) {
-  static const uint32_t kDoomedCount = 10;
+  static const uint32_t kDoomedCount = 11;
   nsTArray<nsCOMPtr<nsISupports>> doomed(kDoomedCount);
 
   SwapToISupportsArray(mWindow, doomed);
   SwapToISupportsArray(mScriptContext, doomed);
   SwapToISupportsArray(mBaseURI, doomed);
   SwapToISupportsArray(mResolvedScriptURI, doomed);
   SwapToISupportsArray(mPrincipal, doomed);
+  SwapToISupportsArray(mStoragePrincipal, doomed);
   SwapToISupportsArray(mLoadingPrincipal, doomed);
   SwapToISupportsArray(mChannel, doomed);
   SwapToISupportsArray(mCSP, doomed);
   SwapToISupportsArray(mLoadGroup, doomed);
   SwapToISupportsArray(mInterfaceRequestor, doomed);
   // Before adding anything here update kDoomedCount above!
 
   MOZ_ASSERT(doomed.Length() == kDoomedCount);
--- a/dom/workers/WorkerLoadInfo.h
+++ b/dom/workers/WorkerLoadInfo.h
@@ -45,16 +45,17 @@ struct WorkerLoadInfoData {
   nsCOMPtr<nsIURI> mResolvedScriptURI;
 
   // This is the principal of the global (parent worker or a window) loading
   // the worker. It can be null if we are executing a ServiceWorker, otherwise,
   // except for data: URL, it must subsumes the worker principal.
   // If we load a data: URL, mPrincipal will be a null principal.
   nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
   nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<nsIPrincipal> mStoragePrincipal;
 
   // Taken from the parent context.
   nsCOMPtr<nsICookieSettings> mCookieSettings;
 
   nsCOMPtr<nsIScriptContext> mScriptContext;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIContentSecurityPolicy> mCSP;
   nsCOMPtr<nsIChannel> mChannel;
@@ -86,16 +87,17 @@ struct WorkerLoadInfoData {
     // actors alive for long after their ActorDestroy() methods are called.
     nsTArray<nsWeakPtr> mTabChildList;
   };
 
   // Only set if we have a custom overriden load group
   RefPtr<InterfaceRequestor> mInterfaceRequestor;
 
   nsAutoPtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+  nsAutoPtr<mozilla::ipc::PrincipalInfo> mStoragePrincipalInfo;
   nsCString mDomain;
   nsString mOrigin;  // Derived from mPrincipal; can be used on worker thread.
 
   nsString mServiceWorkerCacheName;
   Maybe<ServiceWorkerDescriptor> mServiceWorkerDescriptor;
   Maybe<ServiceWorkerRegistrationDescriptor>
       mServiceWorkerRegistrationDescriptor;
 
@@ -107,17 +109,17 @@ struct WorkerLoadInfoData {
   uint64_t mWindowID;
 
   net::ReferrerPolicy mReferrerPolicy;
   bool mFromWindow;
   bool mEvalAllowed;
   bool mReportCSPViolations;
   bool mXHRParamsAllowed;
   bool mPrincipalIsSystem;
-  bool mStorageAllowed;
+  nsContentUtils::StorageAccess mStorageAccess;
   bool mFirstPartyStorageAccessGranted;
   bool mServiceWorkersTestingInWindow;
   OriginAttributes mOriginAttributes;
 
   enum {
     eNotSet,
     eInsecureContext,
     eSecureContext,
@@ -131,24 +133,25 @@ struct WorkerLoadInfoData {
 
 struct WorkerLoadInfo : WorkerLoadInfoData {
   WorkerLoadInfo();
   WorkerLoadInfo(WorkerLoadInfo&& aOther) noexcept;
   ~WorkerLoadInfo();
 
   WorkerLoadInfo& operator=(WorkerLoadInfo&& aOther) = default;
 
-  nsresult SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
-                                    nsILoadGroup* aLoadGroup);
+  nsresult SetPrincipalsOnMainThread(nsIPrincipal* aPrincipal,
+                                     nsIPrincipal* aStoragePrincipal,
+                                     nsILoadGroup* aLoadGroup);
 
-  nsresult GetPrincipalAndLoadGroupFromChannel(nsIChannel* aChannel,
-                                               nsIPrincipal** aPrincipalOut,
-                                               nsILoadGroup** aLoadGroupOut);
+  nsresult GetPrincipalsAndLoadGroupFromChannel(
+      nsIChannel* aChannel, nsIPrincipal** aPrincipalOut,
+      nsIPrincipal** aStoragePrincipalOut, nsILoadGroup** aLoadGroupOut);
 
-  nsresult SetPrincipalFromChannel(nsIChannel* aChannel);
+  nsresult SetPrincipalsFromChannel(nsIChannel* aChannel);
 
   bool FinalChannelPrincipalIsValid(nsIChannel* aChannel);
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   bool PrincipalIsValid() const;
 
   bool PrincipalURIMatchesScriptURL();
 #endif
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1927,23 +1927,25 @@ void WorkerPrivate::SetBaseURI(nsIURI* a
     mLocationInfo.mHost.Assign(host);
   } else {
     mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
   }
 
   nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin);
 }
 
-nsresult WorkerPrivate::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
-                                                 nsILoadGroup* aLoadGroup) {
-  return mLoadInfo.SetPrincipalOnMainThread(aPrincipal, aLoadGroup);
+nsresult WorkerPrivate::SetPrincipalsOnMainThread(
+    nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
+    nsILoadGroup* aLoadGroup) {
+  return mLoadInfo.SetPrincipalsOnMainThread(aPrincipal, aStoragePrincipal,
+                                             aLoadGroup);
 }
 
-nsresult WorkerPrivate::SetPrincipalFromChannel(nsIChannel* aChannel) {
-  return mLoadInfo.SetPrincipalFromChannel(aChannel);
+nsresult WorkerPrivate::SetPrincipalsFromChannel(nsIChannel* aChannel) {
+  return mLoadInfo.SetPrincipalsFromChannel(aChannel);
 }
 
 bool WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
   return mLoadInfo.FinalChannelPrincipalIsValid(aChannel);
 }
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 bool WorkerPrivate::PrincipalURIMatchesScriptURL() {
@@ -2350,17 +2352,17 @@ nsresult WorkerPrivate::GetLoadInfo(JSCo
     if (parentStatus > Running) {
       MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
       return NS_ERROR_FAILURE;
     }
 
     loadInfo.mDomain = aParent->Domain();
     loadInfo.mFromWindow = aParent->IsFromWindow();
     loadInfo.mWindowID = aParent->WindowID();
-    loadInfo.mStorageAllowed = aParent->IsStorageAllowed();
+    loadInfo.mStorageAccess = aParent->StorageAccess();
     loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
     loadInfo.mServiceWorkersTestingInWindow =
         aParent->ServiceWorkersTestingInWindow();
     loadInfo.mParentController = aParent->GetController();
   } else {
     AssertIsOnMainThread();
 
     // Make sure that the IndexedDatabaseManager is set up
@@ -2477,19 +2479,18 @@ nsresult WorkerPrivate::GetLoadInfo(JSCo
       rv = permMgr->TestPermissionFromPrincipal(
           loadInfo.mLoadingPrincipal, NS_LITERAL_CSTRING("systemXHR"), &perm);
       NS_ENSURE_SUCCESS(rv, rv);
 
       loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
 
       loadInfo.mFromWindow = true;
       loadInfo.mWindowID = globalWindow->WindowID();
-      nsContentUtils::StorageAccess access =
+      loadInfo.mStorageAccess =
           nsContentUtils::StorageAllowedForWindow(globalWindow);
-      loadInfo.mStorageAllowed = access > nsContentUtils::StorageAccess::eDeny;
       loadInfo.mCookieSettings = document->CookieSettings();
       loadInfo.mOriginAttributes =
           nsContentUtils::GetOriginAttributes(document);
       loadInfo.mParentController = globalWindow->GetController();
       loadInfo.mSecureContext = loadInfo.mWindow->IsSecureContext()
                                     ? WorkerLoadInfo::eSecureContext
                                     : WorkerLoadInfo::eInsecureContext;
     } else {
@@ -2525,17 +2526,17 @@ nsresult WorkerPrivate::GetLoadInfo(JSCo
         }
         if (NS_FAILED(rv)) {
           return rv;
         }
       }
       loadInfo.mXHRParamsAllowed = true;
       loadInfo.mFromWindow = false;
       loadInfo.mWindowID = UINT64_MAX;
-      loadInfo.mStorageAllowed = true;
+      loadInfo.mStorageAccess = nsContentUtils::StorageAccess::eAllow;
       loadInfo.mCookieSettings = mozilla::net::CookieSettings::Create();
       MOZ_ASSERT(loadInfo.mCookieSettings);
 
       loadInfo.mOriginAttributes = OriginAttributes();
     }
 
     MOZ_ASSERT(loadInfo.mLoadingPrincipal);
     MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());
@@ -2558,17 +2559,17 @@ nsresult WorkerPrivate::GetLoadInfo(JSCo
         clientInfo, ContentPolicyType(aWorkerType), loadInfo.mCookieSettings,
         getter_AddRefs(loadInfo.mChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = NS_GetFinalChannelURI(loadInfo.mChannel,
                                getter_AddRefs(loadInfo.mResolvedScriptURI));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = loadInfo.SetPrincipalFromChannel(loadInfo.mChannel);
+    rv = loadInfo.SetPrincipalsFromChannel(loadInfo.mChannel);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal);
   MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());
 
   *aLoadInfo = std::move(loadInfo);
   return NS_OK;
@@ -3354,16 +3355,21 @@ bool WorkerPrivate::ThawInternal() {
   return true;
 }
 
 void WorkerPrivate::PropagateFirstPartyStorageAccessGrantedInternal() {
   MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
 
   mLoadInfo.mFirstPartyStorageAccessGranted = true;
 
+  WorkerGlobalScope* globalScope = GlobalScope();
+  if (globalScope) {
+    globalScope->FirstPartyStorageAccessGranted();
+  }
+
   for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
     data->mChildWorkers[index]->PropagateFirstPartyStorageAccessGranted();
   }
 }
 
 void WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) {
   MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
   for (uint32_t i = 0; i < data->mTimeouts.Length(); ++i) {
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_workers_workerprivate_h__
 #define mozilla_dom_workers_workerprivate_h__
 
 #include "mozilla/dom/WorkerCommon.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/RelativeTimeline.h"
+#include "nsContentUtils.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIEventTarget.h"
 #include "nsTObserverArray.h"
 
 #include "js/ContextOptions.h"
 #include "mozilla/dom/Worker.h"
 #include "mozilla/dom/WorkerHolder.h"
 #include "mozilla/dom/WorkerLoadInfo.h"
@@ -656,16 +657,21 @@ class WorkerPrivate : public RelativeTim
     mLoadInfo.mChannelInfo = aChannelInfo;
   }
 
   nsIPrincipal* GetPrincipal() const {
     AssertIsOnMainThread();
     return mLoadInfo.mPrincipal;
   }
 
+  nsIPrincipal* GetEffectiveStoragePrincipal() const {
+    AssertIsOnMainThread();
+    return mLoadInfo.mStoragePrincipal;
+  }
+
   nsIPrincipal* GetLoadingPrincipal() const {
     AssertIsOnMainThread();
     return mLoadInfo.mLoadingPrincipal;
   }
 
   const nsAString& Origin() const { return mLoadInfo.mOrigin; }
 
   nsILoadGroup* GetLoadGroup() const {
@@ -674,16 +680,20 @@ class WorkerPrivate : public RelativeTim
   }
 
   bool UsesSystemPrincipal() const { return mLoadInfo.mPrincipalIsSystem; }
 
   const mozilla::ipc::PrincipalInfo& GetPrincipalInfo() const {
     return *mLoadInfo.mPrincipalInfo;
   }
 
+  const mozilla::ipc::PrincipalInfo& GetEffectiveStoragePrincipalInfo() const {
+    return *mLoadInfo.mStoragePrincipalInfo;
+  }
+
   already_AddRefed<nsIChannel> ForgetWorkerChannel() {
     AssertIsOnMainThread();
     return mLoadInfo.mChannel.forget();
   }
 
   nsPIDOMWindowInner* GetWindow() {
     AssertIsOnMainThread();
     return mLoadInfo.mWindow;
@@ -723,20 +733,23 @@ class WorkerPrivate : public RelativeTim
   }
 
   bool XHRParamsAllowed() const { return mLoadInfo.mXHRParamsAllowed; }
 
   void SetXHRParamsAllowed(bool aAllowed) {
     mLoadInfo.mXHRParamsAllowed = aAllowed;
   }
 
-  bool IsStorageAllowed() const {
+  nsContentUtils::StorageAccess StorageAccess() const {
     AssertIsOnWorkerThread();
-    return mLoadInfo.mStorageAllowed ||
-           mLoadInfo.mFirstPartyStorageAccessGranted;
+    if (mLoadInfo.mFirstPartyStorageAccessGranted) {
+      return nsContentUtils::StorageAccess::eAllow;
+    }
+
+    return mLoadInfo.mStorageAccess;
   }
 
   nsICookieSettings* CookieSettings() const {
     // Any thread.
     MOZ_ASSERT(mLoadInfo.mCookieSettings);
     return mLoadInfo.mCookieSettings;
   }
 
@@ -779,20 +792,21 @@ class WorkerPrivate : public RelativeTim
       already_AddRefed<nsIRunnable> aRunnable);
 
   bool ProxyReleaseMainThreadObjects();
 
   void GarbageCollect(bool aShrinking);
 
   void CycleCollect(bool aDummy);
 
-  nsresult SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
-                                    nsILoadGroup* aLoadGroup);
+  nsresult SetPrincipalsOnMainThread(nsIPrincipal* aPrincipal,
+                                     nsIPrincipal* aStoragePrincipal,
+                                     nsILoadGroup* aLoadGroup);
 
-  nsresult SetPrincipalFromChannel(nsIChannel* aChannel);
+  nsresult SetPrincipalsFromChannel(nsIChannel* aChannel);
 
   bool FinalChannelPrincipalIsValid(nsIChannel* aChannel);
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   bool PrincipalURIMatchesScriptURL();
 #endif
 
   void UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup);
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -388,23 +388,33 @@ already_AddRefed<Promise> WorkerGlobalSc
 
 already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
     ErrorResult& aErrorResult) {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   RefPtr<IDBFactory> indexedDB = mIndexedDB;
 
   if (!indexedDB) {
-    if (!mWorkerPrivate->IsStorageAllowed()) {
+    nsContentUtils::StorageAccess access = mWorkerPrivate->StorageAccess();
+
+    if (access == nsContentUtils::StorageAccess::eDeny) {
       NS_WARNING("IndexedDB is not allowed in this worker!");
       aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
       return nullptr;
     }
 
-    const PrincipalInfo& principalInfo = mWorkerPrivate->GetPrincipalInfo();
+    if (access == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
+        !StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
+      NS_WARNING("IndexedDB is not allowed in this worker!");
+      aErrorResult = NS_ERROR_DOM_SECURITY_ERR;
+      return nullptr;
+    }
+
+    const PrincipalInfo& principalInfo =
+        mWorkerPrivate->GetEffectiveStoragePrincipalInfo();
 
     nsresult rv = IDBFactory::CreateForWorker(this, principalInfo,
                                               mWorkerPrivate->WindowID(),
                                               getter_AddRefs(indexedDB));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aErrorResult = rv;
       return nullptr;
     }
@@ -482,16 +492,20 @@ WorkerGlobalScope::GetOrCreateServiceWor
       GetServiceWorkerRegistration(aDescriptor);
   if (!ref) {
     ref = ServiceWorkerRegistration::CreateForWorker(mWorkerPrivate, this,
                                                      aDescriptor);
   }
   return ref.forget();
 }
 
+void WorkerGlobalScope::FirstPartyStorageAccessGranted() {
+  mIndexedDB = nullptr;
+}
+
 DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(
     WorkerPrivate* aWorkerPrivate, const nsString& aName)
     : WorkerGlobalScope(aWorkerPrivate), mName(aName) {}
 
 bool DedicatedWorkerGlobalScope::WrapGlobalObject(
     JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
   mWorkerPrivate->AssertIsOnWorkerThread();
   MOZ_ASSERT(!mWorkerPrivate->IsSharedWorker());
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -185,16 +185,18 @@ class WorkerGlobalScope : public DOMEven
   Maybe<ServiceWorkerDescriptor> GetController() const override;
 
   RefPtr<mozilla::dom::ServiceWorkerRegistration> GetServiceWorkerRegistration(
       const ServiceWorkerRegistrationDescriptor& aDescriptor) const override;
 
   RefPtr<mozilla::dom::ServiceWorkerRegistration>
   GetOrCreateServiceWorkerRegistration(
       const ServiceWorkerRegistrationDescriptor& aDescriptor) override;
+
+  void FirstPartyStorageAccessGranted();
 };
 
 class DedicatedWorkerGlobalScope final : public WorkerGlobalScope {
   const nsString mName;
 
   ~DedicatedWorkerGlobalScope() {}
 
  public:
--- a/dom/workers/remoteworkers/RemoteWorkerChild.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp
@@ -257,26 +257,41 @@ nsresult RemoteWorkerChild::ExecWorkerOn
 
   rv = PopulatePrincipalContentSecurityPolicy(
       loadingPrincipal, aData.loadingPrincipalCsp(),
       aData.loadingPrincipalPreloadCsp());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  nsCOMPtr<nsIPrincipal> storagePrincipal =
+      PrincipalInfoToPrincipal(aData.storagePrincipalInfo(), &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = PopulatePrincipalContentSecurityPolicy(
+      storagePrincipal, aData.storagePrincipalCsp(),
+      aData.storagePrincipalPreloadCsp());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   WorkerLoadInfo info;
   info.mBaseURI = DeserializeURI(aData.baseScriptURL());
   info.mResolvedScriptURI = DeserializeURI(aData.resolvedScriptURL());
 
   info.mPrincipalInfo = new PrincipalInfo(aData.principalInfo());
+  info.mStoragePrincipalInfo = new PrincipalInfo(aData.storagePrincipalInfo());
 
   info.mDomain = aData.domain();
   info.mPrincipal = principal;
+  info.mStoragePrincipal = storagePrincipal;
   info.mLoadingPrincipal = loadingPrincipal;
-  info.mStorageAllowed = aData.isStorageAccessAllowed();
+  info.mStorageAccess = aData.storageAccess();
   info.mOriginAttributes =
       BasePrincipal::Cast(principal)->OriginAttributesRef();
   info.mCookieSettings = net::CookieSettings::Create();
 
   // Default CSP permissions for now.  These will be overrided if necessary
   // based on the script CSP headers during load in ScriptLoader.
   info.mEvalAllowed = true;
   info.mReportCSPViolations = false;
@@ -285,17 +300,18 @@ nsresult RemoteWorkerChild::ExecWorkerOn
                             : WorkerLoadInfo::eInsecureContext;
 
   WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mLoadingPrincipal);
 
   RefPtr<SharedWorkerInterfaceRequestor> requestor =
       new SharedWorkerInterfaceRequestor();
   info.mInterfaceRequestor->SetOuterRequestor(requestor);
 
-  rv = info.SetPrincipalOnMainThread(info.mPrincipal, info.mLoadGroup);
+  rv = info.SetPrincipalsOnMainThread(info.mPrincipal, info.mStoragePrincipal,
+                                      info.mLoadGroup);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   Maybe<ClientInfo> clientInfo;
   if (aData.clientInfo().isSome()) {
     clientInfo.emplace(ClientInfo(aData.clientInfo().ref()));
   }
--- a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh
+++ b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include ClientIPCTypes;
 include PBackgroundSharedTypes;
 include URIParams;
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+using nsContentUtils::StorageAccess from "mozilla/dom/ClientIPCUtils.h";
 
 namespace mozilla {
 namespace dom {
 
 struct RemoteWorkerData
 {
   // This should only be used for devtools.
   nsString originalScriptURL;
@@ -32,23 +33,27 @@ struct RemoteWorkerData
   PrincipalInfo loadingPrincipalInfo;
   ContentSecurityPolicy[] loadingPrincipalCsp;
   ContentSecurityPolicy[] loadingPrincipalPreloadCsp;
 
   PrincipalInfo principalInfo;
   ContentSecurityPolicy[] principalCsp;
   ContentSecurityPolicy[] principalPreloadCsp;
 
+  PrincipalInfo storagePrincipalInfo;
+  ContentSecurityPolicy[] storagePrincipalCsp;
+  ContentSecurityPolicy[] storagePrincipalPreloadCsp;
+
   nsCString domain;
 
   bool isSecureContext;
 
   IPCClientInfo? clientInfo;
 
-  bool isStorageAccessAllowed;
+  StorageAccess storageAccess;
 
   bool isSharedWorker;
 };
 
 // ErrorData/ErrorDataNote correspond to WorkerErrorReport/WorkerErrorNote
 // which in turn correspond to JSErrorReport/JSErrorNotes which allows JS to
 // report complicated errors such as redeclarations that involve multiple
 // distinct lines.  For more generic error-propagation IPC structures, see bug
--- a/dom/workers/sharedworkers/SharedWorker.cpp
+++ b/dom/workers/sharedworkers/SharedWorker.cpp
@@ -97,24 +97,24 @@ already_AddRefed<SharedWorker> SharedWor
     const GlobalObject& aGlobal, const nsAString& aScriptURL,
     const StringOrWorkerOptions& aOptions, ErrorResult& aRv) {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsPIDOMWindowInner> window =
       do_QueryInterface(aGlobal.GetAsSupports());
   MOZ_ASSERT(window);
 
-  // If the window is blocked from accessing storage, do not allow it
-  // to connect to a SharedWorker.  This would potentially allow it
-  // to communicate with other windows that do have storage access.
-  // Allow private browsing, however, as we handle that isolation
-  // via the principal.
   auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
-  if (storageAllowed != nsContentUtils::StorageAccess::eAllow &&
-      storageAllowed != nsContentUtils::StorageAccess::ePrivateBrowsing) {
+  if (storageAllowed == nsContentUtils::StorageAccess::eDeny) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  if (storageAllowed == nsContentUtils::StorageAccess::ePartitionedOrDeny &&
+      !StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   // Assert that the principal private browsing state matches the
   // StorageAccess value.
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   if (storageAllowed == nsContentUtils::StorageAccess::ePrivateBrowsing) {
@@ -171,16 +171,67 @@ already_AddRefed<SharedWorker> SharedWor
   nsTArray<ContentSecurityPolicy> loadingPrincipalPreloadCSP;
   aRv = PopulateContentSecurityPolicyArray(loadInfo.mLoadingPrincipal,
                                            loadingPrincipalCSP,
                                            loadingPrincipalPreloadCSP);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  // Here, the StoragePrincipal is always equal to the SharedWorker's principal
+  // because the channel is not opened yet, and, because of this, it's not
+  // classified. We need to force the correct originAttributes.
+  if (storageAllowed == nsContentUtils::StorageAccess::ePartitionedOrDeny) {
+    nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(window);
+    if (!sop) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsIPrincipal* windowPrincipal = sop->GetPrincipal();
+    if (!windowPrincipal) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    nsIPrincipal* windowStoragePrincipal = sop->GetEffectiveStoragePrincipal();
+    if (!windowStoragePrincipal) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+
+    if (!windowPrincipal->Equals(windowStoragePrincipal)) {
+      loadInfo.mStoragePrincipal =
+          BasePrincipal::Cast(loadInfo.mPrincipal)
+              ->CloneForcingOriginAttributes(
+                  BasePrincipal::Cast(windowStoragePrincipal)
+                      ->OriginAttributesRef());
+    }
+  }
+
+  PrincipalInfo storagePrincipalInfo;
+  if (loadInfo.mPrincipal->Equals(loadInfo.mStoragePrincipal)) {
+    storagePrincipalInfo = principalInfo;
+  } else {
+    aRv = PrincipalToPrincipalInfo(loadInfo.mStoragePrincipal,
+                                   &storagePrincipalInfo);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+  }
+
+  nsTArray<ContentSecurityPolicy> storagePrincipalCSP;
+  nsTArray<ContentSecurityPolicy> storagePrincipalPreloadCSP;
+  aRv = PopulateContentSecurityPolicyArray(loadInfo.mStoragePrincipal,
+                                           storagePrincipalCSP,
+                                           storagePrincipalPreloadCSP);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   // We don't actually care about this MessageChannel, but we use it to 'steal'
   // its 2 connected ports.
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
   RefPtr<MessageChannel> channel = MessageChannel::Constructor(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
@@ -199,25 +250,22 @@ already_AddRefed<SharedWorker> SharedWor
   bool isSecureContext = JS::GetIsSecureContext(js::GetContextRealm(cx));
 
   Maybe<IPCClientInfo> ipcClientInfo;
   Maybe<ClientInfo> clientInfo = window->GetClientInfo();
   if (clientInfo.isSome()) {
     ipcClientInfo.emplace(clientInfo.value().ToIPC());
   }
 
-  bool storageAccessAllowed =
-      storageAllowed > nsContentUtils::StorageAccess::eDeny;
-
   RemoteWorkerData remoteWorkerData(
       nsString(aScriptURL), baseURL, resolvedScriptURL, name,
       loadingPrincipalInfo, loadingPrincipalCSP, loadingPrincipalPreloadCSP,
-      principalInfo, principalCSP, principalPreloadCSP, loadInfo.mDomain,
-      isSecureContext, ipcClientInfo, storageAccessAllowed,
-      true /* sharedWorker */);
+      principalInfo, principalCSP, principalPreloadCSP, storagePrincipalInfo,
+      storagePrincipalCSP, storagePrincipalPreloadCSP, loadInfo.mDomain,
+      isSecureContext, ipcClientInfo, storageAllowed, true /* sharedWorker */);
 
   PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor(
       remoteWorkerData, loadInfo.mWindowID, portIdentifier);
 
   RefPtr<SharedWorkerChild> actor = static_cast<SharedWorkerChild*>(pActor);
   MOZ_ASSERT(actor);
 
   RefPtr<SharedWorker> sharedWorker =
--- a/dom/workers/sharedworkers/SharedWorkerManager.cpp
+++ b/dom/workers/sharedworkers/SharedWorkerManager.cpp
@@ -17,33 +17,37 @@
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 
 // static
 already_AddRefed<SharedWorkerManagerHolder> SharedWorkerManager::Create(
     SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget,
-    const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal) {
+    const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal,
+    const OriginAttributes& aStoragePrincipalAttrs) {
   MOZ_ASSERT(NS_IsMainThread());
 
-  RefPtr<SharedWorkerManager> manager = new SharedWorkerManager(
-      aPBackgroundEventTarget, aData, aLoadingPrincipal);
+  RefPtr<SharedWorkerManager> manager =
+      new SharedWorkerManager(aPBackgroundEventTarget, aData, aLoadingPrincipal,
+                              aStoragePrincipalAttrs);
 
   RefPtr<SharedWorkerManagerHolder> holder =
       new SharedWorkerManagerHolder(manager, aService);
   return holder.forget();
 }
 
 SharedWorkerManager::SharedWorkerManager(
     nsIEventTarget* aPBackgroundEventTarget, const RemoteWorkerData& aData,
-    nsIPrincipal* aLoadingPrincipal)
+    nsIPrincipal* aLoadingPrincipal,
+    const OriginAttributes& aStoragePrincipalAttrs)
     : mPBackgroundEventTarget(aPBackgroundEventTarget),
       mLoadingPrincipal(aLoadingPrincipal),
       mDomain(aData.domain()),
+      mStoragePrincipalAttrs(aStoragePrincipalAttrs),
       mResolvedScriptURL(DeserializeURI(aData.resolvedScriptURL())),
       mName(aData.name()),
       mIsSecureContext(aData.isSecureContext()),
       mSuspended(false),
       mFrozen(false) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aLoadingPrincipal);
 }
@@ -75,33 +79,33 @@ bool SharedWorkerManager::MaybeCreateRem
     mRemoteWorkerController->AddWindowID(aWindowID);
   }
 
   mRemoteWorkerController->AddPortIdentifier(aPortIdentifier);
   return true;
 }
 
 already_AddRefed<SharedWorkerManagerHolder>
-SharedWorkerManager::MatchOnMainThread(SharedWorkerService* aService,
-                                       const nsACString& aDomain,
-                                       nsIURI* aScriptURL,
-                                       const nsAString& aName,
-                                       nsIPrincipal* aLoadingPrincipal) {
+SharedWorkerManager::MatchOnMainThread(
+    SharedWorkerService* aService, const nsACString& aDomain,
+    nsIURI* aScriptURL, const nsAString& aName, nsIPrincipal* aLoadingPrincipal,
+    const OriginAttributes& aStoragePrincipalAttrs) {
   MOZ_ASSERT(NS_IsMainThread());
 
   bool urlEquals;
   if (NS_FAILED(aScriptURL->Equals(mResolvedScriptURL, &urlEquals))) {
     return nullptr;
   }
 
   bool match = aDomain == mDomain && urlEquals && aName == mName &&
                // We want to be sure that the window's principal subsumes the
                // SharedWorker's loading principal and vice versa.
                mLoadingPrincipal->Subsumes(aLoadingPrincipal) &&
-               aLoadingPrincipal->Subsumes(mLoadingPrincipal);
+               aLoadingPrincipal->Subsumes(mLoadingPrincipal) &&
+               mStoragePrincipalAttrs == aStoragePrincipalAttrs;
   if (!match) {
     return nullptr;
   }
 
   RefPtr<SharedWorkerManagerHolder> holder =
       new SharedWorkerManagerHolder(this, aService);
   return holder.forget();
 }
--- a/dom/workers/sharedworkers/SharedWorkerManager.h
+++ b/dom/workers/sharedworkers/SharedWorkerManager.h
@@ -62,24 +62,26 @@ class SharedWorkerManagerWrapper final {
 class SharedWorkerManager final : public RemoteWorkerObserver {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerManager, override);
 
   // Called on main-thread thread methods
 
   static already_AddRefed<SharedWorkerManagerHolder> Create(
       SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget,
-      const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal);
+      const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal,
+      const OriginAttributes& aStoragePrincipalAttrs);
 
   // Returns a holder if this manager matches. The holder blocks the shutdown of
   // the manager.
   already_AddRefed<SharedWorkerManagerHolder> MatchOnMainThread(
       SharedWorkerService* aService, const nsACString& aDomain,
       nsIURI* aScriptURL, const nsAString& aName,
-      nsIPrincipal* aLoadingPrincipal);
+      nsIPrincipal* aLoadingPrincipal,
+      const OriginAttributes& aStoragePrincipalAttrs);
 
   // RemoteWorkerObserver
 
   void CreationFailed() override;
 
   void CreationSucceeded() override;
 
   void ErrorReceived(const ErrorValue& aValue) override;
@@ -109,24 +111,26 @@ class SharedWorkerManager final : public
 
   void RegisterHolder(SharedWorkerManagerHolder* aHolder);
 
   void UnregisterHolder(SharedWorkerManagerHolder* aHolder);
 
  private:
   SharedWorkerManager(nsIEventTarget* aPBackgroundEventTarget,
                       const RemoteWorkerData& aData,
-                      nsIPrincipal* aLoadingPrincipal);
+                      nsIPrincipal* aLoadingPrincipal,
+                      const OriginAttributes& aStoragePrincipalAttrs);
 
   ~SharedWorkerManager();
 
   nsCOMPtr<nsIEventTarget> mPBackgroundEventTarget;
 
   nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
   nsCString mDomain;
+  OriginAttributes mStoragePrincipalAttrs;
   nsCOMPtr<nsIURI> mResolvedScriptURL;
   nsString mName;
   bool mIsSecureContext;
   bool mSuspended;
   bool mFrozen;
 
   // Raw pointers because SharedWorkerParent unregisters itself in
   // ActorDestroy().
--- a/dom/workers/sharedworkers/SharedWorkerService.cpp
+++ b/dom/workers/sharedworkers/SharedWorkerService.cpp
@@ -213,27 +213,24 @@ void SharedWorkerService::GetOrCreateWor
 void SharedWorkerService::GetOrCreateWorkerManagerOnMainThread(
     nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
     const RemoteWorkerData& aData, uint64_t aWindowID,
     const MessagePortIdentifier& aPortIdentifier) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aBackgroundEventTarget);
   MOZ_ASSERT(aActor);
 
+  auto closeMessagePortIdentifier = MakeScopeExit([&] {
+    MessagePort::ForceClose(aPortIdentifier);
+  });
+
   nsresult rv = NS_OK;
-  nsCOMPtr<nsIPrincipal> principal =
-      PrincipalInfoToPrincipal(aData.principalInfo(), &rv);
-  if (NS_WARN_IF(!principal)) {
-    ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor, rv);
-    return;
-  }
-
-  rv = PopulatePrincipalContentSecurityPolicy(principal, aData.principalCsp(),
-                                              aData.principalPreloadCsp());
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  nsCOMPtr<nsIPrincipal> storagePrincipal =
+      PrincipalInfoToPrincipal(aData.storagePrincipalInfo(), &rv);
+  if (NS_WARN_IF(!storagePrincipal)) {
     ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor, rv);
     return;
   }
 
   nsCOMPtr<nsIPrincipal> loadingPrincipal =
       PrincipalInfoToPrincipal(aData.loadingPrincipalInfo(), &rv);
   if (NS_WARN_IF(!loadingPrincipal)) {
     ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor, rv);
@@ -250,27 +247,28 @@ void SharedWorkerService::GetOrCreateWor
 
   RefPtr<SharedWorkerManagerHolder> managerHolder;
 
   // Let's see if there is already a SharedWorker to share.
   nsCOMPtr<nsIURI> resolvedScriptURL =
       DeserializeURI(aData.resolvedScriptURL());
   for (SharedWorkerManager* workerManager : mWorkerManagers) {
     managerHolder = workerManager->MatchOnMainThread(
-        this, aData.domain(), resolvedScriptURL, aData.name(),
-        loadingPrincipal);
+        this, aData.domain(), resolvedScriptURL, aData.name(), loadingPrincipal,
+        BasePrincipal::Cast(storagePrincipal)->OriginAttributesRef());
     if (managerHolder) {
       break;
     }
   }
 
   // Let's create a new one.
   if (!managerHolder) {
-    managerHolder = SharedWorkerManager::Create(this, aBackgroundEventTarget,
-                                                aData, loadingPrincipal);
+    managerHolder = SharedWorkerManager::Create(
+        this, aBackgroundEventTarget, aData, loadingPrincipal,
+        BasePrincipal::Cast(storagePrincipal)->OriginAttributesRef());
 
     mWorkerManagers.AppendElement(managerHolder->Manager());
   } else {
     // We are attaching the actor to an existing one.
     if (managerHolder->Manager()->IsSecureContext() !=
         aData.isSecureContext()) {
       ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor,
                                    NS_ERROR_DOM_SECURITY_ERR);
@@ -279,16 +277,18 @@ void SharedWorkerService::GetOrCreateWor
   }
 
   RefPtr<SharedWorkerManagerWrapper> wrapper =
       new SharedWorkerManagerWrapper(managerHolder.forget());
 
   RefPtr<WorkerManagerCreatedRunnable> r = new WorkerManagerCreatedRunnable(
       wrapper.forget(), aActor, aData, aWindowID, aPortIdentifier);
   aBackgroundEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+
+  closeMessagePortIdentifier.release();
 }
 
 void SharedWorkerService::ErrorPropagationOnMainThread(
     nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
     nsresult aError) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aBackgroundEventTarget);
   MOZ_ASSERT(aActor);
--- a/dom/xml/XMLDocument.cpp
+++ b/dom/xml/XMLDocument.cpp
@@ -124,17 +124,17 @@ nsresult NS_NewDOMDocument(Document** aI
     nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(d);
     NS_ASSERTION(htmlDoc, "HTML Document doesn't implement nsIHTMLDocument?");
     htmlDoc->SetCompatibilityMode(eCompatibility_FullStandards);
     htmlDoc->SetIsXHTML(isXHTML);
   }
   d->SetLoadedAsData(aLoadedAsData);
   d->SetDocumentURI(aDocumentURI);
   // Must set the principal first, since SetBaseURI checks it.
-  d->SetPrincipal(aPrincipal);
+  d->SetPrincipals(aPrincipal, aPrincipal);
   d->SetBaseURI(aBaseURI);
 
   // We need to set the script handling object after we set the principal such
   // that the doc group is assigned correctly.
   if (nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aEventObject)) {
     d->SetScriptHandlingObject(sgo);
   } else if (aEventObject) {
     d->SetScopeObject(aEventObject);
@@ -247,38 +247,40 @@ nsresult XMLDocument::Init() {
   return rv;
 }
 
 void XMLDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
   Document::Reset(aChannel, aLoadGroup);
 }
 
 void XMLDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                             nsIPrincipal* aPrincipal) {
+                             nsIPrincipal* aPrincipal,
+                             nsIPrincipal* aStoragePrincipal) {
   if (mChannelIsPending) {
     StopDocumentLoad();
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannelIsPending = false;
   }
 
-  Document::ResetToURI(aURI, aLoadGroup, aPrincipal);
+  Document::ResetToURI(aURI, aLoadGroup, aPrincipal, aStoragePrincipal);
 }
 
 bool XMLDocument::Load(const nsAString& aUrl, CallerType aCallerType,
                        ErrorResult& aRv) {
   bool hasHadScriptObject = true;
   nsIScriptGlobalObject* scriptObject =
       GetScriptHandlingObject(hasHadScriptObject);
   if (!scriptObject && hasHadScriptObject) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return false;
   }
 
   nsCOMPtr<Document> callingDoc = GetEntryDocument();
   nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+  nsCOMPtr<nsIPrincipal> storagePrincipal = EffectiveStoragePrincipal();
 
   // The callingDoc's Principal and doc's Principal should be the same
   if (callingDoc && (callingDoc->NodePrincipal() != principal)) {
     nsContentUtils::ReportToConsole(
         nsIScriptError::errorFlag, NS_LITERAL_CSTRING("DOM"), callingDoc,
         nsContentUtils::eDOM_PROPERTIES, "XMLDocumentLoadPrincipalMismatch");
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return false;
@@ -365,17 +367,17 @@ bool XMLDocument::Load(const nsAString& 
   // and add ourselves to it. This way any pending requests
   // will be automatically aborted if the user leaves the page.
 
   nsCOMPtr<nsILoadGroup> loadGroup;
   if (callingDoc) {
     loadGroup = callingDoc->GetDocumentLoadGroup();
   }
 
-  ResetToURI(uri, loadGroup, principal);
+  ResetToURI(uri, loadGroup, principal, storagePrincipal);
 
   mListenerManager = elm;
 
   // Create a channel
   nsCOMPtr<nsIInterfaceRequestor> req = nsContentUtils::SameOriginChecker();
 
   nsCOMPtr<nsIChannel> channel;
   // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active,
--- a/dom/xml/XMLDocument.h
+++ b/dom/xml/XMLDocument.h
@@ -21,17 +21,18 @@ namespace dom {
 class XMLDocument : public Document {
  public:
   explicit XMLDocument(const char* aContentType = "application/xml");
 
   NS_INLINE_DECL_REFCOUNTING_INHERITED(XMLDocument, Document)
 
   virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) override;
   virtual void ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                          nsIPrincipal* aPrincipal) override;
+                          nsIPrincipal* aPrincipal,
+                          nsIPrincipal* aStoragePrincipal) override;
 
   virtual void SetSuppressParserErrorElement(bool aSuppress) override;
   virtual bool SuppressParserErrorElement() override;
 
   virtual void SetSuppressParserErrorConsoleMessages(bool aSuppress) override;
   virtual bool SuppressParserErrorConsoleMessages() override;
 
   virtual nsresult StartDocumentLoad(const char* aCommand, nsIChannel* channel,
--- a/dom/xslt/base/txURIUtils.cpp
+++ b/dom/xslt/base/txURIUtils.cpp
@@ -41,16 +41,17 @@ void URIUtils::resolveHref(const nsAStri
     dest.Append(resultHref);
   }
 }  //-- resolveHref
 
 // static
 void URIUtils::ResetWithSource(Document* aNewDoc, nsINode* aSourceNode) {
   nsCOMPtr<Document> sourceDoc = aSourceNode->OwnerDoc();
   nsIPrincipal* sourcePrincipal = sourceDoc->NodePrincipal();
+  nsIPrincipal* sourceStoragePrincipal = sourceDoc->EffectiveStoragePrincipal();
 
   // Copy the channel and loadgroup from the source document.
   nsCOMPtr<nsILoadGroup> loadGroup = sourceDoc->GetDocumentLoadGroup();
   nsCOMPtr<nsIChannel> channel = sourceDoc->GetChannel();
   if (!channel) {
     // Need to synthesize one
     nsresult rv = NS_NewChannel(
         getter_AddRefs(channel), sourceDoc->GetDocumentURI(), sourceDoc,
@@ -61,16 +62,16 @@ void URIUtils::ResetWithSource(Document*
         nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
 
     if (NS_FAILED(rv)) {
       return;
     }
   }
 
   aNewDoc->Reset(channel, loadGroup);
-  aNewDoc->SetPrincipal(sourcePrincipal);
+  aNewDoc->SetPrincipals(sourcePrincipal, sourceStoragePrincipal);
   aNewDoc->SetBaseURI(sourceDoc->GetDocBaseURI());
 
   // Copy charset
   aNewDoc->SetDocumentCharacterSetSource(
       sourceDoc->GetDocumentCharacterSetSource());
   aNewDoc->SetDocumentCharacterSet(sourceDoc->GetDocumentCharacterSet());
 }
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -188,17 +188,18 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHER
 // Document interface
 //
 
 void XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) {
   MOZ_ASSERT_UNREACHABLE("Reset");
 }
 
 void XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                             nsIPrincipal* aPrincipal) {
+                             nsIPrincipal* aPrincipal,
+                             nsIPrincipal* aStoragePrincipal) {
   MOZ_ASSERT_UNREACHABLE("ResetToURI");
 }
 
 void XULDocument::SetContentType(const nsAString& aContentType) {
   NS_ASSERTION(
       aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
       "xul-documents always has content-type application/vnd.mozilla.xul+xml");
   // Don't do anything, xul always has the mimetype
@@ -237,20 +238,31 @@ nsresult XULDocument::StartDocumentLoad(
   // Get the URI.  Note that this should match nsDocShell::OnLoadingSite
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mOriginalURI = mDocumentURI;
 
   // Get the document's principal
   nsCOMPtr<nsIPrincipal> principal;
-  nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
-      mChannel, getter_AddRefs(principal));
+  nsCOMPtr<nsIPrincipal> storagePrincipal;
+  rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipals(
+      mChannel, getter_AddRefs(principal), getter_AddRefs(storagePrincipal));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool equal = principal->Equals(storagePrincipal);
+
   principal = MaybeDowngradePrincipal(principal);
-  SetPrincipal(principal);
+  if (equal) {
+    storagePrincipal = principal;
+  } else {
+    storagePrincipal = MaybeDowngradePrincipal(storagePrincipal);
+  }
+
+  SetPrincipals(principal, storagePrincipal);
 
   ResetStylesheetsToURI(mDocumentURI);
 
   RetrieveRelevantHeaders(aChannel);
 
   mParser = new mozilla::parser::PrototypeDocumentParser(mDocumentURI, this);
   nsCOMPtr<nsIStreamListener> listener = mParser->GetStreamListener();
   listener.forget(aDocListener);
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -54,17 +54,18 @@ class XULDocument final : public XMLDocu
   XULDocument();
 
   // nsISupports interface
   NS_DECL_ISUPPORTS_INHERITED
 
   // Document interface
   virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) override;
   virtual void ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
-                          nsIPrincipal* aPrincipal) override;
+                          nsIPrincipal* aPrincipal,
+                          nsIPrincipal* aStoragePrincipal) override;
 
   virtual nsresult StartDocumentLoad(const char* aCommand, nsIChannel* channel,
                                      nsILoadGroup* aLoadGroup,
                                      nsISupports* aContainer,
                                      nsIStreamListener** aDocListener,
                                      bool aReset = true,
                                      nsIContentSink* aSink = nullptr) override;
   virtual void EndLoad() override;
--- a/editor/spellchecker/EditorSpellCheck.cpp
+++ b/editor/spellchecker/EditorSpellCheck.cpp
@@ -404,17 +404,17 @@ NS_IMETHODIMP
 EditorSpellCheck::GetNextMisspelledWord(nsAString& aNextMisspelledWord) {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
   DeleteSuggestedWordList();
   // Beware! This may flush notifications via synchronous
   // ScrollSelectionIntoView.
   RefPtr<mozSpellChecker> spellChecker(mSpellChecker);
   return spellChecker->NextMisspelledWord(aNextMisspelledWord,
-                                          &mSuggestedWordList);
+                                          mSuggestedWordList);
 }
 
 NS_IMETHODIMP
 EditorSpellCheck::GetSuggestedWord(nsAString& aSuggestedWord) {
   // XXX This is buggy if mSuggestedWordList.Length() is over INT32_MAX.
   if (mSuggestedWordIndex < static_cast<int32_t>(mSuggestedWordList.Length())) {
     aSuggestedWord = mSuggestedWordList[mSuggestedWordIndex];
     mSuggestedWordIndex++;
--- a/editor/spellchecker/TextServicesDocument.cpp
+++ b/editor/spellchecker/TextServicesDocument.cpp
@@ -318,25 +318,23 @@ nsresult TextServicesDocument::ExpandRan
 }
 
 nsresult TextServicesDocument::SetFilterType(uint32_t aFilterType) {
   mTxtSvcFilterType = aFilterType;
 
   return NS_OK;
 }
 
-nsresult TextServicesDocument::GetCurrentTextBlock(nsString* aStr) {
-  NS_ENSURE_TRUE(aStr, NS_ERROR_NULL_POINTER);
-
-  aStr->Truncate();
+nsresult TextServicesDocument::GetCurrentTextBlock(nsAString& aStr) {
+  aStr.Truncate();
 
   NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
 
   nsresult rv = CreateOffsetTable(&mOffsetTable, mFilteredIter,
-                                  &mIteratorStatus, mExtent, aStr);
+                                  &mIteratorStatus, mExtent, &aStr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 nsresult TextServicesDocument::FirstBlock() {
   NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE);
@@ -1059,21 +1057,17 @@ nsresult TextServicesDocument::DeleteSel
   // printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex,
   //        mSelStartOffset, mSelEndIndex, mSelEndOffset);
   // PrintOffsetTable();
   //**** KDEBUG ****
 
   return rv;
 }
 
-nsresult TextServicesDocument::InsertText(const nsString* aText) {
-  if (NS_WARN_IF(!aText)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
+nsresult TextServicesDocument::InsertText(const nsAString& aText) {
   if (NS_WARN_IF(!mTextEditor) || NS_WARN_IF(!SelectionIsValid())) {
     return NS_ERROR_FAILURE;
   }
 
   // If the selection is not collapsed, we need to save
   // off the selection offsets so we can restore the
   // selection and delete the selected content after we've
   // inserted the new text. This is necessary to try and
@@ -1092,22 +1086,22 @@ nsresult TextServicesDocument::InsertTex
 
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // AutoTransactionBatchExternal grabs mTextEditor, so, we don't need to grab
   // the instance with local variable here.
   AutoTransactionBatchExternal treatAsOneTransaction(*mTextEditor);
 
-  nsresult rv = mTextEditor->InsertTextAsAction(*aText);
+  nsresult rv = mTextEditor->InsertTextAsAction(aText);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  int32_t strLength = aText->Length();
+  int32_t strLength = aText.Length();
 
   OffsetEntry* itEntry;
   OffsetEntry* entry = mOffsetTable[mSelStartIndex];
   void* node = entry->mNode;
 
   NS_ASSERTION((entry->mIsValid), "Invalid insertion point!");
 
   if (entry->mStrOffset == mSelStartOffset) {
@@ -2474,17 +2468,17 @@ nsresult TextServicesDocument::GetFirstT
 
   // Restore the iterator:
   return mFilteredIter->PositionAt(node);
 }
 
 nsresult TextServicesDocument::CreateOffsetTable(
     nsTArray<OffsetEntry*>* aOffsetTable,
     FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus,
-    nsRange* aIterRange, nsString* aStr) {
+    nsRange* aIterRange, nsAString* aStr) {
   nsCOMPtr<nsIContent> first;
   nsCOMPtr<nsIContent> prev;
 
   NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER);
 
   ClearOffsetTable(aOffsetTable);
 
   if (aStr) {
--- a/editor/spellchecker/TextServicesDocument.h
+++ b/editor/spellchecker/TextServicesDocument.h
@@ -112,17 +112,17 @@ class TextServicesDocument final : publi
    */
   nsresult SetFilterType(uint32_t aFilterType);
 
   /**
    * Returns the text in the current text block.
    *
    * @param aStr                [OUT] This will contain the text.
    */
-  nsresult GetCurrentTextBlock(nsString* aStr);
+  nsresult GetCurrentTextBlock(nsAString& aStr);
 
   /**
    * Tells the document to point to the first text block in the document.  This
    * method does not adjust the current cursor position or selection.
    */
   nsresult FirstBlock();
 
   enum class BlockSelectionStatus {
@@ -206,17 +206,17 @@ class TextServicesDocument final : publi
   MOZ_CAN_RUN_SCRIPT
   nsresult DeleteSelection();
 
   /**
    * Inserts the given text at the current cursor position.  If there is a
    * selection, it will be deleted before the text is inserted.
    */
   MOZ_CAN_RUN_SCRIPT
-  nsresult InsertText(const nsString* aText);
+  nsresult InsertText(const nsAString& aText);
 
   /**
    * nsIEditActionListener method implementations.
    */
   NS_DECL_NSIEDITACTIONLISTENER
 
   /**
    * Actual edit action listeners.  When you add new method here for listening
@@ -284,17 +284,17 @@ class TextServicesDocument final : publi
                                    int32_t* aSelOffset, int32_t* aSelLength);
 
   bool SelectionIsCollapsed();
   bool SelectionIsValid();
 
   static nsresult CreateOffsetTable(nsTArray<OffsetEntry*>* aOffsetTable,
                                     FilteredContentIterator* aFilteredIter,
                                     IteratorStatus* aIteratorStatus,
-                                    nsRange* aIterRange, nsString* aStr);
+                                    nsRange* aIterRange, nsAString* aStr);
   static nsresult ClearOffsetTable(nsTArray<OffsetEntry*>* aOffsetTable);
 
   static nsresult NodeHasOffsetEntry(nsTArray<OffsetEntry*>* aOffsetTable,
                                      nsINode* aNode, bool* aHasEntry,
                                      int32_t* aEntryIndex);
 
   nsresult RemoveInvalidOffsetEntries();
   nsresult SplitOffsetEntry(int32_t aTableIndex, int32_t aOffsetIntoEntry);
--- a/extensions/spellcheck/src/mozEnglishWordUtils.cpp
+++ b/extensions/spellcheck/src/mozEnglishWordUtils.cpp
@@ -26,76 +26,81 @@ mozEnglishWordUtils::~mozEnglishWordUtil
 // This needs vast improvement
 
 // static
 bool mozEnglishWordUtils::ucIsAlpha(char16_t aChar) {
   // XXX we have to fix callers to handle the full Unicode range
   return nsUGenCategory::kLetter == mozilla::unicode::GetGenCategory(aChar);
 }
 
-nsresult mozEnglishWordUtils::FindNextWord(const char16_t *word,
-                                           uint32_t length, uint32_t offset,
-                                           int32_t *begin, int32_t *end) {
+bool mozEnglishWordUtils::FindNextWord(const nsAString &aWord, uint32_t offset,
+                                       int32_t *begin, int32_t *end) {
+  if (offset >= aWord.Length()) {
+    *begin = -1;
+    *end = -1;
+    return false;
+  }
+
+  const char16_t *word = aWord.BeginReading();
+  uint32_t length = aWord.Length();
   const char16_t *p = word + offset;
   const char16_t *endbuf = word + length;
   const char16_t *startWord = p;
-  if (p < endbuf) {
-    // XXX These loops should be modified to handle non-BMP characters.
-    // if previous character is a word character, need to advance out of the
-    // word
-    if (offset > 0 && ucIsAlpha(*(p - 1))) {
-      while (p < endbuf && ucIsAlpha(*p)) p++;
-    }
-    while ((p < endbuf) && (!ucIsAlpha(*p))) {
-      p++;
-    }
-    startWord = p;
-    while ((p < endbuf) && ((ucIsAlpha(*p)) || (*p == '\''))) {
+
+  // XXX These loops should be modified to handle non-BMP characters.
+  // if previous character is a word character, need to advance out of the
+  // word
+  if (offset > 0 && ucIsAlpha(*(p - 1))) {
+    while (p < endbuf && ucIsAlpha(*p)) {
       p++;
     }
-
-    // we could be trying to break down a url, we don't want to break a url into
-    // parts, instead we want to find out if it really is a url and if so, skip
-    // it, advancing startWord to a point after the url.
+  }
+  while ((p < endbuf) && (!ucIsAlpha(*p))) {
+    p++;
+  }
+  startWord = p;
+  while ((p < endbuf) && ((ucIsAlpha(*p)) || (*p == '\''))) {
+    p++;
+  }
 
-    // before we spend more time looking to see if the word is a url, look for a
-    // url identifer and make sure that identifer isn't the last character in
-    // the word fragment.
-    if ((p < endbuf - 1) && (*p == ':' || *p == '@' || *p == '.')) {
-      // ok, we have a possible url...do more research to find out if we really
-      // have one and determine the length of the url so we can skip over it.
+  // we could be trying to break down a url, we don't want to break a url into
+  // parts, instead we want to find out if it really is a url and if so, skip
+  // it, advancing startWord to a point after the url.
 
-      if (mURLDetector) {
-        int32_t startPos = -1;
-        int32_t endPos = -1;
+  // before we spend more time looking to see if the word is a url, look for a
+  // url identifer and make sure that identifer isn't the last character in
+  // the word fragment.
+  if ((p < endbuf - 1) && (*p == ':' || *p == '@' || *p == '.')) {
+    // ok, we have a possible url...do more research to find out if we really
+    // have one and determine the length of the url so we can skip over it.
 
-        mURLDetector->FindURLInPlaintext(startWord, endbuf - startWord,
-                                         p - startWord, &startPos, &endPos);
+    if (mURLDetector) {
+      int32_t startPos = -1;
+      int32_t endPos = -1;
 
-        // ok, if we got a url, adjust the array bounds, skip the current url
-        // text and find the next word again
-        if (startPos != -1 && endPos != -1) {
-          startWord = p + endPos + 1;  // skip over the url
-          p = startWord;               // reset p
+      mURLDetector->FindURLInPlaintext(startWord, endbuf - startWord,
+                                       p - startWord, &startPos, &endPos);
 
-          // now recursively call FindNextWord to search for the next word now
-          // that we have skipped the url
-          return FindNextWord(word, length, startWord - word, begin, end);
-        }
+      // ok, if we got a url, adjust the array bounds, skip the current url
+      // text and find the next word again
+      if (startPos != -1 && endPos != -1) {
+        startWord = p + endPos + 1;  // skip over the url
+
+        // now recursively call FindNextWord to search for the next word now
+        // that we have skipped the url
+        return FindNextWord(aWord, startWord - word, begin, end);
       }
     }
+  }
 
-    while ((p > startWord) &&
-           (*(p - 1) == '\'')) {  // trim trailing apostrophes
-      p--;
-    }
-  } else {
-    startWord = endbuf;
+  while ((p > startWord) && (*(p - 1) == '\'')) {  // trim trailing apostrophes
+    p--;
   }
+
   if (startWord == endbuf) {
     *begin = -1;
     *end = -1;
-  } else {
-    *begin = startWord - word;
-    *end = p - word;
+    return false;
   }
-  return NS_OK;
+  *begin = startWord - word;
+  *end = p - word;
+  return true;
 }
--- a/extensions/spellcheck/src/mozEnglishWordUtils.h
+++ b/extensions/spellcheck/src/mozEnglishWordUtils.h
@@ -16,21 +16,22 @@ class mozEnglishWordUtils final {
  public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(mozEnglishWordUtils)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(mozEnglishWordUtils)
 
   mozEnglishWordUtils();
 
   /**
    * Given a unicode string and an offset, find the beginning and end of the
-   * next word. begin and end are -1 if there are no words remaining in the
-   * string. This should really be folded into the Line/WordBreaker.
+   * next word. Return false, begin and end are -1 if there are no words
+   * remaining in the string. This should really be folded into the
+   * Line/WordBreaker.
    */
-  nsresult FindNextWord(const char16_t* word, uint32_t length, uint32_t offset,
-                        int32_t* begin, int32_t* end);
+  bool FindNextWord(const nsAString& aWord, uint32_t offset, int32_t* begin,
+                    int32_t* end);
 
  protected:
   virtual ~mozEnglishWordUtils();
 
   static bool ucIsAlpha(char16_t aChar);
 
   nsCOMPtr<mozITXTToHTMLConv>
       mURLDetector;  // used to detect urls so the spell checker can skip them.
--- a/extensions/spellcheck/src/mozSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozSpellChecker.cpp
@@ -70,52 +70,49 @@ TextServicesDocument *mozSpellChecker::G
 nsresult mozSpellChecker::SetDocument(
     TextServicesDocument *aTextServicesDocument, bool aFromStartofDoc) {
   mTextServicesDocument = aTextServicesDocument;
   mFromStart = aFromStartofDoc;
   return NS_OK;
 }
 
 nsresult mozSpellChecker::NextMisspelledWord(nsAString &aWord,
-                                             nsTArray<nsString> *aSuggestions) {
-  if (!aSuggestions || !mConverter) return NS_ERROR_NULL_POINTER;
+                                             nsTArray<nsString> &aSuggestions) {
+  if (NS_WARN_IF(!mConverter)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   int32_t selOffset;
-  int32_t begin, end;
   nsresult result;
   result = SetupDoc(&selOffset);
-  bool isMisspelled, done;
   if (NS_FAILED(result)) return result;
 
+  bool done;
   while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) {
-    nsString str;
-    result = mTextServicesDocument->GetCurrentTextBlock(&str);
-
-    if (NS_FAILED(result)) {
-      return result;
-    }
-    do {
-      result = mConverter->FindNextWord(str.get(), str.Length(), selOffset,
-                                        &begin, &end);
-      if (NS_SUCCEEDED(result) && begin != -1) {
-        const nsAString &currWord = Substring(str, begin, end - begin);
-        result = CheckWord(currWord, &isMisspelled, aSuggestions);
-        if (isMisspelled) {
-          aWord = currWord;
-          MOZ_KnownLive(mTextServicesDocument)
-              ->SetSelection(begin, end - begin);
-          // After ScrollSelectionIntoView(), the pending notifications might
-          // be flushed and PresShell/PresContext/Frames may be dead.
-          // See bug 418470.
-          mTextServicesDocument->ScrollSelectionIntoView();
-          return NS_OK;
-        }
+    int32_t begin, end;
+    nsAutoString str;
+    mTextServicesDocument->GetCurrentTextBlock(str);
+    while (mConverter->FindNextWord(str, selOffset, &begin, &end)) {
+      const nsDependentSubstring currWord(str, begin, end - begin);
+      bool isMisspelled;
+      result = CheckWord(currWord, &isMisspelled, &aSuggestions);
+      if (NS_WARN_IF(NS_FAILED(result))) {
+        return result;
+      }
+      if (isMisspelled) {
+        aWord = currWord;
+        MOZ_KnownLive(mTextServicesDocument)->SetSelection(begin, end - begin);
+        // After ScrollSelectionIntoView(), the pending notifications might
+        // be flushed and PresShell/PresContext/Frames may be dead.
+        // See bug 418470.
+        mTextServicesDocument->ScrollSelectionIntoView();
+        return NS_OK;
       }
       selOffset = end;
-    } while (end != -1);
+    }
     mTextServicesDocument->NextBlock();
     selOffset = 0;
   }
   return NS_OK;
 }
 
 RefPtr<CheckWordPromise> mozSpellChecker::CheckWords(
     const nsTArray<nsString> &aWords) {
@@ -182,105 +179,107 @@ nsresult mozSpellChecker::CheckWord(cons
     *aIsMisspelled = true;
   }
   return NS_OK;
 }
 
 nsresult mozSpellChecker::Replace(const nsAString &aOldWord,
                                   const nsAString &aNewWord,
                                   bool aAllOccurrences) {
-  if (!mConverter) return NS_ERROR_NULL_POINTER;
-
-  nsAutoString newWord(aNewWord);  // sigh
+  if (NS_WARN_IF(!mConverter)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
-  if (aAllOccurrences) {
-    int32_t selOffset;
-    int32_t startBlock, currentBlock, currOffset;
-    int32_t begin, end;
-    bool done;
-    nsresult result;
-    nsAutoString str;
+  if (!aAllOccurrences) {
+    MOZ_KnownLive(mTextServicesDocument)->InsertText(aNewWord);
+    return NS_OK;
+  }
 
-    // find out where we are
-    result = SetupDoc(&selOffset);
-    if (NS_FAILED(result)) return result;
-    result = GetCurrentBlockIndex(mTextServicesDocument, &startBlock);
-    if (NS_FAILED(result)) return result;
+  int32_t selOffset;
+  int32_t startBlock;
+  int32_t begin, end;
+  bool done;
+  nsresult result;
 
-    // start at the beginning
-    result = mTextServicesDocument->FirstBlock();
-    currOffset = 0;
-    currentBlock = 0;
-    while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) {
-      result = mTextServicesDocument->GetCurrentTextBlock(&str);
-      do {
-        result = mConverter->FindNextWord(str.get(), str.Length(), currOffset,
-                                          &begin, &end);
-        if (NS_SUCCEEDED(result) && (begin != -1)) {
-          if (aOldWord.Equals(Substring(str, begin, end - begin))) {
-            // if we are before the current selection point but in the same
-            // block move the selection point forwards
-            if (currentBlock == startBlock && begin < selOffset) {
-              selOffset +=
-                  int32_t(aNewWord.Length()) - int32_t(aOldWord.Length());
-              if (selOffset < begin) {
-                selOffset = begin;
-              }
-            }
-            MOZ_KnownLive(mTextServicesDocument)
-                ->SetSelection(begin, end - begin);
-            MOZ_KnownLive(mTextServicesDocument)->InsertText(&newWord);
-            mTextServicesDocument->GetCurrentTextBlock(&str);
-            end += (aNewWord.Length() -
-                    aOldWord.Length());  // recursion was cute in GEB, not here.
+  // find out where we are
+  result = SetupDoc(&selOffset);
+  if (NS_WARN_IF(NS_FAILED(result))) {
+    return result;
+  }
+  result = GetCurrentBlockIndex(mTextServicesDocument, &startBlock);
+  if (NS_WARN_IF(NS_FAILED(result))) {
+    return result;
+  }
+
+  // start at the beginning
+  result = mTextServicesDocument->FirstBlock();
+  if (NS_WARN_IF(NS_FAILED(result))) {
+    return result;
+  }
+  int32_t currOffset = 0;
+  int32_t currentBlock = 0;
+  while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) {
+    nsAutoString str;
+    mTextServicesDocument->GetCurrentTextBlock(str);
+    while (mConverter->FindNextWord(str, currOffset, &begin, &end)) {
+      if (aOldWord.Equals(Substring(str, begin, end - begin))) {
+        // if we are before the current selection point but in the same
+        // block move the selection point forwards
+        if (currentBlock == startBlock && begin < selOffset) {
+          selOffset += int32_t(aNewWord.Length()) - int32_t(aOldWord.Length());
+          if (selOffset < begin) {
+            selOffset = begin;
           }
         }
-        currOffset = end;
-      } while (currOffset != -1);
-      mTextServicesDocument->NextBlock();
-      currentBlock++;
-      currOffset = 0;
+        MOZ_KnownLive(mTextServicesDocument)->SetSelection(begin, end - begin);
+        MOZ_KnownLive(mTextServicesDocument)->InsertText(aNewWord);
+        mTextServicesDocument->GetCurrentTextBlock(str);
+        end += (aNewWord.Length() -
+                aOldWord.Length());  // recursion was cute in GEB, not here.
+      }
+      currOffset = end;
     }
-
-    // We are done replacing.  Put the selection point back where we found  it
-    // (or equivalent);
-    result = mTextServicesDocument->FirstBlock();
-    currentBlock = 0;
-    while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done &&
-           currentBlock < startBlock) {
-      mTextServicesDocument->NextBlock();
-    }
+    mTextServicesDocument->NextBlock();
+    currentBlock++;
+    currOffset = 0;
+  }
 
-    // After we have moved to the block where the first occurrence of replace
-    // was done, put the selection to the next word following it. In case there
-    // is no word following it i.e if it happens to be the last word in that
-    // block, then move to the next block and put the selection to the first
-    // word in that block, otherwise when the Setupdoc() is called, it queries
-    // the LastSelectedBlock() and the selection offset of the last occurrence
-    // of the replaced word is taken instead of the first occurrence and things
-    // get messed up as reported in the bug 244969
+  // We are done replacing.  Put the selection point back where we found  it
+  // (or equivalent);
+  result = mTextServicesDocument->FirstBlock();
+  if (NS_WARN_IF(NS_FAILED(result))) {
+    return result;
+  }
+  currentBlock = 0;
+  while (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done &&
+         currentBlock < startBlock) {
+    mTextServicesDocument->NextBlock();
+  }
 
-    if (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) {
-      nsString str;
-      result = mTextServicesDocument->GetCurrentTextBlock(&str);
-      result = mConverter->FindNextWord(str.get(), str.Length(), selOffset,
-                                        &begin, &end);
-      if (end == -1) {
-        mTextServicesDocument->NextBlock();
-        selOffset = 0;
-        result = mTextServicesDocument->GetCurrentTextBlock(&str);
-        result = mConverter->FindNextWord(str.get(), str.Length(), selOffset,
-                                          &begin, &end);
-        MOZ_KnownLive(mTextServicesDocument)->SetSelection(begin, 0);
-      } else {
-        MOZ_KnownLive(mTextServicesDocument)->SetSelection(begin, 0);
-      }
+  // After we have moved to the block where the first occurrence of replace
+  // was done, put the selection to the next word following it. In case there
+  // is no word following it i.e if it happens to be the last word in that
+  // block, then move to the next block and put the selection to the first
+  // word in that block, otherwise when the Setupdoc() is called, it queries
+  // the LastSelectedBlock() and the selection offset of the last occurrence
+  // of the replaced word is taken instead of the first occurrence and things
+  // get messed up as reported in the bug 244969
+
+  if (NS_SUCCEEDED(mTextServicesDocument->IsDone(&done)) && !done) {
+    nsAutoString str;
+    mTextServicesDocument->GetCurrentTextBlock(str);
+    if (mConverter->FindNextWord(str, selOffset, &begin, &end)) {
+      MOZ_KnownLive(mTextServicesDocument)->SetSelection(begin, 0);
+      return NS_OK;
     }
-  } else {
-    MOZ_KnownLive(mTextServicesDocument)->InsertText(&newWord);
+    mTextServicesDocument->NextBlock();
+    mTextServicesDocument->GetCurrentTextBlock(str);
+    if (mConverter->FindNextWord(str, 0, &begin, &end)) {
+      MOZ_KnownLive(mTextServicesDocument)->SetSelection(begin, 0);
+    }
   }
   return NS_OK;
 }
 
 nsresult mozSpellChecker::IgnoreAll(const nsAString &aWord) {
   if (mPersonalDictionary) {
     mPersonalDictionary->IgnoreWord(aWord);
   }
--- a/extensions/spellcheck/src/mozSpellChecker.h
+++ b/extensions/spellcheck/src/mozSpellChecker.h
@@ -48,17 +48,17 @@ class mozSpellChecker final {
   /**
    * Selects (hilites) the next misspelled word in the document.
    * @param aWord will contain the misspelled word.
    * @param aSuggestions is an array of nsStrings, that represent the
    * suggested replacements for the misspelled word.
    */
   MOZ_CAN_RUN_SCRIPT
   nsresult NextMisspelledWord(nsAString& aWord,
-                              nsTArray<nsString>* aSuggestions);
+                              nsTArray<nsString>& aSuggestions);
 
   /**
    * Checks if a word is misspelled. No document is required to use this method.
    * @param aWord is the word to check.
    * @param aIsMisspelled will be set to true if the word is misspelled.
    * @param aSuggestions is an array of nsStrings which represent the
    * suggested replacements for the misspelled word. The array will be empty
    * in chrome process if there aren't any suggestions. If suggestions is
--- a/gfx/webrender_bindings/RenderCompositorEGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp
@@ -24,96 +24,59 @@
 #endif
 
 namespace mozilla {
 namespace wr {
 
 /* static */
 UniquePtr<RenderCompositor> RenderCompositorEGL::Create(
     RefPtr<widget::CompositorWidget> aWidget) {
-#ifdef MOZ_WIDGET_ANDROID
+#ifdef MOZ_WAYLAND
+  if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+    return nullptr;
+  }
+#endif
   if (!RenderThread::Get()->SharedGL()) {
     gfxCriticalNote << "Failed to get shared GL context";
     return nullptr;
   }
   return MakeUnique<RenderCompositorEGL>(aWidget);
-#elif defined(MOZ_WAYLAND)
-  // Disabled SharedGL() usage on wayland. Its usage caused flicker when
-  // multiple windows were opened. See Bug 1535893.
-  if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
-    return nullptr;
-  }
-  RefPtr<gl::GLContext> gl;
-  gl = CreateGLContext();
-  if (!gl) {
-    return nullptr;
-  }
-  return MakeUnique<RenderCompositorEGL>(gl, aWidget);
-#endif
 }
 
-#ifdef MOZ_WAYLAND
-/* static */ already_AddRefed<gl::GLContext>
-RenderCompositorEGL::CreateGLContext() {
-  // Create GLContext with dummy EGLSurface.
-  RefPtr<gl::GLContext> gl =
-      gl::GLContextProviderEGL::CreateForCompositorWidget(
-          nullptr, /* aWebRender */ true, /* aForceAccelerated */ true);
-  if (!gl || !gl->MakeCurrent()) {
-    gfxCriticalNote << "Failed GL context creation for WebRender: "
-                    << gfx::hexa(gl.get());
-    return nullptr;
-  }
-
-  return gl.forget();
-}
-#endif
-
 EGLSurface RenderCompositorEGL::CreateEGLSurface() {
   EGLSurface surface = EGL_NO_SURFACE;
   surface = gl::GLContextEGL::CreateEGLSurfaceForCompositorWidget(
       mWidget, gl::GLContextEGL::Cast(gl())->mConfig);
   if (surface == EGL_NO_SURFACE) {
     gfxCriticalNote << "Failed to create EGLSurface";
   }
   return surface;
 }
 
-#ifdef MOZ_WIDGET_ANDROID
 RenderCompositorEGL::RenderCompositorEGL(
     RefPtr<widget::CompositorWidget> aWidget)
     : RenderCompositor(std::move(aWidget)), mEGLSurface(EGL_NO_SURFACE) {}
-#elif defined(MOZ_WAYLAND)
-RenderCompositorEGL::RenderCompositorEGL(
-    RefPtr<gl::GLContext> aGL, RefPtr<widget::CompositorWidget> aWidget)
-    : RenderCompositor(std::move(aWidget)),
-      mGL(aGL),
-      mEGLSurface(EGL_NO_SURFACE) {
-  MOZ_ASSERT(mGL);
-}
-#endif
 
 RenderCompositorEGL::~RenderCompositorEGL() { DestroyEGLSurface(); }
 
 bool RenderCompositorEGL::BeginFrame() {
 #ifdef MOZ_WAYLAND
   if (mWidget->AsX11() &&
       mWidget->AsX11()->WaylandRequestsUpdatingEGLSurface()) {
     // Destroy EGLSurface if it exists.
     DestroyEGLSurface();
     mEGLSurface = CreateEGLSurface();
-    gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
     if (mEGLSurface) {
       const auto* egl = gl::GLLibraryEGL::Get();
       // Make eglSwapBuffers() non-blocking on wayland
       egl->fSwapInterval(gl::EGL_DISPLAY(), 0);
     }
   }
 #endif
-  if (!gl()->MakeCurrent()) {
+  if (!MakeCurrent()) {
     gfxCriticalNote << "Failed to make render context current, can't draw.";
     return false;
   }
 
 #ifdef MOZ_WIDGET_ANDROID
   java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
 #endif
 
@@ -142,22 +105,17 @@ bool RenderCompositorEGL::Resume() {
   DestroyEGLSurface();
   mEGLSurface = CreateEGLSurface();
   gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
 #endif
   return true;
 }
 
 gl::GLContext* RenderCompositorEGL::gl() const {
-#ifdef MOZ_WIDGET_ANDROID
   return RenderThread::Get()->SharedGL();
-#elif defined(MOZ_WAYLAND)
-  MOZ_ASSERT(mGL);
-  return mGL;
-#endif
 }
 
 bool RenderCompositorEGL::MakeCurrent() {
   gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
   return gl()->MakeCurrent();
 }
 
 void RenderCompositorEGL::DestroyEGLSurface() {
--- a/gfx/webrender_bindings/RenderCompositorEGL.h
+++ b/gfx/webrender_bindings/RenderCompositorEGL.h
@@ -14,22 +14,17 @@ namespace mozilla {
 
 namespace wr {
 
 class RenderCompositorEGL : public RenderCompositor {
  public:
   static UniquePtr<RenderCompositor> Create(
       RefPtr<widget::CompositorWidget> aWidget);
 
-#ifdef MOZ_WIDGET_ANDROID
   explicit RenderCompositorEGL(RefPtr<widget::CompositorWidget> aWidget);
-#elif defined(MOZ_WAYLAND)
-  RenderCompositorEGL(RefPtr<gl::GLContext> aGL,
-                      RefPtr<widget::CompositorWidget> aWidget);
-#endif
   virtual ~RenderCompositorEGL();
 
   bool BeginFrame() override;
   void EndFrame() override;
   void WaitForGPU() override;
   void Pause() override;
   bool Resume() override;
 
@@ -37,25 +32,19 @@ class RenderCompositorEGL : public Rende
 
   bool MakeCurrent() override;
 
   bool UseANGLE() const override { return false; }
 
   LayoutDeviceIntSize GetBufferSize() override;
 
  protected:
-#ifdef MOZ_WAYLAND
-  static already_AddRefed<gl::GLContext> CreateGLContext();
-#endif
   EGLSurface CreateEGLSurface();
 
   void DestroyEGLSurface();
 
-#ifdef MOZ_WAYLAND
-  const RefPtr<gl::GLContext> mGL;
-#endif
   EGLSurface mEGLSurface;
 };
 
 }  // namespace wr
 }  // namespace mozilla
 
 #endif  // MOZILLA_GFX_RENDERCOMPOSITOR_EGL_H
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -864,17 +864,17 @@ static already_AddRefed<gl::GLContext> C
                     << gfx::hexa(gl.get());
     return nullptr;
   }
 
   return gl.forget();
 }
 #endif
 
-#ifdef MOZ_WIDGET_ANDROID
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WAYLAND)
 static already_AddRefed<gl::GLContext> CreateGLContextEGL() {
   nsCString discardFailureId;
   if (!gl::GLLibraryEGL::EnsureInitialized(/* forceAccel */ true,
                                            &discardFailureId)) {
     gfxCriticalNote << "Failed to load EGL library: " << discardFailureId.get();
     return nullptr;
   }
   // Create GLContext with dummy EGLSurface.
@@ -892,18 +892,22 @@ static already_AddRefed<gl::GLContext> C
 
 static already_AddRefed<gl::GLContext> CreateGLContext() {
 #ifdef XP_WIN
   if (gfx::gfxVars::UseWebRenderANGLE()) {
     return CreateGLContextANGLE();
   }
 #endif
 
-#ifdef MOZ_WIDGET_ANDROID
-  return CreateGLContextEGL();
+#if defined(MOZ_WIDGET_ANDROID)
+    return CreateGLContextEGL();
+#elif defined(MOZ_WAYLAND)
+  if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+    return CreateGLContextEGL();
+  }
 #endif
   // We currently only support a shared GLContext
   // with ANGLE.
   return nullptr;
 }
 
 extern "C" {
 
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
@@ -6,17 +6,17 @@ use api::{BorderRadius, BorderSide, Bord
 use api::{LayoutPrimitiveInfo, NormalBorder as ApiNormalBorder, RepeatMode};
 use api::units::*;
 use ellipse::Ellipse;
 use euclid::vec2;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
 use prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
 use prim_store::{EdgeAaSegmentMask, ScrollNodeAndClipChain};
-use prim_store::borders::NormalBorderPrim;
+use prim_store::borders::{NormalBorderPrim, NormalBorderData};
 use util::{lerp, RectHelpers};
 
 // Using 2048 as the maximum radius in device space before which we
 // start stretching is up for debate.
 // the value must be chosen so that the corners will not use an
 // unreasonable amount of memory but should allow crisp corners in the
 // common cases.
 
@@ -867,31 +867,23 @@ pub fn create_border_segments(
     );
 }
 
 /// Computes the maximum scale that we allow for this set of border parameters.
 /// capping the scale will result in rendering very large corners at a lower
 /// resolution and stretching them, so they will have the right shape, but
 /// blurrier.
 pub fn get_max_scale_for_border(
-    radii: &BorderRadius,
-    widths: &LayoutSideOffsets
+    border_data: &NormalBorderData,
 ) -> LayoutToDeviceScale {
-    let r = radii.top_left.width
-        .max(radii.top_left.height)
-        .max(radii.top_right.width)
-        .max(radii.top_right.height)
-        .max(radii.bottom_left.width)
-        .max(radii.bottom_left.height)
-        .max(radii.bottom_right.width)
-        .max(radii.bottom_right.height)
-        .max(widths.top)
-        .max(widths.bottom)
-        .max(widths.left)
-        .max(widths.right);
+    let mut r = 1.0;
+    for segment in &border_data.border_segments {
+        let size = segment.local_task_size;
+        r = size.width.max(size.height.max(r));
+    }
 
     LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
 }
 
 fn add_segment(
     task_rect: DeviceRect,
     style0: BorderStyle,
     style1: BorderStyle,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -2578,18 +2578,17 @@ impl PrimitiveStore {
                 // power-of-2 boundary ensures we never scale up, only down --- avoiding
                 // jaggies. It also ensures we never scale down by more than a factor of
                 // 2, avoiding bad downscaling quality.
                 let scale_width = clamp_to_scale_factor(scale.0, false);
                 let scale_height = clamp_to_scale_factor(scale.1, false);
                 // Pick the maximum dimension as scale
                 let world_scale = LayoutToWorldScale::new(scale_width.max(scale_height));
                 let mut scale = world_scale * device_pixel_scale;
-                let max_scale = get_max_scale_for_border(&border_data.border.radius,
-                                                         &border_data.widths);
+                let max_scale = get_max_scale_for_border(border_data);
                 scale.0 = scale.0.min(max_scale.0);
 
                 // For each edge and corner, request the render task by content key
                 // from the render task cache. This ensures that the render task for
                 // this segment will be available for batching later in the frame.
                 let mut handles: SmallVec<[RenderTaskCacheEntryHandle; 8]> = SmallVec::new();
 
                 for segment in &border_data.border_segments {
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/border/max-scale-ref.yaml
@@ -0,0 +1,9 @@
+---
+root:
+  items:
+    - type: border
+      bounds: [ 0, 0, 4000, 1 ]
+      width: [ 1, 0, 0, 0 ]
+      border-type: normal
+      style: solid
+      color: red
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/border/max-scale.yaml
@@ -0,0 +1,12 @@
+---
+root:
+  items:
+    - type: stacking-context
+      transform: scale(3000, 1)
+      items:
+        - type: border
+          bounds: [ 0, 0, 100, 100 ]
+          width: [ 1, 0, 0, 0 ]
+          border-type: normal
+          style: solid
+          color: red
--- a/gfx/wr/wrench/reftests/border/reftest.list
+++ b/gfx/wr/wrench/reftests/border/reftest.list
@@ -23,8 +23,9 @@ platform(linux,mac) == dotted-corner-sma
 == overlapping.yaml overlapping.png
 == zero-width.yaml blank.yaml
 platform(linux,mac) == small-dotted-border.yaml small-dotted-border.png
 == discontinued-dash.yaml discontinued-dash.png
 platform(linux,mac) == border-dashed-dotted-caching.yaml border-dashed-dotted-caching.png
 != small-inset-outset.yaml small-inset-outset-notref.yaml
 == no-aa.yaml green-square.yaml
 border-double-1px.yaml border-double-1px-ref.yaml
+== max-scale.yaml max-scale-ref.yaml
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -209,17 +209,21 @@ function namedChildFrom(sourceElement, t
 
   if (!sourceChild) {
     console.warn(
       `An element named "${childName}" wasn't found in the source.`
     );
     return textNode(translatedChild);
   }
 
-  if (sourceChild.localName !== translatedChild.localName) {
+  if (sourceChild.localName !== translatedChild.localName &&
+      // Create a specific exception for img vs. image mismatches,
+      // see bug 1543493
+      !(translatedChild.localName == "img" &&
+        sourceChild.localName == "image")) {
     console.warn(
       `An element named "${childName}" was found in the translation ` +
       `but its type ${translatedChild.localName} didn't match the ` +
       `element found in the source (${sourceChild.localName}).`
     );
     return textNode(translatedChild);
   }
 
--- a/intl/l10n/test/dom/test_domloc.xul
+++ b/intl/l10n/test/dom/test_domloc.xul
@@ -20,16 +20,17 @@
     const bundle = new FluentBundle(locales);
     bundle.addMessages(`
 file-menu =
     .label = File
     .accesskey = F
 new-tab =
     .label = New Tab
     .accesskey = N
+container = Some text with an <image data-l10n-name="foo"> inside it.
 `);
     yield bundle;
   }
 
   SimpleTest.waitForExplicitFinish();
 
 
   const domLoc = new DOMLocalization(
@@ -42,23 +43,27 @@ new-tab =
     domLoc.connectRoot(document.documentElement);
     await domLoc.translateRoots();
 
     is(document.getElementById('file-menu').getAttribute('label'), 'File');
     is(document.getElementById('file-menu').getAttribute('accesskey'), 'F');
 
     is(document.getElementById('new-tab').getAttribute('label'), 'New Tab');
     is(document.getElementById('new-tab').getAttribute('accesskey'), 'N');
+
+    ok(document.querySelector("image"),
+       "Image should still be present after localization.");
     SimpleTest.finish();
   }
 
   window.onload = foo;
 
   ]]>
   </script>
+  <description data-l10n-id="container"><image data-l10n-name="foo"/></description>
 
   <menubar id="main-menubar">
     <menu id="file-menu" data-l10n-id="file-menu">
       <menupopup id="menu_FilePopup">
         <menuitem id="new-tab" data-l10n-id="new-tab">
         </menuitem>
       </menupopup>
     </menu>
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -281,18 +281,23 @@ js_option('--enable-small-chunk-size',
           help='Allocate memory for JS GC things in smaller chunks')
 
 set_define('JS_GC_SMALL_CHUNK_SIZE',
            depends(when='--enable-small-chunk-size')(lambda: True))
 
 
 # Trace logging.
 # =======================================================
+@depends(milestone)
+def default_trace_logging(milestone):
+    return milestone.is_nightly
+
+
 js_option('--enable-trace-logging',
-          default=depends(when=moz_debug)(lambda: True),
+          default=default_trace_logging,
           help='{Enable|Disable} trace logging')
 
 set_config('ENABLE_TRACE_LOGGING',
            depends_if('--enable-trace-logging')(lambda x: True))
 set_define('JS_TRACE_LOGGING',
            depends_if('--enable-trace-logging')(lambda x: True))
 
 
--- a/js/public/TraceLoggerAPI.h
+++ b/js/public/TraceLoggerAPI.h
@@ -121,16 +121,24 @@ class TraceLoggerCollectorBuffer {
   JSContext* cx_;
   size_t length_;
   size_t dataIndex_;
   size_t bufferIndex_;
   ImplType* buffer_;
 };
 
 #ifdef JS_TRACE_LOGGING
+
+// Initialize the trace logger.  This must be called before using any of the
+// other trace logging functions.
+extern JS_PUBLIC_API bool InitTraceLogger();
+
+// Return whether the trace logger is supported in this browser session.
+extern JS_PUBLIC_API bool TraceLoggerSupported();
+
 // Begin trace logging events.  This will activate some of the
 // textId's for various events and set the global option
 // JSJITCOMPILER_ENABLE_TRACELOGGER to true.
 // This does nothing except return if the trace logger is already active.
 extern JS_PUBLIC_API void StartTraceLogger(JSContext* cx);
 
 // Stop trace logging events.  All textId's will be set to false, and the
 // global JSJITCOMPILER_ENABLE_TRACELOGGER will be set to false.
@@ -139,16 +147,18 @@ extern JS_PUBLIC_API void StopTraceLogge
 
 // Clear and free any event data that was recorded by the trace logger.
 extern JS_PUBLIC_API void ResetTraceLogger(void);
 
 #else
 // Define empty inline functions for when trace logging compilation is not
 // enabled.  TraceLogging.cpp will not be built in that case so we need to
 // provide something for any routines that reference these.
+inline bool InitTraceLogger() { return true; }
+inline bool TraceLoggerSupported() { return false; }
 inline void StartTraceLogger(JSContext* cx) {}
 inline void StopTraceLogger(JSContext* cx) {}
 inline void ResetTraceLogger(void) {}
 inline size_t TraceLoggerDictionaryImpl::NextChunk(JSContext* cx,
                                                    size_t* dataIndex,
                                                    ImplType buffer[],
                                                    size_t bufferSize) {
   return 0;
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -310,16 +310,20 @@ class MOZ_STACK_CLASS frontend::Standalo
   }
 };
 
 AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx,
                                            const TraceLoggerTextId id,
                                            const ErrorReporter& errorReporter)
 #ifdef JS_TRACE_LOGGING
     : logger_(TraceLoggerForCurrentThread(cx)) {
+  if (!logger_) {
+    return;
+  }
+
   // If the tokenizer hasn't yet gotten any tokens, use the line and column
   // numbers from CompileOptions.
   uint32_t line, column;
   if (errorReporter.hasTokenizationStarted()) {
     line = errorReporter.options().lineno;
     column = errorReporter.options().column;
   } else {
     errorReporter.currentLineAndColumn(&line, &column);
@@ -335,32 +339,40 @@ AutoFrontendTraceLog::AutoFrontendTraceL
 #endif
 
 AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx,
                                            const TraceLoggerTextId id,
                                            const ErrorReporter& errorReporter,
                                            FunctionBox* funbox)
 #ifdef JS_TRACE_LOGGING
     : logger_(TraceLoggerForCurrentThread(cx)) {
+  if (!logger_) {
+    return;
+  }
+
   frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(),
                          funbox->startLine, funbox->startColumn);
   frontendLog_.emplace(logger_, *frontendEvent_);
   typeLog_.emplace(logger_, id);
 }
 #else
 {
 }
 #endif
 
 AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx,
                                            const TraceLoggerTextId id,
                                            const ErrorReporter& errorReporter,
                                            ParseNode* pn)
 #ifdef JS_TRACE_LOGGING
     : logger_(TraceLoggerForCurrentThread(cx)) {
+  if (!logger_) {
+    return;
+  }
+
   uint32_t line, column;
   errorReporter.lineAndColumnAt(pn->pn_pos.begin, &line, &column);
   frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(),
                          line, column);
   frontendLog_.emplace(logger_, *frontendEvent_);
   typeLog_.emplace(logger_, id);
 }
 #else
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2302,17 +2302,18 @@ bool BytecodeEmitter::emitYieldOp(JSOp o
 
   uint32_t resumeIndex;
   if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) {
     return false;
   }
 
   SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex);
 
-  return emit1(JSOP_DEBUGAFTERYIELD);
+  ptrdiff_t unusedOffset;
+  return emitJumpTargetOp(JSOP_AFTERYIELD, &unusedOffset);
 }
 
 bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) {
   // ParseNodeKind::SetThis is used to update |this| after a super() call
   // in a derived class constructor.
 
   MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis));
   MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name));
--- a/js/src/jit-test/tests/debug/bug1368736.js
+++ b/js/src/jit-test/tests/debug/bug1368736.js
@@ -1,13 +1,13 @@
 g = newGlobal({newCompartment: true});
 hits = 0;
 Debugger(g).onDebuggerStatement = function(frame) {
-    // Set a breakpoint at the JSOP_DEBUGAFTERYIELD op.
-    frame.script.setBreakpoint(75, {hit: function() { hits++; }});
+    // Set a breakpoint at the JSOP_AFTERYIELD op.
+    frame.script.setBreakpoint(79, {hit: function() { hits++; }});
 }
 g.eval(`
 function* range() {
     debugger;
     for (var i = 0; i < 3; i++) {
         yield i;
     }
 }
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -321,17 +321,19 @@ MethodStatus BaselineCompiler::compile()
     baselineScript->setModifiesArguments();
   }
   if (handler.analysis().usesEnvironmentChain()) {
     baselineScript->setUsesEnvironmentChain();
   }
 
 #ifdef JS_TRACE_LOGGING
   // Initialize the tracelogger instrumentation.
-  baselineScript->initTraceLogger(script, traceLoggerToggleOffsets_);
+  if (JS::TraceLoggerSupported()) {
+    baselineScript->initTraceLogger(script, traceLoggerToggleOffsets_);
+  }
 #endif
 
   // Compute yield/await native resume addresses.
   baselineScript->computeResumeNativeOffsets(script);
 
   if (compileDebugInstrumentation()) {
     baselineScript->setHasDebugInstrumentation();
   }
@@ -403,16 +405,24 @@ static void LoadInt32OperandSignExtendTo
 
 static void LoadUint24Operand(MacroAssembler& masm, Register pc, size_t offset,
                               Register dest) {
   // Load the opcode and operand, then left shift to discard the opcode.
   masm.load32(Address(pc, offset), dest);
   masm.rshift32(Imm32(8), dest);
 }
 
+static void LoadInlineValueOperand(MacroAssembler& masm, Register pc,
+                                   ValueOperand dest) {
+  // Note: the Value might be unaligned but as above we rely on all our
+  // platforms having appropriate support for unaligned accesses (except for
+  // floating point instructions on ARM).
+  masm.loadValue(Address(pc, sizeof(jsbytecode)), dest);
+}
+
 template <>
 void BaselineCompilerCodeGen::loadScript(Register dest) {
   masm.movePtr(ImmGCPtr(handler.script()), dest);
 }
 
 template <>
 void BaselineInterpreterCodeGen::loadScript(Register dest) {
   masm.loadPtr(frame.addressOfInterpreterScript(), dest);
@@ -2166,17 +2176,19 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_DOUBLE() {
   frame.push(GET_INLINE_VALUE(handler.pc()));
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_DOUBLE() {
-  MOZ_CRASH("NYI: interpreter JSOP_DOUBLE");
+  LoadInlineValueOperand(masm, PCRegAtStart, R0);
+  frame.push(R0);
+  return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_BIGINT() {
   frame.push(handler.script()->getConst(GET_UINT32_INDEX(handler.pc())));
   return true;
 }
 
@@ -5437,17 +5449,21 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_AWAIT() {
   return emit_JSOP_YIELD();
 }
 
 template <typename Handler>
-bool BaselineCodeGen<Handler>::emit_JSOP_DEBUGAFTERYIELD() {
+bool BaselineCodeGen<Handler>::emit_JSOP_AFTERYIELD() {
+  if (!emit_JSOP_JUMPTARGET()) {
+    return false;
+  }
+
   auto ifDebuggee = [this]() {
     frame.assertSyncedStack();
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
     prepareVMCall();
     pushBytecodePCArg();
     pushArg(R0.scratchReg());
 
     using Fn = bool (*)(JSContext*, BaselineFrame*, jsbytecode*, bool*);
@@ -5514,17 +5530,17 @@ bool BaselineCompilerCodeGen::emit_JSOP_
 
   // Load the BaselineScript or call a stub if we don't have one.
   Label interpret;
   masm.loadPtr(Address(scratch1, JSScript::offsetOfBaselineScript()), scratch1);
   masm.branchPtr(Assembler::BelowOrEqual, scratch1,
                  ImmPtr(BASELINE_DISABLED_SCRIPT), &interpret);
 
 #ifdef JS_TRACE_LOGGING
-  if (!emitTraceLoggerResume(scratch1, regs)) {
+  if (JS::TraceLoggerSupported() && !emitTraceLoggerResume(scratch1, regs)) {
     return false;
   }
 #endif
 
   // Push |undefined| for all formals.
   Register scratch2 = regs.takeAny();
   Label loop, loopDone;
   masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch2);
@@ -6096,17 +6112,17 @@ bool BaselineCodeGen<Handler>::emitProlo
 
   emitInitializeLocals();
 
   if (handler.needsEarlyStackCheck()) {
     masm.bind(&earlyStackCheckFailed);
   }
 
 #ifdef JS_TRACE_LOGGING
-  if (!emitTraceLoggerEnter()) {
+  if (JS::TraceLoggerSupported() && !emitTraceLoggerEnter()) {
     return false;
   }
 #endif
 
   // Record the offset of the prologue, because Ion can bailout before
   // the env chain is initialized.
   bailoutPrologueOffset_ = CodeOffset(masm.currentOffset());
 
@@ -6151,17 +6167,17 @@ template <typename Handler>
 bool BaselineCodeGen<Handler>::emitEpilogue() {
   // Record the offset of the epilogue, so we can do early return from
   // Debugger handlers during on-stack recompile.
   debugOsrEpilogueOffset_ = CodeOffset(masm.currentOffset());
 
   masm.bind(&return_);
 
 #ifdef JS_TRACE_LOGGING
-  if (!emitTraceLoggerExit()) {
+  if (JS::TraceLoggerSupported() && !emitTraceLoggerExit()) {
     return false;
   }
 #endif
 
   masm.moveToStackPtr(BaselineFrameReg);
   masm.pop(BaselineFrameReg);
 
   emitProfilerExitFrame();
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -200,17 +200,17 @@ namespace jit {
   _(JSOP_ISNOITER)              \
   _(JSOP_ENDITER)               \
   _(JSOP_ISGENCLOSING)          \
   _(JSOP_GENERATOR)             \
   _(JSOP_INITIALYIELD)          \
   _(JSOP_YIELD)                 \
   _(JSOP_AWAIT)                 \
   _(JSOP_TRYSKIPAWAIT)          \
-  _(JSOP_DEBUGAFTERYIELD)       \
+  _(JSOP_AFTERYIELD)            \
   _(JSOP_FINALYIELDRVAL)        \
   _(JSOP_RESUME)                \
   _(JSOP_ASYNCAWAIT)            \
   _(JSOP_ASYNCRESOLVE)          \
   _(JSOP_CALLEE)                \
   _(JSOP_ENVCALLEE)             \
   _(JSOP_SUPERBASE)             \
   _(JSOP_SUPERFUN)              \
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -329,17 +329,17 @@ static void PatchBaselineFramesForDebugM
   //  J. From the warmup counter in the prologue.
   //
   // On to Off:
   //  - All the ways above.
   //  C. From the debug trap handler.
   //  D. From the debug prologue.
   //  E. From the debug epilogue.
   //  G. From GeneratorThrowOrReturn
-  //  K. From a JSOP_DEBUGAFTERYIELD instruction.
+  //  K. From a JSOP_AFTERYIELD instruction.
   //
   // Cycles (On to Off to On)+ or (Off to On to Off)+:
   //  F. Undo cases B, C, D, E, I or J above on previously patched yet unpopped
   //     frames.
   //
   // In general, we patch the return address from the VM call to return to a
   // "continuation fixer" to fix up machine state (registers and stack
   // state). Specifics on what needs to be done are documented below.
@@ -505,20 +505,19 @@ static void PatchBaselineFramesForDebugM
             recompInfo->resumeAddr = bl->debugOsrPrologueEntryAddr();
             popFrameReg = true;
             break;
 
           case RetAddrEntry::Kind::DebugAfterYield:
             // Case K above.
             //
             // Resume at the next instruction.
-            MOZ_ASSERT(*pc == JSOP_DEBUGAFTERYIELD);
-            recompInfo->resumeAddr =
-                bl->nativeCodeForPC(script, pc + JSOP_DEBUGAFTERYIELD_LENGTH,
-                                    &recompInfo->slotInfo);
+            MOZ_ASSERT(*pc == JSOP_AFTERYIELD);
+            recompInfo->resumeAddr = bl->nativeCodeForPC(
+                script, pc + JSOP_AFTERYIELD_LENGTH, &recompInfo->slotInfo);
             popFrameReg = true;
             break;
 
           default:
             // Case E above.
             //
             // We patch a jump directly to the epilogue after popping the
             // frame reg and checking for forced return.
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3676,18 +3676,20 @@ void CodeGenerator::visitReturn(LReturn*
 void CodeGenerator::visitOsrEntry(LOsrEntry* lir) {
   Register temp = ToRegister(lir->temp());
 
   // Remember the OSR entry offset into the code buffer.
   masm.flushBuffer();
   setOsrEntryOffset(masm.size());
 
 #ifdef JS_TRACE_LOGGING
-  emitTracelogStopEvent(TraceLogger_Baseline);
-  emitTracelogStartEvent(TraceLogger_IonMonkey);
+  if (JS::TraceLoggerSupported()) {
+    emitTracelogStopEvent(TraceLogger_Baseline);
+    emitTracelogStartEvent(TraceLogger_IonMonkey);
+  }
 #endif
 
   // If profiling, save the current frame pointer to a per-thread global field.
   if (isProfilerInstrumentationEnabled()) {
     masm.profilerEnterFrame(masm.getStackPointer(), temp);
   }
 
   // Allocate the full frame for this function
@@ -4683,34 +4685,38 @@ void CodeGenerator::visitCallNative(LCal
 
   // Construct native exit frame.
   uint32_t safepointOffset = masm.buildFakeExitFrame(tempReg);
   masm.enterFakeExitFrameForNative(argContextReg, tempReg,
                                    call->mir()->isConstructing());
 
   markSafepointAt(safepointOffset, call);
 
-  emitTracelogStartEvent(TraceLogger_Call);
+  if (JS::TraceLoggerSupported()) {
+    emitTracelogStartEvent(TraceLogger_Call);
+  }
 
   // Construct and execute call.
   masm.setupUnalignedABICall(tempReg);
   masm.passABIArg(argContextReg);
   masm.passABIArg(argUintNReg);
   masm.passABIArg(argVpReg);
   JSNative native = target->native();
   if (call->ignoresReturnValue() && target->hasJitInfo()) {
     const JSJitInfo* jitInfo = target->jitInfo();
     if (jitInfo->type() == JSJitInfo::IgnoresReturnValueNative) {
       native = jitInfo->ignoresReturnValueMethod;
     }
   }
   masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, native), MoveOp::GENERAL,
                    CheckUnsafeCallWithABI::DontCheckHasExitFrame);
 
-  emitTracelogStopEvent(TraceLogger_Call);
+  if (JS::TraceLoggerSupported()) {
+    emitTracelogStopEvent(TraceLogger_Call);
+  }
 
   // Test for failure.
   masm.branchIfFalseBool(ReturnReg, masm.failureLabel());
 
   if (call->mir()->maybeCrossRealm()) {
     masm.switchToRealm(gen->realm->realmPtr(), ReturnReg);
   }
 
@@ -10928,16 +10934,17 @@ bool CodeGenerator::link(JSContext* cx, 
     Assembler::PatchDataWithValueCheck(
         CodeLocationLabel(code, ionScriptLabels_[i]), ImmPtr(ionScript),
         ImmPtr((void*)-1));
   }
 
 #ifdef JS_TRACE_LOGGING
   bool TLFailed = false;
 
+  MOZ_ASSERT_IF(!JS::TraceLoggerSupported(), patchableTLEvents_.length() == 0);
   for (uint32_t i = 0; i < patchableTLEvents_.length(); i++) {
     TraceLoggerEvent event(patchableTLEvents_[i].event);
     if (!event.hasTextId() || !ionScript->addTraceLoggerEvent(event)) {
       TLFailed = true;
       break;
     }
     Assembler::PatchDataWithValueCheck(
         CodeLocationLabel(code, patchableTLEvents_[i].offset),
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2517,17 +2517,17 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
 
     // Generators / Async (bug 1317690)
     case JSOP_EXCEPTION:
     case JSOP_ISGENCLOSING:
     case JSOP_INITIALYIELD:
     case JSOP_YIELD:
     case JSOP_FINALYIELDRVAL:
     case JSOP_RESUME:
-    case JSOP_DEBUGAFTERYIELD:
+    case JSOP_AFTERYIELD:
     case JSOP_AWAIT:
     case JSOP_TRYSKIPAWAIT:
     case JSOP_GENERATOR:
     case JSOP_ASYNCAWAIT:
     case JSOP_ASYNCRESOLVE:
 
     // Misc
     case JSOP_DELNAME:
@@ -2559,17 +2559,18 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
 #ifdef DEBUG
   return abort(AbortReason::Disable, "Unsupported opcode: %s", CodeName[op]);
 #else
   return abort(AbortReason::Disable, "Unsupported opcode: %d", op);
 #endif
 }
 
 AbortReasonOr<Ok> IonBuilder::restartLoop(const CFGBlock* cfgHeader) {
-  AutoTraceLog logCompile(traceLogger(), TraceLogger_IonBuilderRestartLoop);
+  AutoTraceLog logCompile(TraceLoggerForCurrentThread(),
+                          TraceLogger_IonBuilderRestartLoop);
 
   spew("New types at loop header, restarting loop body");
 
   if (JitOptions.limitScriptSize) {
     if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS) {
       return abort(AbortReason::Disable,
                    "Aborted while processing control flow");
     }
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -1007,18 +1007,16 @@ class IonBuilder : public MIRGenerator,
   const JSAtomState& names() { return realm->runtime()->names(); }
 
   bool hadActionableAbort() const {
     MOZ_ASSERT(!actionableAbortScript_ ||
                (actionableAbortPc_ && actionableAbortMessage_));
     return actionableAbortScript_ != nullptr;
   }
 
-  TraceLoggerThread* traceLogger() { return TraceLoggerForCurrentThread(); }
-
   void actionableAbortLocationAndMessage(JSScript** abortScript,
                                          jsbytecode** abortPc,
                                          const char** abortMessage) {
     MOZ_ASSERT(hadActionableAbort());
     *abortScript = actionableAbortScript_;
     *abortPc = actionableAbortPc_;
     *abortMessage = actionableAbortMessage_;
   }
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1004,17 +1004,17 @@ bool InterpretResume(JSContext* cx, Hand
                                 UndefinedHandleValue, args, rval);
 }
 
 bool DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc,
                      bool* mustReturn) {
   // The BaselineFrame has just been constructed by JSOP_RESUME in the
   // caller. We need to set its debuggee flag as necessary.
   //
-  // If a breakpoint is set on JSOP_DEBUGAFTERYIELD, or stepping is enabled,
+  // If a breakpoint is set on JSOP_AFTERYIELD, or stepping is enabled,
   // we may already have done this work. Don't fire onEnterFrame again.
   if (frame->script()->isDebuggee() && !frame->isDebuggee()) {
     frame->setIsDebuggee();
     ResumeMode resumeMode = Debugger::onResumeFrame(cx, frame);
     return HandlePrologueResumeMode(cx, frame, pc, mustReturn, resumeMode);
   }
 
   *mustReturn = false;
@@ -1119,18 +1119,18 @@ bool HandleDebugTrap(JSContext* cx, Base
                      bool* mustReturn) {
   *mustReturn = false;
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc =
       script->baselineScript()->retAddrEntryFromReturnAddress(retAddr).pc(
           script);
 
-  if (*pc == JSOP_DEBUGAFTERYIELD) {
-    // JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag and call the
+  if (*pc == JSOP_AFTERYIELD) {
+    // JSOP_AFTERYIELD will set the frame's debuggee flag and call the
     // onEnterFrame handler, but if we set a breakpoint there we have to do
     // it now.
     MOZ_ASSERT(!frame->isDebuggee());
 
     if (!DebugAfterYield(cx, frame, pc, mustReturn)) {
       return false;
     }
     if (*mustReturn) {
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -128,25 +128,30 @@ bool CodeGeneratorShared::generateProlog
 
   // Ensure that the Ion frame is properly aligned.
   masm.assertStackAlignment(JitStackAlignment, 0);
 
   // Note that this automatically sets MacroAssembler::framePushed().
   masm.reserveStack(frameSize());
   masm.checkStackAlignment();
 
-  emitTracelogIonStart();
+  if (JS::TraceLoggerSupported()) {
+    emitTracelogIonStart();
+  }
+
   return true;
 }
 
 bool CodeGeneratorShared::generateEpilogue() {
   MOZ_ASSERT(!gen->compilingWasm());
   masm.bind(&returnLabel_);
 
-  emitTracelogIonStop();
+  if (JS::TraceLoggerSupported()) {
+    emitTracelogIonStop();
+  }
 
   masm.freeStack(frameSize());
   MOZ_ASSERT(masm.framePushed() == 0);
 
   // If profiling, reset the per-thread global lastJitFrame to point to
   // the previous frame.
   if (isProfilerInstrumentationEnabled()) {
     masm.profilerExitFrame();
--- a/js/src/vm/BytecodeUtil.h
+++ b/js/src/vm/BytecodeUtil.h
@@ -355,16 +355,17 @@ static inline bool BytecodeFallsThrough(
   }
 }
 
 static inline bool BytecodeIsJumpTarget(JSOp op) {
   switch (op) {
     case JSOP_JUMPTARGET:
     case JSOP_LOOPHEAD:
     case JSOP_LOOPENTRY:
+    case JSOP_AFTERYIELD:
       return true;
     default:
       return false;
   }
 }
 
 MOZ_ALWAYS_INLINE unsigned StackUses(jsbytecode* pc) {
   JSOp op = JSOp(*pc);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2054,17 +2054,17 @@ ResumeMode Debugger::fireEnterFrame(JSCo
   MOZ_ASSERT(hook->isCallable());
 
   RootedValue scriptFrame(cx);
 
   FrameIter iter(cx);
 
 #if DEBUG
   // Assert that the hook won't be able to re-enter the generator.
-  if (iter.hasScript() && *iter.pc() == JSOP_DEBUGAFTERYIELD) {
+  if (iter.hasScript() && *iter.pc() == JSOP_AFTERYIELD) {
     auto* genObj = GetGeneratorObjectForFrame(cx, iter.abstractFramePtr());
     MOZ_ASSERT(genObj->isRunning() || genObj->isClosing());
   }
 #endif
 
   Maybe<AutoRealm> ar;
   ar.emplace(cx, object);
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -1015,17 +1015,17 @@ class Debugger : private mozilla::Linked
    * suspended earlier.
    *
    * There is no separate user-visible Debugger.onResumeFrame hook; this
    * fires .onEnterFrame (again, since we're re-entering the frame).
    *
    * Unfortunately, the interpreter and the baseline JIT arrange for this to
    * be called in different ways. The interpreter calls it from JSOP_RESUME,
    * immediately after pushing the resumed frame; the JIT calls it from
-   * JSOP_DEBUGAFTERYIELD, just after the generator resumes. The difference
+   * JSOP_AFTERYIELD, just after the generator resumes. The difference
    * should not be user-visible.
    */
   static inline ResumeMode onResumeFrame(JSContext* cx, AbstractFramePtr frame);
 
   /*
    * Announce to the debugger a |debugger;| statement on has been
    * encountered on the youngest JS frame on |cx|. Call whatever hooks have
    * been registered to observe this.
--- a/js/src/vm/GeneratorObject.cpp
+++ b/js/src/vm/GeneratorObject.cpp
@@ -310,17 +310,17 @@ bool AbstractGeneratorObject::isAfterAwa
 bool AbstractGeneratorObject::isAfterYieldOrAwait(JSOp op) {
   if (isClosed() || isClosing() || isRunning()) {
     return false;
   }
 
   JSScript* script = callee().nonLazyScript();
   jsbytecode* code = script->code();
   uint32_t nextOffset = script->resumeOffsets()[resumeIndex()];
-  if (code[nextOffset] != JSOP_DEBUGAFTERYIELD) {
+  if (code[nextOffset] != JSOP_AFTERYIELD) {
     return false;
   }
 
   static_assert(JSOP_YIELD_LENGTH == JSOP_INITIALYIELD_LENGTH,
                 "JSOP_YIELD and JSOP_INITIALYIELD must have the same length");
   static_assert(JSOP_YIELD_LENGTH == JSOP_AWAIT_LENGTH,
                 "JSOP_YIELD and JSOP_AWAIT must have the same length");
 
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -146,16 +146,20 @@ JS_PUBLIC_API const char* JS::detail::In
   RETURN_IF_FAIL(js::CreateHelperThreadsState());
   RETURN_IF_FAIL(FutexThread::initialize());
   RETURN_IF_FAIL(js::gcstats::Statistics::initialize());
 
 #ifdef JS_SIMULATOR
   RETURN_IF_FAIL(js::jit::SimulatorProcess::initialize());
 #endif
 
+#ifdef JS_TRACE_LOGGING
+  RETURN_IF_FAIL(JS::InitTraceLogger());
+#endif
+
   libraryInitState = InitState::Running;
   return nullptr;
 }
 
 #undef RETURN_IF_FAIL
 
 JS_PUBLIC_API void JS_ShutDown(void) {
   MOZ_ASSERT(
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4117,22 +4117,23 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
             goto error;
           default:
             MOZ_CRASH("bad resumeKind");
         }
       }
       ADVANCE_AND_DISPATCH(0);
     }
 
-    CASE(JSOP_DEBUGAFTERYIELD) {
-      // No-op in the interpreter, as AbstractGeneratorObject::resume takes care
-      // of fixing up InterpreterFrames.
+    CASE(JSOP_AFTERYIELD) {
+      // AbstractGeneratorObject::resume takes care of setting the frame's
+      // debuggee flag.
       MOZ_ASSERT_IF(REGS.fp()->script()->isDebuggee(), REGS.fp()->isDebuggee());
-    }
-    END_CASE(JSOP_DEBUGAFTERYIELD)
+      COUNT_COVERAGE();
+    }
+    END_CASE(JSOP_AFTERYIELD)
 
     CASE(JSOP_FINALYIELDRVAL) {
       ReservedRooted<JSObject*> gen(&rootObject0, &REGS.sp[-1].toObject());
       REGS.sp--;
       AbstractGeneratorObject::finalSuspend(gen);
       goto successful_return_continuation;
     }
 
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -519,18 +519,18 @@ static bool Snapshot(JSContext* cx, Hand
    *
    * We don't do this in the general case because (a) doing so is slow,
    * and (b) it also breaks the web, which expects enumeration order to
    * follow the order in which properties are added, in certain cases.
    * Since ECMA does not specify an enumeration order for objects, both
    * behaviors are technically correct to do.
    */
 
-  jsid* ids = props->begin();
-  size_t n = props->length();
+  jsid* ids = props.begin();
+  size_t n = props.length();
 
   RootedIdVector tmp(cx);
   if (!tmp.resize(n)) {
     return false;
   }
   PodCopy(tmp.begin(), ids, n);
 
   if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx))) {
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -1341,27 +1341,30 @@ struct ObjectGroupRealm::AllocationSiteK
 
   void operator=(AllocationSiteKey&& key) {
     script = std::move(key.script);
     offset = key.offset;
     kind = key.kind;
     proto = std::move(key.proto);
   }
 
-  static inline uint32_t hash(AllocationSiteKey key) {
-    return uint32_t(
-        size_t(key.script.unbarrieredGet()->offsetToPC(key.offset)) ^ key.kind ^
-        MovableCellHasher<JSObject*>::hash(key.proto.unbarrieredGet()));
+  static inline HashNumber hash(const AllocationSiteKey& key) {
+    JSScript* script = key.script.unbarrieredGet();
+    JSObject* proto = key.proto.unbarrieredGet();
+    HashNumber hash = mozilla::HashGeneric(key.offset, key.kind);
+    hash = mozilla::AddToHash(hash, MovableCellHasher<JSScript*>::hash(script));
+    hash = mozilla::AddToHash(hash, MovableCellHasher<JSObject*>::hash(proto));
+    return hash;
   }
 
   static inline bool match(const AllocationSiteKey& a,
                            const AllocationSiteKey& b) {
-    return DefaultHasher<JSScript*>::match(a.script.unbarrieredGet(),
-                                           b.script.unbarrieredGet()) &&
-           a.offset == b.offset && a.kind == b.kind &&
+    return a.offset == b.offset && a.kind == b.kind &&
+           MovableCellHasher<JSScript*>::match(a.script.unbarrieredGet(),
+                                               b.script.unbarrieredGet()) &&
            MovableCellHasher<JSObject*>::match(a.proto, b.proto);
   }
 
   void trace(JSTracer* trc) {
     TraceRoot(trc, &script, "AllocationSiteKey script");
     TraceNullableRoot(trc, &proto, "AllocationSiteKey proto");
   }
 
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2240,25 +2240,27 @@
      * by the JITs so the script always runs in the interpreter.
      *
      *   Category: Other
      *   Operands:
      *   Stack: =>
      */ \
     MACRO(JSOP_FORCEINTERPRETER, 207, "forceinterpreter", NULL, 1, 0, 0, JOF_BYTE) \
     /*
-     * Bytecode emitted after 'yield' expressions to help the Debugger fix up
-     * the frame in the JITs. No-op in the interpreter.
+     * Bytecode emitted after 'yield' expressions. This is useful for the
+     * Debugger and AbstractGeneratorObject::isAfterYieldOrAwait. It's treated
+     * as jump target op so that the Baseline Interpreter can efficiently
+     * restore the frame's interpreterICEntry when resuming a generator.
      *
-     *   Category: Operators
-     *   Type: Debugger
-     *   Operands:
+     *   Category: Statements
+     *   Type: Generator
+     *   Operands: uint32_t icIndex
      *   Stack: =>
      */ \
-    MACRO(JSOP_DEBUGAFTERYIELD, 208, "debugafteryield", NULL, 1, 0, 0, JOF_BYTE) \
+    MACRO(JSOP_AFTERYIELD, 208, "afteryield", NULL, 5, 0, 0, JOF_ICINDEX) \
     /*
      * Pops the generator and the return value 'promise', stops interpretation
      * and returns 'promise'. Pushes resolved value onto the stack.
      *
      *   Category: Statements
      *   Type: Generator
      *   Operands: uint24_t resumeIndex
      *   Stack: promise, gen => resolved
--- a/js/src/vm/TraceLogging.cpp
+++ b/js/src/vm/TraceLogging.cpp
@@ -23,34 +23,39 @@
 #include "vm/Runtime.h"
 #include "vm/Time.h"
 #include "vm/TraceLoggingGraph.h"
 
 #include "jit/JitFrames-inl.h"
 
 using namespace js;
 
-TraceLoggerThreadState* traceLoggerState = nullptr;
+static TraceLoggerThreadState* traceLoggerState = nullptr;
+
+static bool getTraceLoggerSupported() {
+  char* str = getenv("JS_TRACE_LOGGING");
 
-static bool EnsureTraceLoggerState() {
-  if (MOZ_LIKELY(traceLoggerState)) {
+  if (!str) {
+    // Default to unsupported.
+    return false;
+  }
+
+  if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0 ||
+      strcmp(str, "0") == 0) {
+    return false;
+  }
+
+  if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0 ||
+      strcmp(str, "1") == 0) {
     return true;
   }
 
-  traceLoggerState = js_new<TraceLoggerThreadState>();
-  if (!traceLoggerState) {
-    return false;
-  }
-
-  if (!traceLoggerState->init()) {
-    DestroyTraceLoggerThreadState();
-    return false;
-  }
-
-  return true;
+  fprintf(stderr, "Warning: I didn't understand JS_TRACE_LOGGING=\"%s\"\n",
+          str);
+  return false;
 }
 
 size_t js::SizeOfTraceLogState(mozilla::MallocSizeOf mallocSizeOf) {
   return traceLoggerState ? traceLoggerState->sizeOfIncludingThis(mallocSizeOf)
                           : 0;
 }
 
 void js::ResetTraceLogger() {
@@ -70,19 +75,17 @@ void js::DestroyTraceLoggerThreadState()
 
 #ifdef DEBUG
 bool js::CurrentThreadOwnsTraceLoggerThreadStateLock() {
   return traceLoggerState && traceLoggerState->lock.ownedByCurrentThread();
 }
 #endif
 
 void js::DestroyTraceLogger(TraceLoggerThread* logger) {
-  if (!EnsureTraceLoggerState()) {
-    return;
-  }
+  MOZ_ASSERT(traceLoggerState);
   traceLoggerState->destroyLogger(logger);
 }
 
 bool TraceLoggerThread::init() {
   if (!events.init()) {
     return false;
   }
 
@@ -1275,17 +1278,17 @@ void TraceLoggerThreadState::disableText
     jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), false);
   }
   if (textId == TraceLogger_Engine) {
     jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), false);
   }
 }
 
 TraceLoggerThread* js::TraceLoggerForCurrentThread(JSContext* maybecx) {
-  if (!EnsureTraceLoggerState()) {
+  if (!traceLoggerState) {
     return nullptr;
   }
   return traceLoggerState->forCurrentThread(maybecx);
 }
 
 TraceLoggerThread* TraceLoggerThreadState::forCurrentThread(
     JSContext* maybecx) {
   if (!jit::JitOptions.enableTraceLogger) {
@@ -1332,30 +1335,30 @@ void TraceLoggerThreadState::destroyLogg
   MOZ_ASSERT(logger);
   LockGuard<Mutex> guard(lock);
 
   logger->remove();
   js_delete(logger);
 }
 
 bool js::TraceLogTextIdEnabled(uint32_t textId) {
-  if (!EnsureTraceLoggerState()) {
+  if (!traceLoggerState) {
     return false;
   }
   return traceLoggerState->isTextIdEnabled(textId);
 }
 
 void js::TraceLogEnableTextId(JSContext* cx, uint32_t textId) {
-  if (!EnsureTraceLoggerState()) {
+  if (!traceLoggerState) {
     return;
   }
   traceLoggerState->enableTextId(cx, textId);
 }
 void js::TraceLogDisableTextId(JSContext* cx, uint32_t textId) {
-  if (!EnsureTraceLoggerState()) {
+  if (!traceLoggerState) {
     return;
   }
   traceLoggerState->disableTextId(cx, textId);
 }
 
 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, JSScript* script)
     : TraceLoggerEvent(type, script->filename(), script->lineno(),
                        script->column()) {}
@@ -1417,20 +1420,42 @@ TraceLoggerEvent& TraceLoggerEvent::oper
 
 TraceLoggerEvent::TraceLoggerEvent(const TraceLoggerEvent& other)
     : payload_(other.payload_) {
   if (hasExtPayload()) {
     extPayload()->use();
   }
 }
 
+JS_PUBLIC_API bool JS::InitTraceLogger() {
+  MOZ_RELEASE_ASSERT(!traceLoggerState);
+
+  if (!getTraceLoggerSupported()) {
+    return true;
+  }
+
+  traceLoggerState = js_new<TraceLoggerThreadState>();
+  if (!traceLoggerState) {
+    return false;
+  }
+
+  if (!traceLoggerState->init()) {
+    DestroyTraceLoggerThreadState();
+    return false;
+  }
+
+  return true;
+}
+
+JS_PUBLIC_API bool JS::TraceLoggerSupported() { return traceLoggerState; }
+
 JS_PUBLIC_API void JS::ResetTraceLogger(void) { js::ResetTraceLogger(); }
 
 JS_PUBLIC_API void JS::StartTraceLogger(JSContext* cx) {
-  if (!EnsureTraceLoggerState()) {
+  if (!traceLoggerState) {
     return;
   }
 
   if (!jit::JitOptions.enableTraceLogger) {
     LockGuard<Mutex> guard(traceLoggerState->lock);
     traceLoggerState->enableTextIdsForProfiler();
     jit::JitOptions.enableTraceLogger = true;
   }
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3470,17 +3470,17 @@ void js::TypeMonitorResult(JSContext* cx
 void js::TypeMonitorResult(JSContext* cx, JSScript* script, jsbytecode* pc,
                            const js::Value& rval) {
   // Allow the non-TYPESET scenario to simplify stubs used in compound
   // opcodes.
   if (!(CodeSpec[*pc].format & JOF_TYPESET)) {
     return;
   }
 
-  if (!script->hasBaselineScript()) {
+  if (!script->types()) {
     return;
   }
 
   TypeMonitorResult(cx, script, pc, TypeSet::GetValueType(rval));
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeScript
--- a/js/xpconnect/src/BackstagePass.h
+++ b/js/xpconnect/src/BackstagePass.h
@@ -24,16 +24,20 @@ class BackstagePass : public nsIGlobalOb
                       public nsSupportsWeakReference {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIXPCSCRIPTABLE
   NS_DECL_NSICLASSINFO
 
   virtual nsIPrincipal* GetPrincipal() override { return mPrincipal; }
 
+  virtual nsIPrincipal* GetEffectiveStoragePrincipal() override {
+    return nullptr;
+  }
+
   virtual JSObject* GetGlobalJSObject() override;
 
   void ForgetGlobalObject() { mWrapper = nullptr; }
 
   void SetGlobalObject(JSObject* global);
 
   explicit BackstagePass(nsIPrincipal* prin)
       : mPrincipal(prin), mWrapper(nullptr) {}
--- a/js/xpconnect/src/SandboxPrivate.h
+++ b/js/xpconnect/src/SandboxPrivate.h
@@ -40,16 +40,18 @@ class SandboxPrivate : public nsIGlobalO
   static SandboxPrivate* GetPrivate(JSObject* obj) {
     // The type used to cast to void needs to match the one in Create.
     return static_cast<SandboxPrivate*>(
         static_cast<nsIScriptObjectPrincipal*>(JS_GetPrivate(obj)));
   }
 
   nsIPrincipal* GetPrincipal() override { return mPrincipal; }
 
+  nsIPrincipal* GetEffectiveStoragePrincipal() override { return nullptr; }
+
   JSObject* GetGlobalJSObject() override { return GetWrapper(); }
 
   void ForgetGlobalObject(JSObject* obj) { ClearWrapper(obj); }
 
   virtual JSObject* WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> aGivenProto) override {
     MOZ_CRASH("SandboxPrivate doesn't use DOM bindings!");
   }
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -14,28 +14,28 @@
 #include "js/Proxy.h"
 #include "js/Wrapper.h"
 
 #include "nsAtom.h"
 #include "nsISupports.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsIGlobalObject.h"
-#include "nsPIDOMWindow.h"
 #include "nsWrapperCache.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "mozilla/dom/JSSlots.h"
 #include "mozilla/fallible.h"
 #include "nsMathUtils.h"
 #include "nsStringBuffer.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/Preferences.h"
 
 class nsGlobalWindowInner;
+class nsIGlobalObject;
 class nsIPrincipal;
 class nsIHandleReportCallback;
 struct nsXPTInterfaceInfo;
 
 namespace mozilla {
 class BasePrincipal;
 
 namespace dom {
--- a/layout/build/nsContentDLF.cpp
+++ b/layout/build/nsContentDLF.cpp
@@ -272,17 +272,17 @@ already_AddRefed<Document> nsContentDLF:
   }
 
   // initialize
   nsCOMPtr<nsIURI> uri;
   NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("about:blank"));
   if (!uri) {
     return nullptr;
   }
-  blankDoc->ResetToURI(uri, aLoadGroup, aPrincipal);
+  blankDoc->ResetToURI(uri, aLoadGroup, aPrincipal, aPrincipal);
   blankDoc->SetContainer(aContainer);
 
   // add some simple content structure
   nsNodeInfoManager* nim = blankDoc->NodeInfoManager();
 
   RefPtr<mozilla::dom::NodeInfo> htmlNodeInfo;
 
   // generate an html html element
--- a/layout/forms/nsNumberControlFrame.cpp
+++ b/layout/forms/nsNumberControlFrame.cpp
@@ -284,17 +284,20 @@ class FocusTextField : public Runnable {
  public:
   FocusTextField(nsIContent* aNumber, nsIContent* aTextField)
       : mozilla::Runnable("FocusTextField"),
         mNumber(aNumber),
         mTextField(aTextField) {}
 
   NS_IMETHOD Run() override {
     if (mNumber->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
-      HTMLInputElement::FromNode(mTextField)->Focus(IgnoreErrors());
+      // This job shouldn't be triggered by a WebIDL interface, hence the
+      // default options can be used.
+      FocusOptions options;
+      HTMLInputElement::FromNode(mTextField)->Focus(options, IgnoreErrors());
     }
 
     return NS_OK;
   }
 
  private:
   nsCOMPtr<nsIContent> mNumber;
   nsCOMPtr<nsIContent> mTextField;
@@ -527,17 +530,21 @@ bool nsNumberControlFrame::IsFocused() c
   return mTextField->State().HasState(NS_EVENT_STATE_FOCUS) ||
          mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
 }
 
 void nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent) {
   if (aEvent->mOriginalTarget != mTextField) {
     // Move focus to our text field
     RefPtr<HTMLInputElement> textField = HTMLInputElement::FromNode(mTextField);
-    textField->Focus(IgnoreErrors());
+
+    // Use default FocusOptions, because this method isn't supposed to be called
+    // from a WebIDL interface.
+    FocusOptions options;
+    textField->Focus(options, IgnoreErrors());
   }
 }
 
 void nsNumberControlFrame::HandleSelectCall() {
   RefPtr<HTMLInputElement> textField = HTMLInputElement::FromNode(mTextField);
   textField->Select();
 }
 
--- a/layout/style/CSSFontFaceRule.cpp
+++ b/layout/style/CSSFontFaceRule.cpp
@@ -51,17 +51,20 @@ void CSSFontFaceRuleDecl::SetCssText(con
                                      ErrorResult& aRv) {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);  // bug 443978
 }
 
 NS_IMETHODIMP
 CSSFontFaceRuleDecl::GetPropertyValue(const nsAString& aPropName,
                                       nsAString& aResult) {
   aResult.Truncate();
-  GetPropertyValue(nsCSSProps::LookupFontDesc(aPropName), aResult);
+  nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(aPropName);
+  if (descID != eCSSFontDesc_UNKNOWN) {
+    GetPropertyValue(descID, aResult);
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CSSFontFaceRuleDecl::RemoveProperty(const nsAString& aPropName,
                                     nsAString& aResult) {
   nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(aPropName);
   NS_ASSERTION(descID >= eCSSFontDesc_UNKNOWN && descID < eCSSFontDesc_COUNT,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -94,33 +94,43 @@ already_AddRefed<CSSValue> GetBackground
         nsCSSProps::ValueToKeywordEnum(aLayers.mLayers[i].*aMember, aTable));
     valueList->AppendCSSValue(val.forget());
   }
 
   return valueList.forget();
 }
 
 // Whether aDocument needs to restyle for aElement
-static bool DocumentNeedsRestyle(const Document* aDocument, Element* aElement,
-                                 nsAtom* aPseudo) {
-  PresShell* presShell = aDocument->GetPresShell();
+static bool ElementNeedsRestyle(Element* aElement, nsAtom* aPseudo) {
+  const Document* doc = aElement->GetComposedDoc();
+  if (!doc) {
+    // If the element is out of the document we don't return styles for it, so
+    // nothing to do.
+    return false;
+  }
+
+  PresShell* presShell = doc->GetPresShell();
   if (!presShell) {
-    return true;
+    // If there's no pres-shell we'll just either mint a new style from our
+    // caller document, or return no styles, so nothing to do (unless our owner
+    // element needs to get restyled, which could cause us to gain a pres shell,
+    // but the caller checks that).
+    return false;
   }
 
-  nsPresContext* presContext = presShell->GetPresContext();
-  MOZ_ASSERT(presContext);
-
   // Unfortunately we don't know if the sheet change affects mElement or not, so
   // just assume it will and that we need to flush normally.
   ServoStyleSet* styleSet = presShell->StyleSet();
   if (styleSet->StyleSheetsHaveChanged()) {
     return true;
   }
 
+  nsPresContext* presContext = presShell->GetPresContext();
+  MOZ_ASSERT(presContext);
+
   // Pending media query updates can definitely change style on the element. For
   // example, if you change the zoom factor and then call getComputedStyle, you
   // should be able to observe the style with the new media queries.
   //
   // TODO(emilio): Does this need to also check the user font set? (it affects
   // ch / ex units).
   if (presContext->HasPendingMediaQueryUpdates()) {
     // So gotta flush.
@@ -146,17 +156,17 @@ static bool DocumentNeedsRestyle(const D
 
   // For Servo, we need to process the restyle-hint-invalidations first, to
   // expand LaterSiblings hint, so that we can look whether ancestors need
   // restyling.
   RestyleManager* restyleManager = presContext->RestyleManager();
   restyleManager->ProcessAllPendingAttributeAndStateInvalidations();
 
   if (!presContext->EffectCompositor()->HasPendingStyleUpdates() &&
-      !aDocument->GetServoRestyleRoot()) {
+      !doc->GetServoRestyleRoot()) {
     return false;
   }
 
   // Then if there is a restyle root, we check if the root is an ancestor of
   // this content. If it is not, then we don't need to restyle immediately.
   // Note this is different from Gecko: we only check if any ancestor needs
   // to restyle _itself_, not descendants, since dirty descendants can be
   // another subtree.
@@ -760,39 +770,31 @@ void nsComputedDOMStyle::SetResolvedComp
 void nsComputedDOMStyle::SetFrameComputedStyle(mozilla::ComputedStyle* aStyle,
                                                uint64_t aGeneration) {
   ClearComputedStyle();
   mComputedStyle = aStyle;
   mComputedStyleGeneration = aGeneration;
   mPresShellId = mPresShell->GetPresShellId();
 }
 
-bool nsComputedDOMStyle::NeedsToFlush(Document* aDocument) const {
-  // If mElement is not in the same document, we could do some checks to know if
-  // there are some pending restyles can be ignored across documents (since we
-  // will use the caller document's style), but it can be complicated and should
-  // be an edge case, so we just don't bother to do the optimization in this
-  // case.
-  //
-  // FIXME(emilio): This is likely to want GetComposedDoc() instead of
-  // OwnerDoc().
-  if (aDocument != mElement->OwnerDoc()) {
+bool nsComputedDOMStyle::NeedsToFlush() const {
+  // We always compute styles from the element's owner document.
+  if (ElementNeedsRestyle(mElement, mPseudo)) {
     return true;
   }
-  if (DocumentNeedsRestyle(aDocument, mElement, mPseudo)) {
-    return true;
-  }
+
+  Document* doc = mElement->OwnerDoc();
   // If parent document is there, also needs to check if there is some change
   // that needs to flush this document (e.g. size change for iframe).
-  while (Document* parentDocument = aDocument->GetParentDocument()) {
-    Element* element = parentDocument->FindContentForSubDocument(aDocument);
-    if (DocumentNeedsRestyle(parentDocument, element, nullptr)) {
+  while (Document* parentDocument = doc->GetParentDocument()) {
+    Element* element = parentDocument->FindContentForSubDocument(doc);
+    if (ElementNeedsRestyle(element, nullptr)) {
       return true;
     }
-    aDocument = parentDocument;
+    doc = parentDocument;
   }
 
   return false;
 }
 
 void nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) {
   nsCOMPtr<Document> document = do_QueryReferent(mDocumentWeak);
   if (!document) {
@@ -801,17 +803,17 @@ void nsComputedDOMStyle::UpdateCurrentSt
   }
 
   // TODO(emilio): We may want to handle a few special-cases here:
   //
   //  * https://github.com/w3c/csswg-drafts/issues/1964
   //  * https://github.com/w3c/csswg-drafts/issues/1548
 
   // If the property we are computing relies on layout, then we must flush.
-  const bool needsToFlush = aNeedsLayoutFlush || NeedsToFlush(document);
+  const bool needsToFlush = aNeedsLayoutFlush || NeedsToFlush();
   if (needsToFlush) {
     // Flush _before_ getting the presshell, since that could create a new
     // presshell.  Also note that we want to flush the style on the document
     // we're computing style in, not on the document mElement is in -- the two
     // may be different.
     document->FlushPendingNotifications(aNeedsLayoutFlush ? FlushType::Layout
                                                           : FlushType::Style);
   }
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -278,17 +278,16 @@ class nsComputedDOMStyle final : public 
   /* Display properties */
   already_AddRefed<CSSValue> DoGetTransform();
   already_AddRefed<CSSValue> DoGetTransformOrigin();
   already_AddRefed<CSSValue> DoGetPerspectiveOrigin();
 
   /* Column properties */
   already_AddRefed<CSSValue> DoGetColumnRuleWidth();
 
-
   // For working around a MSVC bug. See related comment in
   // GenerateComputedDOMStyleGenerated.py.
   already_AddRefed<CSSValue> DummyGetter();
 
   /* Helper functions */
   void SetValueFromComplexColor(nsROCSSPrimitiveValue* aValue,
                                 const mozilla::StyleColor& aColor);
   void SetValueToPosition(const mozilla::Position& aPosition,
@@ -385,19 +384,19 @@ class nsComputedDOMStyle final : public 
   already_AddRefed<CSSValue> CreatePrimitiveValueForBasicShape(
       const mozilla::UniquePtr<mozilla::StyleBasicShape>& aStyleBasicShape);
   void BoxValuesToString(nsAString& aString,
                          const nsTArray<nsStyleCoord>& aBoxValues,
                          bool aClampNegativeCalc);
   void BasicShapeRadiiToString(nsAString& aCssText,
                                const mozilla::BorderRadius&);
 
-  // Find out if we can safely skip flushing for aDocument (i.e. pending
-  // restyles does not affect mContent).
-  bool NeedsToFlush(Document*) const;
+  // Find out if we can safely skip flushing (i.e. pending restyles do not
+  // affect mElement).
+  bool NeedsToFlush() const;
 
   static ComputedStyleMap* GetComputedStyleMap();
 
   // We don't really have a good immutable representation of "presentation".
   // Given the way GetComputedStyle is currently used, we should just grab the
   // presshell, if any, from the document.
   nsWeakPtr mDocumentWeak;
   RefPtr<Element> mElement;
--- a/layout/style/test/test_font_face_parser.html
+++ b/layout/style/test/test_font_face_parser.html
@@ -327,16 +327,18 @@ function runTest() {
            testset[curTest].rule + " rule count");
            is(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/,
            testset[curTest].rule + " rule type");
 
         var d = testset[curTest].d;
         var s = sheet.cssRules[0].style;
         var n = 0;
 
+        is(s.getPropertyValue("pointless"), "", "Unknown descriptors don't assert");
+
         // everything is set that should be
         for (var name in d) {
           is(s.getPropertyValue(name), d[name],
              testset[curTest].rule + " (prop " + name + ")");
           n++;
         }
         // nothing else is set
         is(s.length, n, testset[curTest].rule + "prop count");
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2228,16 +2228,22 @@ VARCACHE_PREF(
 )
 
 VARCACHE_PREF(
   "privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts",
    privacy_resistFingerprinting_autoDeclineNoUserInputCanvasPrompts,
   RelaxedAtomicBool, false
 )
 
+VARCACHE_PREF(
+  "privacy.storagePrincipal.enabledForTrackers",
+   privacy_storagePrincipal_enabledForTrackers,
+  RelaxedAtomicBool, false
+)
+
 // Password protection
 VARCACHE_PREF(
   "browser.safebrowsing.passwords.enabled",
    browser_safebrowsing_passwords_enabled,
   bool, false
 )
 
 // Malware protection
--- a/moz.configure
+++ b/moz.configure
@@ -315,25 +315,26 @@ def check_objdir_backend_reuse(build_env
                     build_env.topobjdir, prev)
 
 
 option('--disable-gtest-in-build',
        help='Force disable building the gtest libxul during the build.',
        when='--enable-compile-environment')
 
 # Determine whether to build the gtest xul. This happens in automation
-# on Android and Desktop platforms with the exception of Windows PGO, where
-# linking xul-gtest.dll takes too long.
+# on Android and Desktop platforms with the exception of:
+#  - Windows PGO, where linking xul-gtest.dll takes too long;
+#  - Android other than x86_64, where gtest is not required.
 @depends('MOZ_PGO', build_project, target, 'MOZ_AUTOMATION', '--disable-gtest-in-build',
          enable_tests, when='--enable-compile-environment')
 def build_gtest(pgo, build_project, target, automation, enabled, enable_tests):
     if not enable_tests or not enabled:
         return None
     if (automation and build_project in ('browser', 'mobile/android') and
-        not (pgo and target.os == 'WINNT')):
+        not ((pgo and target.os == 'WINNT') or (target.os == 'Android' and target.cpu != 'x86_64'))):
         return True
 
 set_config('LINK_GTEST_DURING_COMPILE', build_gtest)
 
 # Localization
 # ==============================================================
 option('--enable-ui-locale', default='en-US',
        help='Select the user interface locale (default: en-US)')
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -184,16 +184,22 @@ LoadInfo::LoadInfo(
           mTopLevelStorageAreaPrincipal =
               innerWindow->GetTopLevelStorageAreaPrincipal();
         } else if (contextOuter->IsTopLevelWindow()) {
           Document* doc = innerWindow->GetExtantDoc();
           if (!doc || (!doc->StorageAccessSandboxed() &&
                        !nsContentUtils::IsInPrivateBrowsing(doc))) {
             mTopLevelStorageAreaPrincipal = innerWindow->GetPrincipal();
           }
+
+          // If this is the first level iframe, innerWindow is our top-level
+          // principal.
+          if (!mTopLevelPrincipal) {
+            mTopLevelPrincipal = innerWindow->GetPrincipal();
+          }
         }
 
         mDocumentHasLoaded = innerWindow->IsDocumentLoaded();
 
         if (innerWindow->IsFrame()) {
           // For resources within iframes, we actually want the
           // top-level document's flag, not the iframe document's.
           mDocumentHasLoaded = false;
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -11,16 +11,17 @@
 
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Monitor.h"
+#include "mozilla/StoragePrincipalHelper.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/Telemetry.h"
 #include "nsCategoryCache.h"
 #include "nsContentUtils.h"
 #include "nsFileStreams.h"
 #include "nsHashKeys.h"
 #include "nsHttp.h"
 #include "nsIAsyncStreamCopier.h"
@@ -1846,38 +1847,43 @@ nsresult NS_LoadPersistentPropertiesFrom
   NS_ENSURE_SUCCESS(rv, rv);
 
   properties.swap(*outResult);
   return NS_OK;
 }
 
 bool NS_UsePrivateBrowsing(nsIChannel *channel) {
   OriginAttributes attrs;
-  bool result = NS_GetOriginAttributes(channel, attrs);
+  bool result = NS_GetOriginAttributes(channel, attrs, false);
   NS_ENSURE_TRUE(result, result);
   return attrs.mPrivateBrowsingId > 0;
 }
 
 bool NS_GetOriginAttributes(nsIChannel *aChannel,
-                            mozilla::OriginAttributes &aAttributes) {
+                            mozilla::OriginAttributes &aAttributes,
+                            bool aUsingStoragePrincipal) {
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
   loadInfo->GetOriginAttributes(&aAttributes);
 
   bool isPrivate = false;
   nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
   if (pbChannel) {
     nsresult rv = pbChannel->GetIsChannelPrivate(&isPrivate);
     NS_ENSURE_SUCCESS(rv, false);
   } else {
     // Some channels may not implement nsIPrivateBrowsingChannel
     nsCOMPtr<nsILoadContext> loadContext;
     NS_QueryNotificationCallbacks(aChannel, loadContext);
     isPrivate = loadContext && loadContext->UsePrivateBrowsing();
   }
   aAttributes.SyncAttributesWithPrivateBrowsing(isPrivate);
+
+  if (aUsingStoragePrincipal) {
+    StoragePrincipalHelper::PrepareOriginAttributes(aChannel, aAttributes);
+  }
   return true;
 }
 
 bool NS_HasBeenCrossOrigin(nsIChannel *aChannel, bool aReport) {
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
   // TYPE_DOCUMENT loads have a null LoadingPrincipal and can not be cross
   // origin.
   if (!loadInfo->LoadingPrincipal()) {
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -605,19 +605,22 @@ inline void NS_QueryNotificationCallback
 /**
  * Returns true if channel is using Private Browsing, or false if not.
  * Returns false if channel's callbacks don't implement nsILoadContext.
  */
 bool NS_UsePrivateBrowsing(nsIChannel *channel);
 
 /**
  * Extract the OriginAttributes from the channel's triggering principal.
+ * If aUsingStoragePrincipal is set to true, the originAttributes could have
+ * first-party isolation domain set to the top-level URI.
  */
 bool NS_GetOriginAttributes(nsIChannel *aChannel,
-                            mozilla::OriginAttributes &aAttributes);
+                            mozilla::OriginAttributes &aAttributes,
+                            bool aUsingStoragePrincipal = false);
 
 /**
  * Returns true if the channel has visited any cross-origin URLs on any
  * URLs that it was redirected through.
  */
 bool NS_HasBeenCrossOrigin(nsIChannel *aChannel, bool aReport = false);
 
 /**
--- a/netwerk/cookie/CookieServiceChild.cpp
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/SystemGroup.h"
+#include "mozilla/StoragePrincipalHelper.h"
 #include "nsCookie.h"
 #include "nsCookieService.h"
 #include "nsContentUtils.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIChannel.h"
 #include "nsCookiePermission.h"
 #include "nsIEffectiveTLDService.h"
@@ -170,16 +171,17 @@ void CookieServiceChild::TrackCookieLoad
     // object handy.
     if (!firstPartyStorageAccessGranted) {
       AntiTrackingCommon::NotifyBlockingDecision(
           aChannel, AntiTrackingCommon::BlockingDecision::eBlock,
           rejectedReason);
     }
   }
   mozilla::OriginAttributes attrs = loadInfo->GetOriginAttributes();
+  StoragePrincipalHelper::PrepareOriginAttributes(aChannel, attrs);
   URIParams uriParams;
   SerializeURI(uri, uriParams);
   bool isSafeTopLevelNav = NS_IsSafeTopLevelNav(aChannel);
   bool isSameSiteForeign = NS_IsSameSiteForeign(aChannel, uri);
   SendPrepareCookieList(uriParams, isForeign, isTrackingResource,
                         firstPartyStorageAccessGranted, isSafeTopLevelNav,
                         isSameSiteForeign, attrs);
 }
@@ -289,16 +291,17 @@ void CookieServiceChild::GetCookieString
   bool requireHostMatch;
   nsAutoCString baseDomain;
 
   nsCOMPtr<nsILoadInfo> loadInfo;
   mozilla::OriginAttributes attrs;
   if (aChannel) {
     loadInfo = aChannel->LoadInfo();
     attrs = loadInfo->GetOriginAttributes();
+    StoragePrincipalHelper::PrepareOriginAttributes(aChannel, attrs);
   }
 
   nsCookieService::GetBaseDomain(TLDService, aHostURI, baseDomain,
                                  requireHostMatch);
   nsCookieKey key(baseDomain, attrs);
   CookiesList *cookiesList = nullptr;
   mCookiesMap.Get(key, &cookiesList);
 
@@ -557,28 +560,29 @@ nsresult CookieServiceChild::SetCookieSt
   mozilla::OriginAttributes attrs;
   if (aChannel) {
     nsCOMPtr<nsIURI> channelURI;
     aChannel->GetURI(getter_AddRefs(channelURI));
     SerializeURI(channelURI, channelURIParams);
 
     MOZ_ASSERT(loadInfo);
     attrs = loadInfo->GetOriginAttributes();
+    StoragePrincipalHelper::PrepareOriginAttributes(aChannel, attrs);
   } else {
     SerializeURI(nullptr, channelURIParams);
   }
 
   Maybe<LoadInfoArgs> optionalLoadInfoArgs;
   LoadInfoToLoadInfoArgs(loadInfo, &optionalLoadInfoArgs);
 
   // Asynchronously call the parent.
   if (mIPCOpen) {
     SendSetCookieString(hostURIParams, channelURIParams, optionalLoadInfoArgs,
                         isForeign, isTrackingResource,
-                        firstPartyStorageAccessGranted, cookieString,
+                        firstPartyStorageAccessGranted, attrs, cookieString,
                         stringServerTime, aFromHttp);
   }
 
   bool requireHostMatch;
   nsCString baseDomain;
   nsCookieService::GetBaseDomain(mTLDService, aHostURI, baseDomain,
                                  requireHostMatch);
 
--- a/netwerk/cookie/CookieServiceParent.cpp
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/net/CookieServiceParent.h"
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/net/NeckoParent.h"
 
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ipc/URIUtils.h"
+#include "mozilla/StoragePrincipalHelper.h"
 #include "nsArrayUtils.h"
 #include "nsCookieService.h"
 #include "nsIChannel.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrivateBrowsingChannel.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
@@ -121,16 +122,18 @@ void CookieServiceParent::TrackCookieLoa
   nsCOMPtr<nsIURI> uri;
   aChannel->GetURI(getter_AddRefs(uri));
 
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
   mozilla::OriginAttributes attrs = loadInfo->GetOriginAttributes();
   bool isSafeTopLevelNav = NS_IsSafeTopLevelNav(aChannel);
   bool aIsSameSiteForeign = NS_IsSameSiteForeign(aChannel, uri);
 
+  StoragePrincipalHelper::PrepareOriginAttributes(aChannel, attrs);
+
   // Send matching cookies to Child.
   nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil;
   thirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   bool isForeign = true;
   thirdPartyUtil->IsThirdPartyChannel(aChannel, uri, &isForeign);
 
   bool isTrackingResource = false;
   bool storageAccessGranted = false;
@@ -202,18 +205,19 @@ void CookieServiceParent::ActorDestroy(A
   // Nothing needed here. Called right before destructor since this is a
   // non-refcounted class.
 }
 
 mozilla::ipc::IPCResult CookieServiceParent::RecvSetCookieString(
     const URIParams &aHost, const Maybe<URIParams> &aChannelURI,
     const Maybe<LoadInfoArgs> &aLoadInfoArgs, const bool &aIsForeign,
     const bool &aIsTrackingResource,
-    const bool &aFirstPartyStorageAccessGranted, const nsCString &aCookieString,
-    const nsCString &aServerTime, const bool &aFromHttp) {
+    const bool &aFirstPartyStorageAccessGranted, const OriginAttributes &aAttrs,
+    const nsCString &aCookieString, const nsCString &aServerTime,
+    const bool &aFromHttp) {
   if (!mCookieService) return IPC_OK();
 
   // Deserialize URI. Having a host URI is mandatory and should always be
   // provided by the child; thus we consider failure fatal.
   nsCOMPtr<nsIURI> hostURI = DeserializeURI(aHost);
   if (!hostURI) return IPC_FAIL_NO_REASON(this);
 
   nsCOMPtr<nsIURI> channelURI = DeserializeURI(aChannelURI);
@@ -235,25 +239,20 @@ mozilla::ipc::IPCResult CookieServicePar
   if (NS_WARN_IF(NS_FAILED(rv))) {
     // No reason to kill the content process.
     return IPC_OK();
   }
 
   // NB: dummyChannel could be null if something failed in CreateDummyChannel.
   nsDependentCString cookieString(aCookieString, 0);
 
-  OriginAttributes attrs;
-  if (loadInfo) {
-    attrs = loadInfo->GetOriginAttributes();
-  }
-
   // We set this to true while processing this cookie update, to make sure
   // we don't send it back to the same content process.
   mProcessingCookie = true;
   mCookieService->SetCookieStringInternal(
       hostURI, aIsForeign, aIsTrackingResource, aFirstPartyStorageAccessGranted,
-      cookieString, aServerTime, aFromHttp, attrs, dummyChannel);
+      cookieString, aServerTime, aFromHttp, aAttrs, dummyChannel);
   mProcessingCookie = false;
   return IPC_OK();
 }
 
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/cookie/CookieServiceParent.h
+++ b/netwerk/cookie/CookieServiceParent.h
@@ -44,18 +44,18 @@ class CookieServiceParent : public PCook
  protected:
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvSetCookieString(
       const URIParams &aHost, const Maybe<URIParams> &aChannelURI,
       const Maybe<LoadInfoArgs> &aLoadInfoArgs, const bool &aIsForeign,
       const bool &aIsTrackingResource,
       const bool &aFirstPartyStorageAccessGranted,
-      const nsCString &aCookieString, const nsCString &aServerTime,
-      const bool &aFromHttp);
+      const OriginAttributes &aAttrs, const nsCString &aCookieString,
+      const nsCString &aServerTime, const bool &aFromHttp);
 
   mozilla::ipc::IPCResult RecvPrepareCookieList(
       const URIParams &aHost, const bool &aIsForeign,
       const bool &aIsTrackingResource,
       const bool &aFirstPartyStorageAccessGranted,
       const bool &aIsSafeTopLevelNav, const bool &aIsSameSiteForeign,
       const OriginAttributes &aAttrs);
 
--- a/netwerk/cookie/PCookieService.ipdl
+++ b/netwerk/cookie/PCookieService.ipdl
@@ -70,16 +70,17 @@ parent:
    * @see mozIThirdPartyUtil.isThirdPartyChannel
    */
   nested(inside_cpow) async SetCookieString(URIParams host,
                                             URIParams? channelURI,
                                             LoadInfoArgs? loadInfoArgs,
                                             bool isForeign,
                                             bool isTrackingResource,
                                             bool firstPartyStorageAccessGranted,
+                                            OriginAttributes aStoragePrincipalAttrs,
                                             nsCString cookieString,
                                             nsCString serverTime,
                                             bool aFromHttp);
 
   async PrepareCookieList(URIParams host,
                           bool isForeign,
                           bool isTrackingResource,
                           bool firstPartyStorageAccessGranted,
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -1985,17 +1985,18 @@ nsresult nsCookieService::GetCookieStrin
     if (AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
             httpChannel, aHostURI, nullptr)) {
       firstPartyStorageAccessGranted = true;
     }
   }
 
   OriginAttributes attrs;
   if (aChannel) {
-    NS_GetOriginAttributes(aChannel, attrs);
+    NS_GetOriginAttributes(aChannel, attrs,
+                           true /* considering storage principal */);
   }
 
   bool isSafeTopLevelNav = NS_IsSafeTopLevelNav(aChannel);
   bool isSameSiteForeign = NS_IsSameSiteForeign(aChannel, aHostURI);
   nsAutoCString result;
   GetCookieStringInternal(aHostURI, aChannel, isForeign, isTrackingResource,
                           firstPartyStorageAccessGranted, isSafeTopLevelNav,
                           isSameSiteForeign, aHttpBound, attrs, result);
@@ -2104,17 +2105,18 @@ nsresult nsCookieService::SetCookieStrin
     if (AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
             httpChannel, aHostURI, nullptr)) {
       firstPartyStorageAccessGranted = true;
     }
   }
 
   OriginAttributes attrs;
   if (aChannel) {
-    NS_GetOriginAttributes(aChannel, attrs);
+    NS_GetOriginAttributes(aChannel, attrs,
+                           true /* considering storage principal */);
   }
 
   nsDependentCString cookieString(aCookieHeader);
   nsDependentCString serverTime(aServerTime ? aServerTime : "");
   SetCookieStringInternal(aHostURI, isForeign, isTrackingResource,
                           firstPartyStorageAccessGranted, cookieString,
                           serverTime, aFromHttp, attrs, aChannel);
   return NS_OK;
@@ -4035,16 +4037,22 @@ CookieStatus nsCookieService::CheckPrefs
   }
 
   // No cookies allowed if this request comes from a tracker, in a 3rd party
   // context, when anti-tracking protection is enabled and when we don't have
   // access to the first-party cookie jar.
   if (aIsForeign && aIsTrackingResource && !aFirstPartyStorageAccessGranted &&
       aCookieSettings->GetCookieBehavior() ==
           nsICookieService::BEHAVIOR_REJECT_TRACKER) {
+    if (StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
+      MOZ_ASSERT(!aOriginAttrs.mFirstPartyDomain.IsEmpty(),
+                 "We must have a StoragePrincipal here!");
+      return STATUS_ACCEPTED;
+    }
+
     COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI,
                       aCookieHeader, "cookies are disabled in trackers");
     *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
     return STATUS_REJECTED;
   }
 
   // check default prefs.
   // Check aFirstPartyStorageAccessGranted when checking aCookieBehavior
--- a/parser/prototype/PrototypeDocumentParser.cpp
+++ b/parser/prototype/PrototypeDocumentParser.cpp
@@ -79,17 +79,18 @@ PrototypeDocumentParser::Parse(nsIURI* a
   //        due to being accessed before a profile has been selected (e.g.
   //        loading chrome for the profile manager itself). This must be
   //        parsed from disk.
   nsresult rv;
   if (proto) {
     mCurrentPrototype = proto;
 
     // Set up the right principal on the document.
-    mDocument->SetPrincipal(proto->DocumentPrincipal());
+    mDocument->SetPrincipals(proto->DocumentPrincipal(),
+                             proto->DocumentPrincipal());
   } else {
     // It's just a vanilla document load. Create a parser to deal
     // with the stream n' stuff.
 
     nsCOMPtr<nsIParser> parser;
     // Get the document's principal
     nsCOMPtr<nsIPrincipal> principal = mDocument->NodePrincipal();
     rv =
@@ -180,17 +181,17 @@ nsresult PrototypeDocumentParser::Prepar
 
   // Store the new prototype right away so if there are multiple requests
   // for the same document they all get the same prototype.
   if (IsChromeURI(mDocumentURI) &&
       nsXULPrototypeCache::GetInstance()->IsEnabled()) {
     nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
   }
 
-  mDocument->SetPrincipal(aDocumentPrincipal);
+  mDocument->SetPrincipals(aDocumentPrincipal, aDocumentPrincipal);
 
   // Create a XUL content sink, a parser, and kick off a load for
   // the document.
   RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
 
   rv = sink->Init(mDocument, mCurrentPrototype);
   NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
   if (NS_FAILED(rv)) return rv;
--- a/taskcluster/docker/funsize-update-generator/scripts/funsize.py
+++ b/taskcluster/docker/funsize-update-generator/scripts/funsize.py
@@ -88,16 +88,34 @@ def is_lzma_compressed_mar(mar):
     result = MarReader(open(mar, 'rb')).compression_type == 'xz'
     if result:
         log.info("%s is lzma compressed", mar)
     else:
         log.info("%s is not lzma compressed", mar)
     return result
 
 
+def validate_mar_channel_id(mar, channel_ids):
+    log.info("Checking %s for MAR_CHANNEL_ID %s", mar, channel_ids)
+    # We may get a string with a list representation, or a single entry string.
+    channel_ids = set(channel_ids.split(','))
+
+    product_info = MarReader(open(mar, 'rb')).productinfo
+    if not isinstance(product_info, tuple):
+        raise ValueError("Malformed product information in mar: {}".format(product_info))
+
+    found_channel_ids = set(product_info[1].split(','))
+
+    if not found_channel_ids.issubset(channel_ids):
+        raise ValueError("MAR_CHANNEL_ID mismatch, {} not in {}".format(
+            product_info[1], channel_ids))
+
+    log.info("%s channel %s in %s", mar, product_info[1], channel_ids)
+
+
 @redo.retriable()
 def get_secret(secret_name):
     secrets_url = "http://taskcluster/secrets/v1/secret/{}"
     log.debug("Fetching {}".format(secret_name))
     r = requests.get(secrets_url.format(secret_name))
     # 403: If unauthorized, just give up.
     if r.status_code == 403:
         log.info("Unable to get secret key")
@@ -225,16 +243,17 @@ async def generate_partial(work_env, fro
         env['MAR_OLD_FORMAT'] = '1'
     elif 'MAR_OLD_FORMAT' in env:
         del env['MAR_OLD_FORMAT']
     make_incremental_update = os.path.join(work_env.workdir,
                                            "make_incremental_update.sh")
     cmd = " ".join([make_incremental_update, dest_mar, from_dir, to_dir])
 
     await run_command(cmd, cwd=work_env.workdir, env=env, label=dest_mar.split('/')[-1])
+    validate_mar_channel_id(dest_mar, mar_data["ACCEPTED_MAR_CHANNEL_IDS"])
 
 
 def get_hash(path, hash_type="sha512"):
     h = hashlib.new(hash_type)
     with open(path, "rb") as f:
         h.update(f.read())
     return h.hexdigest()
 
@@ -305,17 +324,17 @@ async def manage_partial(partial_def, fi
     )
     await work_env.setup()
 
     for mar in (partial_def["from_mar"], partial_def["to_mar"]):
         verify_allowed_url(mar, allowed_url_prefixes)
 
     complete_mars = {}
     use_old_format = False
-
+    check_channels_in_files = list()
     for mar_type, f in (("from", partial_def["from_mar"]), ("to", partial_def["to_mar"])):
         dest = os.path.join(work_env.workdir, "{}.mar".format(mar_type))
         unpack_dir = os.path.join(work_env.workdir, mar_type)
 
         metric_tags = [
             "platform:{}".format(partial_def['platform']),
         ]
         with ddstats.timer('mar.download.time', tags=metric_tags):
@@ -325,16 +344,18 @@ async def manage_partial(partial_def, fi
             verify_signature(dest, signing_certs)
 
         complete_mars["%s_size" % mar_type] = os.path.getsize(dest)
         complete_mars["%s_hash" % mar_type] = get_hash(dest)
 
         with ddstats.timer('mar.unpack.time'):
             await unpack(work_env, dest, unpack_dir)
 
+        check_channels_in_files.append(dest)
+
         if mar_type == 'from':
             version = get_option(unpack_dir, filename="application.ini",
                                  section="App", option="Version")
             major = int(version.split(".")[0])
             # The updater for versions less than 56.0 requires BZ2
             # compressed MAR files
             if major < 56:
                 use_old_format = True
@@ -365,16 +386,20 @@ async def manage_partial(partial_def, fi
         "from_mar": partial_def["from_mar"],
         "to_mar": partial_def["to_mar"],
         "platform": partial_def["platform"],
         "locale": partial_def["locale"],
     }
     # Override ACCEPTED_MAR_CHANNEL_IDS if needed
     if "ACCEPTED_MAR_CHANNEL_IDS" in os.environ:
         mar_data["ACCEPTED_MAR_CHANNEL_IDS"] = os.environ["ACCEPTED_MAR_CHANNEL_IDS"]
+
+    for filename in check_channels_in_files:
+        validate_mar_channel_id(filename, mar_data["ACCEPTED_MAR_CHANNEL_IDS"])
+
     for field in ("update_number", "previousVersion", "previousBuildNumber",
                   "toVersion", "toBuildNumber"):
         if field in partial_def:
             mar_data[field] = partial_def[field]
     mar_data.update(complete_mars)
 
     # if branch not set explicitly use repo-name
     mar_data['branch'] = partial_def.get('branch', mar_data['repo'].rstrip('/').split('/')[-1])
--- a/testing/geckodriver/build.rs
+++ b/testing/geckodriver/build.rs
@@ -66,17 +66,17 @@ impl Hg {
         S: AsRef<OsStr>,
     {
         Command::new("hg")
             .env("HGPLAIN", "1")
             .args(args)
             .output()
             .ok()
             .and_then(|r| String::from_utf8(r.stdout).ok())
-            .map(|s| s.trim_right().into())
+            .map(|s| s.trim_end().into())
     }
 }
 
 impl BuildInfo for Hg {
     fn hash(&self) -> Option<String> {
         self.exec(&["log", "-r.", "-T{node|short}"])
     }
 
@@ -94,17 +94,17 @@ impl Git {
         S: AsRef<OsStr>,
     {
         Command::new("git")
             .env("GIT_CONFIG_NOSYSTEM", "1")
             .args(args)
             .output()
             .ok()
             .and_then(|r| String::from_utf8(r.stdout).ok())
-            .map(|s| s.trim_right().into())
+            .map(|s| s.trim_end().into())
     }
 
     fn to_hg_sha(&self, git_sha: String) -> Option<String> {
         self.exec(&["cinnabar", "git2hg", &git_sha])
     }
 }
 
 impl BuildInfo for Git {
--- a/testing/web-platform/mach_commands.py
+++ b/testing/web-platform/mach_commands.py
@@ -191,17 +191,16 @@ class WebPlatformTestsUpdater(MozbuildOb
             update.run_update(logger, **kwargs)
         except Exception:
             import pdb
             import traceback
             traceback.print_exc()
 #            pdb.post_mortem()
 
 
-
 def create_parser_update():
     from update import updatecommandline
     return updatecommandline.create_parser()
 
 
 def create_parser_create():
     import argparse
     p = argparse.ArgumentParser()
@@ -228,16 +227,23 @@ def create_parser_manifest_update():
     return manifestupdate.create_parser()
 
 
 def create_parser_metadata_summary():
     import metasummary
     return metasummary.create_parser()
 
 
+def create_parser_serve():
+    sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
+                                                    "tests", "tools")))
+    import serve
+    return serve.serve.get_parser()
+
+
 @CommandProvider
 class MachCommands(MachCommandBase):
     def setup(self):
         self._activate_virtualenv()
 
     @Command("web-platform-tests",
              category="testing",
              conditions=[conditions.is_firefox_or_android],
@@ -298,18 +304,32 @@ class MachCommands(MachCommandBase):
              category="testing",
              description="Update web-platform-test manifests.",
              parser=create_parser_manifest_update)
     def wpt_manifest_update(self, **params):
         self.setup()
         wpt_setup = self._spawn(WebPlatformTestsRunnerSetup)
         wpt_runner = WebPlatformTestsRunner(wpt_setup)
         logger = wpt_runner.setup_logging(**params)
+        logger.warning("The wpt manifest is now automatically updated, "
+                       "so running this command is usually unnecessary")
         return 0 if wpt_runner.update_manifest(logger, **params) else 1
 
+    @Command("wpt-serve",
+             category="testing",
+             description="Run the wpt server",
+             parser=create_parser_serve)
+    def wpt_manifest_serve(self, **params):
+        self.setup()
+        import logging
+        logger = logging.getLogger("web-platform-tests")
+        logger.addHandler(logging.StreamHandler(sys.stdout))
+        import serve
+        return 0 if serve.serve.run(**params) else 1
+
     @Command("wpt-metadata-summary",
              category="testing",
              description="Create a json summary of the wpt metadata",
              parser=create_parser_metadata_summary)
     def wpt_summary(self, **params):
         import metasummary
         wpt_setup = self._spawn(WebPlatformTestsRunnerSetup)
         return metasummary.run(wpt_setup.topsrcdir, wpt_setup.topobjdir, **params)
deleted file mode 100644
--- a/testing/web-platform/meta/html/interaction/focus/processing-model/preventScroll.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[preventScroll.html]
-  [elm.focus({preventScroll: true})]
-    expected: FAIL
-
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -891,17 +891,18 @@ AntiTrackingCommon::AddFirstPartyStorage
     topInnerWindow->SaveStorageAccessGranted(permissionKey);
 
     // Let's inform the parent window.
     parentWindow->StorageAccessGranted();
 
     nsIChannel* channel =
         pwin->GetCurrentInnerWindow()->GetExtantDoc()->GetChannel();
 
-    pwin->NotifyContentBlockingEvent(blockReason, channel, false, trackingURI);
+    pwin->NotifyContentBlockingEvent(blockReason, channel, false, trackingURI,
+                                     Some(aReason));
 
     ReportUnblockingToConsole(parentWindow,
                               NS_ConvertUTF8toUTF16(trackingOrigin),
                               NS_ConvertUTF8toUTF16(origin), aReason);
 
     if (XRE_IsParentProcess()) {
       LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
            trackingOrigin.get(), origin.get()));
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/StoragePrincipalHelper.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StoragePrincipalHelper.h"
+
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs.h"
+#include "nsContentUtils.h"
+#include "nsIHttpChannel.h"
+
+namespace mozilla {
+
+namespace {
+
+already_AddRefed<nsIURI> MaybeGetFirstPartyURI(nsIChannel* aChannel) {
+  MOZ_ASSERT(aChannel);
+
+  if (!StaticPrefs::privacy_storagePrincipal_enabledForTrackers()) {
+    return nullptr;
+  }
+
+  // Let's use the storage principal only if we need to partition the cookie
+  // jar.
+  nsContentUtils::StorageAccess access =
+      nsContentUtils::StorageAllowedForChannel(aChannel);
+  if (access != nsContentUtils::StorageAccess::ePartitionedOrDeny) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+  if (!httpChannel) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(httpChannel->IsThirdPartyTrackingResource());
+
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+  nsCOMPtr<nsIPrincipal> toplevelPrincipal = loadInfo->GetTopLevelPrincipal();
+  if (!toplevelPrincipal) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIURI> principalURI;
+  nsresult rv = toplevelPrincipal->GetURI(getter_AddRefs(principalURI));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  return principalURI.forget();
+}
+
+}  // namespace
+
+// static
+nsresult StoragePrincipalHelper::Create(nsIChannel* aChannel,
+                                        nsIPrincipal* aPrincipal,
+                                        nsIPrincipal** aStoragePrincipal) {
+  MOZ_ASSERT(aChannel);
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(aStoragePrincipal);
+
+  auto scopeExit = MakeScopeExit([&] {
+    nsCOMPtr<nsIPrincipal> storagePrincipal = aPrincipal;
+    storagePrincipal.forget(aStoragePrincipal);
+  });
+
+  nsCOMPtr<nsIURI> principalURI = MaybeGetFirstPartyURI(aChannel);
+  if (!principalURI) {
+    return NS_OK;
+  }
+
+  scopeExit.release();
+
+  nsCOMPtr<nsIPrincipal> storagePrincipal =
+      BasePrincipal::Cast(aPrincipal)
+          ->CloneForcingFirstPartyDomain(principalURI);
+
+  storagePrincipal.forget(aStoragePrincipal);
+  return NS_OK;
+}
+
+// static
+nsresult StoragePrincipalHelper::PrepareOriginAttributes(
+    nsIChannel* aChannel, OriginAttributes& aOriginAttributes) {
+  MOZ_ASSERT(aChannel);
+
+  nsCOMPtr<nsIURI> principalURI = MaybeGetFirstPartyURI(aChannel);
+  if (!principalURI) {
+    return NS_OK;
+  }
+
+  aOriginAttributes.SetFirstPartyDomain(false, principalURI,
+                                        true /* aForced */);
+  return NS_OK;
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/StoragePrincipalHelper.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StoragePrincipalHelper_h
+#define mozilla_StoragePrincipalHelper_h
+
+/**
+ * StoragePrincipal
+ * ~~~~~~~~~~~~~~~~
+ *
+ * StoragePrincipal is the nsIPrincipal to be used to open the cookie jar of a
+ * resource's origin. Normally, the StoragePrincipal corresponds to the
+ * resource's origin, but, in some scenarios, it can be different: it can have
+ * some extra origin attributes.
+ *
+ * Each storage component, should always use the StoragePrincipal instead of the
+ * 'real' one in order to implement the partitioning correctly.
+ *
+ * On the web, each resource has its own origin (see
+ * https://html.spec.whatwg.org/multipage/origin.html#concept-origin) and each
+ * origin has its own cookie jar, containing cookies, storage data, cache and so
+ * on.
+ *
+ * In addition, gecko has a set of attributes to differentiate the same origin
+ * in different contexts (OriginAttributes). The main ones are:
+ * - privateBrowsingId, for private browsing navigation.
+ * - userContextId, for containers
+ * - firstPartyIsolation, for TOR.
+ *
+ * In gecko-world, the origin and its attributes are stored and managed by the
+ * nsIPrincipal interface. Both resource's Principal and resource's
+ * StoragePrincipal are nsIPrincipal interfaces and, normally, they are the same
+ * object.
+ *
+ * Here is the way you can obtain the two Principals:
+ * From a Document:
+ * - Document's principal: nsINode::NodePrincipal
+ * - Document's StoragePrincipal: Document::EffectiveStoragePrincipal
+ * From a Global object:
+ * - nsIScriptObjectPrincipal::getPrincipal
+ * - nsIScriptObjectPrincipal::getEffectiveStoragePrincipal
+ * From a Worker:
+ * - WorkerPrivate::GetPrincipal().
+ * - WorkerPrivate::GetEffectiveStoragePrincipal();
+ * For a nsIChannel, the final principals must be calculated and they can be
+ * obtained by calling:
+ * - nsIScriptSecurityManager::getChannelResultPrincipal() or
+ * - nsIScriptSecurityManager::getChannelResultStoragePrincipal().
+ *
+ * Principal VS StoragePrincipal
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * At the moment, we are experimenting the partitioning of cookie jars for 3rd
+ * party trackers: each 3rd party origin, detected as a tracker, will have a
+ * partitioned cookie jar, created by the tracker's origin, plus, the
+ * first-party domain.
+ *
+ * This means that, for those origins, StoragePrincipal will be  equal to the
+ * main Principal but it will also have the 'first-party-domain' attribute set
+ * as the first-party URL's domain. Because of this, the tracker's cookie jar
+ * will be partitioned and it will be unique per first-party domain.
+ *
+ * The naminig is important. This is why Document has the StoragePrincipal
+ * stored in a member variable called mIntrinsicStoragePrincipal: this
+ * storagePrincipal is immutable even when Document::EffectiveStoragePrincipal
+ * returns the main principal.
+ *
+ * Storage access permission
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * A resource's origin and its attributes are immutable. StoragePrincipal can
+ * change: when a tracker has the storage permission granted, its
+ * StoragePrincipal becomes equal to its document's principal. In this way, the
+ * tracker will have access to its first-party cookie jar, escaping from the
+ * its partitioning.
+ *
+ * To know more about when the storage access permission is granted, see the
+ * anti-tracking project's documentation.
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API and
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Privacy/Storage_access_policy#Storage_access_grants
+ *
+ *
+ * When the storage access permission is granted, any of the StoragePrincipal
+ * getter methods will return the main principal instead of the storage one, and
+ * each storage component should consider the new Principal only.
+ *
+ * There are several ways to receive storage-permission notifications:
+ * - Add some code in nsGlobalWindowInner::StorageAccessGranted().
+ * - WorkerScope::FirstPartyStorageAccessGranted for Workers.
+ * - observe the permission changes (not recommended)
+ *
+ * SharedWorkers and BroadcastChannels
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * SharedWorker and BroadcastChannel instances latch the effective storage
+ * principal at the moment of their creation.  Existing bindings to the
+ * partitioned storage principal will continue to exist and operate even as it
+ * becomes possible to create bindings associated with the non-partitioned node
+ * principal.  This makes it possible for such globals to bi-directionally
+ * bridge information between partitioned and non-partitioned principals.
+ *
+ * {Dedicated,Shared,Service}Workers
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * The storage access permission propagation happens with a ControlRunnable.
+ * This could impact the use of sync event-loops. Take a reference of the
+ * principal you want to use because it can change!
+ *
+ * ServiceWorkers are currently disabled for partitioned contexts.
+ *
+ * Client API uses the main principal always because there is not a direct
+ * connection between this API and the cookie jar. If we want to support
+ * ServiceWorkers in partitioned context, this part must be revisited.
+ */
+
+class nsIChannel;
+class nsIPrincipal;
+
+namespace mozilla {
+
+class OriginAttributes;
+
+class StoragePrincipalHelper final {
+ public:
+  static nsresult Create(nsIChannel* aChannel, nsIPrincipal* aPrincipal,
+                         nsIPrincipal** aStoragePrincipal);
+
+  static nsresult PrepareOriginAttributes(nsIChannel* aChannel,
+                                          OriginAttributes& aOriginAttributes);
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_StoragePrincipalHelper_h
--- a/toolkit/components/antitracking/moz.build
+++ b/toolkit/components/antitracking/moz.build
@@ -4,20 +4,22 @@
 # 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 = ('Core', 'DOM: Security')
 
 EXPORTS.mozilla = [
     'AntiTrackingCommon.h',
+    'StoragePrincipalHelper.h',
 ]
 
 UNIFIED_SOURCES += [
     'AntiTrackingCommon.cpp',
+    'StoragePrincipalHelper.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/extensions/cookie',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyStorage.html
@@ -0,0 +1,44 @@
+<html>
+<head>
+  <title>3rd party content!</title>
+  <script type="text/javascript" src="https://example.com/browser/toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js"></script>
+</head>
+<body>
+<h1>Here the 3rd party content!</h1>
+<script>
+
+function info(msg) {
+  parent.postMessage({ type: "info", msg }, "*");
+}
+
+function ok(what, msg) {
+  parent.postMessage({ type: "ok", what: !!what, msg }, "*");
+}
+
+function is(a, b, msg) {
+  ok(a === b, msg);
+}
+
+onmessage = function(e) {
+  let data = e.data;
+  let runnableStr = `(() => {return (${data});})();`;
+  let runnable = eval(runnableStr); // eslint-disable-line no-eval
+
+  let win = window.open("3rdPartyStorageWO.html");
+  win.onload = async _ => {
+    /* import-globals-from storageAccessAPIHelpers.js */
+    await noStorageAccessInitially();
+
+    await runnable.call(this, this, win, false /* allowed */);
+    /* import-globals-from storageAccessAPIHelpers.js */
+    await callRequestStorageAccess();
+    await runnable.call(this, this, win, true /* allowed */);
+
+    win.close();
+    parent.postMessage({ type: "finish" }, "*");
+  };
+};
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/3rdPartyStorageWO.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+  <title>1st party content!</title>
+</head>
+<body>
+<h1>Here the 1st party content!</h1>
+</body>
+</html>
copy from toolkit/components/antitracking/test/browser/head.js
copy to toolkit/components/antitracking/test/browser/antitracking_head.js
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/antitracking_head.js
@@ -1,48 +1,19 @@
-const TEST_DOMAIN = "http://example.net/";
-const TEST_DOMAIN_2 = "http://xn--exmple-cua.test/";
-const TEST_DOMAIN_3 = "https://xn--hxajbheg2az3al.xn--jxalpdlp/";
-const TEST_DOMAIN_4 = "http://prefixexample.com/";
-const TEST_DOMAIN_5 = "http://test/";
-const TEST_DOMAIN_6 = "http://mochi.test:8888/";
-const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org/";
-const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com/";
-const TEST_4TH_PARTY_DOMAIN = "http://not-tracking.example.com/";
-const TEST_ANOTHER_3RD_PARTY_DOMAIN = "https://another-tracking.example.net/";
-
-const TEST_PATH = "browser/toolkit/components/antitracking/test/browser/";
+/* 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/. */
 
-const TEST_TOP_PAGE = TEST_DOMAIN + TEST_PATH + "page.html";
-const TEST_TOP_PAGE_2 = TEST_DOMAIN_2 + TEST_PATH + "page.html";
-const TEST_TOP_PAGE_3 = TEST_DOMAIN_3 + TEST_PATH + "page.html";
-const TEST_TOP_PAGE_4 = TEST_DOMAIN_4 + TEST_PATH + "page.html";
-const TEST_TOP_PAGE_5 = TEST_DOMAIN_5 + TEST_PATH + "page.html";
-const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + TEST_PATH + "page.html";
-const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html";
-const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html";
-const TEST_3RD_PARTY_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
-const TEST_3RD_PARTY_PAGE_WO = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyWO.html";
-const TEST_3RD_PARTY_PAGE_UI = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyUI.html";
-const TEST_3RD_PARTY_PAGE_WITH_SVG = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartySVG.html";
-const TEST_4TH_PARTY_PAGE = TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
-const TEST_ANOTHER_3RD_PARTY_PAGE = TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
+/* import-globals-from head.js */
 
-const BEHAVIOR_ACCEPT         = Ci.nsICookieService.BEHAVIOR_ACCEPT;
-const BEHAVIOR_REJECT         = Ci.nsICookieService.BEHAVIOR_REJECT;
-const BEHAVIOR_LIMIT_FOREIGN  = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
-const BEHAVIOR_REJECT_FOREIGN = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
-const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+"use strict";
 
 var gFeatures = undefined;
 
-let {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
-
-requestLongerTimeout(3);
-
 this.AntiTracking = {
   runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs,
           windowOpenTest = true, userInteractionTest = true,
           expectedBlockingNotifications = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
           runInPrivateWindow = false, iframeSandbox = null, accessRemoval = null,
           callbackAfterRemoval = null) {
     // Here we want to test that a 3rd party context is simply blocked.
     this._createTask({
@@ -728,8 +699,9 @@ this.AntiTracking = {
       BrowserTestUtils.removeTab(tab);
 
       if (runInPrivateWindow) {
         win.close();
       }
     });
   },
 };
+
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -4,31 +4,36 @@ prefs =
   dom.storage_access.prompt.testing=true
   dom.storage_access.prompt.testing.allow=true
   dom.testing.sync-content-blocking-notifications=true
 
 support-files =
   container.html
   embedder.html
   head.js
+  antitracking_head.js
+  storageprincipal_head.js
   image.sjs
   imageCacheWorker.js
   page.html
   3rdParty.html
   3rdPartySVG.html
   3rdPartyUI.html
   3rdPartyWO.html
   3rdPartyOpen.html
   3rdPartyOpenUI.html
   empty.js
   empty.html
   popup.html
   server.sjs
   storageAccessAPIHelpers.js
+  3rdPartyStorage.html
+  3rdPartyStorageWO.html
   !/browser/modules/test/browser/head.js
+  !/browser/base/content/test/general/head.js
 
 [browser_allowListNotifications.js]
 skip-if = serviceworker_e10s
 support-files = subResources.sjs
 [browser_addonHostPermissionIgnoredInTP.js]
 [browser_allowListSeparationInPrivateAndNormalWindows.js]
 skip-if = os == "mac" && !debug # Bug 1503778
 [browser_backgroundImageAssertion.js]
@@ -82,8 +87,17 @@ skip-if = serviceworker_e10s
 [browser_localStorageEvents.js]
 support-files = localStorage.html
 [browser_partitionedLocalStorage.js]
 [browser_partitionedLocalStorage_events.js]
 support-files = localStorageEvents.html
 [browser_workerPropagation.js]
 support-files = workerIframe.html
 [browser_cookieBetweenTabs.js]
+[browser_partitionedMessaging.js]
+[browser_partitionedIndexedDB.js]
+[browser_partitionedCookies.js]
+support-files = cookies.sjs
+[browser_partitionedDOMCache.js]
+[browser_partitionedServiceWorkers.js]
+support-files = matchAll.js
+[browser_partitionedSharedWorkers.js]
+support-files = sharedWorker.js
--- a/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
@@ -5,16 +5,19 @@
 // the pages loaded under this test in the allow list, which would result in
 // the test not passing because no blocking notifications would be observed.
 
 // Testing the reverse case would also be interesting, but unfortunately there
 // isn't a super easy way to do that with our antitracking test framework since
 // private windows wouldn't send any blocking notifications as they don't have
 // storage access in the first place.
 
+/* import-globals-from antitracking_head.js */
+
+"use strict";
 add_task(async _ => {
   let uri = Services.io.newURI("https://example.net");
   Services.perms.add(uri, "trackingprotection-pb",
                      Services.perms.ALLOW_ACTION);
 
   registerCleanupFunction(_ => {
     Services.perms.removeAll();
   });
--- a/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
@@ -1,11 +1,13 @@
 // This test works by setting up an exception for the tracker domain, which
 // disables all the anti-tracking tests.
 
+/* import-globals-from antitracking_head.js */
+
 add_task(async _ => {
   Services.perms.add(Services.io.newURI("https://tracking.example.org"),
                      "cookie", Services.perms.ALLOW_ACTION);
   Services.perms.add(Services.io.newURI("https://tracking.example.com"),
                      "cookie", Services.perms.ALLOW_ACTION);
 
   registerCleanupFunction(_ => {
     Services.perms.removeAll();
--- a/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Set/Get Cookies",
   // Blocking callback
   async _ => {
     is(document.cookie, "", "No cookies for me");
     document.cookie = "name=value";
     is(document.cookie, "", "No cookies for me");
 
     await fetch("server.sjs").then(r => r.text()).then(text => {
--- a/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 requestLongerTimeout(2);
 
 AntiTracking.runTest("DOM Cache",
   async _ => {
     await caches.open("wow").then(
       _ => { ok(false, "DOM Cache cannot be used!"); },
       _ => { ok(true, "DOM Cache cannot be used!"); });
   },
--- a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("IndexedDB",
   // blocking callback
   async _ => {
     try {
       indexedDB.open("test", "1");
       ok(false, "IDB should be blocked");
     } catch (e) {
       ok(true, "IDB should be blocked");
--- a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("IndexedDB in workers",
   async _ => {
     function blockCode() {
       try {
         indexedDB.open("test", "1");
         postMessage(false);
       } catch (e) {
         postMessage(e.name == "SecurityError");
--- a/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("localStorage",
   async _ => {
     is(window.localStorage, null, "LocalStorage is null");
     try {
       localStorage.foo = 42;
       ok(false, "LocalStorage cannot be used!");
     } catch (e) {
       ok(true, "LocalStorage cannot be used!");
--- a/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("BroadcastChannel",
   async _ => {
     try {
       new BroadcastChannel("hello");
       ok(false, "BroadcastChannel cannot be used!");
     } catch (e) {
       ok(true, "BroadcastChannel cannot be used!");
       is(e.name, "SecurityError", "We want a security error message.");
--- a/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 gFeatures = "noopener";
 
 AntiTracking.runTest("Blocking in the case of noopener windows",
   async _ => {
     is(window.localStorage, null, "LocalStorage is null");
     try {
       localStorage.foo = 42;
       ok(false, "LocalStorage cannot be used!");
--- a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("ServiceWorkers",
   async _ => {
     await navigator.serviceWorker.register("empty.js").then(
       _ => { ok(false, "ServiceWorker cannot be used!"); },
       _ => { ok(true, "ServiceWorker cannot be used!"); }).
       catch(e => ok(false, "Promise rejected: " + e));
   },
   null,
--- a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 requestLongerTimeout(2);
 
 AntiTracking.runTest("ServiceWorkers and Storage Access API",
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     await noStorageAccessInitially();
 
     await navigator.serviceWorker.register("empty.js").then(
--- a/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("sessionStorage",
   async _ => {
     let shouldThrow = SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") == SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT;
 
     let hasThrown;
     try {
       sessionStorage.foo = 42;
       hasThrown = false;
--- a/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 requestLongerTimeout(2);
 
 AntiTracking.runTest("SharedWorkers",
   async _ => {
     try {
       new SharedWorker("a.js", "foo");
       ok(false, "SharedWorker cannot be used!");
     } catch (e) {
--- a/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
+++ b/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
@@ -1,11 +1,13 @@
 // This test works by setting up an exception for the tracker domain, which
 // disables all the anti-tracking tests.
 
+/* import-globals-from antitracking_head.js */
+
 add_task(async _ => {
   Services.perms.add(Services.io.newURI("https://tracking.example.org"),
                      "cookie", Services.perms.DENY_ACTION);
   Services.perms.add(Services.io.newURI("https://tracking.example.com"),
                      "cookie", Services.perms.DENY_ACTION);
 
   registerCleanupFunction(_ => {
     Services.perms.removeAll();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js
@@ -0,0 +1,49 @@
+/* import-globals-from storageprincipal_head.js */
+
+StoragePrincipalHelper.runTest("HTTP Cookies",
+  async (win3rdParty, win1stParty, allowed) => {
+    await win3rdParty.fetch("cookies.sjs?3rd").then(r => r.text());
+    await win3rdParty.fetch("cookies.sjs").then(r => r.text()).then(text => {
+      is(text, "cookie:foopy=3rd", "3rd party cookie set");
+    });
+
+    await win1stParty.fetch("cookies.sjs?first").then(r => r.text());
+    await win1stParty.fetch("cookies.sjs").then(r => r.text()).then(text => {
+      is(text, "cookie:foopy=first", "First party cookie set");
+    });
+
+    await win3rdParty.fetch("cookies.sjs").then(r => r.text()).then(text => {
+      if (allowed) {
+        is(text, "cookie:foopy=first", "3rd party has the first party cookie set");
+      } else {
+        is(text, "cookie:foopy=3rd", "3rd party has not the first party cookie set");
+      }
+    });
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  });
+
+StoragePrincipalHelper.runTest("DOM Cookies",
+  async (win3rdParty, win1stParty, allowed) => {
+    win3rdParty.document.cookie = "foo=3rd";
+    is(win3rdParty.document.cookie, "foo=3rd", "3rd party cookie set");
+
+    win1stParty.document.cookie = "foo=first";
+    is(win1stParty.document.cookie, "foo=first", "First party cookie set");
+
+    if (allowed) {
+      is(win3rdParty.document.cookie, "foo=first", "3rd party has the first party cookie set");
+    } else {
+      is(win3rdParty.document.cookie, "foo=3rd", "3rd party has not the first party cookie set");
+    }
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js
@@ -0,0 +1,21 @@
+/* import-globals-from storageprincipal_head.js */
+
+StoragePrincipalHelper.runTest("DOMCache",
+  async (win3rdParty, win1stParty, allowed) => {
+    // DOM Cache is not supported. Always blocked.
+    await win3rdParty.caches.open("wow").then(
+      _ => { ok(allowed, "DOM Cache cannot be used!"); },
+      _ => { ok(!allowed, "DOM Cache cannot be used!"); }
+    );
+
+    await win1stParty.caches.open("wow").then(
+      _ => { ok(true, "DOM Cache shoulw be available"); },
+      _ => { ok(false, "DOM Cache shoulw be available"); },
+    );
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js
@@ -0,0 +1,46 @@
+/* import-globals-from storageprincipal_head.js */
+
+StoragePrincipalHelper.runTest("IndexedDB",
+  async (win3rdParty, win1stParty, allowed) => {
+    await new Promise(resolve => {
+      let a = win1stParty.indexedDB.open("test", 1);
+      ok(!!a, "IDB should not be blocked in 1st party contexts");
+
+      a.onsuccess = e => {
+        let db = e.target.result;
+        is(db.objectStoreNames.length, 1, "We have 1 objectStore");
+        is(db.objectStoreNames[0], "foobar", "We have 'foobar' objectStore");
+        resolve();
+      };
+
+      a.onupgradeneeded = e => {
+        let db = e.target.result;
+        is(db.objectStoreNames.length, 0, "We have 0 objectStores");
+        db.createObjectStore("foobar", { keyPath: "test" });
+      };
+    });
+
+    await new Promise(resolve => {
+      let a = win3rdParty.indexedDB.open("test", 1);
+      ok(!!a, "IDB should not be blocked in 3rd party contexts");
+
+      a.onsuccess = e => {
+        let db = e.target.result;
+
+        if (allowed) {
+          is(db.objectStoreNames.length, 1, "We have 1 objectStore");
+          is(db.objectStoreNames[0], "foobar", "We have 'foobar' objectStore");
+        } else {
+          is(db.objectStoreNames.length, 0, "We have 0 objectStore");
+        }
+        resolve();
+      };
+    });
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  });
+
--- a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("localStorage and Storage Access API",
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     await noStorageAccessInitially();
 
     let shouldThrow = SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") == SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT;
 
     is(window.localStorage == null, shouldThrow,
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js
@@ -0,0 +1,18 @@
+/* import-globals-from storageprincipal_head.js */
+
+StoragePrincipalHelper.runTest("BroadcastChannel",
+  async (win3rdParty, win1stParty, allowed) => {
+    let a = new win3rdParty.BroadcastChannel("hello");
+    ok(!!a, "BroadcastChannel should be created by 3rd party iframe");
+
+    let b = new win1stParty.BroadcastChannel("hello");
+    ok(!!b, "BroadcastChannel should be created by 1st party iframe");
+
+    // BroadcastChannel uses the incument global, this means that its CTOR will
+    // always use the 3rd party iframe's window as global.
+  },
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js
@@ -0,0 +1,65 @@
+/* import-globals-from storageprincipal_head.js */
+
+StoragePrincipalHelper.runTest("ServiceWorkers",
+  async (win3rdParty, win1stParty, allowed) => {
+    // ServiceWorkers are not supported. Always blocked.
+    await win3rdParty.navigator.serviceWorker.register("empty.js").then(
+      _ => { ok(allowed, "Success: ServiceWorker cannot be used!"); },
+      _ => { ok(!allowed, "Failed: ServiceWorker cannot be used!"); });
+
+    await win1stParty.navigator.serviceWorker.register("empty.js").then(
+      _ => { ok(true, "Success: ServiceWorker should be available!"); },
+      _ => { ok(false, "Failed: ServiceWorker should be available!"); });
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+
+  [["dom.serviceWorkers.exemptFromPerDomainMax", true],
+   ["dom.ipc.processCount", 1],
+   ["dom.serviceWorkers.enabled", true],
+   ["dom.serviceWorkers.testing.enabled", true]]);
+
+StoragePrincipalHelper.runTest("ServiceWorkers - MatchAll",
+  async (win3rdParty, win1stParty, allowed) => {
+    if (!win1stParty.sw) {
+      let reg = await win1stParty.navigator.serviceWorker.register("matchAll.js");
+      if (reg.installing.state !== "activated") {
+        await new Promise(resolve => {
+            let w = reg.installing;
+            w.addEventListener("statechange", function onStateChange() {
+              if (w.state === "activated") {
+                w.removeEventListener("statechange", onStateChange);
+                win1stParty.sw = reg.active;
+                resolve();
+              }
+            });
+          });
+      }
+    }
+
+    let msgPromise = new Promise(resolve => {
+      win1stParty.navigator.serviceWorker.addEventListener("message", msg => {
+        resolve(msg.data);
+      });
+    });
+
+    win1stParty.sw.postMessage(win3rdParty.location.href);
+    let msg = await msgPromise;
+
+    is(allowed, msg, "We want to have the 3rd party window controlled.");
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+
+  [["dom.serviceWorkers.exemptFromPerDomainMax", true],
+   ["dom.ipc.processCount", 1],
+   ["dom.serviceWorkers.enabled", true],
+   ["dom.serviceWorkers.testing.enabled", true]]);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js
@@ -0,0 +1,36 @@
+/* import-globals-from storageprincipal_head.js */
+
+StoragePrincipalHelper.runTest("SharedWorkers",
+  async (win3rdParty, win1stParty, allowed) => {
+    let sh1 = new win1stParty.SharedWorker("sharedWorker.js");
+    await new Promise(resolve => {
+      sh1.port.onmessage = e => {
+        is(e.data, 1, "We expected 1 connection");
+        resolve();
+      };
+      sh1.port.postMessage("count");
+    });
+
+    let sh3 = new win3rdParty.SharedWorker("sharedWorker.js");
+    await new Promise(resolve => {
+      sh3.port.onmessage = e => {
+        ok(!allowed, "We should be here only if the SharedWorker is partitioned");
+        is(e.data, 1, "We expected 1 connection for 3rd party SharedWorker");
+        resolve();
+      };
+      sh3.onerror = _ => {
+        ok(allowed, "We should be here only if the SharedWorker is not partitioned");
+        resolve();
+      };
+      sh3.port.postMessage("count");
+    });
+
+    sh1.port.postMessage("close");
+    sh3.port.postMessage("close");
+  },
+
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  });
--- a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Test whether we receive any persistent permissions in normal windows",
   // Blocking callback
   async _ => {
     // Nothing to do here!
   },
 
   // Non blocking callback
   async _ => {
--- a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Test whether we receive any persistent permissions in private windows",
   // Blocking callback
   async _ => {
     // Nothing to do here!
   },
 
   // Non blocking callback
   async _ => {
--- a/toolkit/components/antitracking/test/browser/browser_script.js
+++ b/toolkit/components/antitracking/test/browser/browser_script.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 add_task(async function() {
   info("Starting subResources test");
 
   await SpecialPowers.flushPrefEnv();
   await SpecialPowers.pushPrefEnv({"set": [
     ["browser.contentblocking.allowlist.annotations.enabled", true],
     ["browser.contentblocking.allowlist.storage.enabled", true],
     ["network.cookie.cookieBehavior", Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER],
--- a/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
+++ b/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("localStorage with a tracker that is whitelisted via a pref",
   async _ => {
     let shouldThrow = SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") == SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT;
 
     let hasThrown;
     try {
       localStorage.foo = 42;
       hasThrown = false;
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPrivateWindow.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPrivateWindow.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Storage Access API called in a private window",
   // blocking callback
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess();
     ok(!threw, "requestStorageAccess should not throw");
     ok(rejected, "requestStorageAccess shouldn't be available");
   },
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Storage Access API returns promises that maintain user activation for calling its reject handler",
   // blocking callback
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess(dwu => {
       ok(dwu.isHandlingUserInput,
          "Promise reject handler must run as if we're handling user input");
     }, true);
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Storage Access API returns promises that maintain user activation",
   // blocking callback
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess(dwu => {
       ok(dwu.isHandlingUserInput,
          "Promise handler must run as if we're handling user input");
     });
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 AntiTracking.runTest("Storage Access is removed when subframe navigates",
   // blocking callback
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     await noStorageAccessInitially();
   },
 
   // non-blocking callback
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 let counter = 0;
 
 AntiTracking.runTest("Storage Access API called in a sandboxed iframe",
   // blocking callback
   async _ => {
     /* import-globals-from storageAccessAPIHelpers.js */
     let [threw, rejected] = await callRequestStorageAccess();
     ok(!threw, "requestStorageAccess should not throw");
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
@@ -1,8 +1,10 @@
+/* import-globals-from antitracking_head.js */
+
 add_task(async function() {
   info("Starting subResources test");
 
   await SpecialPowers.flushPrefEnv();
   await SpecialPowers.pushPrefEnv({"set": [
     ["dom.storage_access.enabled", true],
     ["browser.contentblocking.allowlist.annotations.enabled", true],
     ["browser.contentblocking.allowlist.storage.enabled", true],
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/cookies.sjs
@@ -0,0 +1,12 @@
+function handleRequest(aRequest, aResponse) {
+  aResponse.setStatusLine(aRequest.httpVersion, 200);
+  let cookie = "";
+  if (aRequest.hasHeader("Cookie")) {
+    cookie = aRequest.getHeader("Cookie");
+  }
+  aResponse.write("cookie:" + cookie);
+
+  if (aRequest.queryString) {
+    aResponse.setHeader("Set-Cookie", "foopy=" + aRequest.queryString);
+  }
+}
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -1,8 +1,15 @@
+/* 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/. */
+
+"use strict";
+
 const TEST_DOMAIN = "http://example.net/";
 const TEST_DOMAIN_2 = "http://xn--exmple-cua.test/";
 const TEST_DOMAIN_3 = "https://xn--hxajbheg2az3al.xn--jxalpdlp/";
 const TEST_DOMAIN_4 = "http://prefixexample.com/";
 const TEST_DOMAIN_5 = "http://test/";
 const TEST_DOMAIN_6 = "http://mochi.test:8888/";
 const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org/";
 const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com/";
@@ -20,716 +27,27 @@ const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + 
 const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html";
 const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html";
 const TEST_3RD_PARTY_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
 const TEST_3RD_PARTY_PAGE_WO = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyWO.html";
 const TEST_3RD_PARTY_PAGE_UI = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyUI.html";
 const TEST_3RD_PARTY_PAGE_WITH_SVG = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartySVG.html";
 const TEST_4TH_PARTY_PAGE = TEST_4TH_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
 const TEST_ANOTHER_3RD_PARTY_PAGE = TEST_ANOTHER_3RD_PARTY_DOMAIN + TEST_PATH + "3rdParty.html";
+const TEST_3RD_PARTY_STORAGE_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyStorage.html";
 
 const BEHAVIOR_ACCEPT         = Ci.nsICookieService.BEHAVIOR_ACCEPT;
 const BEHAVIOR_REJECT         = Ci.nsICookieService.BEHAVIOR_REJECT;
 const BEHAVIOR_LIMIT_FOREIGN  = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
 const BEHAVIOR_REJECT_FOREIGN = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
 const BEHAVIOR_REJECT_TRACKER = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
 
-var gFeatures = undefined;
-
-let {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
-
 requestLongerTimeout(3);
 
-this.AntiTracking = {
-  runTest(name, callbackTracking, callbackNonTracking, cleanupFunction, extraPrefs,
-          windowOpenTest = true, userInteractionTest = true,
-          expectedBlockingNotifications = Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER,
-          runInPrivateWindow = false, iframeSandbox = null, accessRemoval = null,
-          callbackAfterRemoval = null) {
-    // Here we want to test that a 3rd party context is simply blocked.
-    this._createTask({
-      name,
-      cookieBehavior: BEHAVIOR_REJECT_TRACKER,
-      blockingByContentBlockingRTUI: true,
-      allowList: false,
-      callback: callbackTracking,
-      extraPrefs,
-      expectedBlockingNotifications,
-      runInPrivateWindow,
-      iframeSandbox,
-      accessRemoval,
-      callbackAfterRemoval,
-    });
-    this._createCleanupTask(cleanupFunction);
-
-    this._createTask({
-      name,
-      cookieBehavior: BEHAVIOR_REJECT_TRACKER,
-      blockingByContentBlockingRTUI: false,
-      allowList: true,
-      callback: callbackTracking,
-      extraPrefs,
-      expectedBlockingNotifications,
-      runInPrivateWindow,
-      iframeSandbox,
-      accessRemoval,
-      callbackAfterRemoval,
-    });
-    this._createCleanupTask(cleanupFunction);
-
-    if (callbackNonTracking) {
-      let runExtraTests = true;
-      let options = {};
-      if (typeof callbackNonTracking == "object") {
-        options.callback = callbackNonTracking.callback;
-        runExtraTests = callbackNonTracking.runExtraTests;
-        if ("cookieBehavior" in callbackNonTracking) {
-          options.cookieBehavior = callbackNonTracking.cookieBehavior;
-        } else {
-          options.cookieBehavior = BEHAVIOR_ACCEPT;
-        }
-        if ("blockingByContentBlockingRTUI" in callbackNonTracking) {
-          options.blockingByContentBlockingRTUI =
-            callbackNonTracking.blockingByContentBlockingRTUI;
-        } else {
-          options.blockingByContentBlockingRTUI = false;
-        }
-        if ("blockingByAllowList" in callbackNonTracking) {
-          options.blockingByAllowList =
-            callbackNonTracking.blockingByAllowList;
-        } else {
-          options.blockingByAllowList = false;
-        }
-        callbackNonTracking = options.callback;
-        options.accessRemoval = null;
-        options.callbackAfterRemoval = null;
-      }
-
-      // Phase 1: Here we want to test that a 3rd party context is not blocked if pref is off.
-      if (runExtraTests) {
-        // There are five ways in which the third-party context may not be blocked:
-        //   * If the cookieBehavior pref causes it to not be blocked.
-        //   * If the contentBlocking pref causes it to not be blocked.
-        //   * If both of these prefs cause it to not be blocked.
-        //   * If the top-level page is on the content blocking allow list.
-        //   * If the contentBlocking third-party cookies UI pref is off, the allow list will be ignored.
-        // All of these cases are tested here.
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_ACCEPT,
-          blockingByContentBlockingRTUI: true,
-          allowList: false,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_ACCEPT,
-          blockingByContentBlockingRTUI: false,
-          allowList: true,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_ACCEPT,
-          blockingByContentBlockingRTUI: false,
-          allowList: false,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_REJECT,
-          blockingByContentBlockingRTUI: true,
-          allowList: false,
-          callback: callbackTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_LIMIT_FOREIGN,
-          blockingByContentBlockingRTUI: true,
-          allowList: true,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_REJECT_FOREIGN,
-          blockingByContentBlockingRTUI: true,
-          allowList: true,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_REJECT_TRACKER,
-          blockingByContentBlockingRTUI: true,
-          allowList: true,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: 0,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval,
-          callbackAfterRemoval,
-        });
-        this._createCleanupTask(cleanupFunction);
-
-        this._createTask({
-          name,
-          cookieBehavior: BEHAVIOR_REJECT_TRACKER,
-          blockingByContentBlockingRTUI: false,
-          allowList: false,
-          callback: callbackNonTracking,
-          extraPrefs,
-          expectedBlockingNotifications: false,
-          runInPrivateWindow,
-          iframeSandbox,
-          accessRemoval: null, // only passed with non-blocking callback
-          callbackAfterRemoval: null,
-          thirdPartyPage: TEST_ANOTHER_3RD_PARTY_PAGE,
-        });
-        this._createCleanupTask(cleanupFunction);
-      }
-
-      // Phase 2: Here we want to test that a third-party context doesn't
-      // get blocked with when the same origin is opened through window.open().
-      if (windowOpenTest) {
-        this._createWindowOpenTask(name, callbackTracking, callbackNonTracking,
-                                   runInPrivateWindow, iframeSandbox, extraPrefs);
-        this._createCleanupTask(cleanupFunction);
-      }
-
-      // Phase 3: Here we want to test that a third-party context doesn't
-      // get blocked with user interaction present
-      if (userInteractionTest) {
-        this._createUserInteractionTask(name, callbackTracking, callbackNonTracking,
-                                        runInPrivateWindow, iframeSandbox, extraPrefs);
-        this._createCleanupTask(cleanupFunction);
-      }
-    }
-  },
-
-  async interactWithTracker() {
-    let windowClosed = new Promise(resolve => {
-      Services.ww.registerNotification(function notification(aSubject, aTopic, aData) {
-        if (aTopic == "domwindowclosed") {
-          Services.ww.unregisterNotification(notification);
-          resolve();
-        }
-      });
-    });
-
-    info("Let's interact with the tracker");
-    window.open(TEST_3RD_PARTY_DOMAIN + TEST_PATH + "3rdPartyOpenUI.html");
-    await windowClosed;
-  },
-
-  async _setupTest(win, cookieBehavior, blockingByContentBlockingRTUI,
-                   extraPrefs) {
-    await SpecialPowers.flushPrefEnv();
-    await SpecialPowers.pushPrefEnv({"set": [
-      ["dom.storage_access.enabled", true],
-      ["browser.contentblocking.allowlist.annotations.enabled", blockingByContentBlockingRTUI],
-      ["browser.contentblocking.allowlist.storage.enabled", blockingByContentBlockingRTUI],
-      ["network.cookie.cookieBehavior", cookieBehavior],
-      ["privacy.trackingprotection.enabled", false],
-      ["privacy.trackingprotection.pbmode.enabled", false],
-      ["privacy.trackingprotection.annotate_channels", cookieBehavior != BEHAVIOR_ACCEPT],
-      [win.ContentBlocking.prefIntroCount, win.ContentBlocking.MAX_INTROS],
-      ["privacy.restrict3rdpartystorage.userInteractionRequiredForHosts", "tracking.example.com,tracking.example.org"],
-    ]});
-
-    if (extraPrefs && Array.isArray(extraPrefs) && extraPrefs.length) {
-      await SpecialPowers.pushPrefEnv({"set": extraPrefs });
-
-      for (let item of extraPrefs) {
-        // When setting up skip URLs, we need to wait to ensure our prefs
-        // actually take effect.  In order to do this, we set up a skip list
-        // observer and wait until it calls us back.
-        if (item[0] == "urlclassifier.trackingAnnotationSkipURLs") {
-          info("Waiting for the skip list service to initialize...");
-          let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"]
-                             .getService(Ci.nsIURIClassifier);
-          let feature = classifier.getFeatureByName("tracking-annotation");
-          await TestUtils.waitForCondition(() => feature.skipHostList == item[1].toLowerCase(),
-                                           "Skip list service initialized");
-          break;
-        }
-      }
-    }
-
-    await UrlClassifierTestUtils.addTestTrackers();
-  },
-
-  _createTask(options) {
-    add_task(async function() {
-      info("Starting " + (options.cookieBehavior != BEHAVIOR_ACCEPT ? "blocking" : "non-blocking") + " cookieBehavior (" + options.cookieBehavior + ") and " +
-                         (options.blockingByContentBlockingRTUI ? "" : "no") + " contentBlocking third-party cookies UI with" +
-                         (options.allowList ? "" : "out") + " allow list test " + options.name +
-                         " running in a " + (options.runInPrivateWindow ? "private" : "normal") + " window " +
-                         " with iframe sandbox set to " + options.iframeSandbox +
-                         " and access removal set to " + options.accessRemoval +
-                         (typeof options.thirdPartyPage == "string" ? (
-                            " and third party page set to " + options.thirdPartyPage) : ""));
-
-      is(!!options.callbackAfterRemoval, !!options.accessRemoval,
-         "callbackAfterRemoval must be passed when accessRemoval is non-null");
-
-      let win = window;
-      if (options.runInPrivateWindow) {
-        win = OpenBrowserWindow({private: true});
-        await TestUtils.topicObserved("browser-delayed-startup-finished");
-      }
-
-      await AntiTracking._setupTest(win, options.cookieBehavior,
-                                    options.blockingByContentBlockingRTUI,
-                                    options.extraPrefs);
-
-      let cookieBlocked = 0;
-      let listener = {
-        onContentBlockingEvent(webProgress, request, event) {
-          if ((event & options.expectedBlockingNotifications)) {
-            ++cookieBlocked;
-          }
-        },
-      };
-      win.gBrowser.addProgressListener(listener);
-
-      info("Creating a new tab");
-      let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
-      win.gBrowser.selectedTab = tab;
-
-      let browser = win.gBrowser.getBrowserForTab(tab);
-      await BrowserTestUtils.browserLoaded(browser);
-
-      if (options.allowList) {
-        info("Disabling content blocking for this page");
-        win.ContentBlocking.disableForCurrentPage();
-
-        // The previous function reloads the browser, so wait for it to load again!
-        await BrowserTestUtils.browserLoaded(browser);
-      }
+const {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
 
-      info("Creating a 3rd party content");
-      let doAccessRemovalChecks = typeof options.accessRemoval == "string" &&
-                                  options.cookieBehavior == BEHAVIOR_REJECT_TRACKER &&
-                                  options.blockingByContentBlockingRTUI &&
-                                  !options.allowList;
-      let thirdPartyPage;
-      if (typeof options.thirdPartyPage == "string") {
-        thirdPartyPage = options.thirdPartyPage;
-      } else {
-        thirdPartyPage = TEST_3RD_PARTY_PAGE;
-      }
-      await ContentTask.spawn(browser,
-                              { page: thirdPartyPage,
-                                nextPage: TEST_4TH_PARTY_PAGE,
-                                callback: options.callback.toString(),
-                                callbackAfterRemoval: options.callbackAfterRemoval ?
-                                  options.callbackAfterRemoval.toString() : null,
-                                accessRemoval: options.accessRemoval,
-                                iframeSandbox: options.iframeSandbox,
-                                allowList: options.allowList,
-                                doAccessRemovalChecks },
-                              async function(obj) {
-        let id = "id" + Math.random();
-        await new content.Promise(resolve => {
-          let ifr = content.document.createElement("iframe");
-          ifr.id = id;
-          ifr.onload = function() {
-            info("Sending code to the 3rd party content");
-            let callback = obj.allowList + "!!!" + obj.callback;
-            ifr.contentWindow.postMessage(callback, "*");
-          };
-          if (typeof obj.iframeSandbox == "string") {
-            ifr.setAttribute("sandbox", obj.iframeSandbox);
-          }
-
-          content.addEventListener("message", function msg(event) {
-            if (event.data.type == "finish") {
-              content.removeEventListener("message", msg);
-              resolve();
-              return;
-            }
-
-            if (event.data.type == "ok") {
-              ok(event.data.what, event.data.msg);
-              return;
-            }
-
-            if (event.data.type == "info") {
-              info(event.data.msg);
-              return;
-            }
-
-            ok(false, "Unknown message");
-          });
-
-          content.document.body.appendChild(ifr);
-          ifr.src = obj.page;
-        });
-
-        if (obj.doAccessRemovalChecks) {
-          info(`Running after removal checks (${obj.accessRemoval})`);
-          switch (obj.accessRemoval) {
-          case "navigate-subframe":
-            await new content.Promise(resolve => {
-              let ifr = content.document.getElementById(id);
-              let oldWindow = ifr.contentWindow;
-              ifr.onload = function() {
-                info("Sending code to the old 3rd party content");
-                oldWindow.postMessage(obj.callbackAfterRemoval, "*");
-              };
-              if (typeof obj.iframeSandbox == "string") {
-                ifr.setAttribute("sandbox", obj.iframeSandbox);
-              }
-
-              content.addEventListener("message", function msg(event) {
-                if (event.data.type == "finish") {
-                  content.removeEventListener("message", msg);
-                  resolve();
-                  return;
-                }
-
-                if (event.data.type == "ok") {
-                  ok(event.data.what, event.data.msg);
-                  return;
-                }
-
-                if (event.data.type == "info") {
-                  info(event.data.msg);
-                  return;
-                }
-
-                ok(false, "Unknown message");
-              });
-
-              ifr.src = obj.nextPage;
-            });
-            break;
-          default:
-            ok(false, "Unexpected accessRemoval code passed: " + obj.accessRemoval);
-            break;
-          }
-        }
-      });
-
-      if (options.allowList) {
-        info("Enabling content blocking for this page");
-        win.ContentBlocking.enableForCurrentPage();
-
-        // The previous function reloads the browser, so wait for it to load again!
-        await BrowserTestUtils.browserLoaded(browser);
-      }
-
-      win.gBrowser.removeProgressListener(listener);
-
-      is(!!cookieBlocked, !!options.expectedBlockingNotifications, "Checking cookie blocking notifications");
-
-      info("Removing the tab");
-      BrowserTestUtils.removeTab(tab);
-
-      if (options.runInPrivateWindow) {
-        win.close();
-      }
-    });
-  },
-
-  _createCleanupTask(cleanupFunction) {
-    add_task(async function() {
-      info("Cleaning up.");
-      if (cleanupFunction) {
-        await cleanupFunction();
-      }
-    });
-  },
-
-  _createWindowOpenTask(name, blockingCallback, nonBlockingCallback, runInPrivateWindow,
-                        iframeSandbox, extraPrefs) {
-    add_task(async function() {
-      info("Starting window-open test " + name);
-
-      let win = window;
-      if (runInPrivateWindow) {
-        win = OpenBrowserWindow({private: true});
-        await TestUtils.topicObserved("browser-delayed-startup-finished");
-      }
-
-      await AntiTracking._setupTest(win, BEHAVIOR_REJECT_TRACKER, true, extraPrefs);
-
-      info("Creating a new tab");
-      let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
-      win.gBrowser.selectedTab = tab;
-
-      let browser = win.gBrowser.getBrowserForTab(tab);
-      await BrowserTestUtils.browserLoaded(browser);
-
-      let pageURL = TEST_3RD_PARTY_PAGE_WO;
-      if (gFeatures == "noopener") {
-        pageURL += "?noopener";
-      }
-
-      info("Creating a 3rd party content");
-      await ContentTask.spawn(browser,
-                              { page: pageURL,
-                                blockingCallback: blockingCallback.toString(),
-                                nonBlockingCallback: nonBlockingCallback.toString(),
-                                iframeSandbox,
-                              },
-                              async function(obj) {
-        await new content.Promise(resolve => {
-          let ifr = content.document.createElement("iframe");
-          ifr.onload = function() {
-            info("Sending code to the 3rd party content");
-            ifr.contentWindow.postMessage(obj, "*");
-          };
-          if (typeof obj.iframeSandbox == "string") {
-            ifr.setAttribute("sandbox", obj.iframeSandbox);
-          }
-
-          content.addEventListener("message", function msg(event) {
-            if (event.data.type == "finish") {
-              content.removeEventListener("message", msg);
-              resolve();
-              return;
-            }
-
-            if (event.data.type == "ok") {
-              ok(event.data.what, event.data.msg);
-              return;
-            }
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/antitracking_head.js",
+  this);
 
-            if (event.data.type == "info") {
-              info(event.data.msg);
-              return;
-            }
-
-            ok(false, "Unknown message");
-          });
-
-          content.document.body.appendChild(ifr);
-          ifr.src = obj.page;
-        });
-      });
-
-      info("Removing the tab");
-      BrowserTestUtils.removeTab(tab);
-
-      if (runInPrivateWindow) {
-        win.close();
-      }
-    });
-  },
-
-  _createUserInteractionTask(name, blockingCallback, nonBlockingCallback,
-                             runInPrivateWindow, iframeSandbox, extraPrefs) {
-    add_task(async function() {
-      info("Starting user-interaction test " + name);
-
-      let win = window;
-      if (runInPrivateWindow) {
-        win = OpenBrowserWindow({private: true});
-        await TestUtils.topicObserved("browser-delayed-startup-finished");
-      }
-
-      await AntiTracking._setupTest(win, BEHAVIOR_REJECT_TRACKER, true, extraPrefs);
-
-      info("Creating a new tab");
-      let tab = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
-      win.gBrowser.selectedTab = tab;
-
-      let browser = win.gBrowser.getBrowserForTab(tab);
-      await BrowserTestUtils.browserLoaded(browser);
-
-      info("Creating a 3rd party content");
-      await ContentTask.spawn(browser,
-                              { page: TEST_3RD_PARTY_PAGE_UI,
-                                popup: TEST_POPUP_PAGE,
-                                blockingCallback: blockingCallback.toString(),
-                                iframeSandbox,
-                              },
-                              async function(obj) {
-        let ifr = content.document.createElement("iframe");
-        let loading = new content.Promise(resolve => { ifr.onload = resolve; });
-        if (typeof obj.iframeSandbox == "string") {
-          ifr.setAttribute("sandbox", obj.iframeSandbox);
-        }
-        content.document.body.appendChild(ifr);
-        ifr.src = obj.page;
-        await loading;
-
-        info("The 3rd party content should not have access to first party storage.");
-        await new content.Promise(resolve => {
-          content.addEventListener("message", function msg(event) {
-            if (event.data.type == "finish") {
-              content.removeEventListener("message", msg);
-              resolve();
-              return;
-            }
-
-            if (event.data.type == "ok") {
-              ok(event.data.what, event.data.msg);
-              return;
-            }
-
-            if (event.data.type == "info") {
-              info(event.data.msg);
-              return;
-            }
-
-            ok(false, "Unknown message");
-          });
-          ifr.contentWindow.postMessage({ callback: obj.blockingCallback }, "*");
-        });
-
-        let windowClosed = new content.Promise(resolve => {
-          Services.ww.registerNotification(function notification(aSubject, aTopic, aData) {
-            if (aTopic == "domwindowclosed") {
-              Services.ww.unregisterNotification(notification);
-              resolve();
-            }
-          });
-        });
-
-        info("Opening a window from the iframe.");
-        ifr.contentWindow.open(obj.popup);
-
-        info("Let's wait for the window to be closed");
-        await windowClosed;
-
-        info("First time, the 3rd party content should not have access to first party storage " +
-             "because the tracker did not have user interaction");
-        await new content.Promise(resolve => {
-          content.addEventListener("message", function msg(event) {
-            if (event.data.type == "finish") {
-              content.removeEventListener("message", msg);
-              resolve();
-              return;
-            }
-
-            if (event.data.type == "ok") {
-              ok(event.data.what, event.data.msg);
-              return;
-            }
-
-            if (event.data.type == "info") {
-              info(event.data.msg);
-              return;
-            }
-
-            ok(false, "Unknown message");
-          });
-          ifr.contentWindow.postMessage({ callback: obj.blockingCallback }, "*");
-        });
-      });
-
-      await AntiTracking.interactWithTracker();
-
-      await ContentTask.spawn(browser,
-                              { page: TEST_3RD_PARTY_PAGE_UI,
-                                popup: TEST_POPUP_PAGE,
-                                nonBlockingCallback: nonBlockingCallback.toString(),
-                                iframeSandbox,
-                              },
-                              async function(obj) {
-        let ifr = content.document.createElement("iframe");
-        let loading = new content.Promise(resolve => { ifr.onload = resolve; });
-        if (typeof obj.iframeSandbox == "string") {
-          ifr.setAttribute("sandbox", obj.iframeSandbox);
-        }
-        content.document.body.appendChild(ifr);
-        ifr.src = obj.page;
-        await loading;
-
-        let windowClosed = new content.Promise(resolve => {
-          Services.ww.registerNotification(function notification(aSubject, aTopic, aData) {
-            if (aTopic == "domwindowclosed") {
-              Services.ww.unregisterNotification(notification);
-              resolve();
-            }
-          });
-        });
-
-        info("Opening a window from the iframe.");
-        ifr.contentWindow.open(obj.popup);
-
-        info("Let's wait for the window to be closed");
-        await windowClosed;
-
-        info("The 3rd party content should now have access to first party storage.");
-        await new content.Promise(resolve => {
-          content.addEventListener("message", function msg(event) {
-            if (event.data.type == "finish") {
-              content.removeEventListener("message", msg);
-              resolve();
-              return;
-            }
-
-            if (event.data.type == "ok") {
-              ok(event.data.what, event.data.msg);
-              return;
-            }
-
-            if (event.data.type == "info") {
-              info(event.data.msg);
-              return;
-            }
-
-            ok(false, "Unknown message");
-          });
-          ifr.contentWindow.postMessage({ callback: obj.nonBlockingCallback }, "*");
-        });
-      });
-
-      info("Removing the tab");
-      BrowserTestUtils.removeTab(tab);
-
-      if (runInPrivateWindow) {
-        win.close();
-      }
-    });
-  },
-};
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/storageprincipal_head.js",
+  this);
--- a/toolkit/components/antitracking/test/browser/imageCacheWorker.js
+++ b/toolkit/components/antitracking/test/browser/imageCacheWorker.js
@@ -1,10 +1,12 @@
 /* import-globals-from head.js */
+/* import-globals-from antitracking_head.js */
 /* import-globals-from browser_imageCache4.js */
+
 AntiTracking.runTest("Image cache - should load the image three times.",
   // blocking callback
   async _ => {
     // Let's load the image twice here.
     let img = document.createElement("img");
     document.body.appendChild(img);
     img.src = "https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/image.sjs";
     await new Promise(resolve => { img.onload = resolve; });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/matchAll.js
@@ -0,0 +1,13 @@
+self.addEventListener("message", async e => {
+  let clients = await self.clients.matchAll({type: "window", includeUncontrolled: true});
+
+  let hasWindow = false;
+  for (let client of clients) {
+    if (e.data == client.url) {
+      hasWindow = true;
+      break;
+    }
+  }
+