Bug 1458060 - Implement ability to pin/unpin a selection of tabs. r?jaws draft multiselect_pin_unpin
authorAbdoulaye O. Ly <ablayelyfondou@gmail.com>
Sat, 23 Jun 2018 18:07:48 +0000
branchmultiselect_pin_unpin
changeset 812174 72173352225bbd9275714caa226ef348dae5987b
parent 811977 6041c030780420b6205cf2d6640513606609884c
push id114473
push userbmo:ablayelyfondou@gmail.com
push dateThu, 28 Jun 2018 17:51:09 +0000
reviewersjaws
bugs1458060
milestone63.0a1
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">