Bug 1458060 - Implement ability to pin/unpin a selection of tabs. r=jaws
authorAbdoulaye O. Ly <ablayelyfondou@gmail.com>
Sat, 23 Jun 2018 18:07:48 +0000
changeset 424470 8ca7657db998d8ae4e4f59c9a62c59ff1467d825
parent 424469 86bbfe5a3a398d52f6e7ced69cdb970ff9f9594f
child 424471 a9fc2e1230218572f529ce51b44008f1efa40928
push id34210
push userapavel@mozilla.com
push dateSat, 30 Jun 2018 09:48:57 +0000
treeherdermozilla-central@c0cd065ee5c8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws
bugs1458060
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1458060 - Implement ability to pin/unpin a selection of tabs. r=jaws MozReview-Commit-ID: Hqt8QrqQ62V
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/tabbrowser.js
browser/base/content/test/tabs/browser.ini
browser/base/content/test/tabs/browser_multiselect_tabs_pin_unpin.js
browser/locales/en-US/chrome/browser/browser.dtd
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7860,19 +7860,25 @@ var TabContextMenu = {
     menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
 
     // Session store
     document.getElementById("context_undoCloseTab").disabled =
       SessionStore.getClosedTabCount(window) == 0;
 
-    // Only one of pin/unpin should be visible
-    document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
-    document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
+    // Only one of pin/unpin/multiselect-pin/multiselect-unpin should be visible
+    let contextPinTab = document.getElementById("context_pinTab");
+    contextPinTab.hidden = this.contextTab.pinned || multiselectionContext;
+    let contextUnpinTab = document.getElementById("context_unpinTab");
+    contextUnpinTab.hidden = !this.contextTab.pinned || multiselectionContext;
+    let contextPinSelectedTabs = document.getElementById("context_pinSelectedTabs");
+    contextPinSelectedTabs.hidden = this.contextTab.pinned || !multiselectionContext;
+    let contextUnpinSelectedTabs = document.getElementById("context_unpinSelectedTabs");
+    contextUnpinSelectedTabs.hidden = !this.contextTab.pinned || !multiselectionContext;
 
     // Disable "Close Tabs to the Right" if there are no tabs
     // following it.
     document.getElementById("context_closeTabsToTheEnd").disabled =
       gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
 
     // Disable "Close other Tabs" if there are no unpinned tabs.
     let unpinnedTabsToClose = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -104,16 +104,22 @@
                 oncommand="gBrowser.toggleMuteAudioOnMultiSelectedTabs(TabContextMenu.contextTab);"/>
       <menuseparator/>
       <menuitem id="context_pinTab" label="&pinTab.label;"
                 accesskey="&pinTab.accesskey;"
                 oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
       <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
                 accesskey="&unpinTab.accesskey;"
                 oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
+      <menuitem id="context_pinSelectedTabs" label="&pinSelectedTabs.label;" hidden="true"
+                accesskey="&pinSelectedTabs.accesskey;"
+                oncommand="gBrowser.pinMultiSelectedTabs();"/>
+      <menuitem id="context_unpinSelectedTabs" label="&unpinSelectedTabs.label;" hidden="true"
+                accesskey="&unpinSelectedTabs.accesskey;"
+                oncommand="gBrowser.unpinMultiSelectedTabs();"/>
       <menuitem id="context_duplicateTab" label="&duplicateTab.label;"
                 accesskey="&duplicateTab.accesskey;"
                 oncommand="duplicateTabIn(TabContextMenu.contextTab, 'tab');"/>
       <menu id="context_reopenInContainer"
             label="&reopenInContainer.label;"
             accesskey="&reopenInContainer.accesskey;"
             hidden="true">
         <menupopup oncommand="reopenInContainer(event);"
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -3730,16 +3730,28 @@ window._gBrowser = {
         tab.activeMediaBlocked && tabMuted
       );
     }
     for (let tab of tabsToToggle) {
       tab.toggleMuteAudio();
     }
   },
 
+  pinMultiSelectedTabs() {
+    for (let tab of this.selectedTabs) {
+        this.pinTab(tab);
+    }
+  },
+
+  unpinMultiSelectedTabs() {
+    for (let tab of this.selectedTabs) {
+        this.unpinTab(tab);
+    }
+  },
+
   activateBrowserForPrintPreview(aBrowser) {
     this._printPreviewBrowsers.add(aBrowser);
     if (this._switcher) {
       this._switcher.activateBrowserForPrintPreview(aBrowser);
     }
     aBrowser.docShellIsActive = true;
   },
 
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -18,16 +18,17 @@ tags = audiochannel
 skip-if = (verify && debug && (os == 'linux'))
 support-files =
   test_bug1358314.html
 [browser_isLocalAboutURI.js]
 [browser_multiselect_tabs_active_tab_selected_by_default.js]
 [browser_multiselect_tabs_close_using_shortcuts.js]
 [browser_multiselect_tabs_close.js]
 [browser_multiselect_tabs_mute_unmute.js]
+[browser_multiselect_tabs_pin_unpin.js]
 [browser_multiselect_tabs_positional_attrs.js]
 [browser_multiselect_tabs_using_Ctrl.js]
 [browser_multiselect_tabs_using_Shift.js]
 [browser_navigatePinnedTab.js]
 [browser_new_file_whitelisted_http_tab.js]
 skip-if = !e10s # Test only relevant for e10s.
 [browser_new_tab_insert_position.js]
 skip-if = (debug && os == 'linux' && bits == 32) #Bug 1455882, disabled on Linux32 for almost permafailing
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_pin_unpin.js
@@ -0,0 +1,75 @@
+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 menuItemPinTab = document.getElementById("context_pinTab");
+    let menuItemUnpinTab = document.getElementById("context_unpinTab");
+    let menuItemPinSelectedTabs = document.getElementById("context_pinSelectedTabs");
+    let menuItemUnpinSelectedTabs = document.getElementById("context_unpinSelectedTabs");
+
+    is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
+
+    await BrowserTestUtils.switchTab(gBrowser, tab1);
+    await triggerClickOn(tab2, { ctrlKey: true });
+
+    ok(tab1.multiselected, "Tab1 is multiselected");
+    ok(tab2.multiselected, "Tab2 is multiselected");
+    ok(!tab3.multiselected, "Tab3 is not multiselected");
+
+    // Check the context menu with a non-multiselected tab
+    updateTabContextMenu(tab3);
+    ok(!tab3.pinned, "Tab3 is unpinned");
+    is(menuItemPinTab.hidden, false, "Pin Tab is visible");
+    is(menuItemUnpinTab.hidden, true, "Unpin Tab is hidden");
+    is(menuItemPinSelectedTabs.hidden, true, "Pin Selected Tabs is hidden");
+    is(menuItemUnpinSelectedTabs.hidden, true, "Unpin Selected Tabs is hidden");
+
+    // Check the context menu with a multiselected and unpinned tab
+    updateTabContextMenu(tab2);
+    ok(!tab2.pinned, "Tab2 is unpinned");
+    is(menuItemPinTab.hidden, true, "Pin Tab is hidden");
+    is(menuItemUnpinTab.hidden, true, "Unpin Tab is hidden");
+    is(menuItemPinSelectedTabs.hidden, false, "Pin Selected Tabs is visible");
+    is(menuItemUnpinSelectedTabs.hidden, true, "Unpin Selected Tabs is hidden");
+
+    let tab1Pinned = BrowserTestUtils.waitForEvent(tab1, "TabPinned");
+    let tab2Pinned = BrowserTestUtils.waitForEvent(tab2, "TabPinned");
+    menuItemPinSelectedTabs.click();
+    await tab1Pinned;
+    await tab2Pinned;
+
+    ok(tab1.pinned, "Tab1 is pinned");
+    ok(tab2.pinned, "Tab2 is pinned");
+    ok(!tab3.pinned, "Tab3 is unpinned");
+
+    // Check the context menu with a multiselected and pinned tab
+    updateTabContextMenu(tab2);
+    ok(tab2.pinned, "Tab2 is pinned");
+    is(menuItemPinTab.hidden, true, "Pin Tab is hidden");
+    is(menuItemUnpinTab.hidden, true, "Unpin Tab is hidden");
+    is(menuItemPinSelectedTabs.hidden, true, "Pin Selected Tabs is hidden");
+    is(menuItemUnpinSelectedTabs.hidden, false, "Unpin Selected Tabs is visible");
+
+    let tab1Unpinned = BrowserTestUtils.waitForEvent(tab1, "TabUnpinned");
+    let tab2Unpinned = BrowserTestUtils.waitForEvent(tab2, "TabUnpinned");
+    menuItemUnpinSelectedTabs.click();
+    await tab1Unpinned;
+    await tab2Unpinned;
+
+    ok(!tab1.pinned, "Tab1 is unpinned");
+    ok(!tab2.pinned, "Tab2 is unpinned");
+    ok(!tab3.pinned, "Tab3 is unpinned");
+
+    BrowserTestUtils.removeTab(tab1);
+    BrowserTestUtils.removeTab(tab2);
+    BrowserTestUtils.removeTab(tab3);
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -29,18 +29,27 @@ a tab (i.e. it is a verb, not adjective)
 <!ENTITY  duplicateTab.accesskey             "D">
 <!-- LOCALIZATION NOTE (closeTabsToTheEnd.label): This should indicate the
 direction in which tabs are closed, i.e. locales that use RTL mode should say
 left instead of right. -->
 <!ENTITY  closeTabsToTheEnd.label            "Close Tabs to the Right">
 <!ENTITY  closeTabsToTheEnd.accesskey        "i">
 <!ENTITY  closeOtherTabs.label               "Close Other Tabs">
 <!ENTITY  closeOtherTabs.accesskey           "o">
+
 <!ENTITY  closeSelectedTabs.label            "Close Selected Tabs">
 <!ENTITY  closeSelectedTabs.accesskey        "S">
+<!ENTITY  pinSelectedTabs.label              "Pin Tabs">
+<!-- Pin Tab and Pin Selected Tabs have the same accesskey
+but will never be visible at the same time. -->
+<!ENTITY  pinSelectedTabs.accesskey          "P">
+<!ENTITY  unpinSelectedTabs.label            "Unpin Tabs">
+<!-- Unpin Tab and Unpin Selected Tabs have the same accesskey
+but will never be visible at the same time. -->
+<!ENTITY  unpinSelectedTabs.accesskey        "b">
 
 <!-- LOCALIZATION NOTE (pinTab.label, unpinTab.label): "Pin" is being
 used as a metaphor for expressing the fact that these tabs are "pinned" to the
 left edge of the tabstrip. Really we just want the string to express the idea
 that this is a lightweight and reversible action that keeps your tab where you
 can reach it easily. -->
 <!ENTITY  pinTab.label                       "Pin Tab">
 <!ENTITY  pinTab.accesskey                   "P">