Bug 1824040: Make the loading URL opened in _blank target to be the target of Session Restore r=mak,smaug,farre,whimboo

Differential Revision: https://phabricator.services.mozilla.com/D173790
This commit is contained in:
Daisuke Akatsuka 2023-04-14 01:59:44 +00:00
parent 9cb4b77faa
commit 73dc3820cc
15 changed files with 266 additions and 26 deletions

View File

@ -6651,7 +6651,7 @@
isURL: true,
});
this.mBrowser._initialURI = originalLocation;
this.mBrowser.browsingContext.nonWebControlledBlankURI = originalLocation;
if (this.mTab.selected && !gBrowser.userTypedValue) {
gURLBar.setURI();
}

View File

@ -69,7 +69,7 @@ add_task(async function normal_page__foreground__abort() {
finalState: {
tab: WAIT_A_BIT_LOADING_TITLE,
urlbar: WAIT_A_BIT_URL,
history: [],
history: [WAIT_A_BIT_URL],
},
});
});
@ -95,6 +95,16 @@ add_task(async function normal_page__foreground__timeout() {
});
});
add_task(async function normal_page__foreground__session_restore() {
await doSessionRestoreTest({
link: "wait-a-bit--blank-target",
openBy: OPEN_BY.CLICK,
openAs: OPEN_AS.FOREGROUND,
expectedSessionHistory: [WAIT_A_BIT_URL],
expectedSessionRestored: true,
});
});
add_task(async function normal_page__background__click() {
await doTestInSameWindow({
link: "wait-a-bit--blank-target",
@ -178,3 +188,12 @@ add_task(async function normal_page__background__timeout() {
},
});
});
add_task(async function normal_page__background__session_restore() {
await doSessionRestoreTest({
link: "wait-a-bit--blank-target",
openBy: OPEN_BY.CLICK,
openAs: OPEN_AS.BACKGROUND,
expectedSessionRestored: false,
});
});

View File

@ -73,3 +73,12 @@ add_task(async function normal_page__by_script__timeout() {
},
});
});
add_task(async function normal_page__by_script__session_restore() {
await doSessionRestoreTest({
link: "wait-a-bit--by-script",
openBy: OPEN_BY.CLICK,
openAs: OPEN_AS.FOREGROUND,
expectedSessionRestored: false,
});
});

View File

@ -75,3 +75,12 @@ add_task(async function normal_page__no_target__timeout() {
},
});
});
add_task(async function normal_page__no_target__session_restore() {
await doSessionRestoreTest({
link: "wait-a-bit--no-target",
openBy: OPEN_BY.CLICK,
openAs: OPEN_AS.FOREGROUND,
expectedSessionRestored: false,
});
});

View File

@ -74,6 +74,15 @@ add_task(async function normal_page__other_target__foreground__timeout() {
});
});
add_task(async function normal_page__foreground__session_restore() {
await doSessionRestoreTest({
link: "wait-a-bit--other-target",
openBy: OPEN_BY.CLICK,
openAs: OPEN_AS.FOREGROUND,
expectedSessionRestored: false,
});
});
add_task(async function normal_page__other_target__background() {
await doTestInSameWindow({
link: "wait-a-bit--other-target",
@ -136,3 +145,12 @@ add_task(async function normal_page__other_target__background__timeout() {
},
});
});
add_task(async function normal_page__foreground__session_restore() {
await doSessionRestoreTest({
link: "wait-a-bit--other-target",
openBy: OPEN_BY.CLICK,
openAs: OPEN_AS.BACKGROUND,
expectedSessionRestored: false,
});
});

View File

@ -54,22 +54,12 @@ async function doTestInSameWindow({
HOME_URL
);
const onLoadStarted = new Promise(resolve =>
gBrowser.addTabsProgressListener({
onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
gBrowser.removeTabsProgressListener(this);
resolve(gBrowser.getTabForBrowser(aBrowser));
}
},
})
);
info(`Open link for ${link} by ${openBy} as ${openAs}`);
const onNewTabCreated = waitForNewTabWithLoadRequest();
const href = await openLink(browser, link, openBy, openAs);
info("Wait until starting to load in the target tab");
const target = await onLoadStarted;
const target = await onNewTabCreated;
Assert.equal(target.selected, openAs === OPEN_AS.FOREGROUND);
Assert.equal(gURLBar.value, loadingState.urlbar);
Assert.equal(target.textLabel.textContent, loadingState.tab);
@ -119,7 +109,10 @@ async function doTestWithNewWindow({ link, expectedSetURICalled }) {
});
let isSetURIWhileLoading = false;
sandbox.stub(win.gURLBar, "setURI").callsFake(uri => {
if (!uri && win.gBrowser.selectedBrowser._initialURI) {
if (
!uri &&
win.gBrowser.selectedBrowser.browsingContext.nonWebControlledBlankURI
) {
isSetURIWhileLoading = true;
}
});
@ -132,7 +125,7 @@ async function doTestWithNewWindow({ link, expectedSetURICalled }) {
Assert.equal(isSetURIWhileLoading, expectedSetURICalled);
Assert.equal(
!!win.gBrowser.selectedBrowser._initialURI,
!!win.gBrowser.selectedBrowser.browsingContext.nonWebControlledBlankURI,
expectedSetURICalled
);
@ -142,6 +135,61 @@ async function doTestWithNewWindow({ link, expectedSetURICalled }) {
await SpecialPowers.popPrefEnv();
}
async function doSessionRestoreTest({
link,
openBy,
openAs,
expectedSessionHistory,
expectedSessionRestored,
}) {
await BrowserTestUtils.withNewTab("about:blank", async browser => {
BrowserTestUtils.loadURIString(browser, HOME_URL);
await BrowserTestUtils.browserLoaded(
gBrowser.selectedBrowser,
false,
HOME_URL
);
info(`Open link for ${link} by ${openBy} as ${openAs}`);
const onNewTabCreated = waitForNewTabWithLoadRequest();
const href = await openLink(browser, link, openBy, openAs);
const target = await onNewTabCreated;
await BrowserTestUtils.waitForCondition(
() =>
target.linkedBrowser.browsingContext
.mostRecentLoadingSessionHistoryEntry
);
info("Close the session");
const sessionPromise = BrowserTestUtils.waitForSessionStoreUpdate(target);
BrowserTestUtils.removeTab(target);
await sessionPromise;
info("Restore the session");
const restoredTab = SessionStore.undoCloseTab(window, 0);
await BrowserTestUtils.browserLoaded(restoredTab.linkedBrowser);
info("Check the loaded URL of restored tab");
Assert.equal(
restoredTab.linkedBrowser.currentURI.spec === href,
expectedSessionRestored
);
if (expectedSessionRestored) {
info("Check the session history of restored tab");
const sessionHistory = await new Promise(r =>
SessionStore.getSessionHistory(restoredTab, r)
);
Assert.deepEqual(
sessionHistory.entries.map(e => e.url),
expectedSessionHistory
);
}
BrowserTestUtils.removeTab(restoredTab);
});
}
async function openLink(browser, link, openBy, openAs) {
let href;
const openAsBackground = openAs === OPEN_AS.BACKGROUND;
@ -195,3 +243,16 @@ async function synthesizeMouse(browser, link, event) {
}
);
}
async function waitForNewTabWithLoadRequest() {
return new Promise(resolve =>
gBrowser.addTabsProgressListener({
onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
gBrowser.removeTabsProgressListener(this);
resolve(gBrowser.getTabForBrowser(aBrowser));
}
},
})
);
}

View File

@ -459,13 +459,15 @@ export var SessionStore = {
aBrowser,
aBrowsingContext,
aPermanentKey,
aData
aData,
aForStorage
) {
return SessionStoreInternal.updateSessionStoreFromTablistener(
aBrowser,
aBrowsingContext,
aPermanentKey,
aData
aData,
aForStorage
);
},
@ -1357,7 +1359,8 @@ var SessionStoreInternal = {
browser,
browsingContext,
permanentKey,
update
update,
forStorage = false
) {
permanentKey = browser?.permanentKey ?? permanentKey;
if (!permanentKey) {
@ -1380,10 +1383,18 @@ var SessionStoreInternal = {
);
if (listener) {
let historychange = listener.collect(permanentKey, browsingContext, {
collectFull: !!update.sHistoryNeeded,
writeToCache: false,
});
let historychange =
// If it is not the scheduled update (tab closed, window closed etc),
// try to store the loading non-web-controlled page opened in _blank
// first.
(forStorage &&
lazy.SessionHistory.collectNonWebControlledBlankLoadingSession(
browsingContext
)) ||
listener.collect(permanentKey, browsingContext, {
collectFull: !!update.sHistoryNeeded,
writeToCache: false,
});
if (historychange) {
update.data.historychange = historychange;

View File

@ -1,6 +1,7 @@
[DEFAULT]
tags = local
[test_restore_loading_tab.py]
[test_restore_manually_with_pinned_tabs.py]
[test_restore_windows_after_restart_and_quit.py]
[test_restore_windows_after_windows_shutdown.py]

View File

@ -0,0 +1,69 @@
# 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/.
from urllib.parse import quote
from marionette_harness import MarionetteTestCase, WindowManagerMixin
def inline(doc):
return "data:text/html;charset=utf-8,{}".format(quote(doc))
class TestRestoreLoadingPage(WindowManagerMixin, MarionetteTestCase):
def setUp(self):
super(TestRestoreLoadingPage, self).setUp()
self.delayed_page = self.marionette.absolute_url("slow")
def do_test(self, html, is_restoring_expected):
self.marionette.navigate(inline(html.format(self.delayed_page)))
link = self.marionette.find_element("id", "link")
link.click()
self.marionette.restart(in_app=True)
with self.marionette.using_context("chrome"):
urls = self.marionette.execute_script(
"return gBrowser.tabs.map(t => t.linkedBrowser.currentURI.spec);"
)
if is_restoring_expected:
self.assertEqual(
len(urls),
2,
msg="The tab opened should be restored",
)
self.assertEqual(
urls[1],
self.delayed_page,
msg="The tab restored is correct",
)
else:
self.assertEqual(
len(urls),
1,
msg="The tab opened should not be restored",
)
self.close_all_tabs()
def test_target_blank(self):
self.do_test("<a id='link' href='{}' target='_blank'>click</a>", True)
def test_target_other(self):
self.do_test("<a id='link' href='{}' target='other'>click</a>", False)
def test_by_script(self):
self.do_test(
"""
<a id='link'>click</a>
<script>
document.getElementById("link").addEventListener(
"click",
() => window.open("{}", "_blank");
)
</script>
""",
False,
)

View File

@ -384,7 +384,8 @@ export class UrlbarInput {
uri ||
(this.window.gBrowser.selectedBrowser.browsingContext.sessionHistory
?.count === 0 &&
this.window.gBrowser.selectedBrowser._initialURI) ||
this.window.gBrowser.selectedBrowser.browsingContext
.nonWebControlledBlankURI) ||
this.window.gBrowser.currentURI;
// Strip off usernames and passwords for the location bar
try {

View File

@ -3015,6 +3015,16 @@ void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId,
widget->StopAsyncAutoscroll(guid);
}
already_AddRefed<nsISHEntry>
CanonicalBrowsingContext::GetMostRecentLoadingSessionHistoryEntry() {
if (mLoadingEntries.IsEmpty()) {
return nullptr;
}
RefPtr<SessionHistoryEntry> entry = mLoadingEntries.LastElement().mEntry;
return entry.forget();
}
NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,

View File

@ -379,6 +379,8 @@ class CanonicalBrowsingContext final : public BrowsingContext {
void SetForceAppWindowActive(bool, ErrorResult&);
void RecomputeAppWindowVisibility();
already_AddRefed<nsISHEntry> GetMostRecentLoadingSessionHistoryEntry();
protected:
// Called when the browsing context is being discarded.
void CanonicalDiscard();

View File

@ -411,6 +411,8 @@ interface CanonicalBrowsingContext : BrowsingContext {
*/
undefined stopApzAutoscroll(unsigned long long aScrollId,
unsigned long aPresShellId);
readonly attribute nsISHEntry? mostRecentLoadingSessionHistoryEntry;
};
[Exposed=Window, ChromeOnly]

View File

@ -69,7 +69,7 @@ var SessionStoreFuncInternal = {
);
},
updateSessionStoreForStorage: function SSF_updateSessionStoreForWindow(
updateSessionStoreForStorage: function SSF_updateSessionStoreForStorage(
aBrowser,
aBrowsingContext,
aPermanentKey,
@ -80,7 +80,8 @@ var SessionStoreFuncInternal = {
aBrowser,
aBrowsingContext,
aPermanentKey,
{ data: { storage: aData }, epoch: aEpoch }
{ data: { storage: aData }, epoch: aEpoch },
true
);
},
};

View File

@ -36,6 +36,12 @@ export var SessionHistory = Object.freeze({
);
},
collectNonWebControlledBlankLoadingSession(browsingContext) {
return SessionHistoryInternal.collectNonWebControlledBlankLoadingSession(
browsingContext
);
},
restore(docShell, tabData) {
if (Services.appinfo.sessionHistoryInParent) {
throw new Error("Use SessionHistory.restoreFromParent instead");
@ -151,6 +157,27 @@ var SessionHistoryInternal = {
return data;
},
collectNonWebControlledBlankLoadingSession(browsingContext) {
if (
browsingContext.sessionHistory?.count === 0 &&
browsingContext.nonWebControlledBlankURI &&
browsingContext.mostRecentLoadingSessionHistoryEntry
) {
return {
entries: [
this.serializeEntry(
browsingContext.mostRecentLoadingSessionHistoryEntry
),
],
index: 0,
fromIdx: -1,
requestedIndex: browsingContext.sessionHistory.requestedIndex + 1,
};
}
return null;
},
/**
* Get an object that is a serialized representation of a History entry.
*