Bug 1819675 - Introduce a feature pref to toggle the recently-closed tabs from all windows behavior.r=sclements,dao,extension-reviewers,fxview-reviewers,robwu,sessionstore-reviewers

* Add a default-true pref to provide an escape hatch allowing us to revert to previous behavior
* in which recently-closed tabs are per-window,
* and undoing closed tabs restores them to the window they were closed from.
* Ensure we set the pref for tests which depend on its value
* Add some spot-checks in tests with the pref off

Differential Revision: https://phabricator.services.mozilla.com/D179574
This commit is contained in:
Sam Foster
2023-07-06 22:49:52 +00:00
parent b23b17ffa1
commit 1bd6b02cd7
12 changed files with 284 additions and 58 deletions

View File

@@ -1127,6 +1127,14 @@ pref("browser.sessionstore.resume_from_crash", true);
pref("browser.sessionstore.resume_session_once", false);
pref("browser.sessionstore.resuming_after_os_restart", false);
// Toggle for the behavior to include closed tabs from all windows in
// recently-closed tab lists & counts, and re-open tabs into the current window
#ifdef NIGHTLY_BUILD
pref("browser.sessionstore.closedTabsFromAllWindows", true);
#else
pref("browser.sessionstore.closedTabsFromAllWindows", false);
#endif
// Minimal interval between two save operations in milliseconds (while the user is idle).
pref("browser.sessionstore.interval.idle", 3600000); // 1h

View File

@@ -1,4 +1,6 @@
[DEFAULT]
prefs =
browser.sessionstore.closedTabsFromAllWindows=true
[browser_file_close_tabs.js]
[browser_file_menu_import_wizard.js]

View File

@@ -127,6 +127,61 @@ add_task(async function test_recently_closed_tabs_nonprivate() {
openedTabs.length = 0;
});
add_task(async function test_recently_closed_tabs_nonprivate_pref_off() {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", false]],
});
await resetHistory();
is(openedTabs.length, 0, "Got expected openedTabs length");
const win1 = window;
const win2 = await BrowserTestUtils.openNewBrowserWindow();
await openTab(win1, "https://example.com");
// we're going to close a tab and don't want to accidentally close the window when it has 0 tabs
await openTab(win2, "about:about");
await openTab(win2, "https://example.org");
is(openedTabs.length, 3, "Got expected openedTabs length");
info("Checking the menuitem is initially disabled in both windows");
for (let win of [win1, win2]) {
await checkMenu(win, {
menuItemDisabled: true,
});
}
is(win2, BrowserWindowTracker.getTopWindow(), "Check topWindow");
await closeTab(win2.gBrowser.selectedTab);
is(openedTabs.length, 2, "Got expected openedTabs length");
is(
SessionStore.getClosedTabCount(),
1,
"Expect closed tab count of 1 after closing a tab"
);
await checkMenu(win1, {
menuItemDisabled: true,
});
await checkMenu(win2, {
menuItemDisabled: false,
});
// clean up
info("clean up opened window");
const sessionStoreChanged = TestUtils.topicObserved(
"sessionstore-closed-objects-changed"
);
await BrowserTestUtils.closeWindow(win2);
await sessionStoreChanged;
info("starting tab cleanup");
while (gBrowser.tabs.length > 1) {
await closeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
}
info("finished tab cleanup");
openedTabs.length = 0;
SpecialPowers.popPrefEnv();
});
add_task(async function test_recently_closed_tabs_mixed_private() {
await resetHistory();
is(openedTabs.length, 0, "Got expected openedTabs length");
@@ -209,3 +264,90 @@ add_task(async function test_recently_closed_tabs_mixed_private() {
info("finished tab cleanup");
openedTabs.length = 0;
});
add_task(async function test_recently_closed_tabs_mixed_private_pref_off() {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", false]],
});
await resetHistory();
is(openedTabs.length, 0, "Got expected openedTabs length");
is(
SessionStore.getClosedTabCount(),
0,
"Expect closed tab count of 0 after reset"
);
await openTab(window, "about:robots");
await openTab(window, "https://example.com");
const privateWin = await BrowserTestUtils.openNewBrowserWindow({
private: true,
});
await openTab(privateWin, "about:about");
await openTab(privateWin, "https://example.org");
is(openedTabs.length, 4, "Got expected openedTabs length");
for (let win of [window, privateWin]) {
await checkMenu(win, {
menuItemDisabled: true,
});
}
await closeTab(privateWin.gBrowser.selectedTab);
is(openedTabs.length, 3, "Got expected openedTabs length");
is(
SessionStore.getClosedTabCount(privateWin),
1,
"Expect closed tab count of 1 for private windows"
);
is(
SessionStore.getClosedTabCount(window),
0,
"Expect closed tab count of 0 for non-private windows"
);
// the menu should be enabled only for the private window
await checkMenu(window, {
menuItemDisabled: true,
});
await checkMenu(privateWin, {
menuItemDisabled: false,
});
await resetHistory();
is(
SessionStore.getClosedTabCount(privateWin),
0,
"Expect 0 closed tab count after reset"
);
is(
SessionStore.getClosedTabCount(window),
0,
"Expect 0 closed tab count after reset"
);
info("closing tab in non-private window");
await closeTab(window.gBrowser.selectedTab);
is(openedTabs.length, 2, "Got expected openedTabs length");
// the menu should be enabled only for the non-private window
await checkMenu(window, {
menuItemDisabled: false,
});
await checkMenu(privateWin, {
menuItemDisabled: true,
});
// clean up
info("closing private window");
await BrowserTestUtils.closeWindow(privateWin);
await TestUtils.waitForTick();
info("starting tab cleanup");
while (gBrowser.tabs.length > 1) {
await closeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
}
info("finished tab cleanup");
openedTabs.length = 0;
SpecialPowers.popPrefEnv();
});

View File

@@ -7,6 +7,8 @@ support-files =
test_process_flags_chrome.html
helper_origin_attrs_testing.js
file_about_srcdoc.html
prefs =
browser.sessionstore.closedTabsFromAllWindows=true
[browser_addAdjacentNewTab.js]
[browser_addTab_index.js]

View File

@@ -2,6 +2,8 @@
support-files =
head.js
support/test_967000_charEncoding_page.html
prefs =
browser.sessionstore.closedTabsFromAllWindows=true
[browser_1003588_no_specials_in_panel.js]
[browser_1008559_anchor_undo_restore.js]

View File

@@ -1,6 +1,7 @@
[DEFAULT]
tags = webextensions
prefs =
browser.sessionstore.closedTabsFromAllWindows=true
# We don't want to reset this at the end of the test, so that we don't have
# to spawn a new extension child process for each test unit.
dom.ipc.keepProcessesAlive.extension=1

View File

@@ -156,9 +156,7 @@ async function run_test_extension(incognitoOverride, testData) {
await extension.unload();
}
add_task(
async function test_sessions_get_recently_closed_private_incognito_spanning() {
await run_test_extension("spanning", {
const spanningTestData = {
private: {
initialTabURL: "https://example.com/",
tabToClose: "https://example.org/?private",
@@ -176,13 +174,28 @@ add_task(
incognito: false,
},
},
};
add_task(
async function test_sessions_get_recently_closed_private_incognito_spanning() {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", true]],
});
await run_test_extension("spanning", spanningTestData);
SpecialPowers.popPrefEnv();
}
);
add_task(
async function test_sessions_get_recently_closed_private_incognito_spanning_pref_off() {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", false]],
});
await run_test_extension("spanning", spanningTestData);
SpecialPowers.popPrefEnv();
}
);
add_task(
async function test_sessions_get_recently_closed_private_incognito_not_allowed() {
await run_test_extension("not_allowed", {
const notAllowedTestData = {
private: {
initialTabURL: "https://example.com/",
tabToClose: "https://example.org/?private",
@@ -200,30 +213,24 @@ add_task(
incognito: false,
},
},
};
add_task(
async function test_sessions_get_recently_closed_private_incognito_not_allowed() {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", true]],
});
await run_test_extension("not_allowed", notAllowedTestData);
SpecialPowers.popPrefEnv();
}
);
add_task(
async function test_sessions_get_recently_closed_private_incognito_spanning() {
await run_test_extension("spanning", {
private: {
initialTabURL: "https://example.com/",
tabToClose: "https://example.org/?private",
// restore should succeed when incognito is allowed
expected: {
url: "https://example.org/?private",
incognito: true,
},
},
notPrivate: {
initialTabURL: "https://example.com/",
tabToClose: "https://example.org/?notprivate",
expected: {
url: "https://example.org/?notprivate",
incognito: false,
},
},
async function test_sessions_get_recently_closed_private_incognito_not_allowed_pref_off() {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", false]],
});
await run_test_extension("not_allowed", notAllowedTestData);
SpecialPowers.popPrefEnv();
}
);

View File

@@ -1,6 +1,7 @@
[DEFAULT]
support-files = head.js
prefs =
browser.sessionstore.closedTabsFromAllWindows=true
browser.tabs.firefox-view.logLevel=All
[browser_cfr_message.js]

View File

@@ -15,6 +15,13 @@ XPCOMUtils.defineLazyGetter(lazy, "l10n", () => {
return new Localization(["browser/recentlyClosed.ftl"], true);
});
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"closedTabsFromAllWindowsEnabled",
"browser.sessionstore.closedTabsFromAllWindows",
true
);
export var RecentlyClosedTabsAndWindowsMenuUtils = {
/**
* Builds up a document fragment of UI items for the recently closed tabs.
@@ -30,7 +37,9 @@ export var RecentlyClosedTabsAndWindowsMenuUtils = {
getTabsFragment(aWindow, aTagName, aPrefixRestoreAll = false) {
let doc = aWindow.document;
const fragment = doc.createDocumentFragment();
const browserWindows = lazy.SessionStore.getWindows(aWindow);
const browserWindows = lazy.closedTabsFromAllWindowsEnabled
? lazy.SessionStore.getWindows(aWindow)
: [aWindow];
const tabCount = lazy.SessionStore.getClosedTabCount(aWindow);
if (tabCount > 0) {
@@ -112,7 +121,9 @@ export var RecentlyClosedTabsAndWindowsMenuUtils = {
*/
onRestoreAllTabsCommand(aEvent) {
const currentWindow = aEvent.target.ownerGlobal;
const browserWindows = lazy.SessionStore.getWindows(currentWindow);
const browserWindows = lazy.closedTabsFromAllWindowsEnabled
? lazy.SessionStore.getWindows(currentWindow)
: [currentWindow];
for (const sourceWindow of browserWindows) {
const count = lazy.SessionStore.getClosedTabCountForWindow(sourceWindow);
for (let index = 0; index < count; index++) {

View File

@@ -407,6 +407,11 @@ export var SessionStore = {
* @param {Window} [aWindow] Optional window argument used to determine if we're counting for private or non-private windows
*/
getClosedTabCount: function ss_getClosedTabCount(aWindow) {
if (!SessionStoreInternal._closedTabsFromAllWindowsEnabled) {
return this.getClosedTabCountForWindow(
aWindow ?? SessionStoreInternal._getTopWindow()
);
}
return SessionStoreInternal.getClosedTabCount(aWindow);
},
@@ -423,6 +428,11 @@ export var SessionStore = {
* @param {Window} [aWindow] Optional window argument used to determine if we're collecting data for private or non-private windows
*/
getClosedTabData: function ss_getClosedTabData(aWindow) {
if (!SessionStoreInternal._closedTabsFromAllWindowsEnabled) {
return this.getClosedTabDataForWindow(
aWindow ?? SessionStoreInternal._getTopWindow()
);
}
return SessionStoreInternal.getClosedTabData(aWindow);
},
@@ -1201,6 +1211,16 @@ var SessionStoreInternal = {
);
this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
this._closedTabsFromAllWindowsEnabled = this._prefBranch.getBoolPref(
"sessionstore.closedTabsFromAllWindows",
true
);
this._prefBranch.addObserver(
"sessionstore.closedTabsFromAllWindows",
this,
true
);
this._max_windows_undo = this._prefBranch.getIntPref(
"sessionstore.max_windows_undo"
);
@@ -2699,6 +2719,12 @@ var SessionStoreInternal = {
"sessionstore.restore_on_demand"
);
break;
case "sessionstore.closedTabsFromAllWindows":
this._closedTabsFromAllWindowsEnabled = this._prefBranch.getBoolPref(
"sessionstore.closedTabsFromAllWindows",
true
);
break;
}
},

View File

@@ -62,6 +62,7 @@ support-files =
prefs =
network.cookie.cookieBehavior=5
gfx.font_rendering.fallback.async=false
browser.sessionstore.closedTabsFromAllWindows=true
#NB: the following are disabled
# browser_464620_a.html

View File

@@ -147,7 +147,7 @@ add_task(async function test_ClosedTabMethods() {
closedCount = SessionStore.getClosedTabCount();
Assert.equal(3, closedCount, "3 closed tab for all windows");
const allWindowsClosedTabs = SessionStore.getClosedTabData();
let allWindowsClosedTabs = SessionStore.getClosedTabData();
Assert.equal(
closedCount,
allWindowsClosedTabs.length,
@@ -157,6 +157,29 @@ add_task(async function test_ClosedTabMethods() {
Assert.ok(tabData.sourceWindowId, "each tab has a sourceWindowId property");
}
// ***********************************
// check with the pref off
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.closedTabsFromAllWindows", false]],
});
closedCount = SessionStore.getClosedTabCount();
Assert.equal(2, closedCount, "2 closed tabs for the top window");
allWindowsClosedTabs = SessionStore.getClosedTabData();
Assert.equal(
closedCount,
2,
"getClosedTabData returned the number of entries for the top window"
);
for (let tabData of allWindowsClosedTabs) {
Assert.ok(tabData.sourceWindowId, "each tab has a sourceWindowId property");
}
SpecialPowers.popPrefEnv();
//
// ***********************************
sessionStoreUpdated = TestUtils.topicObserved(
"sessionstore-closed-objects-changed"
);