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:
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
[DEFAULT]
|
||||
prefs =
|
||||
browser.sessionstore.closedTabsFromAllWindows=true
|
||||
|
||||
[browser_file_close_tabs.js]
|
||||
[browser_file_menu_import_wizard.js]
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[DEFAULT]
|
||||
support-files = head.js
|
||||
prefs =
|
||||
browser.sessionstore.closedTabsFromAllWindows=true
|
||||
browser.tabs.firefox-view.logLevel=All
|
||||
|
||||
[browser_cfr_message.js]
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user