Bug 887515 - Restore multiple tab closings using Undo Close Tab(s) after a multiple-tab operation. r=fluent-reviewers,flod,Gijs

Differential Revision: https://phabricator.services.mozilla.com/D76087
This commit is contained in:
Jared Wein 2020-05-30 05:38:56 +00:00
Родитель 882614bb0f
Коммит 803b52c5dd
11 изменённых файлов: 201 добавлений и 20 удалений

Просмотреть файл

@ -7,9 +7,9 @@
<vbox class="panel-subview-body">
<toolbarbutton id="allTabsMenu-undoCloseTab"
class="subviewbutton subviewbutton-iconic"
data-l10n-id="all-tabs-menu-undo-close-tab"
data-l10n-id="all-tabs-menu-undo-close-tabs"
key="key_undoCloseTab"
command="History:UndoCloseTab"/>
observes="History:UndoCloseTab"/>
<toolbarbutton id="allTabsMenu-searchTabs"
class="subviewbutton subviewbutton-iconic"
oncommand="gTabsPanel.searchTabs();"

Просмотреть файл

@ -99,7 +99,7 @@
oncommand="OpenBrowserWindow({fission: false});"
hidden="true"/>
#endif
<command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
<command id="History:UndoCloseTab" oncommand="undoCloseTab();" data-l10n-args='{"tabCount": 1}'/>
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
<command id="wrCaptureCmd" oncommand="gGfxUtils.webrenderCapture();" disabled="true"/>

Просмотреть файл

@ -8039,19 +8039,27 @@ function AddKeywordForSearchField() {
*/
function undoCloseTab(aIndex) {
// wallpaper patch to prevent an unnecessary blank tab (bug 343895)
var blankTabToRemove = null;
let blankTabToRemove = null;
if (gBrowser.tabs.length == 1 && gBrowser.selectedTab.isEmpty) {
blankTabToRemove = gBrowser.selectedTab;
}
var tab = null;
if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) {
tab = SessionStore.undoCloseTab(window, aIndex || 0);
let tab = null;
// aIndex is undefined if the function is called without a specific tab to restore.
let tabsToRemove =
aIndex !== undefined
? [aIndex]
: new Array(SessionStore.getLastClosedTabCount(window)).fill(0);
for (let index of tabsToRemove) {
if (SessionStore.getClosedTabCount(window) > index) {
tab = SessionStore.undoCloseTab(window, index);
if (blankTabToRemove) {
gBrowser.removeTab(blankTabToRemove);
if (blankTabToRemove) {
gBrowser.removeTab(blankTabToRemove);
}
}
}
SessionStore.setLastClosedTabCount(window, 1);
return tab;
}

Просмотреть файл

@ -185,12 +185,18 @@
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle, TabContextMenu.contextTab.multiselected);"/>
</menu>
<menuseparator/>
<menuitem id="context_closeTabsToTheEnd" data-lazy-l10n-id="close-tabs-to-the-end"
oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
<menuitem id="context_closeOtherTabs" data-lazy-l10n-id="close-other-tabs"
oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
<menu id="context_closeTabOptions"
data-lazy-l10n-id="tab-context-close-multiple-tabs">
<menupopup id="closeTabOptions">
<menuitem id="context_closeTabsToTheEnd" data-lazy-l10n-id="close-tabs-to-the-end"
oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
<menuitem id="context_closeOtherTabs" data-lazy-l10n-id="close-other-tabs"
oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
</menupopup>
</menu>
<menuitem id="context_undoCloseTab"
data-lazy-l10n-id="undo-close-tab"
data-lazy-l10n-id="tab-context-undo-close-tabs"
data-l10n-args='{"tabCount": 1}'
observes="History:UndoCloseTab"/>
<menuitem id="context_closeTab" data-lazy-l10n-id="close-tab"
oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>

Просмотреть файл

@ -2545,6 +2545,13 @@
);
}
// Don't use document.l10n.setAttributes because the FTL file is loaded
// lazily and we won't be able to resolve the string.
document
.getElementById("History:UndoCloseTab")
.setAttribute("data-l10n-args", JSON.stringify({ tabCount: 1 }));
SessionStore.setLastClosedTabCount(window, 1);
// if we're adding tabs, we're past interrupt mode, ditch the owner
if (this.selectedTab.owner) {
this.selectedTab.owner = null;
@ -3294,6 +3301,7 @@
return;
}
let initialTabCount = tabs.length;
this._clearMultiSelectionLocked = true;
// Guarantee that _clearMultiSelectionLocked lock gets released.
@ -3329,6 +3337,17 @@
this._clearMultiSelectionLocked = false;
this.avoidSingleSelectedTab();
let closedTabsCount =
initialTabCount - tabs.filter(t => !t.closing).length;
// Don't use document.l10n.setAttributes because the FTL file is loaded
// lazily and we won't be able to resolve the string.
document
.getElementById("History:UndoCloseTab")
.setAttribute(
"data-l10n-args",
JSON.stringify({ tabCount: closedTabsCount })
);
SessionStore.setLastClosedTabCount(window, closedTabsCount);
},
removeCurrentTab(aParams) {

Просмотреть файл

@ -98,6 +98,7 @@ skip-if = !e10s # Tab spinner is e10s only.
[browser_tabSwitchPrintPreview.js]
skip-if = os == 'mac'
[browser_tabswitch_updatecommands.js]
[browser_undo_close_tabs.js]
[browser_viewsource_of_data_URI_in_file_process.js]
[browser_visibleTabs_bookmarkAllTabs.js]
[browser_visibleTabs_contextMenu.js]

Просмотреть файл

@ -0,0 +1,102 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const PREF_WARN_ON_CLOSE = "browser.tabs.warnOnCloseOtherTabs";
add_task(async function setPref() {
await SpecialPowers.pushPrefEnv({
set: [[PREF_WARN_ON_CLOSE, false]],
});
});
add_task(async function withMultiSelectedTabs() {
let initialTab = gBrowser.selectedTab;
let tab1 = await addTab("https://example.com/1");
let tab2 = await addTab("https://example.com/2");
let tab3 = await addTab("https://example.com/3");
let tab4 = await addTab("https://example.com/4");
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
gBrowser.selectedTab = tab2;
await triggerClickOn(tab4, { shiftKey: true });
ok(!initialTab.multiselected, "InitialTab is not multiselected");
ok(!tab1.multiselected, "Tab1 is not multiselected");
ok(tab2.multiselected, "Tab2 is multiselected");
ok(tab3.multiselected, "Tab3 is multiselected");
ok(tab4.multiselected, "Tab4 is multiselected");
is(gBrowser.multiSelectedTabsCount, 3, "Two multiselected tabs");
gBrowser.removeMultiSelectedTabs();
await TestUtils.waitForCondition(
() => gBrowser.tabs.length == 2,
"wait for the multiselected tabs to close"
);
is(
SessionStore.getLastClosedTabCount(window),
3,
"SessionStore should know how many tabs were just closed"
);
undoCloseTab();
await TestUtils.waitForCondition(
() => gBrowser.tabs.length == 5,
"wait for the tabs to reopen"
);
info("waiting for the browsers to finish loading");
// Check that the tabs are restored in the correct order
for (let tabId of [2, 3, 4]) {
let browser = gBrowser.tabs[tabId].linkedBrowser;
await ContentTask.spawn(browser, tabId, async aTabId => {
await ContentTaskUtils.waitForCondition(() => {
return (
content?.document?.readyState == "complete" &&
content?.document?.location.href == "https://example.com/" + aTabId
);
}, "waiting for tab " + aTabId + " to load");
});
}
gBrowser.removeAllTabsBut(initialTab);
});
add_task(async function withCloseTabsToTheRight() {
let initialTab = gBrowser.selectedTab;
let tab1 = await addTab("https://example.com/1");
await addTab("https://example.com/2");
await addTab("https://example.com/3");
await addTab("https://example.com/4");
gBrowser.removeTabsToTheEndFrom(tab1);
await TestUtils.waitForCondition(
() => gBrowser.tabs.length == 2,
"wait for the multiselected tabs to close"
);
is(
SessionStore.getLastClosedTabCount(window),
3,
"SessionStore should know how many tabs were just closed"
);
undoCloseTab();
await TestUtils.waitForCondition(
() => gBrowser.tabs.length == 5,
"wait for the tabs to reopen"
);
info("waiting for the browsers to finish loading");
// Check that the tabs are restored in the correct order
for (let tabId of [2, 3, 4]) {
let browser = gBrowser.tabs[tabId].linkedBrowser;
ContentTask.spawn(browser, tabId, async aTabId => {
await ContentTaskUtils.waitForCondition(() => {
return (
content?.document?.readyState == "complete" &&
content?.document?.location.href == "https://example.com/" + aTabId
);
}, "waiting for tab " + aTabId + " to load");
});
}
gBrowser.removeAllTabsBut(initialTab);
});

Просмотреть файл

@ -328,6 +328,13 @@ var SessionStore = {
);
},
getLastClosedTabCount(aWindow) {
return SessionStoreInternal.getLastClosedTabCount(aWindow);
},
setLastClosedTabCount(aWindow, aNumber) {
return SessionStoreInternal.setLastClosedTabCount(aWindow, aNumber);
},
getClosedTabCount: function ss_getClosedTabCount(aWindow) {
return SessionStoreInternal.getClosedTabCount(aWindow);
},
@ -723,6 +730,7 @@ var SessionStoreInternal = {
this._initPrefs();
this._initialized = true;
this._closedTabCache = new WeakMap();
Telemetry.getHistogramById("FX_SESSION_RESTORE_PRIVACY_LEVEL").add(
Services.prefs.getIntPref("browser.sessionstore.privacy_level")
@ -3175,6 +3183,28 @@ var SessionStoreInternal = {
return newTab;
},
getLastClosedTabCount(aWindow) {
if ("__SSi" in aWindow) {
// Blank tabs cannot be undo-closed, so the number returned by
// the ClosedTabCache can be greater than the return value of
// getClosedTabCount. We won't restore blank tabs, so we return
// the minimum of these two values.
return Math.min(
this._closedTabCache.get(aWindow) || 1,
this.getClosedTabCount(aWindow)
);
}
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
},
setLastClosedTabCount(aWindow, aNumber) {
if ("__SSi" in aWindow) {
return this._closedTabCache.set(aWindow, aNumber);
}
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
},
getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
if ("__SSi" in aWindow) {
return this._windows[aWindow.__SSi]._closedTabs.length;

Просмотреть файл

@ -2,8 +2,12 @@
# 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/.
all-tabs-menu-undo-close-tab =
.label = Undo Close Tab
all-tabs-menu-undo-close-tabs =
.label =
{ $tabCount ->
[1] Undo Close Tab
*[other] Undo Close Tabs
}
# "Search" is a verb, as in "Search through tabs".
all-tabs-menu-search-tabs =

Просмотреть файл

@ -53,8 +53,15 @@ move-to-end =
move-to-new-window =
.label = Move to New Window
.accesskey = W
undo-close-tab =
.label = Undo Close Tab
tab-context-close-multiple-tabs =
.label = Close Multiple Tabs
.accesskey = M
tab-context-undo-close-tabs =
.label =
{ $tabCount ->
[1] Undo Close Tab
*[other] Undo Close Tabs
}
.accesskey = U
close-tab =
.label = Close Tab

Просмотреть файл

@ -17,8 +17,12 @@ toolbar-context-menu-bookmark-selected-tabs =
toolbar-context-menu-select-all-tabs =
.label = Select All Tabs
.accesskey = S
toolbar-context-menu-undo-close-tab =
.label = Undo Close Tab
toolbar-context-menu-undo-close-tabs =
.label =
{ $tabCount ->
[1] Undo Close Tab
*[other] Undo Close Tabs
}
.accesskey = U
toolbar-context-menu-manage-extension =