diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 49df4df72c9..d9512cb734e 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -7930,6 +7930,9 @@ var TabContextMenu = { let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs; document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1; document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned; + + // Disable "Move to Group" if it's a pinned tab. + document.getElementById("context_tabViewMenu").disabled = this.contextTab.pinned; } }; diff --git a/browser/base/content/tabview/drag.js b/browser/base/content/tabview/drag.js index 17b1ac12c67..97000ca7adc 100644 --- a/browser/base/content/tabview/drag.js +++ b/browser/base/content/tabview/drag.js @@ -118,8 +118,8 @@ Drag.prototype = { // OH SNAP! - // if we aren't holding down the meta key... - if (!Keys.meta) { + // if we aren't holding down the meta key or have trenches disabled... + if (!Keys.meta && !Trenches.disabled) { // snappable = true if we aren't a tab on top of something else, and // there's no active drop site... let snappable = !(this.item.isATabItem && diff --git a/browser/base/content/tabview/groupitems.js b/browser/base/content/tabview/groupitems.js index 21a3ab7e984..e2de885ed1b 100644 --- a/browser/base/content/tabview/groupitems.js +++ b/browser/base/content/tabview/groupitems.js @@ -1386,39 +1386,7 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), { // TabItems will have handled the new tab and added the tabItem property. // We don't have to check if it's an app tab (and therefore wouldn't have a // TabItem), since we've just created it. - let newItem = newTab.tabItem; - - var self = this; - iQ(newItem.container).css({opacity: 0}); - let $anim = iQ("
") - .addClass("newTabAnimatee") - .css({ - top: newItem.bounds.top + 5, - left: newItem.bounds.left + 5, - width: newItem.bounds.width - 10, - height: newItem.bounds.height - 10, - zIndex: 999, - opacity: 0 - }) - .appendTo("body") - .animate({opacity: 1}, { - duration: 500, - complete: function() { - $anim.animate({ - top: 0, - left: 0, - width: window.innerWidth, - height: window.innerHeight - }, { - duration: 270, - complete: function() { - iQ(newItem.container).css({opacity: 1}); - newItem.zoomIn(!url); - $anim.remove(); - } - }); - } - }); + newTab.tabItem.zoomIn(!url); }, // ---------- @@ -1542,6 +1510,7 @@ let GroupItems = { }, // ---------- + // Function: _handleAttrModified // watch for icon changes on app tabs _handleAttrModified: function GroupItems__handleAttrModified(xulTab) { if (xulTab.ownerDocument.defaultView != gWindow || !xulTab.pinned) @@ -1561,16 +1530,18 @@ let GroupItems = { }, // ---------- - // when a tab becomes pinned, add it to the app tab tray in all groups - handleTabPin: function GroupItems_handleTabPin(xulTab) { + // Function: addAppTab + // Adds the given xul:tab to the app tab tray in all groups + addAppTab: function GroupItems_addAppTab(xulTab) { this.groupItems.forEach(function(groupItem) { groupItem.addAppTab(xulTab); }); }, // ---------- - // when a tab becomes unpinned, remove it from the app tab tray in all groups - handleTabUnpin: function GroupItems_handleTabUnpin(xulTab) { + // Function: removeAppTab + // Removes the given xul:tab from the app tab tray in all groups + removeAppTab: function GroupItems_removeAppTab(xulTab) { this.groupItems.forEach(function(groupItem) { groupItem.removeAppTab(xulTab); }); @@ -1582,7 +1553,7 @@ let GroupItems = { getNextID: function GroupItems_getNextID() { var result = this.nextID; this.nextID++; - this.save(); + this._save(); return result; }, @@ -1602,20 +1573,22 @@ let GroupItems = { // Function: saveAll // Saves GroupItems state, as well as the state of all of the groupItems. saveAll: function GroupItems_saveAll() { - this.save(); + this._save(); this.groupItems.forEach(function(groupItem) { groupItem.save(); }); }, // ---------- - // Function: save + // Function: _save // Saves GroupItems state. - save: function GroupItems_save() { + _save: function GroupItems__save() { if (!this._inited) // too soon to save now return; - Storage.saveGroupItemsData(gWindow, {nextID:this.nextID}); + let activeGroupId = this._activeGroupItem ? this._activeGroupItem.id : null; + Storage.saveGroupItemsData( + gWindow, { nextID: this.nextID, activeGroupId: activeGroupId }); }, // ---------- @@ -1637,8 +1610,14 @@ let GroupItems = { // If no data, sets up blank slate (including "new tabs" groupItem). reconstitute: function GroupItems_reconstitute(groupItemsData, groupItemData) { try { - if (groupItemsData && groupItemsData.nextID) - this.nextID = groupItemsData.nextID; + let activeGroupId; + + if (groupItemsData) { + if (groupItemsData.nextID) + this.nextID = groupItemsData.nextID; + if (groupItemsData.activeGroupId) + activeGroupId = groupItemsData.activeGroupId; + } if (groupItemData) { for (var id in groupItemData) { @@ -1652,9 +1631,15 @@ let GroupItems = { } } } + // set active group item + if (activeGroupId) { + let activeGroupItem = this.groupItem(activeGroupId); + if (activeGroupItem) + this.setActiveGroupItem(activeGroupItem); + } this._inited = true; - this.save(); // for nextID + this._save(); // for nextID } catch(e) { Utils.log("error in recons: "+e); } @@ -1763,38 +1748,83 @@ let GroupItems = { // Given a , files it in the appropriate groupItem. newTab: function GroupItems_newTab(tabItem) { let activeGroupItem = this.getActiveGroupItem(); - let orphanTab = this.getActiveOrphanTab(); -// Utils.log('newTab', activeGroupItem, orphanTab); + + // 1. Active group + // 2. Active orphan + // 3. First visible non-app tab (that's not the tab in question), whether it's an + // orphan or not (make a new group if it's an orphan, add it to the group if it's + // not) + // 4. First group + // 5. First orphan that's not the tab in question + // 6. At this point there should be no groups or tabs (except for app tabs and the + // tab in question): make a new group + if (activeGroupItem) { activeGroupItem.add(tabItem); - } else if (orphanTab) { - let newGroupItemBounds = orphanTab.getBoundsWithTitle(); - newGroupItemBounds.inset(-40,-40); - let newGroupItem = new GroupItem([orphanTab, tabItem], {bounds: newGroupItemBounds}); - newGroupItem.snap(); - this.setActiveGroupItem(newGroupItem); - } else { - this.positionNewTabAtBottom(tabItem); + return; } - }, - // ---------- - // Function: positionNewTabAtBottom - // Does what it says on the tin. - // TODO: Make more robust and improve documentation, - // Also, this probably belongs in tabitems.js - // Bug 586558 - positionNewTabAtBottom: function GroupItems_positionNewTabAtBottom(tabItem) { - let windowBounds = Items.getSafeWindowBounds(); + let orphanTabItem = this.getActiveOrphanTab(); + if (!orphanTabItem) { + let otherTab; + // find first visible non-app tab in the tabbar. + gBrowser.visibleTabs.some(function(tab) { + if (!tab.pinned && tab != tabItem.tab) { + otherTab = tab; + return true; + } + return false; + }); - let itemBounds = new Rect( - windowBounds.right - TabItems.tabWidth, - windowBounds.bottom - TabItems.tabHeight, - TabItems.tabWidth, - TabItems.tabHeight - ); + if (otherTab) { + // the first visible tab belongs to a group, add the new tabItem into + // that group + if (otherTab.tabItem.parent) { + let groupItem = otherTab.tabItem.parent; + groupItem.add(tabItem); + this.setActiveGroupItem(groupItem); + return; + } + // the first visible tab is an orphan tab, set the orphan tab, and + // create a new group for orphan tab and new tabItem + orphanTabItem = otherTab.tabItem; + } - tabItem.setBounds(itemBounds); + if (!orphanTabItem) { + // add the new tabItem to the first group item + if (this.groupItems.length > 0) { + let groupItem = this.groupItems[0]; + groupItem.add(tabItem); + this.setActiveGroupItem(groupItem); + return; + } + // set the orphan tab, and create a new group for orphan tab and + // new tabItem + let orphanedTabs = this.getOrphanedTabs(); + if (orphanedTabs.length > 0) + orphanTabItem = orphanedTabs[0]; + } + } + + // create new group for orphan tab and new tabItem + let tabItems; + let newGroupItemBounds; + // the orphan tab would be the same as tabItem when all tabs are app tabs + // and a new tab is created. + if (orphanTabItem && orphanTabItem.tab != tabItem.tab) { + newGroupItemBounds = orphanTabItem.getBoundsWithTitle(); + tabItems = [orphanTabItem, tabItem]; + } else { + tabItem.setPosition(60, 60, true); + newGroupItemBounds = tabItem.getBounds(); + tabItems = [tabItem]; + } + + newGroupItemBounds.inset(-40,-40); + let newGroupItem = + new GroupItem(tabItems, { bounds: newGroupItemBounds }); + newGroupItem.snap(); + this.setActiveGroupItem(newGroupItem); }, // ---------- @@ -1825,6 +1855,7 @@ let GroupItems = { } this._activeGroupItem = groupItem; + this._save(); }, // ---------- @@ -1909,6 +1940,13 @@ let GroupItems = { if (groupItems.length > 0) { groupItems.some(function(groupItem) { if (!groupItem.hidden) { + // restore the last active tab in the group + let activeTab = groupItem.getActiveTab(); + if (activeTab) { + tabItem = activeTab; + return true; + } + // if no tab is active, use the first one var child = groupItem.getChild(0); if (child) { tabItem = child; @@ -1930,6 +1968,13 @@ let GroupItems = { var firstGroupItems = groupItems.slice(currentIndex + 1); firstGroupItems.some(function(groupItem) { if (!groupItem.hidden) { + // restore the last active tab in the group + let activeTab = groupItem.getActiveTab(); + if (activeTab) { + tabItem = activeTab; + return true; + } + // if no tab is active, use the first one var child = groupItem.getChild(0); if (child) { tabItem = child; @@ -1947,6 +1992,13 @@ let GroupItems = { var secondGroupItems = groupItems.slice(0, currentIndex); secondGroupItems.some(function(groupItem) { if (!groupItem.hidden) { + // restore the last active tab in the group + let activeTab = groupItem.getActiveTab(); + if (activeTab) { + tabItem = activeTab; + return true; + } + // if no tab is active, use the first one var child = groupItem.getChild(0); if (child) { tabItem = child; diff --git a/browser/base/content/tabview/items.js b/browser/base/content/tabview/items.js index ccd7bbd96d7..88ff81c0d93 100644 --- a/browser/base/content/tabview/items.js +++ b/browser/base/content/tabview/items.js @@ -187,7 +187,10 @@ Item.prototype = { stop: function() { drag.info.stop(); drag.info = null; - } + }, + // The minimum the mouse must move after mouseDown in order to move an + // item + minDragDistance: 3 }; // ___ drop @@ -592,64 +595,67 @@ Item.prototype = { // ___ mousemove var handleMouseMove = function(e) { // positioning - var mouse = new Point(e.pageX, e.pageY); - var box = self.getBounds(); - box.left = startPos.x + (mouse.x - startMouse.x); - box.top = startPos.y + (mouse.y - startMouse.y); - - self.setBounds(box, true); - - // drag events + var mouse = new Point(e.pageX, e.pageY); if (!startSent) { - if (typeof self.dragOptions.start == "function") - self.dragOptions.start.apply(self, - [startEvent, {position: {left: startPos.x, top: startPos.y}}]); - - startSent = true; + if(Math.abs(mouse.x - startMouse.x) > self.dragOptions.minDragDistance || + Math.abs(mouse.y - startMouse.y) > self.dragOptions.minDragDistance) { + if (typeof self.dragOptions.start == "function") + self.dragOptions.start.apply(self, + [startEvent, {position: {left: startPos.x, top: startPos.y}}]); + startSent = true; + } } + if (startSent) { + // drag events + var box = self.getBounds(); + box.left = startPos.x + (mouse.x - startMouse.x); + box.top = startPos.y + (mouse.y - startMouse.y); - if (typeof self.dragOptions.drag == "function") - self.dragOptions.drag.apply(self, [e]); + self.setBounds(box, true); - // drop events - var best = { - dropTarget: null, - score: 0 - }; + if (typeof self.dragOptions.drag == "function") + self.dragOptions.drag.apply(self, [e]); - droppables.forEach(function(droppable) { - var intersection = box.intersection(droppable.bounds); - if (intersection && intersection.area() > best.score) { - var possibleDropTarget = droppable.item; - var accept = true; - if (possibleDropTarget != dropTarget) { - var dropOptions = possibleDropTarget.dropOptions; - if (dropOptions && typeof dropOptions.accept == "function") - accept = dropOptions.accept.apply(possibleDropTarget, [self]); + // drop events + var best = { + dropTarget: null, + score: 0 + }; + + droppables.forEach(function(droppable) { + var intersection = box.intersection(droppable.bounds); + if (intersection && intersection.area() > best.score) { + var possibleDropTarget = droppable.item; + var accept = true; + if (possibleDropTarget != dropTarget) { + var dropOptions = possibleDropTarget.dropOptions; + if (dropOptions && typeof dropOptions.accept == "function") + accept = dropOptions.accept.apply(possibleDropTarget, [self]); + } + + if (accept) { + best.dropTarget = possibleDropTarget; + best.score = intersection.area(); + } + } + }); + + if (best.dropTarget != dropTarget) { + var dropOptions; + if (dropTarget) { + dropOptions = dropTarget.dropOptions; + if (dropOptions && typeof dropOptions.out == "function") + dropOptions.out.apply(dropTarget, [e]); } - if (accept) { - best.dropTarget = possibleDropTarget; - best.score = intersection.area(); + dropTarget = best.dropTarget; + + if (dropTarget) { + dropOptions = dropTarget.dropOptions; + if (dropOptions && typeof dropOptions.over == "function") + dropOptions.over.apply(dropTarget, [e]); } } - }); - - if (best.dropTarget != dropTarget) { - var dropOptions; - if (dropTarget) { - dropOptions = dropTarget.dropOptions; - if (dropOptions && typeof dropOptions.out == "function") - dropOptions.out.apply(dropTarget, [e]); - } - - dropTarget = best.dropTarget; - - if (dropTarget) { - dropOptions = dropTarget.dropOptions; - if (dropOptions && typeof dropOptions.over == "function") - dropOptions.over.apply(dropTarget, [e]); - } } e.preventDefault(); diff --git a/browser/base/content/tabview/tabitems.js b/browser/base/content/tabview/tabitems.js index 6c049ca1188..111d03bacb0 100644 --- a/browser/base/content/tabview/tabitems.js +++ b/browser/base/content/tabview/tabitems.js @@ -1030,10 +1030,16 @@ let TabItems = { item.reconnected = true; found = true; - } else - item.reconnected = item.tab.linkedBrowser.currentURI.spec != 'about:blank'; - + } else { + // if it's not a blank tab or it belongs to a group, it would mean + // the item is reconnected. + item.reconnected = + (item.tab.linkedBrowser.currentURI.spec != 'about:blank' || item.parent); + } item.save(); + + if (item.reconnected) + item._sendToSubscribers("reconnected"); } catch(e) { Utils.log(e); } diff --git a/browser/base/content/tabview/trench.js b/browser/base/content/tabview/trench.js index 8e045ae924b..f7ccf37aab3 100644 --- a/browser/base/content/tabview/trench.js +++ b/browser/base/content/tabview/trench.js @@ -466,9 +466,11 @@ var Trenches = { // nextId - (integer) a counter for the next 's value. // showDebug - (boolean) whether to draw the es or not. // defaultRadius - (integer) the default radius for new es. + // disabled - (boolean) whether trench-snapping is disabled or not. nextId: 0, showDebug: false, defaultRadius: 10, + disabled: false, // --------- // Variables: snapping preferences; used to break ties in snapping. diff --git a/browser/base/content/tabview/ui.js b/browser/base/content/tabview/ui.js index d5f7bb79b75..cb2048de4b4 100644 --- a/browser/base/content/tabview/ui.js +++ b/browser/base/content/tabview/ui.js @@ -448,10 +448,25 @@ let UI = { _addTabActionHandlers: function UI__addTabActionHandlers() { var self = this; + // TabOpen + this._eventListeners.open = function(tab) { + if (tab.ownerDocument.defaultView != gWindow) + return; + + // if it's an app tab, add it to all the group items + if (tab.pinned) + GroupItems.addAppTab(tab); + }; + + // TabClose this._eventListeners.close = function(tab) { if (tab.ownerDocument.defaultView != gWindow) return; + // if it's an app tab, remove it from all the group items + if (tab.pinned) + GroupItems.removeAppTab(tab); + if (self._isTabViewVisible()) { // just closed the selected tab in the TabView interface. if (self._currentTab == tab) @@ -470,13 +485,17 @@ let UI = { // 1) Only go back to the TabView tab when there you close the last // tab of a groupItem. + let closingLastOfGroup = (groupItem && + groupItem._children.length == 1 && + groupItem._children[0].tab == tab); + // 2) Take care of the case where you've closed the last tab in // an un-named groupItem, which means that the groupItem is gone (null) and - // there are no visible tabs. - // Can't use timeout here because user would see a flicker of - // switching to another tab before the TabView interface shows up. - if ((groupItem && groupItem._children.length == 1) || - (groupItem == null && gBrowser.visibleTabs.length <= 1)) { + // there are no visible tabs. + let closingUnnamedGroup = (groupItem == null && + gBrowser.visibleTabs.length <= 1); + + if (closingLastOfGroup || closingUnnamedGroup) { // for the tab focus event to pick up. self._closedLastVisibleTab = true; // remove the zoom prep. @@ -488,6 +507,7 @@ let UI = { } }; + // TabMove this._eventListeners.move = function(tab) { if (tab.ownerDocument.defaultView != gWindow) return; @@ -497,6 +517,7 @@ let UI = { self.setReorderTabItemsOnShow(activeGroupItem); }; + // TabSelect this._eventListeners.select = function(tab) { if (tab.ownerDocument.defaultView != gWindow) return; @@ -504,13 +525,14 @@ let UI = { self.onTabSelect(tab); }; + // Actually register the above handlers for (let name in this._eventListeners) AllTabs.register(name, this._eventListeners[name]); // Start watching for tab pin events, and set up our uninit for same. function handleTabPin(event) { TabItems.handleTabPin(event.originalTarget); - GroupItems.handleTabPin(event.originalTarget); + GroupItems.addAppTab(event.originalTarget); } gBrowser.tabContainer.addEventListener("TabPinned", handleTabPin, false); @@ -521,7 +543,7 @@ let UI = { // Start watching for tab unpin events, and set up our uninit for same. function handleTabUnpin(event) { TabItems.handleTabUnpin(event.originalTarget); - GroupItems.handleTabUnpin(event.originalTarget); + GroupItems.removeAppTab(event.originalTarget); } gBrowser.tabContainer.addEventListener("TabUnpinned", handleTabUnpin, false); diff --git a/browser/base/content/test/tabview/Makefile.in b/browser/base/content/test/tabview/Makefile.in index a30411b1f5c..871c1fe8010 100644 --- a/browser/base/content/test/tabview/Makefile.in +++ b/browser/base/content/test/tabview/Makefile.in @@ -45,9 +45,12 @@ include $(topsrcdir)/config/rules.mk _BROWSER_FILES = \ browser_tabview_apptabs.js \ + browser_tabview_bug580412.js \ browser_tabview_bug587040.js \ browser_tabview_bug587990.js \ + browser_tabview_bug590606.js \ browser_tabview_bug591706.js \ + browser_tabview_bug594176.js \ browser_tabview_bug595191.js \ browser_tabview_bug595518.js \ browser_tabview_bug595943.js \ @@ -56,6 +59,7 @@ _BROWSER_FILES = \ browser_tabview_group.js \ browser_tabview_launch.js \ browser_tabview_multiwindow_search.js \ + browser_tabview_orphaned_tabs.js \ browser_tabview_search.js \ browser_tabview_snapping.js \ browser_tabview_undo_group.js \ diff --git a/browser/base/content/test/tabview/browser_tabview_apptabs.js b/browser/base/content/test/tabview/browser_tabview_apptabs.js index 839bb83b1de..8e9cd089409 100644 --- a/browser/base/content/test/tabview/browser_tabview_apptabs.js +++ b/browser/base/content/test/tabview/browser_tabview_apptabs.js @@ -11,7 +11,7 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is tabview group test. + * The Original Code is tabview app-tab test. * * The Initial Developer of the Original Code is * Mozilla Foundation. @@ -50,51 +50,46 @@ function onTabViewWindowLoaded() { let contentWindow = document.getElementById("tab-view").contentWindow; // establish initial state - is(contentWindow.GroupItems.groupItems.length, 1, "we start with one group (the default)"); + 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 + + // create a group let box = new contentWindow.Rect(20, 20, 180, 180); - let groupItemOne = new contentWindow.GroupItem([], { bounds: box, title: "test1" }); + let groupItemOne = new contentWindow.GroupItem([], + { bounds: box, title: "test1" }); is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups"); contentWindow.GroupItems.setActiveGroupItem(groupItemOne); - + // create a tab let xulTab = gBrowser.loadOneTab("about:blank"); is(gBrowser.tabs.length, 2, "we now have two tabs"); is(groupItemOne._children.length, 1, "the new tab was added to the group"); - + // make sure the group has no app tabs - let appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon"); - is(appTabIcons.length, 0, "there are no app tab icons"); - + is(appTabCount(groupItemOne), 0, "there are no app tab icons"); + // pin the tab, make sure the TabItem goes away and the icon comes on gBrowser.pinTab(xulTab); - - is(groupItemOne._children.length, 0, "the app tab's TabItem was removed from the group"); - - appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon"); - is(appTabIcons.length, 1, "there's now one app tab icon"); + is(groupItemOne._children.length, 0, + "the app tab's TabItem was removed from the group"); + is(appTabCount(groupItemOne), 1, "there's now one app tab icon"); // create a second group and make sure it gets the icon too box.offset(box.width + 20, 0); - let groupItemTwo = new contentWindow.GroupItem([], { bounds: box, title: "test2" }); + let groupItemTwo = new contentWindow.GroupItem([], + { bounds: box, title: "test2" }); is(contentWindow.GroupItems.groupItems.length, 3, "we now have three groups"); - appTabIcons = groupItemTwo.container.getElementsByClassName("appTabIcon"); - is(appTabIcons.length, 1, "there's an app tab icon in the second group"); - + is(appTabCount(groupItemTwo), 1, + "there's an app tab icon in the second group"); + // unpin the tab, make sure the icon goes away and the TabItem comes on gBrowser.unpinTab(xulTab); - is(groupItemOne._children.length, 1, "the app tab's TabItem is back"); + is(appTabCount(groupItemOne), 0, "the icon is gone from group one"); + is(appTabCount(groupItemTwo), 0, "the icon is gone from group 2"); - appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon"); - is(appTabIcons.length, 0, "the icon is gone from group one"); - - appTabIcons = groupItemTwo.container.getElementsByClassName("appTabIcon"); - is(appTabIcons.length, 0, "the icon is gone from group 2"); - // pin the tab again gBrowser.pinTab(xulTab); @@ -104,25 +99,30 @@ function onTabViewWindowLoaded() { // find app tab in group and hit it let onTabViewHidden = function() { window.removeEventListener("tabviewhidden", onTabViewHidden, false); - ok(!TabView.isVisible(), "Tab View is hidden because we clicked on the app tab"); - - // clean up - gBrowser.selectedTab = originalTab; - - gBrowser.unpinTab(xulTab); + ok(!TabView.isVisible(), + "Tab View is hidden because we clicked on the app tab"); + + // delete the app tab and make sure its icon goes away gBrowser.removeTab(xulTab); - is(gBrowser.tabs.length, 1, "we finish with one tab"); - + is(appTabCount(groupItemOne), 0, "closing app tab removes its icon"); + + // clean up groupItemOne.close(); - is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group"); - + + is(contentWindow.GroupItems.groupItems.length, 1, + "we finish with one group"); + is(gBrowser.tabs.length, 1, "we finish with one tab"); ok(!TabView.isVisible(), "we finish with Tab View not visible"); - + finish(); }; window.addEventListener("tabviewhidden", onTabViewHidden, false); - appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon"); + let appTabIcons = groupItemOne.container.getElementsByClassName("appTabIcon"); EventUtils.sendMouseEvent({ type: "click" }, appTabIcons[0], contentWindow); } + +function appTabCount(groupItem) { + return groupItem.container.getElementsByClassName("appTabIcon").length; +} diff --git a/browser/base/content/test/tabview/browser_tabview_bug580412.js b/browser/base/content/test/tabview/browser_tabview_bug580412.js new file mode 100644 index 00000000000..45f2b42efde --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_bug580412.js @@ -0,0 +1,154 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Tab View bug 580412 test. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Michael Yoshitaka Erlewine + * + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +function test() { + waitForExplicitFinish(); + + window.addEventListener("tabviewshown", onTabViewWindowLoaded, false); + if (TabView.isVisible()) + onTabViewWindowLoaded(); + else + TabView.show(); +} + +function onTabViewWindowLoaded() { + window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false); + + let contentWindow = document.getElementById("tab-view").contentWindow; + let [originalTab] = gBrowser.visibleTabs; + + ok(TabView.isVisible(), "Tab View is visible"); + is(contentWindow.GroupItems.groupItems.length, 1, "There is only one group"); + let currentActiveGroup = contentWindow.GroupItems.getActiveGroupItem(); + +// is(currentActiveGroup.getBounds.bottom(), 40, +// "There's currently 40 px between the first group and second group"); + + let endGame = function() { + let onTabViewHidden = function() { + window.removeEventListener("tabviewhidden", onTabViewHidden, false); + ok(!TabView.isVisible(), "TabView is shown"); + finish(); + }; + window.addEventListener("tabviewhidden", onTabViewHidden, false); + + ok(TabView.isVisible(), "TabView is shown"); + + gBrowser.selectedTab = originalTab; + TabView.hide(); + } + + let part1 = function() { +// contentWindow.UI.reset(); + // move down 20 so we're far enough away from the top. + checkSnap(currentActiveGroup, 0, 20, contentWindow, function(snapped){ + ok(!snapped,"Move away from the edge"); + + // Just pick it up and drop it. + checkSnap(currentActiveGroup, 0, 0, contentWindow, function(snapped){ + ok(!snapped,"Just pick it up and drop it"); + + checkSnap(currentActiveGroup, 0, 1, contentWindow, function(snapped){ + ok(snapped,"Drag one pixel: should snap"); + + checkSnap(currentActiveGroup, 0, 5, contentWindow, function(snapped){ + ok(!snapped,"Moving five pixels: shouldn't snap"); + endGame(); + }); + }); + }); + }); + } + + part1(); +} + +function simulateDragDrop(tabItem, offsetX, offsetY, contentWindow) { + // enter drag mode + let dataTransfer; + + EventUtils.synthesizeMouse( + tabItem.container, 1, 1, { type: "mousedown" }, contentWindow); + event = contentWindow.document.createEvent("DragEvents"); + event.initDragEvent( + "dragenter", true, true, contentWindow, 0, 0, 0, 0, 0, + false, false, false, false, 1, null, dataTransfer); + tabItem.container.dispatchEvent(event); + + // drag over + if (offsetX || offsetY) { + let Ci = Components.interfaces; + let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + let rect = tabItem.getBounds(); + for (let i = 1; i <= 5; i++) { + let left = rect.left + 1 + Math.round(i * offsetX / 5); + let top = rect.top + 1 + Math.round(i * offsetY / 5); + utils.sendMouseEvent("mousemove", left, top, 0, 1, 0); + } + event = contentWindow.document.createEvent("DragEvents"); + event.initDragEvent( + "dragover", true, true, contentWindow, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + tabItem.container.dispatchEvent(event); + } + + // drop + EventUtils.synthesizeMouse( + tabItem.container, 0, 0, { type: "mouseup" }, contentWindow); + event = contentWindow.document.createEvent("DragEvents"); + event.initDragEvent( + "drop", true, true, contentWindow, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + tabItem.container.dispatchEvent(event); +} + +function checkSnap(item, offsetX, offsetY, contentWindow, callback) { + let firstTop = item.getBounds().top; + let firstLeft = item.getBounds().left; + let onDrop = function() { + let snapped = false; + item.container.removeEventListener('drop', onDrop, false); + if (item.getBounds().top != firstTop + offsetY) + snapped = true; + if (item.getBounds().left != firstLeft + offsetX) + snapped = true; + callback(snapped); + }; + item.container.addEventListener('drop', onDrop, false); + simulateDragDrop(item, offsetX, offsetY, contentWindow); +} diff --git a/browser/base/content/test/tabview/browser_tabview_bug590606.js b/browser/base/content/test/tabview/browser_tabview_bug590606.js new file mode 100644 index 00000000000..5491cac81d1 --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_bug590606.js @@ -0,0 +1,126 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is bug 590606 test. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Raymond Lee + * + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +let originalTab; +let newTabOne; + +function test() { + waitForExplicitFinish(); + + originalTab = gBrowser.visibleTabs[0]; + // add a tab to the existing group. + newTabOne = gBrowser.addTab(); + + let onTabviewShown = function() { + window.removeEventListener("tabviewshown", onTabviewShown, false); + + let contentWindow = document.getElementById("tab-view").contentWindow; + + is(contentWindow.GroupItems.groupItems.length, 1, + "There is one group item on startup"); + let groupItemOne = contentWindow.GroupItems.groupItems[0]; + is(groupItemOne.getChildren().length, 2, + "There should be two tab items in that group."); + is(gBrowser.selectedTab, groupItemOne.getChild(0).tab, + "The currently selected tab should be the first tab in the groupItemOne"); + + // create another group with a tab. + let groupItemTwo = createEmptyGroupItem(contentWindow, 200); + + let onTabViewHidden = function() { + window.removeEventListener("tabviewhidden", onTabViewHidden, false); + // start the test + testGroupSwitch(contentWindow, groupItemOne, groupItemTwo); + }; + window.addEventListener("tabviewhidden", onTabViewHidden, false); + + // click on the + button + let newTabButton = groupItemTwo.container.getElementsByClassName("newTabButton"); + ok(newTabButton[0], "New tab button exists"); + EventUtils.sendMouseEvent({ type: "click" }, newTabButton[0], contentWindow); + }; + window.addEventListener("tabviewshown", onTabviewShown, false); + TabView.toggle(); +} + +function testGroupSwitch(contentWindow, groupItemOne, groupItemTwo) { + is(gBrowser.selectedTab, groupItemTwo.getChild(0).tab, + "The currently selected tab should be the only tab in the groupItemTwo"); + + // switch to groupItemOne + tabItem = contentWindow.GroupItems.getNextGroupItemTab(false); + if (tabItem) + gBrowser.selectedTab = tabItem.tab; + is(gBrowser.selectedTab, groupItemOne.getChild(0).tab, + "The currently selected tab should be the first tab in the groupItemOne"); + + // switch to the second tab in groupItemOne + gBrowser.selectedTab = groupItemOne.getChild(1).tab; + + // switch to groupItemTwo + tabItem = contentWindow.GroupItems.getNextGroupItemTab(false); + if (tabItem) + gBrowser.selectedTab = tabItem.tab; + is(gBrowser.selectedTab, groupItemTwo.getChild(0).tab, + "The currently selected tab should be the only tab in the groupItemTwo"); + + // switch to groupItemOne + tabItem = contentWindow.GroupItems.getNextGroupItemTab(false); + if (tabItem) + gBrowser.selectedTab = tabItem.tab; + is(gBrowser.selectedTab, groupItemOne.getChild(1).tab, + "The currently selected tab should be the second tab in the groupItemOne"); + + // cleanup. + gBrowser.removeTab(groupItemTwo.getChild(0).tab); + gBrowser.removeTab(newTabOne); + + finish(); +} + +function createEmptyGroupItem(contentWindow, padding) { + let pageBounds = contentWindow.Items.getPageBounds(); + pageBounds.inset(padding, padding); + + let box = new contentWindow.Rect(pageBounds); + box.width = 300; + box.height = 300; + + let emptyGroupItem = new contentWindow.GroupItem([], { bounds: box }); + + return emptyGroupItem; +} diff --git a/browser/base/content/test/tabview/browser_tabview_bug594176.js b/browser/base/content/test/tabview/browser_tabview_bug594176.js new file mode 100644 index 00000000000..8a841eb0e20 --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_bug594176.js @@ -0,0 +1,65 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is bug 594176 test. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Raymond Lee + * + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +function test() { + let [origTab] = gBrowser.visibleTabs; + ok(!origTab.pinned, "The original tab is not pinned"); + + let pinnedTab = gBrowser.addTab(); + gBrowser.pinTab(pinnedTab); + ok(pinnedTab.pinned, "The new tab is pinned"); + + popup(origTab); + ok(!document.getElementById("context_tabViewMenu").disabled, + "The tab view menu is enabled for normal tab"); + + popup(pinnedTab); + ok(document.getElementById("context_tabViewMenu").disabled, + "The tab view menu is disabled for pinned tab"); + + gBrowser.unpinTab(pinnedTab); + popup(pinnedTab); + ok(!document.getElementById("context_tabViewMenu").disabled, + "The tab view menu is enabled for unpinned tab"); + + gBrowser.removeTab(pinnedTab); +} + +function popup(tab) { + document.popupNode = tab; + TabContextMenu.updateContextMenu(document.getElementById("tabContextMenu")); +} diff --git a/browser/base/content/test/tabview/browser_tabview_orphaned_tabs.js b/browser/base/content/test/tabview/browser_tabview_orphaned_tabs.js new file mode 100644 index 00000000000..2d591c5b963 --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_orphaned_tabs.js @@ -0,0 +1,136 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is tabview test for orphaned tabs (bug 595893). + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Raymond Lee + * + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +let tabOne; +let newWin; + +function test() { + waitForExplicitFinish(); + + newWin = + window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", "about:blank"); + + let onLoad = function() { + newWin.removeEventListener("load", onLoad, false); + + tabOne = newWin.gBrowser.addTab(); + + newWin.addEventListener("tabviewshown", onTabViewWindowLoaded, false); + newWin.TabView.toggle(); + } + newWin.addEventListener("load", onLoad, false); +} + +function onTabViewWindowLoaded() { + newWin.removeEventListener("tabviewshown", onTabViewWindowLoaded, false); + + ok(newWin.TabView.isVisible(), "Tab View is visible"); + + let contentWindow = newWin.document.getElementById("tab-view").contentWindow; + + // 1) the tab should belong to a group, and no orphan tabs + ok(tabOne.tabItem.parent, "Tab one belongs to a group"); + is(contentWindow.GroupItems.getOrphanedTabs().length, 0, "No orphaned tabs"); + + // 2) create a group, add a blank tab + let groupItem = createEmptyGroupItem(contentWindow, 200); + + let onTabViewHidden = function() { + newWin.removeEventListener("tabviewhidden", onTabViewHidden, false); + + // 3) the new group item should have one child and no orphan tab + is(groupItem.getChildren().length, 1, "The group item has an item"); + is(contentWindow.GroupItems.getOrphanedTabs().length, 0, "No orphaned tabs"); + + let checkAndFinish = function() { + // 4) check existence of stored group data for tab before finishing + let tabData = contentWindow.Storage.getTabData(tabItem.tab); + ok(tabData && contentWindow.TabItems.storageSanity(tabData) && tabData.groupID, + "Tab two has stored group data"); + + // clean up and finish the test + newWin.gBrowser.removeTab(tabOne); + newWin.gBrowser.removeTab(tabItem.tab); + whenWindowObservesOnce(newWin, "domwindowclosed", function() { + finish(); + }); + newWin.close(); + }; + let tabItem = groupItem.getChild(0); + // the item may not be connected so subscriber would be used in that case. + if (tabItem.reconnected) { + checkAndFinish(); + } else { + tabItem.addSubscriber(tabItem, "reconnected", function() { + tabItem.removeSubscriber(tabItem, "reconnected"); + checkAndFinish(); + }); + } + }; + newWin.addEventListener("tabviewhidden", onTabViewHidden, false); + + // click on the + button + let newTabButton = groupItem.container.getElementsByClassName("newTabButton"); + ok(newTabButton[0], "New tab button exists"); + + EventUtils.sendMouseEvent({ type: "click" }, newTabButton[0], contentWindow); +} + +function createEmptyGroupItem(contentWindow, padding) { + let pageBounds = contentWindow.Items.getPageBounds(); + pageBounds.inset(padding, padding); + + let box = new contentWindow.Rect(pageBounds); + box.width = 300; + box.height = 300; + + let emptyGroupItem = new contentWindow.GroupItem([], { bounds: box }); + + return emptyGroupItem; +} + +function whenWindowObservesOnce(win, topic, callback) { + let windowWatcher = + Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher); + function windowObserver(subject, topicName, aData) { + if (win == subject.QueryInterface(Ci.nsIDOMWindow) && topic == topicName) { + windowWatcher.unregisterNotification(windowObserver); + callback(); + } + } + windowWatcher.registerNotification(windowObserver); +} diff --git a/browser/themes/gnomestripe/browser/tabview/tabview.css b/browser/themes/gnomestripe/browser/tabview/tabview.css index b358056aa5d..5b7f3c76837 100644 --- a/browser/themes/gnomestripe/browser/tabview/tabview.css +++ b/browser/themes/gnomestripe/browser/tabview/tabview.css @@ -32,6 +32,7 @@ body { .thumb { box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.2); + background-color: white; } .favicon { diff --git a/browser/themes/pinstripe/browser/tabview/tabview.css b/browser/themes/pinstripe/browser/tabview/tabview.css index 0df35b67551..6854500698d 100644 --- a/browser/themes/pinstripe/browser/tabview/tabview.css +++ b/browser/themes/pinstripe/browser/tabview/tabview.css @@ -30,6 +30,7 @@ body { .thumb { box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.2); + background-color: white; } .favicon { diff --git a/browser/themes/winstripe/browser/tabview/tabview.css b/browser/themes/winstripe/browser/tabview/tabview.css index 78c9863caa1..0f266131351 100644 --- a/browser/themes/winstripe/browser/tabview/tabview.css +++ b/browser/themes/winstripe/browser/tabview/tabview.css @@ -34,6 +34,7 @@ body { .thumb { box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.1); + background-color: white; } .favicon {