tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdocshell/base/nsDocShell.cpp | 15++++++++++++++-
Mdocshell/test/browser/browser.toml | 5+++++
Adocshell/test/browser/browser_bug2004165.js | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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); + } +);