commit fa9c48352a4d27842232dc14cdd3f85f02add432
parent fa76b18cd3e1404e487620f3ba0048f282058cb1
Author: Vincent Hilla <vhilla@mozilla.com>
Date: Wed, 10 Dec 2025 15:17:07 +0000
Bug 2004165 - Use async load path when restoring initial about:blank from session history. r=dom-core,hsivonen
Differential Revision: https://phabricator.services.mozilla.com/D275780
Diffstat:
3 files changed, 132 insertions(+), 1 deletion(-)
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
@@ -10846,6 +10846,18 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
inheritPrincipal = inheritAttrs && !uri->SchemeIs("data");
}
+ // If a page opens about:blank, it will have a content principal.
+ // If it is then restored after a restart, we might not have initialized
+ // UsesOAC for it. If this is the case, do a normal load (bug 2004647).
+ // XXX bug 2005205 tracks removing this workaround.
+ const auto shouldSkipSyncLoadForSHRestore = [&] {
+ return aLoadState->LoadIsFromSessionHistory() &&
+ aLoadState->PrincipalToInherit() &&
+ !mBrowsingContext->Group()
+ ->UsesOriginAgentCluster(aLoadState->PrincipalToInherit())
+ .isSome();
+ };
+
MOZ_ASSERT_IF(NS_IsAboutBlankAllowQueryAndFragment(uri) &&
aLoadState->PrincipalToInherit(),
inheritPrincipal);
@@ -10853,7 +10865,8 @@ nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
const bool isAboutBlankLoadOntoInitialAboutBlank =
!aLoadState->IsInitialAboutBlankHandlingProhibited() &&
IsAboutBlankLoadOntoInitialAboutBlank(uri,
- aLoadState->PrincipalToInherit());
+ aLoadState->PrincipalToInherit()) &&
+ !shouldSkipSyncLoadForSHRestore();
// FIXME We still have a ton of codepaths that don't pass through
// DocumentLoadListener, so probably need to create session history info
diff --git a/docshell/test/browser/browser.toml b/docshell/test/browser/browser.toml
@@ -274,6 +274,11 @@ skip-if = [
["browser_bug1798780.js"]
+["browser_bug2004165.js"]
+skip-if = [
+ "debug", # bug 2005202
+]
+
["browser_click_link_within_view_source.js"]
["browser_closewatcher_integration.js"]
diff --git a/docshell/test/browser/browser_bug2004165.js b/docshell/test/browser/browser_bug2004165.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { TabStateFlusher } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
+);
+
+// Go to example.com, do window.open() to obtain an initial about:blank with content principal
+const ABOUT_BLANK_FROM_CONTENT_STATE = {
+ entries: [
+ {
+ url: "about:blank",
+ principalToInherit_base64: '{"1":{"0":"https://example.com/"}}',
+ triggeringPrincipal_base64: '{"1":{"0":"https://example.com/"}}',
+ },
+ ],
+ index: 1,
+};
+
+// Ensure ABOUT_BLANK_FROM_CONTENT_STATE matches a tab opened from a content document
+add_task(async function test_about_blank_tab_state_matches_fixture() {
+ const openerTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com/"
+ );
+
+ const newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:blank",
+ true
+ );
+ await SpecialPowers.spawn(openerTab.linkedBrowser, [], () => {
+ content.open("about:blank");
+ });
+ const aboutBlankTab = await newTabPromise;
+
+ await TabStateFlusher.flush(aboutBlankTab.linkedBrowser);
+ const state = JSON.parse(SessionStore.getTabState(aboutBlankTab));
+
+ is(state.entries.length, 1, "Got one SH entry");
+ const actualEntryFixture = {
+ url: state.entries[0].url,
+ principalToInherit_base64: state.entries[0].principalToInherit_base64,
+ triggeringPrincipal_base64: state.entries[0].triggeringPrincipal_base64,
+ };
+ Assert.deepEqual(
+ actualEntryFixture,
+ ABOUT_BLANK_FROM_CONTENT_STATE.entries[0]
+ );
+
+ BrowserTestUtils.removeTab(aboutBlankTab);
+ BrowserTestUtils.removeTab(openerTab);
+});
+
+// Crashtest for bug 2004165 and bug 2005202
+add_task(
+ async function test_restore_initial_about_blank_with_content_principal() {
+ // Need to restore a whole window such that that restoring the about:blank
+ // counts as the initial load and hits the synchronous path.
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // browserLoaded doesn't work reliably for a synchronous load in a different process
+ let restored = BrowserTestUtils.waitForEvent(
+ win.gBrowser.tabContainer,
+ "SSTabRestored"
+ );
+
+ const windowState = {
+ windows: [
+ {
+ tabs: [ABOUT_BLANK_FROM_CONTENT_STATE],
+ selected: 1,
+ },
+ ],
+ selectedWindow: 1,
+ };
+ SessionStore.setWindowState(win, JSON.stringify(windowState), true);
+ await restored;
+
+ ok(true, "Did not crash");
+
+ const tab = win.gBrowser.selectedTab;
+
+ // Sanity check the restored tab
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ let principal = content.document.nodePrincipal;
+ // The crash occured in the synchronous load path, so verify it was taken.
+ // That should be equivalent to the document being initial and committed.
+ const isInitialCommitted =
+ content.document.isInitialDocument &&
+ !content.document.isUncommittedInitialDocument;
+ // XXX The initial fix for bug 2004165 is to skip the sync path. So
+ // assert the opposite till a better fix is implemented. (bug 2005205)
+ Assert.ok(
+ !isInitialCommitted,
+ "about:blank was not restored as initial document"
+ );
+ Assert.ok(
+ principal.isContentPrincipal,
+ "Restored about:blank document has a content principal"
+ );
+ Assert.equal(
+ principal.origin,
+ "https://example.com",
+ "Restored about:blank inherits the origin from https://example.com/"
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ }
+);