Bug 1408061 - Show hidden tabs in all tabs menu r?dao draft
authorMark Striemer <mstriemer@mozilla.com>
Tue, 03 Apr 2018 13:58:48 -0500
changeset 777520 aebe3818f19fd0923113add4ecf41b9e96a84b0f
parent 774791 d0cb92a08c3633877917713f7a17d63d73614957
push id105238
push userbmo:mstriemer@mozilla.com
push dateWed, 04 Apr 2018 22:15:57 +0000
reviewersdao
bugs1408061
milestone61.0a1
Bug 1408061 - Show hidden tabs in all tabs menu r?dao MozReview-Commit-ID: FpbeT1FwEWe
browser/base/content/browser.css
browser/base/content/browser.xul
browser/base/content/tabbrowser.js
browser/base/content/tabbrowser.xml
browser/components/extensions/test/browser/browser_ext_tabs_hide.js
browser/locales/en-US/chrome/browser/browser.dtd
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -127,17 +127,17 @@ panelview[mainview] > .panel-header {
   }
   .tab-stack {
     /* Without this, pinned tabs get a bit too tall when the tabstrip overflows. */
     vertical-align: top;
   }
 }
 
 
-#tabbrowser-tabs:not([overflow="true"]) ~ #alltabs-button,
+#tabbrowser-tabs:not([overflow="true"]):not([hashiddentabs]) ~ #alltabs-button,
 #tabbrowser-tabs[hasadjacentnewtabbutton]:not([overflow="true"]) ~ #new-tab-button,
 #tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
 #tabbrowser-tabs:not([hasadjacentnewtabbutton]) > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
 #TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
   visibility: collapse;
 }
 
 #tabbrowser-tabs:not([overflow="true"])[using-closing-tabs-spacer] ~ #alltabs-button {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -696,16 +696,21 @@
                     label="&undoCloseTab.label;"
                     observes="History:UndoCloseTab"/>
           <menuseparator id="alltabs-popup-separator-1"/>
           <menu id="alltabs_containersTab"
                 label="&newUserContext.label;">
             <menupopup id="alltabs_containersMenuTab" />
           </menu>
           <menuseparator id="alltabs-popup-separator-2"/>
+          <menu id="alltabs_hiddenTabs"
+                label="&hiddenTabs.label;">
+            <menupopup id="alltabs_hiddenTabsMenu"/>
+          </menu>
+          <menuseparator id="alltabs-popup-separator-3"/>
         </menupopup>
       </toolbarbutton>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
       <hbox class="titlebar-placeholder" type="post-tabs"
             ordinal="1000"
             skipintoolbarset="true"/>
 #endif
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -3268,25 +3268,27 @@ window._gBrowser = {
   showOnlyTheseTabs(aTabs) {
     for (let tab of this.tabs) {
       if (!aTabs.includes(tab))
         this.hideTab(tab);
       else
         this.showTab(tab);
     }
 
+    this.tabContainer._updateHiddenTabsStatus();
     this.tabContainer._handleTabSelect(true);
   },
 
   showTab(aTab) {
     if (aTab.hidden) {
       aTab.removeAttribute("hidden");
       this._visibleTabs = null; // invalidate cache
 
       this.tabContainer._updateCloseButtons();
+      this.tabContainer._updateHiddenTabsStatus();
 
       this.tabContainer._setPositionalAttributes();
 
       let event = document.createEvent("Events");
       event.initEvent("TabShow", true, false);
       aTab.dispatchEvent(event);
       SessionStore.deleteTabValue(aTab, "hiddenBy");
     }
@@ -3294,16 +3296,17 @@ window._gBrowser = {
 
   hideTab(aTab, aSource) {
     if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
         !aTab.closing && !aTab._sharingState) {
       aTab.setAttribute("hidden", "true");
       this._visibleTabs = null; // invalidate cache
 
       this.tabContainer._updateCloseButtons();
+      this.tabContainer._updateHiddenTabsStatus();
 
       this.tabContainer._setPositionalAttributes();
 
       let event = document.createEvent("Events");
       event.initEvent("TabHide", true, false);
       aTab.dispatchEvent(event);
       if (aSource) {
         SessionStore.setTabValue(aTab, "hiddenBy", aSource);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -410,16 +410,26 @@
               } else {
                 this.removeAttribute("closebuttons");
               }
             });
           });
         ]]></body>
       </method>
 
+      <method name="_updateHiddenTabsStatus">
+        <body><![CDATA[
+          if (gBrowser.visibleTabs.length < gBrowser.tabs.length) {
+            this.setAttribute("hashiddentabs", "true");
+          } else {
+            this.removeAttribute("hashiddentabs");
+          }
+        ]]></body>
+      </method>
+
       <method name="_handleTabSelect">
         <parameter name="aInstant"/>
         <body><![CDATA[
           if (this.getAttribute("overflow") == "true")
             this.arrowScrollbox.ensureElementIsVisible(this.selectedItem, aInstant);
 
           this.selectedItem._notselectedsinceload = false;
         ]]></body>
@@ -2146,16 +2156,29 @@
     </implementation>
 
     <handlers>
       <handler event="popupshowing">
       <![CDATA[
         if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
           createUserContextMenu(event, {useAccessKeys: false});
           return;
+        } else if (event.target.getAttribute("id") == "alltabs_hiddenTabsMenu") {
+          let fragment = document.createDocumentFragment();
+
+          for (let tab of gBrowser.tabs) {
+            if (tab.hidden) {
+              fragment.appendChild(this._createTabMenuItem(tab));
+            }
+          }
+
+          event.target.textContent = "";
+          event.target.appendChild(fragment);
+
+          return;
         }
 
         let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
 
         if (event.target.getAttribute("anonid") == "newtab-popup" ||
             event.target.id == "newtab-popup") {
           createUserContextMenu(event, {
             useAccessKeys: false,
@@ -2168,16 +2191,20 @@
           containersTab.hidden = !containersEnabled;
           if (PrivateBrowsingUtils.isWindowPrivate(window)) {
             containersTab.setAttribute("disabled", "true");
           }
 
           document.getElementById("alltabs_undoCloseTab").disabled =
             SessionStore.getClosedTabCount(window) == 0;
 
+          let showHiddenTabs = gBrowser.visibleTabs.length < gBrowser.tabs.length;
+          document.getElementById("alltabs_hiddenTabs").hidden = !showHiddenTabs;
+          document.getElementById("alltabs-popup-separator-3").hidden = !showHiddenTabs;
+
           var tabcontainer = gBrowser.tabContainer;
 
           // Listen for changes in the tab bar.
           tabcontainer.addEventListener("TabAttrModified", this);
           tabcontainer.addEventListener("TabClose", this);
 
           let tabs = gBrowser.visibleTabs;
           let fragment = document.createDocumentFragment();
@@ -2193,30 +2220,35 @@
       ]]></handler>
 
       <handler event="popuphidden">
       <![CDATA[
         if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
           return;
         }
 
+        // This could be the visible or hidden tabs menu container.
+        let container = event.target;
+
         // clear out the menu popup and remove the listeners
-        for (let i = this.childNodes.length - 1; i > 0; i--) {
-          let menuItem = this.childNodes[i];
+        for (let i = container.childNodes.length - 1; i > 0; i--) {
+          let menuItem = container.childNodes[i];
           if (menuItem.tab) {
             menuItem.tab.mCorrespondingMenuitem = null;
-            this.removeChild(menuItem);
+            menuItem.remove();
           }
           if (menuItem.hasAttribute("usercontextid")) {
-            this.removeChild(menuItem);
+            menuItem.remove();
           }
         }
-        var tabcontainer = gBrowser.tabContainer;
-        tabcontainer.removeEventListener("TabAttrModified", this);
-        tabcontainer.removeEventListener("TabClose", this);
+
+        if (container == this) {
+          gBrowser.tabContainer.removeEventListener("TabAttrModified", this);
+          gBrowser.tabContainer.removeEventListener("TabClose", this);
+        }
       ]]></handler>
 
       <handler event="DOMMenuItemActive">
       <![CDATA[
         var tab = event.target.tab;
         if (tab) {
           let overLink = tab.linkedBrowser.currentURI.displaySpec;
           if (overLink == "about:blank")
--- a/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
@@ -96,16 +96,21 @@ add_task(async function test_tabs_showhi
   };
 
   // Set up a test session with 2 windows and 5 tabs.
   let oldState = SessionStore.getBrowserState();
   let restored = TestUtils.topicObserved("sessionstore-browser-state-restored");
   SessionStore.setBrowserState(JSON.stringify(sessData));
   await restored;
 
+  for (let win of BrowserWindowIterator()) {
+    let allTabsButton = win.document.getElementById("alltabs-button");
+    is(getComputedStyle(allTabsButton).visibility, "collapse", "The all tabs button is hidden");
+  }
+
   // Attempt to hide all the tabs, however the active tab in each window cannot
   // be hidden, so the result will be 3 hidden tabs.
   extension.sendMessage("hideall");
   await extension.awaitMessage("hidden");
 
   // We have 2 windows in this session.  Otherwin is the non-current window.
   // In each window, the first tab will be the selected tab and should not be
   // hidden.  The rest of the tabs should be hidden at this point.  Hidden
@@ -119,16 +124,19 @@ add_task(async function test_tabs_showhi
     let tabs = Array.from(win.gBrowser.tabs.values());
     ok(!tabs[0].hidden, "first tab not hidden");
     for (let i = 1; i < tabs.length; i++) {
       ok(tabs[i].hidden, "tab hidden value is correct");
       let id = SessionStore.getTabValue(tabs[i], "hiddenBy");
       is(id, extension.id, "tab hiddenBy value is correct");
       await TabStateFlusher.flush(tabs[i].linkedBrowser);
     }
+
+    let allTabsButton = win.document.getElementById("alltabs-button");
+    is(getComputedStyle(allTabsButton).visibility, "visible", "The all tabs button is visible");
   }
 
   // Close the other window then restore it to test that the tabs are
   // restored with proper hidden state, and the correct extension id.
   await BrowserTestUtils.closeWindow(otherwin);
 
   otherwin = SessionStore.undoCloseWindow(0);
   await BrowserTestUtils.waitForEvent(otherwin, "load");
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -55,16 +55,17 @@ can reach it easily. -->
 <!ENTITY  moveToNewWindow.label              "Move to New Window">
 <!ENTITY  moveToNewWindow.accesskey          "W">
 <!ENTITY  bookmarkAllTabs.label              "Bookmark All Tabs…">
 <!ENTITY  bookmarkAllTabs.accesskey          "T">
 <!ENTITY  undoCloseTab.label                 "Undo Close Tab">
 <!ENTITY  undoCloseTab.accesskey             "U">
 <!ENTITY  closeTab.label                     "Close Tab">
 <!ENTITY  closeTab.accesskey                 "c">
+<!ENTITY  hiddenTabs.label                   "Hidden Tabs">
 
 <!ENTITY  listAllTabs.label      "List all tabs">
 
 <!ENTITY tabCmd.label "New Tab">
 <!ENTITY tabCmd.accesskey "T">
 <!ENTITY tabCmd.commandkey "t">
 <!-- LOCALIZATION NOTE (openLocationCmd.label): "Open Location" is only
 displayed on OS X, and only on windows that aren't main browser windows, or