Bug 1458049 - Implement ability to move a selection of tabs into a new window through tab context menu. r=jaws

MozReview-Commit-ID: KrjavwyoF4s

--HG--
extra : rebase_source : 57ef831467cc648b8f5c81e38704c5466955c3a7
This commit is contained in:
Abdoulaye O. Ly 2018-07-13 19:30:58 +00:00
parent dd38ea4159
commit b5f60976d9
4 changed files with 103 additions and 2 deletions

View File

@ -120,7 +120,7 @@
<menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
accesskey="&moveToNewWindow.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
oncommand="gBrowser.replaceTabsWithWindow(TabContextMenu.contextTab);"/>
<menuseparator id="context_sendTabToDevice_separator" class="sync-ui-item"/>
<menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
class="sync-ui-item"

View File

@ -3419,6 +3419,60 @@ window._gBrowser = {
return window.openDialog(getBrowserURL(), "_blank", options, aTab);
},
/**
* Move contextTab (or selected tabs in a mutli-select context)
* to a new browser window, unless it is (they are) already the only tab(s)
* in the current window, in which case this will do nothing.
*/
replaceTabsWithWindow(contextTab) {
let tabs;
if (contextTab.multiselected) {
tabs = this.selectedTabs;
} else {
tabs = [gBrowser.selectedTab];
}
if (this.tabs.length == tabs.length) {
return null;
}
if (tabs.length == 1) {
return this.replaceTabWithWindow(tabs[0]);
}
// The order of the tabs is reserved.
// To avoid mutliple tab-switch, the active tab is "moved" lastly, if applicable.
// If applicable, the active tab remains active in the new window.
let activeTab = gBrowser.selectedTab;
let inactiveTabs = tabs.filter(t => t != activeTab);
let activeTabNewIndex = tabs.indexOf(activeTab);
// Play the closing animation for all selected tabs to give
// immediate feedback while waiting for the new window to appear.
if (this.animationsEnabled) {
for (let tab of tabs) {
tab.style.maxWidth = ""; // ensure that fade-out transition happens
tab.removeAttribute("fadein");
}
}
let win;
let firstInactiveTab = inactiveTabs[0];
firstInactiveTab.linkedBrowser.addEventListener("EndSwapDocShells", function() {
for (let i = 1; i < inactiveTabs.length; i++) {
win.gBrowser.adoptTab(inactiveTabs[i], i);
}
if (activeTabNewIndex > -1) {
win.gBrowser.adoptTab(activeTab, activeTabNewIndex, true /* aSelectTab */);
}
}, { once: true });
win = this.replaceTabWithWindow(firstInactiveTab);
return win;
},
_updateTabsAfterInsert() {
for (let i = 0; i < this.tabs.length; i++) {
this.tabs[i]._tPos = i;
@ -3690,7 +3744,7 @@ window._gBrowser = {
if (!_multiSelectedTabsSet.has(selectedTab)) {
tabs.push(selectedTab);
}
return tabs;
return tabs.sort((a, b) => a._tPos > b._tPos);
},
get multiSelectedTabsCount() {

View File

@ -24,6 +24,7 @@ support-files =
[browser_multiselect_tabs_close_other_tabs.js]
[browser_multiselect_tabs_close_using_shortcuts.js]
[browser_multiselect_tabs_close.js]
[browser_multiselect_tabs_move_to_new_window_contextmenu.js]
[browser_multiselect_tabs_mute_unmute.js]
[browser_multiselect_tabs_pin_unpin.js]
[browser_multiselect_tabs_positional_attrs.js]

View File

@ -0,0 +1,46 @@
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
add_task(async function setPref() {
await SpecialPowers.pushPrefEnv({
set: [[PREF_MULTISELECT_TABS, true]]
});
});
add_task(async function test() {
let tab1 = await addTab();
let tab2 = await addTab();
let tab3 = await addTab();
let tab4 = await addTab();
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
await BrowserTestUtils.switchTab(gBrowser, tab2);
await triggerClickOn(tab1, { ctrlKey: true });
await triggerClickOn(tab3, { ctrlKey: true });
ok(tab1.multiselected, "Tab1 is multiselected");
ok(tab2.multiselected, "Tab2 is multiselected");
ok(tab3.multiselected, "Tab3 is multiselected");
ok(!tab4.multiselected, "Tab4 is not multiselected");
is(gBrowser.multiSelectedTabsCount, 3, "Three multiselected tabs");
let newWindow = gBrowser.replaceTabsWithWindow(tab1);
// waiting for tab2 to close ensure that the newWindow is created,
// thus newWindow.gBrowser used in the second waitForCondition
// will not be undefined.
await TestUtils.waitForCondition(() => tab2.closing, "Wait for tab2 to close");
await TestUtils.waitForCondition(() => newWindow.gBrowser.visibleTabs.length == 3,
"Wait for all three tabs to get moved to the new window");
let gBrowser2 = newWindow.gBrowser;
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
is(gBrowser.visibleTabs.length, 2, "Two tabs now in the old window");
is(gBrowser2.visibleTabs.length, 3, "Three tabs in the new window");
is(gBrowser2.visibleTabs.indexOf(gBrowser2.selectedTab), 1,
"Previously active tab is still the active tab in the new window");
BrowserTestUtils.closeWindow(newWindow);
BrowserTestUtils.removeTab(tab4);
});