Bug 578553 - Implement App-Tab experience in Panorama; [a+r=dietrich]
This commit is contained in:
@@ -64,7 +64,6 @@
|
||||
// title - the title for the groupItem; otherwise blank
|
||||
// dontPush - true if this groupItem shouldn't push away on creation; default is false
|
||||
function GroupItem(listOfEls, options) {
|
||||
try {
|
||||
if (typeof options == 'undefined')
|
||||
options = {};
|
||||
|
||||
@@ -253,6 +252,16 @@ function GroupItem(listOfEls, options) {
|
||||
.appendTo($container)
|
||||
.hide();
|
||||
|
||||
// ___ app tabs: create app tab tray and populate it
|
||||
this.$appTabTray = iQ("<div/>")
|
||||
.addClass("appTabTray")
|
||||
.appendTo($container);
|
||||
|
||||
AllTabs.tabs.forEach(function(xulTab) {
|
||||
if (xulTab.pinned && xulTab.ownerDocument.defaultView == gWindow)
|
||||
self.addAppTab(xulTab);
|
||||
});
|
||||
|
||||
// ___ locking
|
||||
if (this.locked.bounds)
|
||||
$container.css({cursor: 'default'});
|
||||
@@ -292,10 +301,6 @@ function GroupItem(listOfEls, options) {
|
||||
|
||||
this._inited = true;
|
||||
this.save();
|
||||
} catch(e) {
|
||||
Utils.log("Error in GroupItem()");
|
||||
Utils.log(e.stack);
|
||||
}
|
||||
};
|
||||
|
||||
// ----------
|
||||
@@ -307,15 +312,19 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
|
||||
// -----------
|
||||
// Function: setActiveTab
|
||||
// Sets the active <TabItem> for this groupItem
|
||||
// Sets the active <TabItem> for this groupItem; can be null, but only
|
||||
// if there are no children.
|
||||
setActiveTab: function GroupItem_setActiveTab(tab) {
|
||||
Utils.assert(tab && tab.isATabItem, 'tab must be a TabItem');
|
||||
Utils.assertThrow((!tab && this._children.length == 0) || tab.isATabItem,
|
||||
"tab must be null (if no children) or a TabItem");
|
||||
|
||||
this._activeTab = tab;
|
||||
},
|
||||
|
||||
// -----------
|
||||
// Function: getActiveTab
|
||||
// Gets the active <TabItem> for this groupItem
|
||||
// Gets the active <TabItem> for this groupItem; can be null, but only
|
||||
// if there are no children.
|
||||
getActiveTab: function GroupItem_getActiveTab() {
|
||||
return this._activeTab;
|
||||
},
|
||||
@@ -397,6 +406,8 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
box.top += titleHeight;
|
||||
box.height -= titleHeight;
|
||||
|
||||
box.width -= this.$appTabTray.width();
|
||||
|
||||
// Make the computed bounds' "padding" and new tab button margin actually be
|
||||
// themeable --OR-- compute this from actual bounds. Bug 586546
|
||||
box.inset(6, 6);
|
||||
@@ -695,6 +706,13 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
var index = this._children.indexOf(item);
|
||||
if (index != -1)
|
||||
this._children.splice(index, 1);
|
||||
|
||||
if (item == this._activeTab) {
|
||||
if (this._children.length)
|
||||
this._activeTab = this._children[0];
|
||||
else
|
||||
this._activeTab = null;
|
||||
}
|
||||
|
||||
item.setParent(null);
|
||||
item.removeClass("tabInGroupItem");
|
||||
@@ -732,6 +750,33 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
});
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Adds the given xul:tab as an app tab in this group's apptab tray
|
||||
addAppTab: function GroupItem_addAppTab(xulTab) {
|
||||
let self = this;
|
||||
|
||||
let icon = xulTab.image || Utils.defaultFaviconURL;
|
||||
let $appTab = iQ("<img>")
|
||||
.addClass("appTabIcon")
|
||||
.attr("src", icon)
|
||||
.data("xulTab", xulTab)
|
||||
.appendTo(this.$appTabTray)
|
||||
.click(function(event) {
|
||||
if (Utils.isRightClick(event))
|
||||
return;
|
||||
|
||||
GroupItems.setActiveGroupItem(self);
|
||||
GroupItems._updateTabBar();
|
||||
UI.goToTab(iQ(this).data("xulTab"));
|
||||
});
|
||||
|
||||
let columnWidth = $appTab.width();
|
||||
if (parseInt(this.$appTabTray.css("width")) != columnWidth) {
|
||||
this.$appTabTray.css({width: columnWidth});
|
||||
this.arrange();
|
||||
}
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Function: hideExpandControl
|
||||
// Hide the control which expands a stacked groupItem into a quick-look view.
|
||||
@@ -1129,6 +1174,8 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
className.indexOf('name') != -1 ||
|
||||
className.indexOf('close') != -1 ||
|
||||
className.indexOf('newTabButton') != -1 ||
|
||||
className.indexOf('appTabTray') != -1 ||
|
||||
className.indexOf('appTabIcon') != -1 ||
|
||||
className.indexOf('stackExpander') != -1) {
|
||||
return;
|
||||
}
|
||||
@@ -1181,7 +1228,9 @@ GroupItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
GroupItems.setActiveGroupItem(this);
|
||||
let newTab = gBrowser.loadOneTab(url || "about:blank", {inBackground: true});
|
||||
|
||||
// TabItems will have handled the new tab and added the tabItem property
|
||||
// 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;
|
||||
@@ -1604,16 +1653,18 @@ let GroupItems = {
|
||||
|
||||
// ----------
|
||||
// Function: updateActiveGroupItemAndTabBar
|
||||
// Sets active group item and updates tab bar
|
||||
// Sets active TabItem and GroupItem, and updates tab bar appropriately.
|
||||
updateActiveGroupItemAndTabBar: function GroupItems_updateActiveGroupItemAndTabBar(tabItem) {
|
||||
if (tabItem.parent) {
|
||||
let groupItem = tabItem.parent;
|
||||
this.setActiveGroupItem(groupItem);
|
||||
Utils.assertThrow(tabItem && tabItem.isATabItem, "tabItem must be a TabItem");
|
||||
|
||||
let groupItem = tabItem.parent;
|
||||
this.setActiveGroupItem(groupItem);
|
||||
|
||||
if (groupItem)
|
||||
groupItem.setActiveTab(tabItem);
|
||||
} else {
|
||||
this.setActiveGroupItem(null);
|
||||
else
|
||||
this.setActiveOrphanTab(tabItem);
|
||||
}
|
||||
|
||||
this._updateTabBar();
|
||||
},
|
||||
|
||||
@@ -1693,10 +1744,17 @@ let GroupItems = {
|
||||
|
||||
// ----------
|
||||
// Function: moveTabToGroupItem
|
||||
// Used for the right click menu in the tab strip; moves the given tab
|
||||
// into the given group. Does nothing if the tab is an app tab.
|
||||
// Paramaters:
|
||||
// tab - the <xul:tab>.
|
||||
// groupItemId - the <groupItem>'s id. If nothing, create a new <groupItem>.
|
||||
moveTabToGroupItem : function GroupItems_moveTabToGroupItem (tab, groupItemId) {
|
||||
if (tab.pinned)
|
||||
return;
|
||||
|
||||
Utils.assertThrow(tab.tabItem, "tab must be linked to a TabItem");
|
||||
|
||||
let shouldUpdateTabBar = false;
|
||||
let shouldShowTabView = false;
|
||||
let groupItem;
|
||||
|
||||
@@ -478,8 +478,9 @@ Subscribable.prototype = {
|
||||
// Class: Utils
|
||||
// Singelton with common utility functions.
|
||||
let Utils = {
|
||||
// ___ Logging
|
||||
defaultFaviconURL: "chrome://mozapps/skin/places/defaultFavicon.png",
|
||||
|
||||
// ___ Logging
|
||||
useConsole: true, // as opposed to dump
|
||||
showTime: false,
|
||||
|
||||
|
||||
@@ -525,11 +525,7 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
|
||||
.css(orig.css())
|
||||
.removeClass("front");
|
||||
|
||||
// If it's not focused, the onFocus lsitener would handle it.
|
||||
if (gBrowser.selectedTab == tab)
|
||||
UI.onTabSelect(tab);
|
||||
else
|
||||
gBrowser.selectedTab = tab;
|
||||
UI.goToTab(tab);
|
||||
|
||||
if (isNewBlankTab)
|
||||
gWindow.gURLBar.focus();
|
||||
@@ -663,7 +659,7 @@ let TabItems = {
|
||||
|
||||
// When a tab is opened, create the TabItem
|
||||
this._eventListeners["open"] = function(tab) {
|
||||
if (tab.ownerDocument.defaultView != gWindow)
|
||||
if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
|
||||
return;
|
||||
|
||||
self.link(tab);
|
||||
@@ -671,14 +667,14 @@ let TabItems = {
|
||||
// When a tab's content is loaded, show the canvas and hide the cached data
|
||||
// if necessary.
|
||||
this._eventListeners["attrModified"] = function(tab) {
|
||||
if (tab.ownerDocument.defaultView != gWindow)
|
||||
if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
|
||||
return;
|
||||
|
||||
self.update(tab);
|
||||
}
|
||||
// When a tab is closed, unlink.
|
||||
this._eventListeners["close"] = function(tab) {
|
||||
if (tab.ownerDocument.defaultView != gWindow)
|
||||
if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
|
||||
return;
|
||||
|
||||
self.unlink(tab);
|
||||
@@ -689,7 +685,7 @@ let TabItems = {
|
||||
|
||||
// For each tab, create the link.
|
||||
AllTabs.tabs.forEach(function(tab) {
|
||||
if (tab.ownerDocument.defaultView != gWindow)
|
||||
if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
|
||||
return;
|
||||
|
||||
self.link(tab);
|
||||
@@ -722,6 +718,8 @@ let TabItems = {
|
||||
update: function TabItems_update(tab) {
|
||||
try {
|
||||
Utils.assertThrow(tab, "tab");
|
||||
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
|
||||
Utils.assertThrow(tab.tabItem, "should already be linked");
|
||||
|
||||
let shouldDefer = (
|
||||
this.isPaintingPaused() ||
|
||||
@@ -763,7 +761,7 @@ let TabItems = {
|
||||
// ___ icon
|
||||
let iconUrl = tab.image;
|
||||
if (iconUrl == null)
|
||||
iconUrl = "chrome://mozapps/skin/places/defaultFavicon.png";
|
||||
iconUrl = Utils.defaultFaviconURL;
|
||||
|
||||
if (iconUrl != tabItem.favEl.src)
|
||||
tabItem.favEl.src = iconUrl;
|
||||
@@ -812,10 +810,11 @@ let TabItems = {
|
||||
|
||||
// ----------
|
||||
// Function: link
|
||||
// Takes in a xul:tab.
|
||||
// Takes in a xul:tab, creates a TabItem for it and adds it to the scene.
|
||||
link: function TabItems_link(tab){
|
||||
try {
|
||||
Utils.assertThrow(tab, "tab");
|
||||
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
|
||||
Utils.assertThrow(!tab.tabItem, "shouldn't already be linked");
|
||||
new TabItem(tab); // sets tab.tabItem to itself
|
||||
} catch(e) {
|
||||
@@ -825,10 +824,11 @@ let TabItems = {
|
||||
|
||||
// ----------
|
||||
// Function: unlink
|
||||
// Takes in a xul:tab.
|
||||
// Takes in a xul:tab and destroys the TabItem associated with it.
|
||||
unlink: function TabItems_unlink(tab) {
|
||||
try {
|
||||
Utils.assertThrow(tab, "tab");
|
||||
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
|
||||
Utils.assertThrow(tab.tabItem, "should already be linked");
|
||||
|
||||
this.unregister(tab.tabItem);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Platform-independent structural styling for
|
||||
/* Platform-independent structural styling for
|
||||
* <strike>Tab Candy</strike> Panorama
|
||||
----------------------------------*/
|
||||
|
||||
@@ -88,10 +88,20 @@ body {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
}
|
||||
|
||||
/* Groups
|
||||
----------------------------------*/
|
||||
|
||||
.groupItem {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.appTabTray {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Other Items
|
||||
----------------------------------*/
|
||||
|
||||
.groupItem,
|
||||
.info-item {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@@ -106,9 +106,9 @@ let UI = {
|
||||
|
||||
// ___ Dev Menu
|
||||
// This dev menu is not meant for shipping, nor is it of general
|
||||
// interest, but we still need it for the time being. Change the
|
||||
// false below to enable; just remember to change back before
|
||||
// committing. Bug 586721 will track the ultimate removal.
|
||||
// interest, but we still need it for the time being. Change the
|
||||
// false below to enable; just remember to change back before
|
||||
// committing. Bug 586721 will track the ultimate removal.
|
||||
if (false)
|
||||
this._addDevMenu();
|
||||
|
||||
@@ -466,6 +466,16 @@ let UI = {
|
||||
AllTabs.unregister(name, this._eventListeners[name]);
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Selects the given xul:tab in the browser.
|
||||
goToTab: function UI_goToTab(xulTab) {
|
||||
// If it's not focused, the onFocus listener would handle it.
|
||||
if (gBrowser.selectedTab == xulTab)
|
||||
this.onTabSelect(xulTab);
|
||||
else
|
||||
gBrowser.selectedTab = xulTab;
|
||||
},
|
||||
|
||||
// ----------
|
||||
// Function: onTabSelect
|
||||
// Called when the user switches from one tab to another outside of the TabView UI.
|
||||
@@ -491,7 +501,7 @@ let UI = {
|
||||
|
||||
let oldItem = null;
|
||||
let newItem = null;
|
||||
|
||||
|
||||
if (currentTab && currentTab.tabItem)
|
||||
oldItem = currentTab.tabItem;
|
||||
if (tab && tab.tabItem) {
|
||||
@@ -614,13 +624,13 @@ let UI = {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE ||
|
||||
} else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE ||
|
||||
event.keyCode == KeyEvent.DOM_VK_RETURN ||
|
||||
event.keyCode == KeyEvent.DOM_VK_ENTER) {
|
||||
let activeTab = self.getActiveTab();
|
||||
let activeGroupItem = GroupItems.getActiveGroupItem();
|
||||
|
||||
if (activeGroupItem && activeGroupItem.expanded &&
|
||||
if (activeGroupItem && activeGroupItem.expanded &&
|
||||
event.keyCode == KeyEvent.DOM_VK_ESCAPE)
|
||||
activeGroupItem.collapse();
|
||||
else if (activeTab)
|
||||
|
||||
@@ -50,6 +50,7 @@ _BROWSER_FILES = \
|
||||
browser_tabview_search.js \
|
||||
browser_tabview_snapping.js \
|
||||
browser_tabview_bug591706.js \
|
||||
browser_tabview_apptabs.js \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_BROWSER_FILES)
|
||||
|
||||
89
browser/base/content/test/tabview/browser_tabview_apptabs.js
Normal file
89
browser/base/content/test/tabview/browser_tabview_apptabs.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/* ***** 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 group 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 <raymond@appcoast.com>
|
||||
* Ian Gilman <ian@iangilman.com>
|
||||
*
|
||||
* 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);
|
||||
TabView.toggle();
|
||||
}
|
||||
|
||||
function onTabViewWindowLoaded() {
|
||||
window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
|
||||
ok(TabView.isVisible(), "Tab View is visible");
|
||||
|
||||
let contentWindow = document.getElementById("tab-view").contentWindow;
|
||||
|
||||
// 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");
|
||||
|
||||
// create an app tab
|
||||
let appXulTab = gBrowser.loadOneTab("about:blank");
|
||||
gBrowser.pinTab(appXulTab);
|
||||
is(gBrowser.tabs.length, 2, "we now have two tabs");
|
||||
|
||||
// Create a group
|
||||
let box = new contentWindow.Rect(20, 20, 180, 180);
|
||||
let groupItem = new contentWindow.GroupItem([], { bounds: box });
|
||||
is(contentWindow.GroupItems.groupItems.length, 2, "we now have two groups");
|
||||
|
||||
// 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.unpinTab(appXulTab);
|
||||
gBrowser.removeTab(appXulTab);
|
||||
is(gBrowser.tabs.length, 1, "we finish with one tab");
|
||||
|
||||
groupItem.close();
|
||||
is(contentWindow.GroupItems.groupItems.length, 1, "we finish with one group");
|
||||
|
||||
ok(!TabView.isVisible(), "Tab View is not visible");
|
||||
|
||||
finish();
|
||||
};
|
||||
|
||||
window.addEventListener("tabviewhidden", onTabViewHidden, false);
|
||||
|
||||
let appTabButtons = groupItem.$appTabTray[0].getElementsByTagName("img");
|
||||
ok(appTabButtons.length == 1, "there is one app tab button");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, appTabButtons[0], contentWindow);
|
||||
}
|
||||
@@ -183,6 +183,17 @@ body {
|
||||
inset rgba(255, 255, 255, 0.6) 0 0 0 2px; */
|
||||
}
|
||||
|
||||
.appTabTray {
|
||||
top: 34px;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.appTabIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* InfoItems
|
||||
----------------------------------*/
|
||||
|
||||
|
||||
@@ -190,6 +190,17 @@ body {
|
||||
inset rgba(255, 255, 255, 0.6) 0 0 0 2px; */
|
||||
}
|
||||
|
||||
.appTabTray {
|
||||
top: 34px;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.appTabIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* InfoItems
|
||||
----------------------------------*/
|
||||
|
||||
|
||||
@@ -195,6 +195,17 @@ body {
|
||||
inset rgba(255, 255, 255, 0.6) 0 0 0 2px; */
|
||||
}
|
||||
|
||||
.appTabTray {
|
||||
top: 34px;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.appTabIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* InfoItems
|
||||
----------------------------------*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user