Bug 1272416 - Adding in a new alltabs dropdown when containers are enabled and tabs are not overflowing draft
authorJonathan Kingston <jkingston@mozilla.com>
Tue, 31 May 2016 13:26:50 +0100
changeset 376023 0f1440a271b8f94ebe0aec98c17a99ed85ca3bde
parent 375957 1828937da9493b2cd54862b9c520b2ba5c7db92b
child 523048 6270694dbf8613265682af29f78fb3483b573dc2
push id20468
push userjkingston@mozilla.com
push dateTue, 07 Jun 2016 07:03:56 +0000
bugs1272416
milestone50.0a1
Bug 1272416 - Adding in a new alltabs dropdown when containers are enabled and tabs are not overflowing MozReview-Commit-ID: AznBD9VNedA
browser/base/content/browser.css
browser/base/content/tabbrowser.xml
browser/base/content/utilityOverlay.js
browser/components/contextualidentity/test/browser/browser.ini
browser/components/contextualidentity/test/browser/browser_alltabsButton.js
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/windows/browser.css
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -129,16 +129,17 @@ tabbrowser {
 
 .tabbrowser-tabs {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
 }
 
 #tabbrowser-tabs:not([overflow="true"]) ~ #alltabs-button,
 #tabbrowser-tabs:not([overflow="true"]) + #new-tab-button,
 #tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-alltabs-few-button,
 #TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .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 {
   visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
 }
@@ -196,16 +197,17 @@ tabbrowser {
   z-index: 2;
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
   transition: transform 200ms ease-out;
 }
 
+.alltabs-few-popup,
 #alltabs-popup {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
 }
 
 toolbar[printpreview="true"] {
   -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
 }
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4889,22 +4889,41 @@
         <children/>
         <xul:toolbarbutton class="tabs-newtab-button"
                            anonid="tabs-newtab-button"
                            command="cmd_newNavigatorTab"
                            onclick="checkForMiddleClick(this, event);"
                            onmouseover="document.getBindingParent(this)._enterNewTab();"
                            onmouseout="document.getBindingParent(this)._leaveNewTab();"
                            tooltip="dynamic-shortcut-tooltip"/>
+
+        <xul:toolbarbutton anonid="tabs-alltabs-button"
+                           class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-few-button"
+                           type="menu"
+                           removable="false">
+          <xul:menupopup anonid="alltabs-popup"
+                         class="alltabs-few-popup"
+                         position="after_end">
+            <xul:menuitem anonid="tabs-alltabs-containersLabel"
+                          disabled="true" />
+            <xul:menuseparator anonid="alltabs-popup-separator-1"/>
+            <xul:menuitem anonid="alltabs_undoCloseTab"
+                          class="menuitem-iconic"
+                          key="key_undoCloseTab"
+                          observes="History:UndoCloseTab"/>
+            <xul:menuseparator anonid="alltabs-popup-separator-2"/>
+          </xul:menupopup>
+        </xul:toolbarbutton>
+
         <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
                     style="width: 0;"/>
       </xul:arrowscrollbox>
     </content>
 
-    <implementation implements="nsIDOMEventListener">
+    <implementation implements="nsIDOMEventListener, nsIObserver">
       <constructor>
         <![CDATA[
           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
 
           var tab = this.firstChild;
           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
           tab.setAttribute("crop", "end");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
@@ -4913,19 +4932,43 @@
           window.addEventListener("load", this, false);
 
           try {
             this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
           } catch (ex) {
             this._tabAnimationLoggingEnabled = false;
           }
           this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
+          this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
+          Services.prefs.addObserver("privacy.userContext.enabled", this, false);
+
+          let alltabsButton = document.getAnonymousElementByAttribute(this, "anonid", "tabs-alltabs-button");
+          let mainAlltabsButton = document.getElementById("alltabs-button");
+          alltabsButton.setAttribute("tooltiptext", mainAlltabsButton.getAttribute("tooltiptext"));
+          let mainLabel = document.getElementById("alltabs_containersTab");
+          this.alltabsFewContainersLabel.label = mainLabel.label;
+          let mainUndo = document.getElementById("alltabs_undoCloseTab");
+          this.alltabsFewUndoCloseTab.label = mainUndo.label;
         ]]>
       </constructor>
 
+      <destructor>
+        <![CDATA[
+          Services.prefs.removeObserver("privacy.userContext.enabled", this);
+        ]]>
+      </destructor>
+
+      <field name="alltabsFewContainersLabel" readonly="true">
+        document.getAnonymousElementByAttribute(this, "anonid", "tabs-alltabs-containersLabel");
+      </field>
+
+      <field name="alltabsFewUndoCloseTab" readonly="true">
+        document.getAnonymousElementByAttribute(this, "anonid", "alltabs_undoCloseTab");
+      </field>
+
       <field name="tabbrowser" readonly="true">
         document.getElementById(this.getAttribute("tabbrowser"));
       </field>
 
       <field name="tabbox" readonly="true">
         this.tabbrowser.mTabBox;
       </field>
 
@@ -4949,16 +4992,31 @@
       <property name="_isCustomizing" readonly="true">
         <getter>
           let root = document.documentElement;
           return root.getAttribute("customizing") == "true" ||
                  root.getAttribute("customize-exiting") == "true";
         </getter>
       </property>
 
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body><![CDATA[
+          switch(aTopic) {
+            case "nsPref:changed":
+              // This is the only pref observed.
+              let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
+              document.getAnonymousElementByAttribute(this, "anonid", "tabs-alltabs-button").hidden = !containersEnabled;
+              break;
+          }
+        ]]></body>
+      </method>
+
       <method name="_setPositionalAttributes">
         <body><![CDATA[
           let visibleTabs = this.tabbrowser.visibleTabs;
 
           if (!visibleTabs.length)
             return;
 
           let selectedIndex = visibleTabs.indexOf(this.selectedItem);
@@ -6596,37 +6654,52 @@
           if (aMenuitem.firstChild)
             aMenuitem.firstChild.remove();
           if (aTab.hasAttribute("muted"))
             addEndImage().setAttribute("muted", "true");
           else if (aTab.hasAttribute("soundplaying"))
             addEndImage().setAttribute("soundplaying", "true");
         ]]></body>
       </method>
+
     </implementation>
 
     <handlers>
       <handler event="popupshowing">
       <![CDATA[
         if (event.target.getAttribute('id') == "alltabs_containersMenuTab") {
           createUserContextMenu(event);
           return;
         }
-
         let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
-        document.getElementById("alltabs-popup-separator-1").hidden = !containersEnabled;
-        let containersTab = document.getElementById("alltabs_containersTab");
-
-        containersTab.hidden = !containersEnabled;
-        if (PrivateBrowsingUtils.isWindowPrivate(window)) {
-          containersTab.setAttribute("disabled", "true");
+        let undoTab;
+
+        if (event.target.getAttribute('anonid') == "alltabs-popup") {
+          let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
+          let containersLabel = gBrowser.tabContainer.alltabsFewContainersLabel;
+          containersLabel.hidden = !containersEnabled;
+          if (containersEnabled) {
+            createUserContextMenuAfter(containersLabel);
+          }
+
+          undoTab = gBrowser.tabContainer.alltabsFewUndoCloseTab;
+        } else {
+
+          document.getElementById("alltabs-popup-separator-1").hidden = !containersEnabled;
+          let containersTab = document.getElementById("alltabs_containersTab");
+
+          containersTab.hidden = !containersEnabled;
+          if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+            containersTab.setAttribute("disabled", "true");
+          }
+
+          undoTab = document.getElementById("alltabs_undoCloseTab");
         }
 
-        document.getElementById("alltabs_undoCloseTab").disabled =
-          SessionStore.getClosedTabCount(window) == 0;
+        undoTab.disabled = SessionStore.getClosedTabCount(window) == 0;
 
         var tabcontainer = gBrowser.tabContainer;
 
         // Listen for changes in the tab bar.
         tabcontainer.addEventListener("TabAttrModified", this, false);
         tabcontainer.addEventListener("TabClose", this, false);
         tabcontainer.mTabstrip.addEventListener("scroll", this, false);
 
@@ -6646,16 +6719,19 @@
 
         // clear out the menu popup and remove the listeners
         for (let i = this.childNodes.length - 1; i > 0; i--) {
           let menuItem = this.childNodes[i];
           if (menuItem.tab) {
             menuItem.tab.mCorrespondingMenuitem = null;
             this.removeChild(menuItem);
           }
+          if (menuItem.hasAttribute("usercontextid")) {
+            this.removeChild(menuItem);
+          }
         }
         var tabcontainer = gBrowser.tabContainer;
         tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
         tabcontainer.removeEventListener("TabAttrModified", this, false);
         tabcontainer.removeEventListener("TabClose", this, false);
       ]]></handler>
 
       <handler event="DOMMenuItemActive">
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -409,16 +409,30 @@ function checkForMiddleClick(node, event
 // Populate a menu with user-context menu items. This method should be called
 // by onpopupshowing passing the event as first argument. addCommandAttribute
 // param is used to set the 'command' attribute in the new menuitem elements.
 function createUserContextMenu(event, addCommandAttribute = true) {
   while (event.target.hasChildNodes()) {
     event.target.removeChild(event.target.firstChild);
   }
 
+  const docfrag = createUserContextItems(addCommandAttribute);
+
+  event.target.appendChild(docfrag);
+  return true;
+}
+
+function createUserContextMenuAfter(item, addCommandAttribute = true) {
+  const docfrag = createUserContextItems(addCommandAttribute);
+
+  item.parentNode.insertBefore(docfrag, item.nextSibling);
+  return true;
+}
+
+function createUserContextItems(addCommandAttribute = true) {
   let bundle = document.getElementById("bundle_browser");
   let docfrag = document.createDocumentFragment();
 
   ContextualIdentityService.getIdentities().forEach(identity => {
     let menuitem = document.createElement("menuitem");
     menuitem.setAttribute("usercontextid", identity.userContextId);
     menuitem.setAttribute("label", bundle.getString(identity.label));
     menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
@@ -427,19 +441,17 @@ function createUserContextMenu(event, ad
     if (addCommandAttribute) {
       menuitem.setAttribute("command", "Browser:NewUserContextTab");
     }
 
     menuitem.setAttribute("image", identity.icon);
 
     docfrag.appendChild(menuitem);
   });
-
-  event.target.appendChild(docfrag);
-  return true;
+  return docfrag;
 }
 
 // Closes all popups that are ancestors of the node.
 function closeMenus(node)
 {
   if ("tagName" in node) {
     if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     && (node.tagName == "menupopup" || node.tagName == "popup"))
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -5,15 +5,16 @@ support-files =
   file_reflect_cookie_into_title.html
   serviceworker.html
   worker.js
 
 [browser_aboutURLs.js]
 [browser_usercontext.js]
 [browser_usercontextid_tabdrop.js]
 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
+[browser_alltabsButton.js]
 [browser_windowName.js]
 tags = openwindow
 [browser_windowOpen.js]
 tags = openwindow
 [browser_serviceworkers.js]
 [browser_broadcastchannel.js]
 [browser_blobUrl.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_alltabsButton.js
@@ -0,0 +1,34 @@
+"use strict";
+
+// Testing that when the user opens the all tab menu and clicks menu items
+// the correct context id is opened
+
+add_task(function* test() {
+  yield SpecialPowers.pushPrefEnv({"set": [
+      ["privacy.userContext.enabled", true]
+  ]});
+
+  let allTabs = document.getElementById('tabbrowser-tabs');
+  let allTabsButton = document.getAnonymousElementByAttribute(allTabs, "anonid", "tabs-alltabs-button");
+  ok(allTabsButton, "All tabs button exists");
+  ok(!allTabsButton.hidden, "All tabs button is visible");
+  let popup = document.getAnonymousElementByAttribute(allTabs, "anonid", "alltabs-popup");
+
+  for (let i = 1; i <= 4; i++) {
+    let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(allTabsButton, {});
+
+    yield popupShownPromise;
+    let contextIdItem = popup.querySelector(`menuitem[usercontextid="${i}"]`);
+
+    ok(contextIdItem, `User context id ${i} exists`);
+
+    let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+    EventUtils.synthesizeMouseAtCenter(contextIdItem, {});
+
+    let tab = yield waitForTabPromise;
+
+    is(tab.getAttribute('usercontextid'), i, `New tab has UCI equal ${i}`);
+    yield BrowserTestUtils.removeTab(tab);
+  }
+});
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1627,29 +1627,33 @@ html|span.ac-emphasize-text-url {
   background-color: Highlight;
   transition: none;
 }
 
 #TabsToolbar .toolbarbutton-1 {
   margin-bottom: var(--tab-toolbar-navbar-overlap);
 }
 
+.tabs-alltabs-few-button,
 #alltabs-button {
   list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
 }
 
+#TabsToolbar[brighttext] .tabs-alltabs-few-button,
 #TabsToolbar[brighttext] > #alltabs-button,
 #TabsToolbar[brighttext] > toolbarpaletteitem > #alltabs-button {
   list-style-image: url("chrome://browser/skin/tabbrowser/alltabs-inverted.png");
 }
 
+#TabsToolbar .tabs-alltabs-few-button > .toolbarbutton-icon,
 #alltabs-button > .toolbarbutton-icon {
   padding: 9px 6px 6px;
 }
 
+.tabs-alltabs-few-button > .toolbarbutton-menu-dropmarker,
 #alltabs-button > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
 /* All tabs menupopup */
 .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2916,62 +2916,75 @@ toolbarbutton.chevron > .toolbarbutton-m
   }
 
   #TabsToolbar > #new-tab-button > .toolbarbutton-icon,
   #TabsToolbar > toolbarpaletteitem > #new-tab-button > .toolbarbutton-icon {
     width: 18px;
   }
 }
 
+.tabs-alltabs-few-button,
 #alltabs-button {
   list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png);
   -moz-image-region: rect(0, 17px, 20px, 0);
 }
 
+#TabsToolbar[brighttext] .tabs-alltabs-few-button,
 #TabsToolbar[brighttext] #alltabs-button {
   list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon-inverted.png);
 }
 
+.tabs-alltabs-few-button:not([disabled="true"]):hover,
 #alltabs-button:not([disabled="true"]):hover {
   -moz-image-region: rect(0, 34px, 20px, 17px);
 }
 
+.tabs-alltabs-few-button[open="true"]:not([disabled="true"]),
+.tabs-alltabs-few-button:not([disabled="true"]):hover:active,
 #alltabs-button[open="true"]:not([disabled="true"]),
 #alltabs-button:not([disabled="true"]):hover:active {
   -moz-image-region: rect(0, 51px, 20px, 34px);
 }
 
 @media (min-resolution: 2dppx) {
+  .tabs-alltabs-few-button,
   #alltabs-button {
     list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon@2x.png);
     -moz-image-region: rect(0, 34px, 40px, 0);
   }
 
+  #TabsToolbar[brighttext] .tabs-alltabs-few-button,
   #TabsToolbar[brighttext] #alltabs-button {
     list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png);
   }
 
+  .tabs-alltabs-few-button:not([disabled="true"]):hover,
   #alltabs-button:not([disabled="true"]):hover {
     -moz-image-region: rect(0, 68px, 40px, 34px);
   }
 
+  .tabs-alltabs-few-button[open="true"]:not([disabled="true"]),
+  .tabs-alltabs-few-button:not([disabled="true"]):hover:active,
   #alltabs-button[open="true"]:not([disabled="true"]),
   #alltabs-button:not([disabled="true"]):hover:active {
     -moz-image-region: rect(0, 102px, 40px, 68px);
   }
 
+  .tabs-alltabs-few-button > .toolbarbutton-icon,
   #alltabs-button > .toolbarbutton-icon {
     width: 17px;
   }
 }
 
+.tabs-alltabs-few-button > .toolbarbutton-menu-dropmarker,
 #alltabs-button > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
+.tabs-alltabs-few-button > .toolbarbutton-icon,
 #alltabs-button > .toolbarbutton-icon {
   margin-inline-end: 2px;
 }
 
 /* All Tabs Menupopup */
 .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2118,29 +2118,33 @@ html|span.ac-emphasize-text-url {
 #TabsToolbar > toolbarpaletteitem > #new-tab-button > .toolbarbutton-icon {
   width: 16px;
 }
 
 #TabsToolbar > #new-tab-button {
   width: 26px;
 }
 
+.tabs-alltabs-few-button,
 #alltabs-button {
   list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
 }
 
+#TabsToolbar[brighttext] .tabs-alltabs-few-button,
 #TabsToolbar[brighttext] > #alltabs-button,
 #TabsToolbar[brighttext] > toolbarpaletteitem > #alltabs-button {
   list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
 }
 
+.tabs-alltabs-few-button > .toolbarbutton-icon,
 #alltabs-button > .toolbarbutton-icon {
   margin: 0 2px;
 }
 
+.tabs-alltabs-few-button > .toolbarbutton-menu-dropmarker,
 #alltabs-button > .toolbarbutton-menu-dropmarker {
   display: none;
 }
 
 /* All tabs menupopup */
 .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }