From 6dd548e1af44be960931f254abdef8ea4bc2dd41 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Fri, 4 Feb 2011 09:45:41 -0800 Subject: [PATCH] Bug 595965 - App tabs bleed out of the group box r=ian+ehsan, a=blocking --- browser/base/content/tabview/groupitems.js | 95 ++++++++++-- browser/base/content/tabview/tabview.css | 2 +- browser/base/content/test/tabview/Makefile.in | 1 + .../test/tabview/browser_tabview_bug595965.js | 138 ++++++++++++++++++ .../gnomestripe/browser/tabview/tabview.css | 42 +++++- .../pinstripe/browser/tabview/tabview.css | 39 ++++- .../winstripe/browser/tabview/tabview.css | 39 ++++- 7 files changed, 334 insertions(+), 22 deletions(-) create mode 100644 browser/base/content/test/tabview/browser_tabview_bug595965.js diff --git a/browser/base/content/tabview/groupitems.js b/browser/base/content/tabview/groupitems.js index 6a8511da0451..bf85cf366890 100644 --- a/browser/base/content/tabview/groupitems.js +++ b/browser/base/content/tabview/groupitems.js @@ -26,6 +26,7 @@ * Raymond Lee * Tim Taubert * Sean Dunn + * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -226,9 +227,12 @@ function GroupItem(listOfEls, options) { .hide(); // ___ app tabs: create app tab tray and populate it + let appTabTrayContainer = iQ("
") + .addClass("appTabTrayContainer") + .appendTo($container); this.$appTabTray = iQ("
") .addClass("appTabTray") - .appendTo($container); + .appendTo(appTabTrayContainer); AllTabs.tabs.forEach(function(xulTab) { if (xulTab.pinned && xulTab.ownerDocument.defaultView == gWindow) @@ -375,6 +379,74 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), { this.$titleShield.css(css); }, + // ---------- + // Function: adjustAppTabTray + // Used to adjust the appTabTray size, to split the appTabIcons across + // multiple columns when needed - if the groupItem size is too small. + // + // Parameters: + // arrangeGroup - rearrange the groupItem if the number of appTab columns + // changes. If true, then this.arrange() is called, otherwise not. + adjustAppTabTray: function GroupItem_adjustAppTabTray(arrangeGroup) { + let icons = iQ(".appTabIcon", this.$appTabTray); + let container = iQ(this.$appTabTray[0].parentNode); + if (!icons.length) { + // There are no icons, so hide the appTabTray if needed. + if (parseInt(container.css("width")) != 0) { + this.$appTabTray.css("-moz-column-count", 0); + this.$appTabTray.css("height", 0); + container.css("width", 0); + container.css("height", 0); + + if (container.hasClass("appTabTrayContainerTruncated")) + container.removeClass("appTabTrayContainerTruncated"); + + if (arrangeGroup) + this.arrange(); + } + return; + } + + let iconBounds = iQ(icons[0]).bounds(); + let boxBounds = this.getBounds(); + let contentHeight = boxBounds.height - + parseInt(container.css("top")) - + this.$resizer.height(); + let rows = Math.floor(contentHeight / iconBounds.height); + let columns = Math.ceil(icons.length / rows); + let columnsGap = parseInt(this.$appTabTray.css("-moz-column-gap")); + let iconWidth = iconBounds.width + columnsGap; + let maxColumns = Math.floor((boxBounds.width * 0.20) / iconWidth); + + if (columns > maxColumns) + container.addClass("appTabTrayContainerTruncated"); + else if (container.hasClass("appTabTrayContainerTruncated")) + container.removeClass("appTabTrayContainerTruncated"); + + // Need to drop the -moz- prefix when Gecko makes it obsolete. + // See bug 629452. + if (parseInt(this.$appTabTray.css("-moz-column-count")) != columns) + this.$appTabTray.css("-moz-column-count", columns); + + if (parseInt(this.$appTabTray.css("height")) != contentHeight) { + this.$appTabTray.css("height", contentHeight + "px"); + container.css("height", contentHeight + "px"); + } + + let fullTrayWidth = iconWidth * columns - columnsGap; + if (parseInt(this.$appTabTray.css("width")) != fullTrayWidth) + this.$appTabTray.css("width", fullTrayWidth + "px"); + + let trayWidth = iconWidth * Math.min(columns, maxColumns) - columnsGap; + if (parseInt(container.css("width")) != trayWidth) { + container.css("width", trayWidth + "px"); + + // Rearrange the groupItem if the width changed. + if (arrangeGroup) + this.arrange(); + } + }, + // ---------- // Function: getContentBounds // Returns a for the groupItem's content area (which doesn't include the title, etc). @@ -384,7 +456,11 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), { box.top += titleHeight; box.height -= titleHeight; - var appTabTrayWidth = this.$appTabTray.width(); + let appTabTrayContainer = iQ(this.$appTabTray[0].parentNode); + var appTabTrayWidth = appTabTrayContainer.width(); + if (appTabTrayWidth) + appTabTrayWidth += parseInt(appTabTrayContainer.css(UI.rtl ? "left" : "right")); + box.width -= appTabTrayWidth; if (UI.rtl) { box.left += appTabTrayWidth; @@ -453,6 +529,10 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), { var offset = new Point(rect.left - this.bounds.left, rect.top - this.bounds.top); this.bounds = new Rect(rect); + // Make sure the AppTab icons fit the new groupItem size. + if (css.width || css.height) + this.adjustAppTabTray(); + // ___ Deal with children if (css.width || css.height) { this.arrange({animate: !immediately}); //(immediately ? 'sometimes' : true)}); @@ -1054,11 +1134,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), { }); // adjust the tray - let columnWidth = $appTab.width(); - if (parseInt(this.$appTabTray.css("width")) != columnWidth) { - this.$appTabTray.css({width: columnWidth}); - this.arrange(); - } + this.adjustAppTabTray(true); }, // ---------- @@ -1074,10 +1150,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), { }); // adjust the tray - if (!iQ(".appTabIcon", this.$appTabTray).length) { - this.$appTabTray.css({width: 0}); - this.arrange(); - } + this.adjustAppTabTray(true); xulTab.removeEventListener("error", this._onAppTabError, false); }, diff --git a/browser/base/content/tabview/tabview.css b/browser/base/content/tabview/tabview.css index 3a42bbadf533..d643ff69b1e2 100644 --- a/browser/base/content/tabview/tabview.css +++ b/browser/base/content/tabview/tabview.css @@ -101,7 +101,7 @@ body { position: absolute; } -.appTabTray { +.appTabTrayContainer { position: absolute; } diff --git a/browser/base/content/test/tabview/Makefile.in b/browser/base/content/test/tabview/Makefile.in index a12fdde88ae0..667f51295955 100644 --- a/browser/base/content/test/tabview/Makefile.in +++ b/browser/base/content/test/tabview/Makefile.in @@ -66,6 +66,7 @@ _BROWSER_FILES = \ browser_tabview_bug595804.js \ browser_tabview_bug595930.js \ browser_tabview_bug595943.js \ + browser_tabview_bug595965.js \ browser_tabview_bug596781.js \ browser_tabview_bug597248.js \ browser_tabview_bug597360.js \ diff --git a/browser/base/content/test/tabview/browser_tabview_bug595965.js b/browser/base/content/test/tabview/browser_tabview_bug595965.js new file mode 100644 index 000000000000..d23258dd4dbb --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_bug595965.js @@ -0,0 +1,138 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Contributor(s): + * Mihai Sucan + * Raymond Lee + * Ian Gilman + */ + +function test() { + waitForExplicitFinish(); + + newWindowWithTabView(onTabViewShown); +} + +function onTabViewShown(win) { + let TabView = win.TabView; + let gBrowser = win.gBrowser; + let document = win.document; + + ok(TabView.isVisible(), "Tab View is visible"); + + let contentWindow = document.getElementById("tab-view").contentWindow; + let iQ = contentWindow.iQ; + + // establish initial state + is(contentWindow.GroupItems.groupItems.length, 1, + "we start with one group (the default)"); + is(gBrowser.tabs.length, 1, "we start with one tab"); + let originalTab = gBrowser.tabs[0]; + + // create a group + let box = new contentWindow.Rect(20, 20, 210, 200); + let groupItem = new contentWindow.GroupItem([], + { bounds: box, title: "test1" }); + is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups"); + contentWindow.GroupItems.setActiveGroupItem(groupItem); + + // create a tab + let xulTabs = []; + xulTabs.push(gBrowser.loadOneTab("about:blank")); + is(gBrowser.tabs.length, 2, "we now have two tabs"); + is(groupItem._children.length, 1, "the new tab was added to the group"); + + // make sure the group has no app tabs + is(appTabCount(groupItem), 0, "there are no app tab icons"); + + let tray = groupItem.$appTabTray; + let trayContainer = iQ(tray[0].parentNode); + + is(parseInt(trayContainer.css("width")), 0, + "$appTabTray container is not visible"); + + // pin the tab, make sure the TabItem goes away and the icon comes on + gBrowser.pinTab(xulTabs[0]); + is(groupItem._children.length, 0, + "the app tab's TabItem was removed from the group"); + is(appTabCount(groupItem), 1, "there's now one app tab icon"); + + is(tray.css("-moz-column-count"), 1, + "$appTabTray column count is 1"); + isnot(parseInt(trayContainer.css("width")), 0, + "$appTabTray container is visible"); + + let iconHeight = iQ(iQ(".appTabIcon", tray)[0]).height(); + let trayHeight = parseInt(trayContainer.css("height")); + let rows = Math.floor(trayHeight / iconHeight); + let icons = rows * 2; + + // add enough tabs to have two columns + for (let i = 1; i < icons; i++) { + xulTabs.push(gBrowser.loadOneTab("about:blank")); + gBrowser.pinTab(xulTabs[i]); + } + + is(appTabCount(groupItem), icons, "number of app tab icons is correct"); + + is(tray.css("-moz-column-count"), 2, + "$appTabTray column count is 2"); + + ok(!trayContainer.hasClass("appTabTrayContainerTruncated"), + "$appTabTray container does not have .appTabTrayContainerTruncated"); + + // add one more tab + xulTabs.push(gBrowser.loadOneTab("about:blank")); + gBrowser.pinTab(xulTabs[xulTabs.length-1]); + + is(tray.css("-moz-column-count"), 3, + "$appTabTray column count is 3"); + + ok(trayContainer.hasClass("appTabTrayContainerTruncated"), + "$appTabTray container hasClass .appTabTrayContainerTruncated"); + + // remove all but one app tabs + for (let i = 1; i < xulTabs.length; i++) + gBrowser.removeTab(xulTabs[i]); + + is(tray.css("-moz-column-count"), 1, + "$appTabTray column count is 1"); + + is(appTabCount(groupItem), 1, "there's now one app tab icon"); + + ok(!trayContainer.hasClass("appTabTrayContainerTruncated"), + "$appTabTray container does not have .appTabTrayContainerTruncated"); + + // unpin the last remaining tab + gBrowser.unpinTab(xulTabs[0]); + + is(parseInt(trayContainer.css("width")), 0, + "$appTabTray container is not visible"); + + is(appTabCount(groupItem), 0, "there are no app tab icons"); + + is(groupItem._children.length, 1, "the normal tab shows in the group"); + + gBrowser.removeTab(xulTabs[0]); + + // close the group + groupItem.close(); + + hideTabView(function() { + ok(!TabView.isVisible(), "Tab View is hidden"); + + is(contentWindow.GroupItems.groupItems.length, 1, + "we finish with one group"); + is(gBrowser.tabs.length, 1, "we finish with one tab"); + + win.close(); + + executeSoon(finish); + }, win); +} + +function appTabCount(groupItem) { + return groupItem.container.getElementsByClassName("appTabIcon").length; +} + diff --git a/browser/themes/gnomestripe/browser/tabview/tabview.css b/browser/themes/gnomestripe/browser/tabview/tabview.css index 97aabbcf189e..90ba6219b6ed 100644 --- a/browser/themes/gnomestripe/browser/tabview/tabview.css +++ b/browser/themes/gnomestripe/browser/tabview/tabview.css @@ -100,7 +100,8 @@ html[dir=rtl] .close { left: 6px; } -.close:hover { +.close:hover, +.appTabIcon:hover { opacity: 1.0; } @@ -124,7 +125,8 @@ html[dir=rtl] .expander { } .close:hover, -.expander:hover { +.expander:hover, +.appTabIcon:hover { -moz-transition-property: opacity; -moz-transition-duration: 0.5s; -moz-transition-timing-function: ease-out; @@ -245,20 +247,52 @@ html[dir=rtl] .overlay { box-shadow: -3px 3px 5.5px rgba(0,0,0,.5); } -.appTabTray { +.appTabTrayContainer { top: 34px; right: 1px; + -moz-border-start: 1px solid #E1E1E1; + padding: 0 5px; + overflow-x: hidden; + text-align: start; + line-height: 0; } -html[dir=rtl] .appTabTray { +html[dir=rtl] .appTabTrayContainer { right: auto; left: 1px; } +.appTabTray { + display: inline-block; + -moz-column-width: 16px; + -moz-column-count: 0; + -moz-column-gap: 5px; +} + +.appTabTrayContainerTruncated { + padding-bottom: 7px; +} + +.appTabTrayContainerTruncated:after { + content: "…"; + position: absolute; + bottom: 2px; + left: 0; + display: block; + width: 100%; + height: 15px; + line-height: 15px; + text-align: center; + font-size: 15px; +} + .appTabIcon { width: 16px; height: 16px; cursor: pointer; + opacity: 0.8; + padding-bottom: 3px; + display: block; } .undo { diff --git a/browser/themes/pinstripe/browser/tabview/tabview.css b/browser/themes/pinstripe/browser/tabview/tabview.css index 640d78e3e640..7e2f37e350bb 100644 --- a/browser/themes/pinstripe/browser/tabview/tabview.css +++ b/browser/themes/pinstripe/browser/tabview/tabview.css @@ -115,7 +115,8 @@ html[dir=rtl] .expander { } .close:hover, -.expander:hover { +.expander:hover, +.appTabIcon:hover { -moz-transition-property: opacity; -moz-transition-duration: 0.5s; -moz-transition-timing-function: ease-out; @@ -239,20 +240,52 @@ html[dir=rtl] .overlay { box-shadow: -3px 3px 5.5px rgba(0,0,0,.5); } -.appTabTray { +.appTabTrayContainer { top: 34px; right: 1px; + -moz-border-start: 1px solid #E1E1E1; + padding: 0 5px; + overflow-x: hidden; + text-align: start; + line-height: 0; } -html[dir=rtl] .appTabTray { +html[dir=rtl] .appTabTrayContainer { right: auto; left: 1px; } +.appTabTray { + display: inline-block; + -moz-column-width: 16px; + -moz-column-count: 0; + -moz-column-gap: 5px; +} + +.appTabTrayContainerTruncated { + padding-bottom: 7px; +} + +.appTabTrayContainerTruncated:after { + content: "…"; + position: absolute; + bottom: 2px; + left: 0; + display: block; + width: 100%; + height: 15px; + line-height: 15px; + text-align: center; + font-size: 15px; +} + .appTabIcon { width: 16px; height: 16px; cursor: pointer; + opacity: 0.8; + padding-bottom: 3px; + display: block; } .undo { diff --git a/browser/themes/winstripe/browser/tabview/tabview.css b/browser/themes/winstripe/browser/tabview/tabview.css index c1d018cc6373..0c3617f75cef 100644 --- a/browser/themes/winstripe/browser/tabview/tabview.css +++ b/browser/themes/winstripe/browser/tabview/tabview.css @@ -118,7 +118,8 @@ html[dir=rtl] .expander { } .close:hover, -.expander:hover { +.expander:hover, +.appTabIcon:hover { -moz-transition-property: opacity; -moz-transition-duration: 0.5s; -moz-transition-timing-function: ease-out; @@ -262,20 +263,52 @@ html[dir=rtl] .overlay { box-shadow: -3px 3px 5.5px rgba(0,0,0,.5); } -.appTabTray { +.appTabTrayContainer { top: 34px; right: 1px; + -moz-border-start: 1px solid #E1E1E1; + padding: 0 5px; + overflow-x: hidden; + text-align: start; + line-height: 0; } -html[dir=rtl] .appTabTray { +html[dir=rtl] .appTabTrayContainer { right: auto; left: 1px; } +.appTabTray { + display: inline-block; + -moz-column-width: 16px; + -moz-column-count: 0; + -moz-column-gap: 5px; +} + +.appTabTrayContainerTruncated { + padding-bottom: 7px; +} + +.appTabTrayContainerTruncated:after { + content: "…"; + position: absolute; + bottom: 2px; + left: 0; + display: block; + width: 100%; + height: 15px; + line-height: 15px; + text-align: center; + font-size: 15px; +} + .appTabIcon { width: 16px; height: 16px; cursor: pointer; + opacity: 0.8; + padding-bottom: 3px; + display: block; } .undo {