Bug 887515 - Replace multiple tab closing prompt with a menuitem to restore multiple tabs. r=ttaubert

--HG--
extra : rebase_source : 743b8bb459802675e38ef2ab8e8cee2c41b90f90
This commit is contained in:
Jared Wein 2013-07-17 18:48:39 -04:00
parent cc90ec9c73
commit a05c3a6714
15 changed files with 197 additions and 32 deletions

View File

@ -571,7 +571,7 @@ HistoryMenu.prototype = {
m.setAttribute("label", strings.getString("menuRestoreAllTabs.label"));
m.addEventListener("command", function() {
for (var i = 0; i < undoItems.length; i++)
undoCloseTab();
undoCloseTab(0);
}, false);
},

View File

@ -6259,15 +6259,31 @@ function undoCloseTab(aIndex) {
var tab = null;
var ss = Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore);
if (ss.getClosedTabCount(window) > (aIndex || 0)) {
TabView.prepareUndoCloseTab(blankTabToRemove);
tab = ss.undoCloseTab(window, aIndex || 0);
TabView.afterUndoCloseTab();
if (blankTabToRemove)
gBrowser.removeTab(blankTabToRemove);
let numberOfTabsToUndoClose = 0;
if (Number.isInteger(aIndex)) {
if (ss.getClosedTabCount(window) > aIndex) {
numberOfTabsToUndoClose = 1;
} else {
return tab;
}
} else {
numberOfTabsToUndoClose = ss.getNumberOfTabsClosedLast(window);
aIndex = 0;
}
while (numberOfTabsToUndoClose > 0 &&
numberOfTabsToUndoClose--) {
TabView.prepareUndoCloseTab(blankTabToRemove);
tab = ss.undoCloseTab(window, aIndex);
TabView.afterUndoCloseTab();
if (blankTabToRemove) {
gBrowser.removeTab(blankTabToRemove);
blankTabToRemove = null;
}
}
// Reset the number of tabs closed last time to the default.
ss.setNumberOfTabsClosedLast(window, 1);
return tab;
}
@ -7061,10 +7077,15 @@ var TabContextMenu = {
menuItem.disabled = disabled;
// Session store
document.getElementById("context_undoCloseTab").disabled =
Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore).
getClosedTabCount(window) == 0;
let ss = Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore);
let undoCloseTabElement = document.getElementById("context_undoCloseTab");
let closedTabCount = ss.getNumberOfTabsClosedLast(window);
undoCloseTabElement.disabled = closedTabCount == 0;
// Change the label of "Undo Close Tab" to specify if it will undo a batch-close
// or a single close.
let visibleLabel = closedTabCount <= 1 ? "singletablabel" : "multipletablabel";
undoCloseTabElement.setAttribute("label", undoCloseTabElement.getAttribute(visibleLabel));
// Only one of pin/unpin should be visible
document.getElementById("context_pinTab").hidden = this.contextTab.pinned;

View File

@ -103,7 +103,8 @@
oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
<menuseparator/>
<menuitem id="context_undoCloseTab"
label="&undoCloseTab.label;"
singletablabel="&undoCloseTab.label;"
multipletablabel="&undoCloseTabs.label;"
accesskey="&undoCloseTab.accesskey;"
observes="History:UndoCloseTab"/>
<menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"

View File

@ -1565,13 +1565,9 @@
throw new Error("Invalid argument: " + aCloseTabs);
}
if (tabsToClose <= 1)
return true;
const pref = aCloseTabs == this.closingTabsEnum.ALL ?
"browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
var shouldPrompt = Services.prefs.getBoolPref(pref);
if (!shouldPrompt)
if (tabsToClose <= 1 ||
aCloseTabs != this.closingTabsEnum.ALL ||
!Services.prefs.getBoolPref("browser.tabs.warnOnClose"))
return true;
var ps = Services.prompt;
@ -1595,13 +1591,12 @@
+ (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
bundle.getString("tabs.closeButtonMultiple"),
null, null,
aCloseTabs == this.closingTabsEnum.ALL ?
bundle.getString("tabs.closeWarningPromptMe") : null,
bundle.getString("tabs.closeWarningPromptMe"),
warnOnClose);
var reallyClose = (buttonPressed == 0);
// don't set the pref unless they press OK and it's false
if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
if (reallyClose && !warnOnClose.value)
Services.prefs.setBoolPref(pref, false);
return reallyClose;
@ -1629,9 +1624,13 @@
<![CDATA[
if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
let tabs = this.getTabsToTheEndFrom(aTab);
for (let i = tabs.length - 1; i >= 0; --i) {
let numberOfTabsToClose = tabs.length;
for (let i = numberOfTabsToClose - 1; i >= 0; --i) {
this.removeTab(tabs[i], {animate: true});
}
let ss = Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore);
ss.setNumberOfTabsClosedLast(window, numberOfTabsToClose);
}
]]>
</body>
@ -1648,10 +1647,16 @@
let tabs = this.visibleTabs;
this.selectedTab = aTab;
let closedTabs = 0;
for (let i = tabs.length - 1; i >= 0; --i) {
if (tabs[i] != aTab && !tabs[i].pinned)
if (tabs[i] != aTab && !tabs[i].pinned) {
this.removeTab(tabs[i]);
closedTabs++;
}
}
let ss = Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore);
ss.setNumberOfTabsClosedLast(window, closedTabs);
}
]]>
</body>
@ -1680,6 +1685,10 @@
var byMouse = aParams.byMouse;
}
let ss = Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore);
ss.setNumberOfTabsClosedLast(window, 1);
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate &&

View File

@ -184,6 +184,7 @@ MOCHITEST_BROWSER_FILES = \
browser_bug832435.js \
browser_bug839103.js \
browser_bug882977.js \
browser_bug887515.js \
browser_canonizeURL.js \
browser_clearplugindata_noage.html \
browser_clearplugindata.html \

View File

@ -0,0 +1,75 @@
function numClosedTabs()
Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore).
getNumberOfTabsClosedLast(window);
var originalTab;
var tab1Loaded = false;
var tab2Loaded = false;
function verifyUndoMultipleClose() {
if (!tab1Loaded || !tab2Loaded)
return;
gBrowser.removeAllTabsBut(originalTab);
updateTabContextMenu();
let undoCloseTabElement = document.getElementById("context_undoCloseTab");
ok(!undoCloseTabElement.disabled, "Undo Close Tabs should be enabled.");
is(numClosedTabs(), 2, "There should be 2 closed tabs.");
is(gBrowser.tabs.length, 1, "There should only be 1 open tab");
updateTabContextMenu();
is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("multipletablabel"),
"The label should be showing that the command will restore multiple tabs");
undoCloseTab();
is(gBrowser.tabs.length, 3, "There should be 3 open tabs");
updateTabContextMenu();
is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("singletablabel"),
"The label should be showing that the command will restore a single tab");
gBrowser.removeTabsToTheEndFrom(originalTab);
updateTabContextMenu();
ok(!undoCloseTabElement.disabled, "Undo Close Tabs should be enabled.");
is(numClosedTabs(), 2, "There should be 2 closed tabs.");
is(gBrowser.tabs.length, 1, "There should only be 1 open tab");
updateTabContextMenu();
is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("multipletablabel"),
"The label should be showing that the command will restore multiple tabs");
finish();
}
function test() {
waitForExplicitFinish();
registerCleanupFunction(function() {
originalTab.linkedBrowser.loadURI("about:blank");
originalTab = null;
});
let undoCloseTabElement = document.getElementById("context_undoCloseTab");
updateTabContextMenu();
is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("singletablabel"),
"The label should be showing that the command will restore a single tab");
originalTab = gBrowser.selectedTab;
gBrowser.selectedBrowser.loadURI("http://mochi.test:8888/");
var tab1 = gBrowser.addTab("http://mochi.test:8888/");
var tab2 = gBrowser.addTab("http://mochi.test:8888/");
var browser1 = gBrowser.getBrowserForTab(tab1);
browser1.addEventListener("load", function onLoad1() {
browser1.removeEventListener("load", onLoad1, true);
tab1Loaded = true;
tab1 = null;
verifyUndoMultipleClose();
}, true);
var browser2 = gBrowser.getBrowserForTab(tab2);
browser2.addEventListener("load", function onLoad2() {
browser2.removeEventListener("load", onLoad2, true);
tab2Loaded = true;
tab2 = null;
verifyUndoMultipleClose();
}, true);
}

View File

@ -25,7 +25,7 @@ interface nsIDOMNode;
* |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
*/
[scriptable, uuid(59bfaf00-e3d8-4728-b4f0-cc0b9dfb4806)]
[scriptable, uuid(0aa5492c-15ad-4376-8eac-28895796826e)]
interface nsISessionStore : nsISupports
{
/**
@ -105,6 +105,20 @@ interface nsISessionStore : nsISupports
nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab,
[optional] in long aDelta);
/**
* Set the number of tabs that was closed during the last close-tabs
* operation. This helps us keep track of batch-close operations so
* we can restore multiple tabs at once.
*/
void setNumberOfTabsClosedLast(in nsIDOMWindow aWindow, in unsigned long aNumber);
/**
* Get the number of tabs that was closed during the last close-tabs
* operation. This helps us keep track of batch-close operations so
* we can restore multiple tabs at once.
*/
unsigned long getNumberOfTabsClosedLast(in nsIDOMWindow aWindow);
/**
* Get the number of restore-able tabs for a browser window
*/

View File

@ -170,6 +170,14 @@ this.SessionStore = {
return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
},
getNumberOfTabsClosedLast: function ss_getNumberOfTabsClosedLast(aWindow) {
return SessionStoreInternal.getNumberOfTabsClosedLast(aWindow);
},
setNumberOfTabsClosedLast: function ss_setNumberOfTabsClosedLast(aWindow, aNumber) {
return SessionStoreInternal.setNumberOfTabsClosedLast(aWindow, aNumber);
},
getClosedTabCount: function ss_getClosedTabCount(aWindow) {
return SessionStoreInternal.getClosedTabCount(aWindow);
},
@ -1529,6 +1537,28 @@ let SessionStoreInternal = {
return newTab;
},
setNumberOfTabsClosedLast: function ssi_setNumberOfTabsClosedLast(aWindow, aNumber) {
if ("__SSi" in aWindow) {
return NumberOfTabsClosedLastPerWindow.set(aWindow, aNumber);
}
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
},
/* Used to undo batch tab-close operations. Defaults to 1. */
getNumberOfTabsClosedLast: function ssi_getNumberOfTabsClosedLast(aWindow) {
if ("__SSi" in aWindow) {
// Blank tabs cannot be undo-closed, so the number returned by
// the NumberOfTabsClosedLastPerWindow 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(NumberOfTabsClosedLastPerWindow.get(aWindow) || 1,
this.getClosedTabCount(aWindow));
}
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
},
getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
if ("__SSi" in aWindow) {
return this._windows[aWindow.__SSi]._closedTabs.length;
@ -4694,6 +4724,11 @@ let DyingWindowCache = {
}
};
// A map storing the number of tabs last closed per windoow. This only
// stores the most recent tab-close operation, and is used to undo
// batch tab-closing operations.
let NumberOfTabsClosedLastPerWindow = new WeakMap();
// A set of tab attributes to persist. We will read a given list of tab
// attributes when collecting tab data and will re-set those attributes when
// the given tab data is restored to a new tab.

View File

@ -40,4 +40,8 @@ function test() {
"Invalid window for getWindowValue throws");
ok(test(function() ss.setWindowValue({}, "", "")),
"Invalid window for setWindowValue throws");
ok(test(function() ss.getNumberOfTabsClosedLast({})),
"Invalid window for getNumberOfTabsClosedLast throws");
ok(test(function() ss.setNumberOfTabsClosedLast({}, 1)),
"Invalid window for setNumberOfTabsClosedLast throws");
}

View File

@ -38,5 +38,5 @@ function onTabViewWindowLoaded() {
gBrowser.removeTab(tabTwo);
finish();
});
});
}, 0);
}

View File

@ -64,7 +64,7 @@ function test() {
createBlankTab();
afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab);
});
}, 0);
});
}
@ -94,7 +94,7 @@ function test() {
gBrowser.removeTab(gBrowser.tabs[0]);
afterAllTabsLoaded(finishTest);
});
}, 0);
});
}

View File

@ -81,7 +81,7 @@ function test() {
gBrowser.removeTab(gBrowser.tabs[1]);
gBrowser.removeTab(gBrowser.tabs[1]);
hideTabView(finishTest);
});
}, 0);
}
waitForExplicitFinish();

View File

@ -20,7 +20,7 @@ function test() {
whenTabViewIsHidden(function() {
win.gBrowser.removeTab(win.gBrowser.selectedTab);
executeSoon(function() {
win.undoCloseTab();
win.undoCloseTab(0);
groupItemTwo.addSubscriber("childAdded", function onChildAdded(data) {
groupItemTwo.removeSubscriber("childAdded", onChildAdded);

View File

@ -359,7 +359,7 @@ function newWindowWithState(state, callback) {
function restoreTab(callback, index, win) {
win = win || window;
let tab = win.undoCloseTab(index || 0);
let tab = win.undoCloseTab(index);
let tabItem = tab._tabViewTabItem;
let finalize = function () {

View File

@ -46,6 +46,11 @@ can reach it easily. -->
<!ENTITY bookmarkAllTabs.accesskey "T">
<!ENTITY undoCloseTab.label "Undo Close Tab">
<!ENTITY undoCloseTab.accesskey "U">
<!-- LOCALIZATION NOTE (undoCloseTabs.label) : This label is used
when the previous tab-closing operation closed more than one tab. It
replaces the undoCloseTab.label and will use the same accesskey as the
undoCloseTab.label so users will not need to learn new keyboard controls. -->
<!ENTITY undoCloseTabs.label "Undo Close Tabs">
<!ENTITY closeTab.label "Close Tab">
<!ENTITY closeTab.accesskey "c">