Merge mozilla-central to tabcandy-central.

This commit is contained in:
Edward Lee 2010-08-09 10:38:19 -07:00
commit a869c43b41
59 changed files with 9676 additions and 54 deletions

View File

@ -54,6 +54,7 @@ DIRS += content/test
endif
EXTRA_JS_MODULES = \
content/AllTabs.js \
content/openLocationLastURL.jsm \
content/NetworkPrioritizer.jsm \
content/stylePanel.jsm \
@ -83,3 +84,6 @@ endif
ifneq (,$(filter windows, $(MOZ_WIDGET_TOOLKIT)))
DEFINES += -DMENUBAR_CAN_AUTOHIDE=1
endif
libs::
$(PYTHON) $(topsrcdir)/config/nsinstall.py $(srcdir)/content/tabview/modules/* $(FINAL_TARGET)/modules/tabview

View File

@ -0,0 +1,220 @@
/* ***** 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 AllTabs.
*
* 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):
* Edward Lee <edilee@mozilla.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 ***** */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
let EXPORTED_SYMBOLS = ["AllTabs"];
let AllTabs = {
//////////////////////////////////////////////////////////////////////////////
//// Public
//////////////////////////////////////////////////////////////////////////////
/**
* Get an array of all tabs from all tabbrowser windows.
*
* @usage let numAllTabs = AllTabs.tabs.length;
* AllTabs.tabs.forEach(handleAllTabs);
*/
get tabs() {
// Get tabs from each browser window and flatten them into one array
let browserWindows = AllTabs.allBrowserWindows;
return Array.concat.apply({}, browserWindows.map(function(browserWindow) {
return Array.slice(browserWindow.gBrowser.tabs);
}));
},
/**
* Attach a callback for when a tab's attributes change such as title, busy
* state, icon, etc.
*
* There is also an unbind method off of this method to remove the callback.
*
* @param callback
* Callback that gets called with the tab being changed as "this" and
* the event as the first argument.
* @usage AllTabs.onChange(handleChange);
* AllTabs.onChange.unbind(handleChange);
*/
get onChange() AllTabs.makeBind("onChange"),
/**
* Attach a callback for when a tab is closed.
*
* There is also an unbind method off of this method to remove the callback.
*
* @param callback
* Callback that gets called with the tab being closed as "this" and
* the event as the first argument.
* @usage AllTabs.onClose(handleClose);
* AllTabs.onClose.unbind(handleClose);
*/
get onClose() AllTabs.makeBind("onClose"),
/**
* Attach a callback for when a tab is moved.
*
* There is also an unbind method off of this method to remove the callback.
*
* @param callback
* Callback that gets called with the tab being moved as "this" and
* the event as the first argument.
* @usage AllTabs.onMove(handleMove);
* AllTabs.onMove.unbind(handleMove);
*/
get onMove() AllTabs.makeBind("onMove"),
/**
* Attach a callback for when a tab is opened.
*
* There is also an unbind method off of this method to remove the callback.
*
* @param callback
* Callback that gets called with the tab being opened as "this" and
* the event as the first argument.
* @usage AllTabs.onOpen(handleOpen);
* AllTabs.onOpen.unbind(handleOpen);
*/
get onOpen() AllTabs.makeBind("onOpen"),
/**
* Attach a callback for when a tab is selected.
*
* There is also an unbind method off of this method to remove the callback.
*
* @param callback
* Callback that gets called with the tab being selected as "this" and
* the event as the first argument.
* @usage AllTabs.onSelect(handleSelect);
* AllTabs.onSelect.unbind(handleSelect);
*/
get onSelect() AllTabs.makeBind("onSelect"),
//////////////////////////////////////////////////////////////////////////////
//// Private
//////////////////////////////////////////////////////////////////////////////
get allBrowserWindows() {
let browserWindows = [];
let windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements())
browserWindows.push(windows.getNext());
return browserWindows;
},
eventMap: {
TabAttrModified: "onChange",
TabClose: "onClose",
TabMove: "onMove",
TabOpen: "onOpen",
TabSelect: "onSelect",
},
registerBrowserWindow: function registerBrowserWindow(browserWindow) {
// Add a listener for each tab even to trigger the matching topic
[i for (i in Iterator(AllTabs.eventMap))].forEach(function([tabEvent, topic]) {
browserWindow.addEventListener(tabEvent, function(event) {
AllTabs.trigger(topic, event.originalTarget, event);
}, true);
});
},
listeners: {},
makeBind: function makeBind(topic) {
delete AllTabs[topic];
AllTabs.listeners[topic] = [];
// Allow adding listeners to this topic
AllTabs[topic] = function bind(callback) {
AllTabs.listeners[topic].push(callback);
};
// Allow removing listeners from this topic
AllTabs[topic].unbind = function unbind(callback) {
let index = AllTabs.listeners[topic].indexOf(callback);
if (index != -1)
AllTabs.listeners[topic].splice(index, 1);
};
return AllTabs[topic];
},
trigger: function trigger(topic, tab, event) {
// Make sure we've gotten listeners before trying to call
let listeners = AllTabs.listeners[topic];
if (listeners == null)
return;
// Make a copy of the listeners, so it can't change as we call back
listeners.slice().forEach(function(callback) {
try {
callback.call(tab, event);
}
// Ignore failures from the callback
catch(ex) {}
});
},
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
//////////////////////////////////////////////////////////////////////////////
observe: function observe(subject, topic, data) {
switch (topic) {
case "domwindowopened":
subject.addEventListener("load", function() {
subject.removeEventListener("load", arguments.callee, false);
// Now that the window has loaded, only register on browser windows
let doc = subject.document.documentElement;
if (doc.getAttribute("windowtype") == "navigator:browser")
AllTabs.registerBrowserWindow(subject);
}, false);
break;
}
},
};
// Register listeners on all browser windows and future ones
AllTabs.allBrowserWindows.forEach(AllTabs.registerBrowserWindow);
Services.obs.addObserver(AllTabs, "domwindowopened", false);

View File

@ -208,6 +208,9 @@
<menu id="view-menu" label="&viewMenu.label;"
accesskey="&viewMenu.accesskey;">
<menupopup id="menu_viewPopup">
<menuitem id="menu_tabview"
label="&showTabView.label;"
command="Browser:ToggleTabView"/>
<menu id="viewToolbarsMenu"
label="&viewToolbarsMenu.label;"
accesskey="&viewToolbarsMenu.accesskey;">

View File

@ -480,9 +480,9 @@ var PlacesCommandHook = {
var tabList = [];
var seenURIs = {};
var browsers = gBrowser.browsers;
for (var i = 0; i < browsers.length; ++i) {
let uri = browsers[i].currentURI;
let tabs = gBrowser.visibleTabs;
for (let i = 0; i < tabs.length; ++i) {
let uri = tabs[i].linkedBrowser.currentURI;
// skip redundant entries
if (uri.spec in seenURIs)

View File

@ -117,6 +117,7 @@
<command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);"/>
<command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);"/>
<command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
<command id="Browser:ToggleTabView" oncommand="TabView.toggle();"/>
<command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/>
<command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
<command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/>

View File

@ -215,12 +215,13 @@ var ctrlTab = {
if (this._tabList)
return this._tabList;
var list = Array.slice(gBrowser.tabs);
let list = gBrowser.visibleTabs;
if (this._closing)
this.detachTab(this._closing, list);
for (let i = 0; i < gBrowser.tabContainer.selectedIndex; i++)
// Rotate the list until the selected tab is first
while (!list[0].selected)
list.push(list.shift());
if (this.recentlyUsedLimit != 0) {
@ -462,11 +463,12 @@ var ctrlTab = {
} else if (!event.shiftKey) {
event.preventDefault();
event.stopPropagation();
if (gBrowser.tabs.length > 2) {
let tabs = gBrowser.visibleTabs;
if (tabs.length > 2) {
this.open();
} else if (gBrowser.tabs.length == 2) {
gBrowser.selectedTab = gBrowser.selectedTab.nextSibling ||
gBrowser.selectedTab.previousSibling;
} else if (tabs.length == 2) {
let index = gBrowser.selectedTab == tabs[0] ? 1 : 0;
gBrowser.selectedTab = tabs[index];
}
}
}
@ -664,7 +666,7 @@ var allTabs = {
Array.forEach(this.previews, function (preview) {
var tab = preview._tab;
var matches = 0;
if (filter.length) {
if (filter.length && !tab.hidden) {
let tabstring = tab.linkedBrowser.currentURI.spec;
try {
tabstring = decodeURI(tabstring);
@ -673,7 +675,7 @@ var allTabs = {
for (let i = 0; i < filter.length; i++)
matches += tabstring.indexOf(filter[i]) > -1;
}
if (matches < filter.length) {
if (matches < filter.length || tab.hidden) {
preview.hidden = true;
}
else {

View File

@ -0,0 +1,109 @@
# ***** 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 the Tab View
#
# 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>
#
# 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 *****
// New API methods can be added to here.
let TabView = {
init: function TabView_init() {
var iframe = document.createElement("iframe");
iframe.id = "tab-view";
iframe.setAttribute("transparent", "true");
iframe.flex = 1;
iframe.setAttribute("src", "chrome://browser/content/tabview.html");
document.getElementById("tab-view-deck").appendChild(iframe);
},
isVisible: function() {
return (window.document.getElementById("tab-view-deck").selectedIndex == 1);
},
toggle: function() {
let event = document.createEvent("Events");
if (this.isVisible()) {
event.initEvent("tabviewhide", false, false);
} else {
event.initEvent("tabviewshow", false, false);
}
dispatchEvent(event);
},
getWindowTitle: function() {
let brandBundle = document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
return gNavigatorBundle.getFormattedString("tabView.title", [brandShortName]);
},
updateContextMenu: function(tab, popup) {
let tabViewWindow = document.getElementById("tab-view").contentWindow;
let isEmpty = true;
while(popup.lastChild && popup.lastChild.id != "context_namedGroups")
popup.removeChild(popup.lastChild);
if (!tabViewWindow.UI.frameInitalized)
tabViewWindow.UI.initFrame();
let activeGroup = tab.tabItem.parent;
let groupItems = tabViewWindow.GroupItems.groupItems;
let self = this;
groupItems.forEach(function(groupItem) {
if (groupItem.getTitle().length > 0 &&
(!activeGroup || activeGroup.id != groupItem.id)) {
let menuItem = self._createGroupMenuItem(groupItem);
popup.appendChild(menuItem);
isEmpty = false;
}
});
document.getElementById("context_namedGroups").hidden = isEmpty;
},
_createGroupMenuItem : function(groupItem) {
let menuItem = document.createElement("menuitem")
menuItem.setAttribute("class", "group");
menuItem.setAttribute("label", groupItem.getTitle());
menuItem.setAttribute(
"oncommand",
"TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')");
return menuItem;
},
moveTabTo: function(tab, groupItemId) {
let tabViewWindow = document.getElementById("tab-view").contentWindow;
tabViewWindow.GroupItems.moveTabToGroupItem(tab, groupItemId);
}
};

View File

@ -174,6 +174,7 @@ let gInitialPages = [
#include inspector.js
#include browser-places.js
#include browser-tabPreviews.js
#include browser-tabview.js
#ifdef MOZ_SERVICES_SYNC
#include browser-syncui.js
@ -1520,6 +1521,8 @@ function delayedStartup(isLoadingBlank, mustLoadSidebar) {
gSyncUI.init();
#endif
TabView.init();
Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
}
@ -6765,11 +6768,13 @@ var gBookmarkAllTabsHandler = {
this._command = document.getElementById("Browser:BookmarkAllTabs");
gBrowser.tabContainer.addEventListener("TabOpen", this, true);
gBrowser.tabContainer.addEventListener("TabClose", this, true);
gBrowser.tabContainer.addEventListener("TabSelect", this, true);
gBrowser.tabContainer.addEventListener("TabMove", this, true);
this._updateCommandState();
},
_updateCommandState: function BATH__updateCommandState(aTabClose) {
var numTabs = gBrowser.tabs.length;
let numTabs = gBrowser.visibleTabs.length;
// The TabClose event is fired before the tab is removed from the DOM
if (aTabClose)
@ -7795,7 +7800,7 @@ var TabContextMenu = {
updateContextMenu: function updateContextMenu(aPopupMenu) {
this.contextTab = document.popupNode.localName == "tab" ?
document.popupNode : gBrowser.selectedTab;
var disabled = gBrowser.tabs.length == 1;
let disabled = gBrowser.visibleTabs.length == 1;
// Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
document.getElementById("context_closeTab").disabled =
@ -7817,7 +7822,7 @@ var TabContextMenu = {
// Disable "Close other Tabs" if there is only one unpinned tab and
// hide it when the user rightclicked on a pinned tab.
var unpinnedTabs = gBrowser.tabs.length - gBrowser._numPinnedTabs;
let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
}

View File

@ -102,6 +102,9 @@
<script type="application/javascript" src="chrome://browser/content/places/editBookmarkOverlay.js"/>
<deck flex="1" id="tab-view-deck">
<vbox flex="1">
# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
# browser-sets.inc file for sharing with hiddenWindow.xul.
#include browser-sets.inc
@ -110,6 +113,18 @@
<menupopup id="tabContextMenu"
onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
<menu id="context_tabViewMenu" class="menu-iconic" label="&moveTabTo.label;..."
accesskey="&moveTabTo.accesskey;">
<menupopup id="context_tabViewMenuPopup"
onpopupshowing="if (event.target == this) TabView.updateContextMenu(TabContextMenu.contextTab, this);">
<menuitem label="&createNewGroup.label;"
accesskey="&createNewGroup.accesskey;"
oncommand="TabView.moveTabTo(TabContextMenu.contextTab, null);" />
<menuitem id="context_namedGroups" label="&namedGroups.label;"
disabled="true" />
</menupopup>
</menu>
<menuseparator/>
<menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
<menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
@ -892,7 +907,7 @@
iconsize="small" defaulticonsize="small" lockiconsize="true"
aria-label="&tabsToolbar.label;"
context="toolbar-context-menu"
defaultset="tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton"
defaultset="tabbrowser-tabs,new-tab-button,tabview-button,alltabs-button,tabs-closebutton"
collapsed="true">
<tabs id="tabbrowser-tabs"
@ -926,6 +941,12 @@
position="after_end"/>
</toolbarbutton>
<toolbarbutton id="tabview-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
label="&tabViewButton.label;"
command="Browser:ToggleTabView"
tooltiptext="&tabViewButton.tooltip;"
removable="true"/>
<toolbarbutton id="tabs-closebutton"
class="close-button tabs-closebutton"
command="cmd_close"
@ -1080,4 +1101,8 @@
</svg:mask>
</svg:svg>
#endif
</vbox>
</deck>
</window>

View File

@ -88,6 +88,8 @@
<field name="tabs" readonly="true">
this.tabContainer.childNodes;
</field>
<property name="visibleTabs" readonly="true"
onget="return Array.filter(this.tabs, function(tab) !tab.hidden);"/>
<field name="mURIFixup" readonly="true">
Components.classes["@mozilla.org/docshell/urifixup;1"]
.getService(Components.interfaces.nsIURIFixup);
@ -721,7 +723,12 @@
<method name="updateTitlebar">
<body>
<![CDATA[
this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
if (TabView.isVisible()) {
// ToDo: this will be removed when we gain ability to draw o the menu bar.
this.ownerDocument.title = TabView.getWindowTitle();
} else {
this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
}
]]>
</body>
</method>
@ -758,6 +765,7 @@
newBrowser.docShell.isActive = true;
this.mCurrentBrowser = newBrowser;
this.mCurrentTab = this.selectedTab;
this.mCurrentTab.hidden = false;
if (updatePageReport)
this.mCurrentBrowser.updatePageReport();
@ -787,7 +795,6 @@
// Don't switch the fast find or update the titlebar (bug 540248) - this tab switch is temporary
if (!this._previewMode) {
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
this.updateTitlebar();
}
@ -1184,6 +1191,14 @@
b._fastFind = this.fastFind;
b.droppedLinkHandler = handleDroppedLink;
var uniqueId = "panel" + Date.now() + position;
this.mPanelContainer.lastChild.id = uniqueId;
t.linkedPanel = uniqueId;
t.linkedBrowser = b;
t._tPos = position;
if (t.previousSibling && t.previousSibling.selected)
t.setAttribute("afterselected", true);
this.tabContainer.adjustTabstrip();
// Dispatch a new tab notification. We do this once we're
@ -1243,7 +1258,7 @@
var tabsToClose = this.tabs.length;
if (!aAll)
tabsToClose -= 1 + gBrowser._numPinnedTabs;
tabsToClose = this.visibleTabs.length - (1 + this._numPinnedTabs);
if (tabsToClose <= 1)
return true;
@ -1294,11 +1309,12 @@
return;
if (this.warnAboutClosingTabs(false)) {
let tabs = this.visibleTabs;
this.selectedTab = aTab;
for (let i = this.tabs.length - 1; i >= 0; --i) {
if (this.tabs[i] != aTab && !this.tabs[i].pinned)
this.removeTab(this.tabs[i]);
for (let i = tabs.length - 1; i >= 0; --i) {
if (tabs[i] != aTab && !tabs[i].pinned)
this.removeTab(tabs[i]);
}
}
]]>
@ -1381,21 +1397,23 @@
var closeWindow = false;
var newTab = false;
if (this.tabs.length - this._removingTabs.length == 1) {
closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
!window.toolbar.visible ||
this.tabContainer._closeWindowWithLastTab;
if (!TabView.isVisible()) {
closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
!window.toolbar.visible ||
this.tabContainer._closeWindowWithLastTab;
// Closing the tab and replacing it with a blank one is notably slower
// than closing the window right away. If the caller opts in, take
// the fast path.
if (closeWindow &&
aCloseWindowFastpath &&
this._removingTabs.length == 0 &&
(this._windowIsClosing = window.closeWindow(true)))
return null;
// Closing the tab and replacing it with a blank one is notably slower
// than closing the window right away. If the caller opts in, take
// the fast path.
if (closeWindow &&
aCloseWindowFastpath &&
this._removingTabs.length == 0 &&
(this._windowIsClosing = window.closeWindow(true)))
return null;
newTab = true;
}
newTab = true;
}
}
this._removingTabs.push(aTab);
@ -1564,21 +1582,22 @@
return;
}
var tab = aTab;
let removing = this._removingTabs;
let ignoreRemoving = function(tab) removing.indexOf(tab) == -1;
// Switch to a visible tab unless there aren't any remaining
let remainingTabs = this.visibleTabs.filter(ignoreRemoving);
if (remainingTabs.length == 0)
remainingTabs = Array.filter(this.tabs, ignoreRemoving);
// Try to find a remaining tab that comes after the given tab
var tab = aTab;
do {
tab = tab.nextSibling;
} while (tab && this._removingTabs.indexOf(tab) != -1);
} while (tab && remainingTabs.indexOf(tab) == -1);
if (!tab) {
tab = aTab;
do {
tab = tab.previousSibling;
} while (tab && this._removingTabs.indexOf(tab) != -1);
}
this.selectedTab = tab;
// If no tab was found, give the end of the remaining tabs
this.selectedTab = tab ? tab : remainingTabs.pop();
]]>
</body>
</method>
@ -1651,10 +1670,11 @@
<method name="reloadAllTabs">
<body>
<![CDATA[
var l = this.mPanelContainer.childNodes.length;
let tabs = this.visibleTabs;
let l = tabs.length;
for (var i = 0; i < l; i++) {
try {
this.getBrowserAtIndex(i).reload();
this.getBrowserForTab(tabs[i]).reload();
} catch (e) {
// ignore failure to reload so others will be reloaded
}
@ -1751,19 +1771,33 @@
</body>
</method>
<method name="showOnlyTheseTabs">
<parameter name="aTabs"/>
<body>
<![CDATA[
Array.forEach(this.tabs, function(tab) {
tab.hidden = aTabs.indexOf(tab) == -1 && !tab.pinned && !tab.selected;
});
]]>
</body>
</method>
<method name="selectTabAtIndex">
<parameter name="aIndex"/>
<parameter name="aEvent"/>
<body>
<![CDATA[
let tabs = this.visibleTabs;
// count backwards for aIndex < 0
if (aIndex < 0)
aIndex += this.tabs.length;
aIndex += tabs.length;
if (aIndex >= 0 &&
aIndex < this.tabs.length &&
aIndex != this.tabContainer.selectedIndex)
this.selectedTab = this.tabs[aIndex];
if (aIndex >= 0 && aIndex < tabs.length) {
let nextTab = tabs[aIndex];
if (nextTab != this.selectedTab)
this.selectedTab = nextTab;
}
if (aEvent) {
aEvent.preventDefault();
@ -1805,7 +1839,7 @@
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.tabs.length == 1)
if (this.visibleTabs.length == 1)
return null;
// tell a new window to take the "dropped" tab
@ -2570,6 +2604,8 @@
this.setAttribute("closebuttons", "noclose");
else {
let tab = this.childNodes.item(this.tabbrowser._numPinnedTabs);
if (tab && tab.hidden)
tab = this.tabbrowser.visibleTabs[0];
if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
this.setAttribute("closebuttons", "alltabs");
else
@ -3366,7 +3402,7 @@
<![CDATA[
// set up the menu popup
var tabcontainer = gBrowser.tabContainer;
var tabs = tabcontainer.childNodes;
let tabs = gBrowser.visibleTabs;
// Listen for changes in the tab bar.
tabcontainer.addEventListener("TabOpen", this, false);

View File

@ -0,0 +1,296 @@
/* ***** 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 drag.js.
*
* The Initial Developer of the Original Code is
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// **********
// Title: drag.js
// ----------
// Variable: drag
// The Drag that's currently in process.
var drag = {
info: null,
zIndex: 100
};
// ##########
// Class: Drag (formerly DragInfo)
// Helper class for dragging <Item>s
//
// ----------
// Constructor: Drag
// Called to create a Drag in response to an <Item> draggable "start" event.
// Note that it is also used partially during <Item>'s resizable method as well.
//
// Parameters:
// item - The <Item> being dragged
// event - The DOM event that kicks off the drag
// isResizing - (boolean) is this a resizing instance? or (if false) dragging?
// isFauxDrag - (boolean) true if a faux drag, which is used when simply snapping.
var Drag = function(item, event, isResizing, isFauxDrag) {
try {
Utils.assert('must be an item, or at least a faux item',
item && (item.isAnItem || item.isAFauxItem));
this.isResizing = isResizing || false;
this.item = item;
this.el = item.container;
this.$el = iQ(this.el);
this.parent = this.item.parent;
this.startPosition = new Point(event.clientX, event.clientY);
this.startTime = Date.now();
this.item.isDragging = true;
this.item.setZ(999999);
this.safeWindowBounds = Items.getSafeWindowBounds();
Trenches.activateOthersTrenches(this.el);
if (!isFauxDrag) {
// When a tab drag starts, make it the focused tab.
if (this.item.isAGroupItem) {
var tab = UI.getActiveTab();
if (!tab || tab.parent != this.item) {
if (this.item._children.length)
UI.setActiveTab(this.item._children[0]);
}
} else if (this.item.isATabItem) {
UI.setActiveTab(this.item);
}
}
} catch(e) {
Utils.log(e);
}
};
Drag.prototype = {
// ----------
// Function: snapBounds
// Adjusts the given bounds according to the currently active trenches. Used by <Drag.snap>
//
// Parameters:
// bounds - (<Rect>) bounds
// stationaryCorner - which corner is stationary? by default, the top left.
// "topleft", "bottomleft", "topright", "bottomright"
// assumeConstantSize - (boolean) whether the bounds' dimensions are sacred or not.
// keepProportional - (boolean) if assumeConstantSize is false, whether we should resize
// proportionally or not
// checkItemStatus - (boolean) make sure this is a valid item which should be snapped
snapBounds: function Drag_snapBounds(bounds, stationaryCorner, assumeConstantSize, keepProportional, checkItemStatus) {
var stationaryCorner = stationaryCorner || 'topleft';
var update = false; // need to update
var updateX = false;
var updateY = false;
var newRect;
var snappedTrenches = {};
// OH SNAP!
if ( // if we aren't holding down the meta key...
!Keys.meta
&& (!checkItemStatus // don't check the item status...
// OR we aren't a tab on top of something else, and there's no drop site...
|| (!(this.item.isATabItem && this.item.overlapsWithOtherItems())
&& !iQ(".acceptsDrop").length))
) {
newRect = Trenches.snap(bounds,stationaryCorner,assumeConstantSize,keepProportional);
if (newRect) { // might be false if no changes were made
update = true;
snappedTrenches = newRect.snappedTrenches || {};
bounds = newRect;
}
}
// make sure the bounds are in the window.
newRect = this.snapToEdge(bounds,stationaryCorner,assumeConstantSize,keepProportional);
if (newRect) {
update = true;
bounds = newRect;
Utils.extend(snappedTrenches,newRect.snappedTrenches);
}
Trenches.hideGuides();
for (var edge in snappedTrenches) {
var trench = snappedTrenches[edge];
if (typeof trench == 'object') {
trench.showGuide = true;
trench.show();
} else if (trench === 'edge') {
// show the edge...?
}
}
return update ? bounds : false;
},
// ----------
// Function: snap
// Called when a drag or mousemove occurs. Set the bounds based on the mouse move first, then
// call snap and it will adjust the item's bounds if appropriate. Also triggers the display of
// trenches that it snapped to.
//
// Parameters:
// stationaryCorner - which corner is stationary? by default, the top left.
// "topleft", "bottomleft", "topright", "bottomright"
// assumeConstantSize - (boolean) whether the bounds' dimensions are sacred or not.
// keepProportional - (boolean) if assumeConstantSize is false, whether we should resize
// proportionally or not
snap: function Drag_snap(stationaryCorner, assumeConstantSize, keepProportional) {
var bounds = this.item.getBounds();
bounds = this.snapBounds(bounds, stationaryCorner, assumeConstantSize, keepProportional, true);
if (bounds) {
this.item.setBounds(bounds,true);
return true;
}
return false;
},
// --------
// Function: snapToEdge
// Returns a version of the bounds snapped to the edge if it is close enough. If not,
// returns false. If <Keys.meta> is true, this function will simply enforce the
// window edges.
//
// Parameters:
// rect - (<Rect>) current bounds of the object
// stationaryCorner - which corner is stationary? by default, the top left.
// "topleft", "bottomleft", "topright", "bottomright"
// assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not
// keepProportional - (boolean) if we are allowed to change the rect's size, whether the
// dimensions should scaled proportionally or not.
snapToEdge: function Drag_snapToEdge(rect, stationaryCorner, assumeConstantSize, keepProportional) {
var swb = this.safeWindowBounds;
var update = false;
var updateX = false;
var updateY = false;
var snappedTrenches = {};
var snapRadius = (Keys.meta ? 0 : Trenches.defaultRadius);
if (rect.left < swb.left + snapRadius ) {
if (stationaryCorner.indexOf('right') > -1)
rect.width = rect.right - swb.left;
rect.left = swb.left;
update = true;
updateX = true;
snappedTrenches.left = 'edge';
}
if (rect.right > swb.right - snapRadius) {
if (updateX || !assumeConstantSize) {
var newWidth = swb.right - rect.left;
if (keepProportional)
rect.height = rect.height * newWidth / rect.width;
rect.width = newWidth;
update = true;
} else if (!updateX || !Trenches.preferLeft) {
rect.left = swb.right - rect.width;
update = true;
}
snappedTrenches.right = 'edge';
delete snappedTrenches.left;
}
if (rect.top < swb.top + snapRadius) {
if (stationaryCorner.indexOf('bottom') > -1)
rect.height = rect.bottom - swb.top;
rect.top = swb.top;
update = true;
updateY = true;
snappedTrenches.top = 'edge';
}
if (rect.bottom > swb.bottom - snapRadius) {
if (updateY || !assumeConstantSize) {
var newHeight = swb.bottom - rect.top;
if (keepProportional)
rect.width = rect.width * newHeight / rect.height;
rect.height = newHeight;
update = true;
} else if (!updateY || !Trenches.preferTop) {
rect.top = swb.bottom - rect.height;
update = true;
}
snappedTrenches.top = 'edge';
delete snappedTrenches.bottom;
}
if (update) {
rect.snappedTrenches = snappedTrenches;
return rect;
}
return false;
},
// ----------
// Function: drag
// Called in response to an <Item> draggable "drag" event.
drag: function(event, ui) {
this.snap('topleft',true);
if (this.parent && this.parent.expanded) {
var now = Date.now();
var distance = this.startPosition.distance(new Point(event.clientX, event.clientY));
if (/* now - this.startTime > 500 || */distance > 100) {
this.parent.remove(this.item);
this.parent.collapse();
}
}
},
// ----------
// Function: stop
// Called in response to an <Item> draggable "stop" event.
stop: function() {
Trenches.hideGuides();
this.item.isDragging = false;
if (this.parent && !this.parent.locked.close && this.parent != this.item.parent
&& this.parent.isEmpty()) {
this.parent.close();
}
if (this.parent && this.parent.expanded)
this.parent.arrange();
if (this.item && !this.item.parent) {
this.item.setZ(drag.zIndex);
drag.zIndex++;
this.item.pushAway();
}
Trenches.disactivate();
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,261 @@
/* ***** 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 infoitems.js.
*
* The Initial Developer of the Original Code is
* Ian Gilman <ian@iangilman.com>.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Aza Raskin <aza@mozilla.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
* Ehsan Akhgari <ehsan@mozilla.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 ***** */
// **********
// Title: infoitems.js
(function() {
// ##########
// Class: InfoItem
// An <Item> in TabView used for displaying information, such as the welcome video.
// Note that it implements the <Subscribable> interface.
//
// ----------
// Constructor: InfoItem
//
// Parameters:
// bounds - a <Rect> for where the item should be located
// options - various options for this infoItem (see below)
//
// Possible options:
// locked - see <Item.locked>; default is {}
// dontPush - true if this infoItem shouldn't push away on creation; default is false
window.InfoItem = function(bounds, options) {
try {
Utils.assertThrow('bounds', Utils.isRect(bounds));
if (typeof(options) == 'undefined')
options = {};
this._inited = false;
this.isAnInfoItem = true;
this.defaultSize = bounds.size();
this.locked = (options.locked ? Utils.copy(options.locked) : {});
this.bounds = new Rect(bounds);
this.isDragging = false;
var self = this;
var $container = iQ('<div>')
.addClass('info-item')
.css(this.bounds)
.appendTo('body');
this.$contents = iQ('<div>')
.appendTo($container);
var $close = iQ('<div>')
.addClass('close')
.click(function() {
self.close();
})
.appendTo($container);
// ___ locking
if (this.locked.bounds)
$container.css({cursor: 'default'});
if (this.locked.close)
$close.hide();
// ___ Superclass initialization
this._init($container[0]);
if (this.$debug)
this.$debug.css({zIndex: -1000});
// ___ Finish Up
if (!this.locked.bounds)
this.draggable();
// ___ Position
this.snap();
// ___ Push other objects away
if (!options.dontPush)
this.pushAway();
this._inited = true;
this.save();
} catch(e) {
Utils.log(e);
}
};
// ----------
window.InfoItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// ----------
// Function: getStorageData
// Returns all of the info worth storing about this item.
getStorageData: function() {
var data = null;
try {
data = {
bounds: this.getBounds(),
locked: Utils.copy(this.locked)
};
} catch(e) {
Utils.log(e);
}
return data;
},
// ----------
// Function: save
// Saves this item to persistent storage.
save: function() {
try {
if (!this._inited) // too soon to save now
return;
var data = this.getStorageData();
/*
if (GroupItems.groupItemStorageSanity(data))
Storage.saveGroupItem(Utils.getCurrentWindow(), data);
*/
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: setBounds
// Sets the bounds with the given <Rect>, animating unless "immediately" is false.
setBounds: function(rect, immediately) {
try {
Utils.assertThrow('InfoItem.setBounds: rect must be a real rectangle!', Utils.isRect(rect));
// ___ Determine what has changed
var css = {};
if (rect.left != this.bounds.left)
css.left = rect.left;
if (rect.top != this.bounds.top)
css.top = rect.top;
if (rect.width != this.bounds.width)
css.width = rect.width;
if (rect.height != this.bounds.height)
css.height = rect.height;
if (Utils.isEmptyObject(css))
return;
this.bounds = new Rect(rect);
Utils.assertThrow('InfoItem.setBounds: this.bounds must be a real rectangle!',
Utils.isRect(this.bounds));
// ___ Update our representation
if (immediately) {
iQ(this.container).css(css);
} else {
TabItems.pausePainting();
iQ(this.container).animate(css, {
duration: 350,
easing: "tabviewBounce",
complete: function() {
TabItems.resumePainting();
}
});
}
this._updateDebugBounds();
this.setTrenches(rect);
this.save();
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: setZ
// Set the Z order for the item's container.
setZ: function(value) {
try {
Utils.assertThrow('value must be a number', typeof(value) == 'number');
this.zIndex = value;
iQ(this.container).css({zIndex: value});
if (this.$debug)
this.$debug.css({zIndex: value + 1});
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: close
// Closes the item.
close: function() {
try {
this._sendToSubscribers("close");
this.removeTrenches();
iQ(this.container).fadeOut(function() {
iQ(this).remove();
Items.unsquish();
});
/* Storage.deleteGroupItem(Utils.getCurrentWindow(), this.id); */
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: html
// Sets the item's container's html to the specified value.
html: function(value) {
try {
Utils.assertThrow('value must be a string', typeof(value) == 'string');
this.$contents.html(value);
} catch(e) {
Utils.log(e);
}
}
});
})();

View File

@ -0,0 +1,754 @@
/* ***** 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 iq.js.
*
* The Initial Developer of the Original Code is
* Ian Gilman <ian@iangilman.com>.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Aza Raskin <aza@mozilla.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
*
* This file incorporates work from:
* jQuery JavaScript Library v1.4.2: http://code.jquery.com/jquery-1.4.2.js
* This incorporated work is covered by the following copyright and
* permission notice:
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* 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 ***** */
// **********
// Title: iq.js
// Various helper functions, in the vein of jQuery.
// ----------
// Function: iQ
// Returns an iQClass object which represents an individual element or a group
// of elements. It works pretty much like jQuery(), with a few exceptions,
// most notably that you can't use strings with complex html,
// just simple tags like '<div>'.
function iQ(selector, context) {
// The iQ object is actually just the init constructor 'enhanced'
return new iQClass(selector, context);
};
// A simple way to check for HTML strings or ID strings
// (both of which we optimize for)
let quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/;
// Match a standalone tag
let rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/;
// ##########
// Class: iQClass
// The actual class of iQ result objects, representing an individual element
// or a group of elements.
//
// ----------
// Function: iQClass
// You don't call this directly; this is what's called by iQ().
let iQClass = function(selector, context) {
// Handle $(""), $(null), or $(undefined)
if (!selector) {
return this;
}
// Handle $(DOMElement)
if (selector.nodeType) {
this.context = selector;
this[0] = selector;
this.length = 1;
return this;
}
// The body element only exists once, optimize finding it
if (selector === "body" && !context) {
this.context = document;
this[0] = document.body;
this.selector = "body";
this.length = 1;
return this;
}
// Handle HTML strings
if (typeof selector === "string") {
// Are we dealing with HTML string or an ID?
let match = quickExpr.exec(selector);
// Verify a match, and that no context was specified for #id
if (match && (match[1] || !context)) {
// HANDLE $(html) -> $(array)
if (match[1]) {
let doc = (context ? context.ownerDocument || context : document);
// If a single string is passed in and it's a single tag
// just do a createElement and skip the rest
let ret = rsingleTag.exec(selector);
if (ret) {
if (Utils.isPlainObject(context)) {
Utils.assert('does not support HTML creation with context', false);
} else {
selector = [doc.createElement(ret[1])];
}
} else {
Utils.assert('does not support complex HTML creation', false);
}
return Utils.merge(this, selector);
// HANDLE $("#id")
} else {
let elem = document.getElementById(match[2]);
if (elem) {
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
// HANDLE $("TAG")
} else if (!context && /^\w+$/.test(selector)) {
this.selector = selector;
this.context = document;
selector = document.getElementsByTagName(selector);
return Utils.merge(this, selector);
// HANDLE $(expr, $(...))
} else if (!context || context.iq) {
return (context || iQ(document)).find(selector);
// HANDLE $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return iQ(context).find(selector);
}
// HANDLE $(function)
// Shortcut for document ready
} else if (typeof selector == "function") {
Utils.log('iQ does not support ready functions');
return null;
}
if (typeof selector.selector !== "undefined") {
this.selector = selector.selector;
this.context = selector.context;
}
let ret = this || [];
if (selector != null) {
// The window, strings (and functions) also have 'length'
if (selector.length == null || typeof selector == "string" || selector.setInterval) {
Array.push(ret, selector);
} else {
Utils.merge(ret, selector);
}
}
return ret;
}
iQClass.prototype = {
// Start with an empty selector
selector: "",
// The current version of iQ being used
iq: "1.4.2",
// The default length of a iQ object is 0
length: 0,
// ----------
// Function: each
// Execute a callback for every element in the matched set.
each: function(callback) {
if (typeof callback != "function") {
Utils.assert("each's argument must be a function", false);
return null;
}
for (let i = 0; this[i] != null; i++) {
callback(this[i]);
}
return this;
},
// ----------
// Function: addClass
// Adds the given class(es) to the receiver.
addClass: function(value) {
if (typeof value != "string" || !value) {
Utils.assert('requires a valid string argument', false);
return null;
}
let length = this.length;
for (let i = 0; i < length; i++) {
let elem = this[i];
if (elem.nodeType === 1) {
value.split(/\s+/).forEach(function(className) {
elem.classList.add(className);
});
}
}
return this;
},
// ----------
// Function: removeClass
// Removes the given class(es) from the receiver.
removeClass: function(value) {
if (typeof value != "string" || !value) {
Utils.assert('does not support function argument', false);
return null;
}
let length = this.length;
for (let i = 0; i < length; i++) {
let elem = this[i];
if (elem.nodeType === 1 && elem.className) {
value.split(/\s+/).forEach(function(className) {
elem.classList.remove(className);
});
}
}
return this;
},
// ----------
// Function: hasClass
// Returns true is the receiver has the given css class.
hasClass: function(singleClassName) {
let length = this.length;
for (let i = 0; i < length; i++) {
if (this[i].classList.contains(singleClassName)) {
return true;
}
}
return false;
},
// ----------
// Function: find
// Searches the receiver and its children, returning a new iQ object with
// elements that match the given selector.
find: function(selector) {
let ret = [];
let length = 0;
let l = this.length;
for (let i = 0; i < l; i++) {
length = ret.length;
try {
Utils.merge(ret, this[i].querySelectorAll(selector));
} catch(e) {
Utils.log('iQ.find error (bad selector)', e);
}
if (i > 0) {
// Make sure that the results are unique
for (let n = length; n < ret.length; n++) {
for (let r = 0; r < length; r++) {
if (ret[r] === ret[n]) {
ret.splice(n--, 1);
break;
}
}
}
}
}
return iQ(ret);
},
// ----------
// Function: remove
// Removes the receiver from the DOM.
remove: function() {
for (let i = 0; this[i] != null; i++) {
let elem = this[i];
if (elem.parentNode) {
elem.parentNode.removeChild(elem);
}
}
return this;
},
// ----------
// Function: empty
// Removes all of the reciever's children and HTML content from the DOM.
empty: function() {
for (let i = 0; this[i] != null; i++) {
let elem = this[i];
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
}
return this;
},
// ----------
// Function: width
// Returns the width of the receiver.
width: function() {
return parseInt(this.css('width'));
},
// ----------
// Function: height
// Returns the height of the receiver.
height: function() {
return parseInt(this.css('height'));
},
// ----------
// Function: position
// Returns an object with the receiver's position in left and top
// properties.
position: function() {
return {
left: parseInt(this.css('left')),
top: parseInt(this.css('top'))
};
},
// ----------
// Function: bounds
// Returns a <Rect> with the receiver's bounds.
bounds: function() {
let p = this.position();
return new Rect(p.left, p.top, this.width(), this.height());
},
// ----------
// Function: data
// Pass in both key and value to attach some data to the receiver;
// pass in just key to retrieve it.
data: function(key, value) {
let data = null;
if (typeof value === "undefined") {
Utils.assert('does not yet support multi-objects (or null objects)', this.length == 1);
data = this[0].iQData;
if (data)
return data[key];
else
return null;
}
for (let i = 0; this[i] != null; i++) {
let elem = this[i];
data = elem.iQData;
if (!data)
data = elem.iQData = {};
data[key] = value;
}
return this;
},
// ----------
// Function: html
// Given a value, sets the receiver's innerHTML to it; otherwise returns
// what's already there.
html: function(value) {
Utils.assert('does not yet support multi-objects (or null objects)', this.length == 1);
if (typeof value === "undefined")
return this[0].innerHTML;
this[0].innerHTML = value;
return this;
},
// ----------
// Function: text
// Given a value, sets the receiver's textContent to it; otherwise returns
// what's already there.
text: function(value) {
Utils.assert('does not yet support multi-objects (or null objects)', this.length == 1);
if (typeof value === "undefined") {
return this[0].textContent;
}
return this.empty().append((this[0] && this[0].ownerDocument || document).createTextNode(value));
},
// ----------
// Function: val
// Given a value, sets the receiver's value to it; otherwise returns what's already there.
val: function(value) {
Utils.assert('does not yet support multi-objects (or null objects)', this.length == 1);
if (typeof value === "undefined") {
return this[0].value;
}
this[0].value = value;
return this;
},
// ----------
// Function: appendTo
// Appends the receiver to the result of iQ(selector).
appendTo: function(selector) {
Utils.assert('does not yet support multi-objects (or null objects)', this.length == 1);
iQ(selector).append(this);
return this;
},
// ----------
// Function: append
// Appends the result of iQ(selector) to the receiver.
append: function(selector) {
let object = iQ(selector);
Utils.assert('does not yet support multi-objects (or null objects)', object.length == 1 && this.length == 1);
this[0].appendChild(object[0]);
return this;
},
// ----------
// Function: attr
// Sets or gets an attribute on the element(s).
attr: function(key, value) {
try {
Utils.assert('string key', typeof key === 'string');
if (typeof value === "undefined") {
Utils.assert('retrieval does not support multi-objects (or null objects)', this.length == 1);
return this[0].getAttribute(key);
}
for (let i = 0; this[i] != null; i++) {
this[i].setAttribute(key, value);
}
} catch(e) {
Utils.log(e);
}
return this;
},
// ----------
// Function: css
// Sets or gets CSS properties on the receiver. When setting certain numerical properties,
// will automatically add "px". A property can be removed by setting it to null.
//
// Possible call patterns:
// a: object, b: undefined - sets with properties from a
// a: string, b: undefined - gets property specified by a
// a: string, b: string/number - sets property specified by a to b
css: function(a, b) {
let properties = null;
if (typeof a === 'string') {
let key = a;
if (typeof b === "undefined") {
Utils.assert('retrieval does not support multi-objects (or null objects)', this.length == 1);
let substitutions = {
'MozTransform': '-moz-transform',
'zIndex': 'z-index'
};
return window.getComputedStyle(this[0], null).getPropertyValue(substitutions[key] || key);
}
properties = {};
properties[key] = b;
} else {
properties = a;
}
let pixels = {
'left': true,
'top': true,
'right': true,
'bottom': true,
'width': true,
'height': true
};
for (let i = 0; this[i] != null; i++) {
let elem = this[i];
for (let key in properties) {
let value = properties[key];
if (pixels[key] && typeof(value) != 'string')
value += 'px';
if (value == null) {
elem.style.removeProperty(key);
} else if (key.indexOf('-') != -1)
elem.style.setProperty(key, value, '');
else
elem.style[key] = value;
}
}
return this;
},
// ----------
// Function: animate
// Uses CSS transitions to animate the element.
//
// Parameters:
// css - an object map of the CSS properties to change
// options - an object with various properites (see below)
//
// Possible "options" properties:
// duration - how long to animate, in milliseconds
// easing - easing function to use. Possibilities include
// "tabviewBounce", "easeInQuad". Default is "ease".
// complete - function to call once the animation is done, takes nothing
// in, but "this" is set to the element that was animated.
animate: function(css, options) {
try {
Utils.assert('does not yet support multi-objects (or null objects)', this.length == 1);
if (!options)
options = {};
let easings = {
tabviewBounce: "cubic-bezier(0.0, 0.63, .6, 1.29)",
easeInQuad: 'ease-in', // TODO: make it a real easeInQuad, or decide we don't care
fast: 'cubic-bezier(0.7,0,1,1)'
};
let duration = (options.duration || 400);
let easing = (easings[options.easing] || 'ease');
// The latest versions of Firefox do not animate from a non-explicitly
// set css properties. So for each element to be animated, go through
// and explicitly define 'em.
let rupper = /([A-Z])/g;
this.each(function(elem) {
let cStyle = window.getComputedStyle(elem, null);
for (let prop in css) {
prop = prop.replace(rupper, "-$1").toLowerCase();
iQ(elem).css(prop, cStyle.getPropertyValue(prop));
}
});
this.css({
'-moz-transition-property': 'all', // TODO: just animate the properties we're changing
'-moz-transition-duration': (duration / 1000) + 's',
'-moz-transition-timing-function': easing
});
this.css(css);
let self = this;
Utils.timeout(function() {
self.css({
'-moz-transition-property': 'none',
'-moz-transition-duration': '',
'-moz-transition-timing-function': ''
});
if (typeof options.complete == "function")
options.complete.apply(self);
}, duration);
} catch(e) {
Utils.log(e);
}
return this;
},
// ----------
// Function: fadeOut
// Animates the receiver to full transparency. Calls callback on completion.
fadeOut: function(callback) {
Utils.assert('does not yet support duration', typeof callback == "function"
|| typeof callback === "undefined");
this.animate({
opacity: 0
}, {
duration: 400,
complete: function() {
iQ(this).css({display: 'none'});
if (typeof callback == "function")
callback.apply(this);
}
});
return this;
},
// ----------
// Function: fadeIn
// Animates the receiver to full opacity.
fadeIn: function() {
try {
this.css({display: ''});
this.animate({
opacity: 1
}, {
duration: 400
});
} catch(e) {
Utils.log(e);
}
return this;
},
// ----------
// Function: hide
// Hides the receiver.
hide: function() {
try {
this.css({display: 'none', opacity: 0});
} catch(e) {
Utils.log(e);
}
return this;
},
// ----------
// Function: show
// Shows the receiver.
show: function() {
try {
this.css({display: '', opacity: 1});
} catch(e) {
Utils.log(e);
}
return this;
},
// ----------
// Function: bind
// Binds the given function to the given event type. Also wraps the function
// in a try/catch block that does a Utils.log on any errors.
bind: function(type, func) {
Utils.assert('does not support eventData argument', typeof func == "function");
let handler = function(event) {
try {
return func.apply(this, [event]);
} catch(e) {
Utils.log(e);
}
};
for (let i = 0; this[i] != null; i++) {
let elem = this[i];
if (!elem.iQEventData)
elem.iQEventData = {};
if (!elem.iQEventData[type])
elem.iQEventData[type] = [];
elem.iQEventData[type].push({
original: func,
modified: handler
});
elem.addEventListener(type, handler, false);
}
return this;
},
// ----------
// Function: one
// Binds the given function to the given event type, but only for one call;
// automatically unbinds after the event fires once.
one: function(type, func) {
Utils.assert('does not support eventData argument', typeof func == "function");
let handler = function(e) {
iQ(this).unbind(type, handler);
return func.apply(this, [e]);
};
return this.bind(type, handler);
},
// ----------
// Function: unbind
// Unbinds the given function from the given event type.
unbind: function(type, func) {
Utils.assert('Must provide a function', typeof func == "function");
for (let i = 0; this[i] != null; i++) {
let elem = this[i];
let handler = func;
if (elem.iQEventData && elem.iQEventData[type]) {
let count = elem.iQEventData[type].length;
for (let a = 0; a < count; a++) {
let pair = elem.iQEventData[type][a];
if (pair.original == func) {
handler = pair.modified;
elem.iQEventData[type].splice(a, 1);
break;
}
}
}
elem.removeEventListener(type, handler, false);
}
return this;
}
};
// ----------
// Create various event aliases
let events = [
'keyup',
'keydown',
'mouseup',
'mousedown',
'mouseover',
'mouseout',
'mousemove',
'click',
'resize',
'change',
'blur',
'focus'
];
events.forEach(function(event) {
iQClass.prototype[event] = function(func) {
return this.bind(event, func);
};
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
/* ***** 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 Groups.
*
* 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):
* Edward Lee <edilee@mozilla.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 ***** */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let EXPORTED_SYMBOLS = ["Groups"];
let Groups = let (T = {
//////////////////////////////////////////////////////////////////////////////
//// Public
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// Private
//////////////////////////////////////////////////////////////////////////////
init: function init() {
// Only allow calling init once
T.init = function() T;
// load all groups data
// presumably we can load from app global, not a window
// how do we know which window has which group?
// load tab data to figure out which go into which group
// set up interface for subscribing to our data
return T;
}
}) T.init();

View File

@ -0,0 +1,735 @@
/* ***** 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 utils.js.
*
* The Initial Developer of the Original Code is
* Aza Raskin <aza@mozilla.com>
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Ian Gilman <ian@iangilman.com>
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>
*
* This file incorporates work from:
* jQuery JavaScript Library v1.4.2: http://code.jquery.com/jquery-1.4.2.js
* This incorporated work is covered by the following copyright and
* permission notice:
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* 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 ***** */
// **********
// Title: utils.js
let EXPORTED_SYMBOLS = ["Point", "Rect", "Range", "Subscribable", "Utils"];
// #########
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
// ##########
// Class: Point
// A simple point.
//
// Constructor: Point
// If a is a Point, creates a copy of it. Otherwise, expects a to be x,
// and creates a Point with it along with y. If either a or y are omitted,
// 0 is used in their place.
function Point(a, y) {
if (Utils.isPoint(a)) {
this.x = a.x;
this.y = a.y;
} else {
this.x = (Utils.isNumber(a) ? a : 0);
this.y = (Utils.isNumber(y) ? y : 0);
}
};
Point.prototype = {
// ----------
// Function: distance
// Returns the distance from this point to the given <Point>.
distance: function(point) {
var ax = this.x - point.x;
var ay = this.y - point.y;
return Math.sqrt((ax * ax) + (ay * ay));
}
};
// ##########
// Class: Rect
// A simple rectangle. Note that in addition to the left and width, it also has
// a right property; changing one affects the others appropriately. Same for the
// vertical properties.
//
// Constructor: Rect
// If a is a Rect, creates a copy of it. Otherwise, expects a to be left,
// and creates a Rect with it along with top, width, and height.
function Rect(a, top, width, height) {
// Note: perhaps 'a' should really be called 'rectOrLeft'
if (Utils.isRect(a)) {
this.left = a.left;
this.top = a.top;
this.width = a.width;
this.height = a.height;
} else {
this.left = a;
this.top = top;
this.width = width;
this.height = height;
}
};
Rect.prototype = {
get right() this.left + this.width,
set right(value) {
this.width = value - this.left;
},
get bottom() this.top + this.height,
set bottom(value) {
this.height = value - this.top;
},
// ----------
// Variable: xRange
// Gives you a new <Range> for the horizontal dimension.
get xRange() new Range(this.left, this.right),
// ----------
// Variable: yRange
// Gives you a new <Range> for the vertical dimension.
get yRange() new Range(this.top, this.bottom),
// ----------
// Function: intersects
// Returns true if this rectangle intersects the given <Rect>.
intersects: function(rect) {
return (rect.right > this.left
&& rect.left < this.right
&& rect.bottom > this.top
&& rect.top < this.bottom);
},
// ----------
// Function: intersection
// Returns a new <Rect> with the intersection of this rectangle and the give <Rect>,
// or null if they don't intersect.
intersection: function(rect) {
var box = new Rect(Math.max(rect.left, this.left), Math.max(rect.top, this.top), 0, 0);
box.right = Math.min(rect.right, this.right);
box.bottom = Math.min(rect.bottom, this.bottom);
if (box.width > 0 && box.height > 0)
return box;
return null;
},
// ----------
// Function: contains
// Returns a boolean denoting if the <Rect> is contained inside
// of the bounding rect.
//
// Paramaters
// - A <Rect>
contains: function(rect) {
return(rect.left > this.left
&& rect.right < this.right
&& rect.top > this.top
&& rect.bottom < this.bottom)
},
// ----------
// Function: center
// Returns a new <Point> with the center location of this rectangle.
center: function() {
return new Point(this.left + (this.width / 2), this.top + (this.height / 2));
},
// ----------
// Function: size
// Returns a new <Point> with the dimensions of this rectangle.
size: function() {
return new Point(this.width, this.height);
},
// ----------
// Function: position
// Returns a new <Point> with the top left of this rectangle.
position: function() {
return new Point(this.left, this.top);
},
// ----------
// Function: area
// Returns the area of this rectangle.
area: function() {
return this.width * this.height;
},
// ----------
// Function: inset
// Makes the rect smaller (if the arguments are positive) as if a margin is added all around
// the initial rect, with the margin widths (symmetric) being specified by the arguments.
//
// Paramaters
// - A <Point> or two arguments: x and y
inset: function(a, b) {
if (Utils.isPoint(a)) {
b = a.y;
a = a.x;
}
this.left += a;
this.width -= a * 2;
this.top += b;
this.height -= b * 2;
},
// ----------
// Function: offset
// Moves (translates) the rect by the given vector.
//
// Paramaters
// - A <Point> or two arguments: x and y
offset: function(a, b) {
if (Utils.isPoint(a)) {
this.left += a.x;
this.top += a.y;
} else {
this.left += a;
this.top += b;
}
},
// ----------
// Function: equals
// Returns true if this rectangle is identical to the given <Rect>.
equals: function(rect) {
return (rect.left == this.left
&& rect.top == this.top
&& rect.width == this.width
&& rect.height == this.height);
},
// ----------
// Function: union
// Returns a new <Rect> with the union of this rectangle and the given <Rect>.
union: function(a) {
var newLeft = Math.min(a.left, this.left);
var newTop = Math.min(a.top, this.top);
var newWidth = Math.max(a.right, this.right) - newLeft;
var newHeight = Math.max(a.bottom, this.bottom) - newTop;
var newRect = new Rect(newLeft, newTop, newWidth, newHeight);
return newRect;
},
// ----------
// Function: copy
// Copies the values of the given <Rect> into this rectangle.
copy: function(a) {
this.left = a.left;
this.top = a.top;
this.width = a.width;
this.height = a.height;
},
// ----------
// Function: css
// Returns an object with the dimensions of this rectangle, suitable for
// passing into iQ's css method. You could of course just pass the rectangle
// straight in, but this is cleaner, as it removes all the extraneous
// properties. If you give a <Rect> to <iQClass.css> without this, it will
// ignore the extraneous properties, but result in CSS warnings.
css: function() {
return {
left: this.left,
top: this.top,
width: this.width,
height: this.height
};
}
};
// ##########
// Class: Range
// A physical interval, with a min and max.
//
// Constructor: Range
// Creates a Range with the given min and max
function Range(min, max) {
if (Utils.isRange(min) && !max) { // if the one variable given is a range, copy it.
this.min = min.min;
this.max = min.max;
} else {
this.min = min || 0;
this.max = max || 0;
}
};
Range.prototype = {
// Variable: extent
// Equivalent to max-min
get extent() {
return (this.max - this.min);
},
set extent(extent) {
this.max = extent - this.min;
},
// ----------
// Function: contains
// Whether the <Range> contains the given <Range> or value or not.
//
// Paramaters
// - a number or <Range>
contains: function(value) {
return Utils.isNumber(value) ?
value >= this.min && value <= this.max :
Utils.isRange(value) ?
(value.min <= this.max && this.min <= value.max) :
false;
},
// ----------
// Function: proportion
// Maps the given value to the range [0,1], so that it returns 0 if the value is <= the min,
// returns 1 if the value >= the max, and returns an interpolated "proportion" in (min, max).
//
// Paramaters
// - a number
// - (bool) smooth? If true, a smooth tanh-based function will be used instead of the linear.
proportion: function(value, smooth) {
if (value <= this.min)
return 0;
if (this.max <= value)
return 1;
var proportion = (value - this.min) / this.extent;
if (smooth) {
// The ease function ".5+.5*Math.tanh(4*x-2)" is a pretty
// little graph. It goes from near 0 at x=0 to near 1 at x=1
// smoothly and beautifully.
// http://www.wolframalpha.com/input/?i=.5+%2B+.5+*+tanh%28%284+*+x%29+-+2%29
function tanh(x) {
var e = Math.exp(x);
return (e - 1/e) / (e + 1/e);
}
return .5 - .5 * tanh(2 - 4 * proportion);
}
return proportion;
},
// ----------
// Function: scale
// Takes the given value in [0,1] and maps it to the associated value on the Range.
//
// Paramaters
// - a number in [0,1]
scale: function(value) {
if (value > 1)
value = 1;
if (value < 0)
value = 0;
return this.min + this.extent * value;
}
};
// ##########
// Class: Subscribable
// A mix-in for allowing objects to collect subscribers for custom events.
function Subscribable() {
this.subscribers = null;
};
Subscribable.prototype = {
// ----------
// Function: addSubscriber
// The given callback will be called when the Subscribable fires the given event.
// The refObject is used to facilitate removal if necessary.
addSubscriber: function(refObject, eventName, callback) {
try {
Utils.assertThrow("refObject", refObject);
Utils.assertThrow("callback must be a function", typeof callback == "function");
Utils.assertThrow("eventName must be a non-empty string",
eventName && typeof(eventName) == "string");
if (!this.subscribers)
this.subscribers = {};
if (!this.subscribers[eventName])
this.subscribers[eventName] = [];
var subs = this.subscribers[eventName];
var existing = subs.filter(function(element) {
return element.refObject == refObject;
});
if (existing.length) {
Utils.assert('should only ever be one', existing.length == 1);
existing[0].callback = callback;
} else {
subs.push({
refObject: refObject,
callback: callback
});
}
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: removeSubscriber
// Removes the callback associated with refObject for the given event.
removeSubscriber: function(refObject, eventName) {
try {
Utils.assertThrow("refObject", refObject);
Utils.assertThrow("eventName must be a non-empty string",
eventName && typeof(eventName) == "string");
if (!this.subscribers || !this.subscribers[eventName])
return;
this.subscribers[eventName] = this.subscribers[eventName].filter(function(element) {
return element.refObject != refObject;
});
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: _sendToSubscribers
// Internal routine. Used by the Subscribable to fire events.
_sendToSubscribers: function(eventName, eventInfo) {
try {
Utils.assertThrow("eventName must be a non-empty string",
eventName && typeof(eventName) == "string");
if (!this.subscribers || !this.subscribers[eventName])
return;
var self = this;
var subsCopy = this.subscribers[eventName].concat();
subsCopy.forEach(function(object) {
object.callback(self, eventInfo);
});
} catch(e) {
Utils.log(e);
}
}
};
// ##########
// Class: Utils
// Singelton with common utility functions.
let Utils = {
// ___ Logging
// ----------
// Function: log
// Prints the given arguments to the JavaScript error console as a message.
// Pass as many arguments as you want, it'll print them all.
log: function() {
var text = this.expandArgumentsForLog(arguments);
Services.console.logStringMessage(text);
},
// ----------
// Function: error
// Prints the given arguments to the JavaScript error console as an error.
// Pass as many arguments as you want, it'll print them all.
error: function() {
var text = this.expandArgumentsForLog(arguments);
Cu.reportError("tabview error: " + text);
},
// ----------
// Function: trace
// Prints the given arguments to the JavaScript error console as a message,
// along with a full stack trace.
// Pass as many arguments as you want, it'll print them all.
trace: function() {
var text = this.expandArgumentsForLog(arguments);
// cut off the first two lines of the stack trace, because they're just this function.
let stack = Error().stack.replace(/^.*?\n.*?\n/, "");
// if the caller was assert, cut out the line for the assert function as well.
if (this.trace.caller.name == 'Utils_assert')
stack = stack.replace(/^.*?\n/, "");
this.log('trace: ' + text + '\n' + stack);
},
// ----------
// Function: assert
// Prints a stack trace along with label (as a console message) if condition is false.
assert: function Utils_assert(label, condition) {
if (!condition) {
let text;
if (typeof(label) == 'undefined')
text = 'badly formed assert';
else
text = "tabview assert: " + label;
this.trace(text);
}
},
// ----------
// Function: assertThrow
// Throws label as an exception if condition is false.
assertThrow: function(label, condition) {
if (!condition) {
let text;
if (typeof(label) == 'undefined')
text = 'badly formed assert';
else
text = "tabview assert: " + label;
// cut off the first two lines of the stack trace, because they're just this function.
text += Error().stack.replace(/^.*?\n.*?\n/, "");
throw text;
}
},
// ----------
// Function: expandObject
// Prints the given object to a string, including all of its properties.
expandObject: function(obj) {
var s = obj + ' = {';
for (let prop in obj) {
let value;
try {
value = obj[prop];
} catch(e) {
value = '[!!error retrieving property]';
}
s += prop + ': ';
if (typeof(value) == 'string')
s += '\'' + value + '\'';
else if (typeof(value) == 'function')
s += 'function';
else
s += value;
s += ', ';
}
return s + '}';
},
// ----------
// Function: expandArgumentsForLog
// Expands all of the given args (an array) into a single string.
expandArgumentsForLog: function(args) {
var that = this;
return Array.map(args, function(arg) {
return typeof(arg) == 'object' ? that.expandObject(arg) : arg;
}).join('; ');
},
// ___ Misc
// ----------
// Function: isRightClick
// Given a DOM mouse event, returns true if it was for the right mouse button.
isRightClick: function(event) {
return event.button == 2;
},
// ----------
// Function: isDOMElement
// Returns true if the given object is a DOM element.
isDOMElement: function(object) {
return object instanceof Ci.nsIDOMElement;
},
// ----------
// Function: isNumber
// Returns true if the argument is a valid number.
isNumber: function(n) {
return (typeof(n) == 'number' && !isNaN(n));
},
// ----------
// Function: isRect
// Returns true if the given object (r) looks like a <Rect>.
isRect: function(r) {
return (r
&& this.isNumber(r.left)
&& this.isNumber(r.top)
&& this.isNumber(r.width)
&& this.isNumber(r.height));
},
// ----------
// Function: isRange
// Returns true if the given object (r) looks like a <Range>.
isRange: function(r) {
return (r
&& this.isNumber(r.min)
&& this.isNumber(r.max));
},
// ----------
// Function: isPoint
// Returns true if the given object (p) looks like a <Point>.
isPoint: function(p) {
return (p && this.isNumber(p.x) && this.isNumber(p.y));
},
// ----------
// Function: isPlainObject
// Check to see if an object is a plain object (created using "{}" or "new Object").
isPlainObject: function(obj) {
// Must be an Object.
// Make sure that DOM nodes and window objects don't pass through, as well
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]"
|| obj.nodeType || obj.setInterval) {
return false;
}
// Not own constructor property must be Object
const hasOwnProperty = Object.prototype.hasOwnProperty;
if (obj.constructor
&& !hasOwnProperty.call(obj, "constructor")
&& !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) {}
return key === undefined || hasOwnProperty.call(obj, key);
},
// ----------
// Function: isEmptyObject
// Returns true if the given object has no members.
isEmptyObject: function(obj) {
for (let name in obj)
return false;
return true;
},
// ----------
// Function: copy
// Returns a copy of the argument. Note that this is a shallow copy; if the argument
// has properties that are themselves objects, those properties will be copied by reference.
copy: function(value) {
if (value && typeof(value) == 'object') {
if (Array.isArray(value))
return this.extend([], value);
return this.extend({}, value);
}
return value;
},
// ----------
// Function: merge
// Merge two array-like objects into the first and return it.
merge: function(first, second) {
Array.forEach(second, function(el) Array.push(first, el));
return first;
},
// ----------
// Function: extend
// Pass several objects in and it will combine them all into the first object and return it.
extend: function() {
// copy reference to target object
var target = arguments[0] || {}, i = 1, length = arguments.length, options, name, src, copy;
// Deep copy is not supported
if (typeof target === "boolean") {
this.assert("The first argument of extend cannot be a boolean."
+"Deep copy is not supported.", false);
return target;
}
// Back when this was in iQ + iQ.fn, so you could extend iQ objects with it.
// This is no longer supported.
if (length === 1) {
this.assert("Extending the iQ prototype using extend is not supported.", false);
return target;
}
// Handle case when target is a string or something
if (typeof target != "object" && typeof target != "function") {
target = {};
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) != null) {
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy)
continue;
if (copy !== undefined)
target[name] = copy;
}
}
}
// Return the modified object
return target;
},
// ----------
// Function: timeout
// delay a function call giving the timer as this
timeout: function(func, delay) {
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback({
notify: function notify() {
func.call(timer);
}
}, delay, timer.TYPE_ONE_SHOT);
}
};

View File

@ -0,0 +1,93 @@
/* ***** 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 profile.js.
*
* The Initial Developer of the Original Code is
* Ian Gilman <ian@iangilman.com>
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
// **********
// Title: profile.js
(function() {
// ##########
// Class: Profile
// A simple profiling helper.
// TODO: remove before shipping.
window.Profile = {
// Variable: silent
// If true, disables logging of results.
silent: true,
// Variable: cutoff
// How many ms a wrapped function needs to take before it gets logged.
cutoff: 4,
// Variable: _time
// Private. The time of the last checkpoint.
_time: Date.now(),
// ----------
// Function: wrap
// Wraps the given object with profiling for each method.
wrap: function(obj, name) {
let self = this;
[i for (i in Iterator(obj))].forEach(function([key, val]) {
if (typeof val != "function")
return;
obj[key] = function() {
let start = Date.now();
try {
return val.apply(obj, arguments);
} finally {
let diff = Date.now() - start;
if (diff >= self.cutoff && !self.silent)
Utils.log("profile: " + name + "." + key + " = " + diff + "ms");
}
};
});
},
// ----------
// Function: checkpoint
// Reset the clock. If label is provided, print the time in milliseconds since the last reset.
checkpoint: function(label) {
var now = Date.now();
if (label && !this.silent)
Utils.log("profile checkpoint: " + label + " = " + (now - this._time) + "ms");
this._time = now;
}
};
})();

View File

@ -0,0 +1,247 @@
/* ***** 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 storage.js.
*
* The Initial Developer of the Original Code is
* Ehsan Akhgari <ehsan@mozilla.com>
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* 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 ***** */
// **********
// Title: storage.js
// ##########
// Class: Storage
// Singleton for permanent storage of TabView data.
Storage = {
GROUP_DATA_IDENTIFIER: "tabview-group",
GROUPS_DATA_IDENTIFIER: "tabview-groups",
TAB_DATA_IDENTIFIER: "tabview-tab",
UI_DATA_IDENTIFIER: "tabview-ui",
VISIBILITY_DATA_IDENTIFIER: "tabview-visibility",
// ----------
// Function: onReady
// Calls callback when Storage is ready for business. Could be immediately if it's already ready
// or later if needed.
onReady: function(callback) {
try {
// ToDo: the session store doesn't expose any public methods/variables for
// us to check whether it's loaded or not so using a private one for
// now.
var alreadyReady = gWindow.__SSi;
if (alreadyReady) {
callback();
} else {
var observer = {
observe: function(subject, topic, data) {
try {
if (topic == "browser-delayed-startup-finished") {
if (subject == gWindow) {
callback();
}
}
} catch(e) {
Utils.log(e);
}
}
};
Services.obs.addObserver(
observer, "browser-delayed-startup-finished", false);
}
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: init
// Sets up the object.
init: function() {
this._sessionStore =
Components.classes["@mozilla.org/browser/sessionstore;1"]
.getService(Components.interfaces.nsISessionStore);
},
// ----------
// Function: wipe
// Cleans out all the stored data, leaving empty objects.
wipe: function() {
try {
var self = this;
// ___ Tabs
AllTabs.tabs.forEach(function(tab) {
if (tab.ownerDocument.defaultView != gWindow)
return;
self.saveTab(tab, null);
});
// ___ Other
this.saveGroupItemsData(gWindow, {});
this.saveUIData(gWindow, {});
this._sessionStore.setWindowValue(gWindow, this.GROUP_DATA_IDENTIFIER,
JSON.stringify({}));
} catch (e) {
Utils.log("Error in wipe: "+e);
}
},
// ----------
// Function: saveTab
// Saves the data for a single tab.
saveTab: function(tab, data) {
Utils.assert('tab', tab);
this._sessionStore.setTabValue(tab, this.TAB_DATA_IDENTIFIER,
JSON.stringify(data));
},
// ----------
// Function: getTabData
// Returns the data object associated with a single tab.
getTabData: function(tab) {
Utils.assert('tab', tab);
var existingData = null;
try {
/* Utils.log("readTabData: " + this._sessionStore.getTabValue(tab, this.TAB_DATA_IDENTIFIER)); */
var tabData = this._sessionStore.getTabValue(tab, this.TAB_DATA_IDENTIFIER);
if (tabData != "") {
existingData = JSON.parse(tabData);
}
} catch (e) {
// getWindowValue will fail if the property doesn't exist
Utils.log(e);
}
/* Utils.log('tab', existingData); */
return existingData;
},
// ----------
// Function: saveGroupItem
// Saves the data for a single groupItem, associated with a specific window.
saveGroupItem: function(win, data) {
var id = data.id;
var existingData = this.readGroupItemData(win);
existingData[id] = data;
this._sessionStore.setWindowValue(win, this.GROUP_DATA_IDENTIFIER,
JSON.stringify(existingData));
},
// ----------
// Function: deleteGroupItem
// Deletes the data for a single groupItem from the given window.
deleteGroupItem: function(win, id) {
var existingData = this.readGroupItemData(win);
delete existingData[id];
this._sessionStore.setWindowValue(win, this.GROUP_DATA_IDENTIFIER,
JSON.stringify(existingData));
},
// ----------
// Function: readGroupItemData
// Returns the data for all groupItems associated with the given window.
readGroupItemData: function(win) {
var existingData = {};
try {
/* Utils.log("readGroupItemData" + this._sessionStore.getWindowValue(win, this.GROUP_DATA_IDENTIFIER)); */
existingData = JSON.parse(
this._sessionStore.getWindowValue(win, this.GROUP_DATA_IDENTIFIER)
);
} catch (e) {
// getWindowValue will fail if the property doesn't exist
Utils.log("Error in readGroupItemData: "+e);
}
return existingData;
},
// ----------
// Function: saveGroupItemsData
// Saves the global data for the <GroupItems> singleton for the given window.
saveGroupItemsData: function(win, data) {
this.saveData(win, this.GROUPS_DATA_IDENTIFIER, data);
},
// ----------
// Function: readGroupItemsData
// Reads the global data for the <GroupItems> singleton for the given window.
readGroupItemsData: function(win) {
return this.readData(win, this.GROUPS_DATA_IDENTIFIER);
},
// ----------
// Function: saveUIData
// Saves the global data for the <UIManager> singleton for the given window.
saveUIData: function(win, data) {
this.saveData(win, this.UI_DATA_IDENTIFIER, data);
},
// ----------
// Function: readUIData
// Reads the global data for the <UIManager> singleton for the given window.
readUIData: function(win) {
return this.readData(win, this.UI_DATA_IDENTIFIER);
},
// ----------
// Function: saveData
// Generic routine for saving data to a window.
saveData: function(win, id, data) {
try {
this._sessionStore.setWindowValue(win, id, JSON.stringify(data));
} catch (e) {
Utils.log("Error in saveData: "+e);
}
/* Utils.log('save data', id, data); */
},
// ----------
// Function: readData
// Generic routine for reading data from a window.
readData: function(win, id) {
var existingData = {};
try {
var data = this._sessionStore.getWindowValue(win, id);
if (data)
existingData = JSON.parse(data);
} catch (e) {
Utils.log("Error in readData: "+e);
}
/* Utils.log('read data', id, existingData); */
return existingData;
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,454 @@
html {
overflow: hidden;
/* image-rendering: -moz-crisp-edges; */
}
body {
font-family: Tahoma, sans-serif !important;
padding: 0px;
color: rgba(0,0,0,0.4);
font-size: 12px;
line-height: 16px;
margin: 0 auto;
}
#content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#bg {
background: -moz-linear-gradient(top,#C4C4C4,#9E9E9E);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -999999;
}
/* Tab Styling
----------------------------------*/
.tab {
position: absolute;
padding: 4px 6px 6px 4px;
border: 1px solid rgba(230,230,230,1);
background-color: rgba(245,245,245,1);
overflow: visible !important;
-moz-border-radius: 0.4em;
-moz-box-shadow: inset rgba(255, 255, 255, 0.6) 0 0 0 2px;
cursor: pointer;
}
.tab canvas,
.cached-thumb {
border: 1px solid rgba(0,0,0,0.2);
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
.thumb {
position: relative;
width: 100%;
height: 100%;
}
.thumb-shadow {
position: absolute;
border-bottom: 5px solid rgba(0,0,0,0.05);
margin-right: -12px;
bottom: 2px;
width: 94.5%;
}
.favicon {
position: absolute;
background-color: rgba(245,245,245,1);
-moz-border-radius-bottomright: 0.4em;
-moz-box-shadow:
inset rgba(255, 255, 255, 0.6) 0 -2px 0px,
inset rgba(255, 255, 255, 0.6) -2px 0px 0px;
padding: 4px 6px 6px 4px;
top: 4px;
left: 4px;
border-right: 1px solid rgba(0,0,0,0.2);
border-bottom: 1px solid rgba(0,0,0,0.2);
height: 17px;
width: 17px;
}
.favicon img {
border: none;
width: 16px;
height: 16px;
}
.close {
position: absolute;
top: 6px;
right: 6px;
width: 16px;
height: 16px;
/* background is set in platform.css */
opacity: 0.2;
cursor: pointer;
}
.close:hover {
opacity: 1.0;
}
.expander {
position: absolute;
bottom: 6px;
right: 6px;
width: 16px;
height: 16px;
background: url(chrome://global/skin/icons/resizer.png) no-repeat;
opacity: 0.2;
}
.expander:hover {
opacity: 1.0;
}
.favicon img:hover, .close img:hover, .expander img:hover {
opacity: 1;
border: none;
}
.tab-title {
position: absolute;
top: 100%;
text-align: center;
width: 94.5%;
white-space: nowrap;
overflow: hidden;
}
.stacked {
padding: 0;
}
.stacked .tab-title {
display: none;
}
.stacked .thumb-shadow {
display: none;
}
.stacked .thumb {
-moz-box-shadow: rgba(0,0,0,.2) 1px 1px 6px;
}
.stack-trayed .tab-title {
display: block !important;
text-shadow: rgba(0,0,0,1) 1px 1px 2px;
color: #EEE;
font-size: 11px;
}
.stack-trayed .thumb {
-moz-box-shadow: none !important;
}
.focus {
-moz-box-shadow: rgba(54,79,225,1) 0px 0px 5px -1px !important;
}
.front .tab-title, .front .close, .front .favicon, .front .expander, .front .thumb-shadow {
display: none;
}
.front .focus {
-moz-box-shadow: none !important;
}
/* Tab GroupItem
----------------------------------*/
.tabInGroupItem {
border: none;
-moz-box-shadow: none !important;
}
.groupItem {
position: absolute;
/* float: left; */
cursor: move;
border: 1px solid rgba(230,230,230,1);
background-color: rgba(248,248,248,1);
-moz-border-radius: 0.4em;
-moz-box-shadow:
inset rgba(255, 255, 255, 0.6) 0 0 0 2px,
rgba(0,0,0,0.2) 1px 1px 4px;
}
.groupItem.activeGroupItem {
-moz-box-shadow:
rgba(0,0,0,0.6) 1px 1px 8px;
}
.phantom {
border: 1px solid rgba(190,190,190,1);
}
.overlay {
background-color: rgba(0,0,0,.7) !important;
-moz-box-shadow: 3px 3px 8px rgba(0,0,0,.5);
-moz-border-radius: 0.4em;
/*
border: 1px solid rgba(230,230,230,1);
background-color: rgba(248,248,248,1);
-moz-box-shadow:
rgba(0,0,0, .3) 2px 2px 8px,
inset rgba(255, 255, 255, 0.6) 0 0 0 2px; */
}
/* InfoItems
----------------------------------*/
.info-item {
position: absolute;
cursor: move;
border: 1px solid rgba(230,230,230,1);
background-color: rgba(248,248,248,1);
-moz-border-radius: 0.4em;
-moz-box-shadow:
inset rgba(255, 255, 255, 0.6) 0 0 0 2px,
rgba(0,0,0, .2) 1px 1px 4px;
}
.intro {
margin: 10px;
}
/* Trenches
----------------------------------*/
.guideTrench, .visibleTrench, .activeVisibleTrench {
position: absolute;
}
.guideTrench {
z-index: -101;
opacity: 0.9;
border: 1px dashed rgba(0,0,0,.12);
border-bottom: none;
border-right: none;
-moz-box-shadow: 1px 1px 0 rgba(255,255,255,.15);
}
.visibleTrench {
z-index: -103;
opacity: 0.05;
}
.activeVisibleTrench {
z-index: -102;
opacity: 0;
}
.activeVisibleTrench.activeTrench {
opacity: 0.45;
}
.visibleTrench.border, .activeVisibleTrench.border {
background-color: red;
}
.visibleTrench.guide, .activeVisibleTrench.guide {
background-color: blue;
}
/* Other
----------------------------------*/
.newTabButton {
width: 16px;
height: 15px;
bottom: 10px;
left: 10px;
position: absolute !important;
cursor: pointer;
opacity: .3;
background-image: url(chrome://browser/skin/tabview/new-tab.png);
z-index: 99999;
}
.newTabButton:hover {
opacity: 1;
}
.newTabButtonAlt {
position: absolute;
cursor: pointer;
z-index: 99999;
border: none;
-moz-border-radius: 4px;
font-size: 50px;
line-height: 50px;
height: 57px !important;
width: 70px !important;
margin-top: 4px !important;
text-align: center;
background-color: #888888;
-moz-box-shadow: inset 0px 0px 5px rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.3);
}
.newTabButtonAlt > span {
color: #909090;
text-shadow: 0px 0px 7px rgba(0,0,0,.4), 0 -1px 0 rgba(255,255,255,.6);
font-weight: bold;
}
.active {
-moz-box-shadow: 5px 5px 4px rgba(0,0,0,.5);
}
.acceptsDrop {
-moz-box-shadow: 2px 2px 10px -1px rgba(0,0,0,.6);
}
.titlebar {
font-size: 12px;
line-height: 18px;
height: 18px;
}
input.name {
background: transparent;
border: 1px solid transparent;
color: #999;
margin: 3px 0px 0px 3px;
padding: 1px;
background-image: url(chrome://browser/skin/tabview/edit-light.png);
padding-left: 20px;
}
input.name:hover {
border: 1px solid #ddd;
}
input.name-locked:hover {
border: 1px solid transparent !important;
cursor: default;
}
input.name:focus {
color: #555;
}
input.defaultName {
font-style: italic !important;
background-image-opacity: .1;
color: transparent;
}
input.defaultName:hover {
color: #CCC;
}
.title-container {
cursor: text;
}
.title-shield {
position: absolute;
margin: 3px 0px 0px 3px;
padding: 1px;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 10;
}
.transparentBorder {
border: 1px solid transparent !important;
}
.stackExpander {
position: absolute;
opacity: .4;
cursor: pointer;
}
.stackExpander:hover {
opacity: .7 !important;
}
/* Resizable
----------------------------------*/
.resizer {
background-image: url(chrome://global/skin/icons/resizer.png);
position: absolute;
bottom: 6px;
right: 6px;
opacity: .2;
}
.iq-resizable { }
.iq-resizable-handle {
position: absolute;
font-size: 0.1px;
z-index: 99999;
display: block;
}
.iq-resizable-disabled .iq-resizable-handle, .iq-resizable-autohide .iq-resizable-handle {
display: none;
}
.iq-resizable-se {
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}
/* Utils
----------------------------------*/
.front {
z-index: 999999 !important;
-moz-border-radius: 0 !important;
-moz-box-shadow: none !important;
-moz-transform: none !important;
image-rendering: -moz-crisp-edges;
}
/* Feedback
----------------------------------*/
.bottomButton {
position: absolute;
bottom: 0px;
width: 100px;
height: 20px;
line-height: 20px;
z-index: 99999 !important;
background-color: blue;
text-align: center;
color: white;
background-color: #9E9E9E;
-moz-box-shadow: 0px 0px 4px rgba(0,0,0,.3), inset 0px 1px 0px rgba(255,255,255,.4);
}
.bottomButton:hover {
cursor: pointer;
background-color: #A5A5A5;
-moz-box-shadow: 0px 0px 5px rgba(0,0,0,.6), inset 0px 1px 0px rgba(255,255,255,.4);
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
<head>
<title>&nbsp;</title>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
<link rel="stylesheet" href="tabview.css" type="text/css"/>
<link rel="stylesheet" href="chrome://browser/skin/tabview/platform.css" type="text/css"/>
</head>
<body style="background-color: transparent !important;-moz-appearance: none !important;" transparent="true">
<div id="content">
<div id="bg" />
</div>
<script type="text/javascript;version=1.8" src="tabview.js"></script>
</body>
</html>

View File

@ -0,0 +1,38 @@
// profile.js starts a timer to see how long this file takes to load, so it needs to be first.
// The file should be removed before we ship.
#include profile.js
Components.utils.import("resource://gre/modules/tabview/groups.js");
Components.utils.import("resource://gre/modules/tabview/utils.js");
Components.utils.import("resource://gre/modules/AllTabs.js");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "gWindow", function() {
return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIWebNavigation).
QueryInterface(Components.interfaces.nsIDocShell).
chromeEventHandler.ownerDocument.defaultView;
});
XPCOMUtils.defineLazyGetter(this, "gBrowser", function() gWindow.gBrowser);
XPCOMUtils.defineLazyGetter(this, "gTabViewDeck", function() {
return gWindow.document.getElementById("tab-view-deck");
});
XPCOMUtils.defineLazyGetter(this, "gTabViewFrame", function() {
return gWindow.document.getElementById("tab-view");
});
# NB: Certain files need to evaluate before others
#include iq.js
#include storage.js
#include items.js
#include groupitems.js
#include tabitems.js
#include drag.js
#include trench.js
#include infoitems.js
#include ui.js

View File

@ -0,0 +1,673 @@
/* ***** 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 trench.js.
*
* The Initial Developer of the Original Code is
* Michael Yoshitaka Erlewine <mitcho@mitcho.com>.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Ian Gilman <ian@iangilman.com>
* Aza Raskin <aza@mozilla.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 ***** */
// **********
// Title: trench.js
// ##########
// Class: Trench
//
// Class for drag-snapping regions; called "trenches" as they are long and narrow.
// Constructor: Trench
//
// Parameters:
// element - the DOM element for Item (GroupItem or TabItem) from which the trench is projected
// xory - either "x" or "y": whether the trench's <position> is along the x- or y-axis.
// In other words, if "x", the trench is vertical; if "y", the trench is horizontal.
// type - either "border" or "guide". Border trenches mark the border of an Item.
// Guide trenches extend out (unless they are intercepted) and act as "guides".
// edge - which edge of the Item that this trench corresponds to.
// Either "top", "left", "bottom", or "right".
var Trench = function(element, xory, type, edge) {
//----------
// Variable: id
// (integer) The id for the Trench. Set sequentially via <Trenches.nextId>
this.id = Trenches.nextId++;
// ---------
// Variables: Initial parameters
// element - (DOMElement)
// parentItem - <Item> which projects this trench; to be set with setParentItem
// xory - (string) "x" or "y"
// type - (string) "border" or "guide"
// edge - (string) "top", "left", "bottom", or "right"
this.el = element;
this.parentItem = null;
this.xory = xory; // either "x" or "y"
this.type = type; // "border" or "guide"
this.edge = edge; // "top", "left", "bottom", or "right"
this.$el = iQ(this.el);
//----------
// Variable: dom
// (array) DOM elements for visible reflexes of the Trench
this.dom = [];
//----------
// Variable: showGuide
// (boolean) Whether this trench will project a visible guide (dotted line) or not.
this.showGuide = false;
//----------
// Variable: active
// (boolean) Whether this trench is currently active or not.
// Basically every trench aside for those projected by the Item currently being dragged
// all become active.
this.active = false;
this.gutter = Items.defaultGutter;
//----------
// Variable: position
// (integer) position is the position that we should snap to.
this.position = 0;
//----------
// Variables: some Ranges
// range - (<Range>) explicit range; this is along the transverse axis
// minRange - (<Range>) the minimum active range
// activeRange - (<Range>) the currently active range
this.range = new Range(0,10000);
this.minRange = new Range(0,0);
this.activeRange = new Range(0,10000);
};
Trench.prototype = {
//----------
// Variable: radius
// (integer) radius is how far away we should snap from
get radius() this.customRadius || Trenches.defaultRadius,
setParentItem: function Trench_setParentItem(item) {
if (!item.isAnItem) {
Utils.assert("parentItem must be an Item",false);
return false;
}
this.parentItem = item;
return true;
},
//----------
// Function: setPosition
// set the trench's position.
//
// Parameters:
// position - (integer) px center position of the trench
// range - (<Range>) the explicit active range of the trench
// minRange - (<Range>) the minimum range of the trench
setPosition: function Trench_setPos(position, range, minRange) {
this.position = position;
var page = Items.getPageBounds(true);
// optionally, set the range.
if (Utils.isRange(range)) {
this.range = range;
} else {
this.range = new Range(0, (this.xory == 'x' ? page.height : page.width));
}
// if there's a minRange, set that too.
if (Utils.isRange(minRange))
this.minRange = minRange;
// set the appropriate bounds as a rect.
if (this.xory == "x") // vertical
this.rect = new Rect(this.position - this.radius, this.range.min, 2 * this.radius, this.range.extent);
else // horizontal
this.rect = new Rect(this.range.min, this.position - this.radius, this.range.extent, 2 * this.radius);
this.show(); // DEBUG
},
//----------
// Function: setActiveRange
// set the trench's currently active range.
//
// Parameters:
// activeRange - (<Range>)
setActiveRange: function Trench_setActiveRect(activeRange) {
if (!Utils.isRange(activeRange))
return false;
this.activeRange = activeRange;
if (this.xory == "x") { // horizontal
this.activeRect = new Rect(this.position - this.radius, this.activeRange.min, 2 * this.radius, this.activeRange.extent);
this.guideRect = new Rect(this.position, this.activeRange.min, 0, this.activeRange.extent);
} else { // vertical
this.activeRect = new Rect(this.activeRange.min, this.position - this.radius, this.activeRange.extent, 2 * this.radius);
this.guideRect = new Rect(this.activeRange.min, this.position, this.activeRange.extent, 0);
}
},
//----------
// Function: setWithRect
// Set the trench's position using the given rect. We know which side of the rect we should match
// because we've already recorded this information in <edge>.
//
// Parameters:
// rect - (<Rect>)
setWithRect: function Trench_setWithRect(rect) {
if (!Utils.isRect(rect))
Utils.error('argument must be Rect');
// First, calculate the range for this trench.
// Border trenches are always only active for the length of this range.
// Guide trenches, however, still use this value as its minRange.
if (this.xory == "x")
var range = new Range(rect.top - this.gutter, rect.bottom + this.gutter);
else
var range = new Range(rect.left - this.gutter, rect.right + this.gutter);
if (this.type == "border") {
// border trenches have a range, so set that too.
if (this.edge == "left")
this.setPosition(rect.left - this.gutter, range);
else if (this.edge == "right")
this.setPosition(rect.right + this.gutter, range);
else if (this.edge == "top")
this.setPosition(rect.top - this.gutter, range);
else if (this.edge == "bottom")
this.setPosition(rect.bottom + this.gutter, range);
} else if (this.type == "guide") {
// guide trenches have no range, but do have a minRange.
if (this.edge == "left")
this.setPosition(rect.left, false, range);
else if (this.edge == "right")
this.setPosition(rect.right, false, range);
else if (this.edge == "top")
this.setPosition(rect.top, false, range);
else if (this.edge == "bottom")
this.setPosition(rect.bottom, false, range);
}
},
//----------
// Function: show
//
// Show guide (dotted line), if <showGuide> is true.
//
// If <Trenches.showDebug> is true, we will draw the trench. Active portions are drawn with 0.5
// opacity. If <active> is false, the entire trench will be
// very translucent.
show: function Trench_show() { // DEBUG
if (this.active && this.showGuide) {
if (!this.dom.guideTrench)
this.dom.guideTrench = iQ("<div/>").addClass('guideTrench').css({id: 'guideTrench'+this.id});
var guideTrench = this.dom.guideTrench;
guideTrench.css(this.guideRect.css());
iQ("body").append(guideTrench);
} else {
if (this.dom.guideTrench) {
this.dom.guideTrench.remove();
delete this.dom.guideTrench;
}
}
if (!Trenches.showDebug) {
this.hide(true); // true for dontHideGuides
return;
}
if (!this.dom.visibleTrench)
this.dom.visibleTrench = iQ("<div/>")
.addClass('visibleTrench')
.addClass(this.type) // border or guide
.css({id: 'visibleTrench'+this.id});
var visibleTrench = this.dom.visibleTrench;
if (!this.dom.activeVisibleTrench)
this.dom.activeVisibleTrench = iQ("<div/>")
.addClass('activeVisibleTrench')
.addClass(this.type) // border or guide
.css({id: 'activeVisibleTrench'+this.id});
var activeVisibleTrench = this.dom.activeVisibleTrench;
if (this.active)
activeVisibleTrench.addClass('activeTrench');
else
activeVisibleTrench.removeClass('activeTrench');
visibleTrench.css(this.rect.css());
activeVisibleTrench.css((this.activeRect || this.rect).css());
iQ("body").append(visibleTrench);
iQ("body").append(activeVisibleTrench);
},
//----------
// Function: hide
// Hide the trench.
hide: function Trench_hide(dontHideGuides) {
if (this.dom.visibleTrench)
this.dom.visibleTrench.remove();
if (this.dom.activeVisibleTrench)
this.dom.activeVisibleTrench.remove();
if (!dontHideGuides && this.dom.guideTrench)
this.dom.guideTrench.remove();
},
//----------
// Function: rectOverlaps
// Given a <Rect>, compute whether it overlaps with this trench. If it does, return an
// adjusted ("snapped") <Rect>; if it does not overlap, simply return false.
//
// Note that simply overlapping is not all that is required to be affected by this function.
// Trenches can only affect certain edges of rectangles... for example, a "left"-edge guide
// trench should only affect left edges of rectangles. We don't snap right edges to left-edged
// guide trenches. For border trenches, the logic is a bit different, so left snaps to right and
// top snaps to bottom.
//
// Parameters:
// rect - (<Rect>) the rectangle in question
// stationaryCorner - which corner is stationary? by default, the top left.
// "topleft", "bottomleft", "topright", "bottomright"
// assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not
// keepProportional - (boolean) if we are allowed to change the rect's size, whether the
// dimensions should scaled proportionally or not.
//
// Returns:
// false - if rect does not overlap with this trench
// newRect - (<Rect>) an adjusted version of rect, if it is affected by this trench
rectOverlaps: function Trench_rectOverlaps(rect,stationaryCorner,assumeConstantSize,keepProportional) {
var edgeToCheck;
if (this.type == "border") {
if (this.edge == "left")
edgeToCheck = "right";
else if (this.edge == "right")
edgeToCheck = "left";
else if (this.edge == "top")
edgeToCheck = "bottom";
else if (this.edge == "bottom")
edgeToCheck = "top";
} else { // if trench type is guide or barrier...
edgeToCheck = this.edge;
}
rect.adjustedEdge = edgeToCheck;
switch (edgeToCheck) {
case "left":
if (this.ruleOverlaps(rect.left, rect.yRange)) {
if (stationaryCorner.indexOf('right') > -1)
rect.width = rect.right - this.position;
rect.left = this.position;
return rect;
}
break;
case "right":
if (this.ruleOverlaps(rect.right, rect.yRange)) {
if (assumeConstantSize) {
rect.left = this.position - rect.width;
} else {
var newWidth = this.position - rect.left;
if (keepProportional)
rect.height = rect.height * newWidth / rect.width;
rect.width = newWidth;
}
return rect;
}
break;
case "top":
if (this.ruleOverlaps(rect.top, rect.xRange)) {
if (stationaryCorner.indexOf('bottom') > -1)
rect.height = rect.bottom - this.position;
rect.top = this.position;
return rect;
}
break;
case "bottom":
if (this.ruleOverlaps(rect.bottom, rect.xRange)) {
if (assumeConstantSize) {
rect.top = this.position - rect.height;
} else {
var newHeight = this.position - rect.top;
if (keepProportional)
rect.width = rect.width * newHeight / rect.height;
rect.height = newHeight;
}
return rect;
}
}
return false;
},
//----------
// Function: ruleOverlaps
// Computes whether the given "rule" (a line segment, essentially), given by the position and
// range arguments, overlaps with the current trench. Note that this function assumes that
// the rule and the trench are in the same direction: both horizontal, or both vertical.
//
// Parameters:
// position - (integer) a position in px
// range - (<Range>) the rule's range
ruleOverlaps: function Trench_ruleOverlaps(position, range) {
return (this.position - this.radius < position && position < this.position + this.radius
&& this.activeRange.contains(range));
},
//----------
// Function: adjustRangeIfIntercept
// Computes whether the given boundary (given as a position and its active range), perpendicular
// to the trench, intercepts the trench or not. If it does, it returns an adjusted <Range> for
// the trench. If not, it returns false.
//
// Parameters:
// position - (integer) the position of the boundary
// range - (<Range>) the target's range, on the trench's transverse axis
adjustRangeIfIntercept: function Trench_adjustRangeIfIntercept(position, range) {
if (this.position - this.radius > range.min && this.position + this.radius < range.max) {
var activeRange = new Range(this.activeRange);
// there are three ways this can go:
// 1. position < minRange.min
// 2. position > minRange.max
// 3. position >= minRange.min && position <= minRange.max
if (position < this.minRange.min) {
activeRange.min = Math.min(this.minRange.min,position);
} else if (position > this.minRange.max) {
activeRange.max = Math.max(this.minRange.max,position);
} else {
// this should be impossible because items can't overlap and we've already checked
// that the range intercepts.
}
return activeRange;
}
return false;
},
//----------
// Function: calculateActiveRange
// Computes and sets the <activeRange> for the trench, based on the <GroupItems> around.
// This makes it so trenches' active ranges don't extend through other groupItems.
calculateActiveRange: function Trench_calculateActiveRange() {
// set it to the default: just the range itself.
this.setActiveRange(this.range);
// only guide-type trenches need to set a separate active range
if (this.type != 'guide')
return;
var groupItems = GroupItems.groupItems;
var trench = this;
groupItems.forEach(function(groupItem) {
if (groupItem.isDragging) // floating groupItems don't block trenches
return;
if (trench.el == groupItem.container) // groupItems don't block their own trenches
return;
var bounds = groupItem.getBounds();
var activeRange = new Range();
if (trench.xory == 'y') { // if this trench is horizontal...
activeRange = trench.adjustRangeIfIntercept(bounds.left, bounds.yRange);
if (activeRange)
trench.setActiveRange(activeRange);
activeRange = trench.adjustRangeIfIntercept(bounds.right, bounds.yRange);
if (activeRange)
trench.setActiveRange(activeRange);
} else { // if this trench is vertical...
activeRange = trench.adjustRangeIfIntercept(bounds.top, bounds.xRange);
if (activeRange)
trench.setActiveRange(activeRange);
activeRange = trench.adjustRangeIfIntercept(bounds.bottom, bounds.xRange);
if (activeRange)
trench.setActiveRange(activeRange);
}
});
}
};
// ##########
// Class: Trenches
// Singelton for managing all <Trench>es.
var Trenches = {
// ---------
// Variables:
// nextId - (integer) a counter for the next <Trench>'s <Trench.id> value.
// showDebug - (boolean) whether to draw the <Trench>es or not.
// defaultRadius - (integer) the default radius for new <Trench>es.
nextId: 0,
showDebug: false,
defaultRadius: 10,
// ---------
// Variables: snapping preferences; used to break ties in snapping.
// preferTop - (boolean) prefer snapping to the top to the bottom
// preferLeft - (boolean) prefer snapping to the left to the right
preferTop: true,
preferLeft: true,
trenches: [],
// ---------
// Function: getById
// Return the specified <Trench>.
//
// Parameters:
// id - (integer)
getById: function Trenches_getById(id) {
return this.trenches[id];
},
// ---------
// Function: register
// Register a new <Trench> and returns the resulting <Trench> ID.
//
// Parameters:
// See the constructor <Trench.Trench>'s parameters.
//
// Returns:
// id - (int) the new <Trench>'s ID.
register: function Trenches_register(element, xory, type, edge) {
var trench = new Trench(element, xory, type, edge);
this.trenches[trench.id] = trench;
return trench.id;
},
// ---------
// Function: registerWithItem
// Register a whole set of <Trench>es using an <Item> and returns the resulting <Trench> IDs.
//
// Parameters:
// item - the <Item> to project trenches
// type - either "border" or "guide"
//
// Returns:
// ids - array of the new <Trench>es' IDs.
registerWithItem: function Trenches_registerWithItem(item, type) {
var container = item.container;
var ids = {};
ids.left = Trenches.register(container,"x",type,"left");
ids.right = Trenches.register(container,"x",type,"right");
ids.top = Trenches.register(container,"y",type,"top");
ids.bottom = Trenches.register(container,"y",type,"bottom");
this.getById(ids.left).setParentItem(item);
this.getById(ids.right).setParentItem(item);
this.getById(ids.top).setParentItem(item);
this.getById(ids.bottom).setParentItem(item);
return ids;
},
// ---------
// Function: unregister
// Unregister one or more <Trench>es.
//
// Parameters:
// ids - (integer) a single <Trench> ID or (array) a list of <Trench> IDs.
unregister: function Trenches_unregister(ids) {
if (!Array.isArray(ids))
ids = [ids];
var self = this;
ids.forEach(function(id) {
self.trenches[id].hide();
delete self.trenches[id];
});
},
// ---------
// Function: activateOthersTrenches
// Activate all <Trench>es other than those projected by the current element.
//
// Parameters:
// element - (DOMElement) the DOM element of the Item being dragged or resized.
activateOthersTrenches: function Trenches_activateOthersTrenches(element) {
this.trenches.forEach(function(t) {
if (t.el === element)
return;
if (t.parentItem && (t.parentItem.isAFauxItem || t.parentItem.isDragging
|| t.parentItem.isDropTarget))
return;
t.active = true;
t.calculateActiveRange();
t.show(); // debug
});
},
// ---------
// Function: disactivate
// After <activateOthersTrenches>, disactivates all the <Trench>es again.
disactivate: function Trenches_disactivate() {
this.trenches.forEach(function(t) {
t.active = false;
t.showGuide = false;
t.show();
});
},
// ---------
// Function: hideGuides
// Hide all guides (dotted lines) en masse.
hideGuides: function Trenches_hideGuides() {
this.trenches.forEach(function(t) {
t.showGuide = false;
t.show();
});
},
// ---------
// Function: snap
// Used to "snap" an object's bounds to active trenches and to the edge of the window.
// If the meta key is down (<Key.meta>), it will not snap but will still enforce the rect
// not leaving the safe bounds of the window.
//
// Parameters:
// rect - (<Rect>) the object's current bounds
// stationaryCorner - which corner is stationary? by default, the top left.
// "topleft", "bottomleft", "topright", "bottomright"
// assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not
// keepProportional - (boolean) if we are allowed to change the rect's size, whether the
// dimensions should scaled proportionally or not.
//
// Returns:
// (<Rect>) - the updated bounds, if they were updated
// false - if the bounds were not updated
snap: function Trenches_snap(rect,stationaryCorner,assumeConstantSize,keepProportional) {
// hide all the guide trenches, because the correct ones will be turned on later.
Trenches.hideGuides();
var updated = false;
var updatedX = false;
var updatedY = false;
var snappedTrenches = {};
for (var i in this.trenches) {
var t = this.trenches[i];
if (!t.active || t.parentItem.isDropTarget)
continue;
// newRect will be a new rect, or false
var newRect = t.rectOverlaps(rect,stationaryCorner,assumeConstantSize,keepProportional);
if (newRect) { // if rectOverlaps returned an updated rect...
if (assumeConstantSize && updatedX && updatedY)
break;
if (assumeConstantSize && updatedX && (newRect.adjustedEdge == "left"||newRect.adjustedEdge == "right"))
continue;
if (assumeConstantSize && updatedY && (newRect.adjustedEdge == "top"||newRect.adjustedEdge == "bottom"))
continue;
rect = newRect;
updated = true;
// register this trench as the "snapped trench" for the appropriate edge.
snappedTrenches[newRect.adjustedEdge] = t;
// if updatedX, we don't need to update x any more.
if (newRect.adjustedEdge == "left" && this.preferLeft)
updatedX = true;
if (newRect.adjustedEdge == "right" && !this.preferLeft)
updatedX = true;
// if updatedY, we don't need to update x any more.
if (newRect.adjustedEdge == "top" && this.preferTop)
updatedY = true;
if (newRect.adjustedEdge == "bottom" && !this.preferTop)
updatedY = true;
}
}
if (updated) {
rect.snappedTrenches = snappedTrenches;
return rect;
}
return false;
},
// ---------
// Function: show
// <Trench.show> all <Trench>es.
show: function Trenches_show() {
this.trenches.forEach(function(t) {
t.show();
});
},
// ---------
// Function: toggleShown
// Toggle <Trenches.showDebug> and trigger <Trenches.show>
toggleShown: function Trenches_toggleShown() {
this.showDebug = !this.showDebug;
this.show();
}
};

File diff suppressed because it is too large Load Diff

View File

@ -166,6 +166,8 @@ _BROWSER_FILES = \
browser_selectTabAtIndex.js \
browser_tabfocus.js \
browser_tabs_owner.js \
tabview/browser_tabview_dragdrop.js \
tabview/browser_visibleTabs.js \
discovery.html \
moz.png \
test_bug435035.html \

View File

@ -0,0 +1,150 @@
/* ***** 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 drag and drop 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>
*
* 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();
TabView.toggle();
ok(TabView.isVisible(), "Tab View is visible");
let contentWindow = document.getElementById("tab-view").contentWindow;
// create group one and two
let padding = 10;
let pageBounds = contentWindow.Items.getPageBounds();
pageBounds.inset(padding, padding);
let box = new contentWindow.Rect(pageBounds);
box.width = 300;
box.height = 300;
let groupOne = new contentWindow.GroupItem([], { bounds: box });
ok(groupOne.isEmpty(), "This group is empty");
let groupTwo = new contentWindow.GroupItem([], { bounds: box });
groupOne.addSubscriber(groupOne, "tabAdded", function() {
groupOne.removeSubscriber(groupOne, "tabAdded");
groupTwo.newTab("");
});
groupTwo.addSubscriber(groupTwo, "tabAdded", function() {
groupTwo.removeSubscriber(groupTwo, "tabAdded");
// carry on testing
addTest(contentWindow, groupOne.id, groupTwo.id);
});
let count = 0;
let onTabViewHidden = function() {
// show the tab view.
TabView.toggle();
if (++count == 2) {
window.removeEventListener("tabviewhidden", onTabViewHidden, false);
}
};
window.addEventListener("tabviewhidden", onTabViewHidden, false);
// open tab in group
groupOne.newTab("");
}
function addTest(contentWindow, groupOneId, groupTwoId) {
let groupOne = contentWindow.GroupItems.groupItem(groupOneId);
let groupTwo = contentWindow.GroupItems.groupItem(groupTwoId);
let groupOneTabItemCount = groupOne.getChildren().length;
let groupTwoTabItemCount = groupTwo.getChildren().length;
is(groupOneTabItemCount, 1, "GroupItem one has a tab");
is(groupTwoTabItemCount, 1, "GroupItem two has two tabs");
let srcElement = groupOne.getChild(0).container;
ok(srcElement, "The source element exists");
// calculate the offsets
let groupTwoRect = groupTwo.container.getBoundingClientRect();
let srcElementRect = srcElement.getBoundingClientRect();
let offsetX =
Math.round(groupTwoRect.left + groupTwoRect.width/5) - srcElementRect.left;
let offsetY =
Math.round(groupTwoRect.top + groupTwoRect.height/5) - srcElementRect.top;
simulateDragDrop(srcElement, offsetX, offsetY, contentWindow);
is(groupOne.getChildren().length, --groupOneTabItemCount,
"The number of children in group one is decreased by 1");
is(groupTwo.getChildren().length, ++groupTwoTabItemCount,
"The number of children in group two is increased by 1");
let onTabViewHidden = function() {
window.removeEventListener("tabviewhidden", onTabViewHidden, false);
groupTwo.closeAll();
finish();
};
window.addEventListener("tabviewhidden", onTabViewHidden, false);
TabView.toggle();
}
function simulateDragDrop(srcElement, offsetX, offsetY, contentWindow) {
// enter drag mode
let dataTransfer;
EventUtils.synthesizeMouse(
srcElement, 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);
srcElement.dispatchEvent(event);
// drag over
for (let i = 4; i >= 0; i--)
EventUtils.synthesizeMouse(
srcElement, Math.round(offsetX/5), Math.round(offsetY/4),
{ type: "mousemove" }, contentWindow);
event = contentWindow.document.createEvent("DragEvents");
event.initDragEvent(
"dragover", true, true, contentWindow, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
srcElement.dispatchEvent(event);
// drop
EventUtils.synthesizeMouse(srcElement, 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);
srcElement.dispatchEvent(event);
}

View File

@ -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 tabbrowser visibleTabs 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):
* Edward Lee <edilee@mozilla.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() {
// There should be one tab when we start the test
let [origTab] = gBrowser.visibleTabs;
// Add a tab that will get pinned
let pinned = gBrowser.addTab();
gBrowser.pinTab(pinned);
let testTab = gBrowser.addTab();
let visible = gBrowser.visibleTabs;
is(visible.length, 3, "3 tabs should be open");
is(visible[0], pinned, "the pinned tab is first");
is(visible[1], origTab, "original tab is next");
is(visible[2], testTab, "last created tab is last");
// Only show the test tab (but also get pinned and selected)
is(gBrowser.selectedTab, origTab, "sanity check that we're on the original tab");
gBrowser.showOnlyTheseTabs([testTab]);
is(gBrowser.visibleTabs.length, 3, "all 3 tabs are still visible");
// Select the test tab and only show that (and pinned)
gBrowser.selectedTab = testTab;
gBrowser.showOnlyTheseTabs([testTab]);
visible = gBrowser.visibleTabs;
is(visible.length, 2, "2 tabs should be visible including the pinned");
is(visible[0], pinned, "first is pinned");
is(visible[1], testTab, "next is the test tab");
is(gBrowser.tabs.length, 3, "3 tabs should still be open");
gBrowser.selectTabAtIndex(0);
is(gBrowser.selectedTab, pinned, "first tab is pinned");
gBrowser.selectTabAtIndex(1);
is(gBrowser.selectedTab, testTab, "second tab is the test tab");
gBrowser.selectTabAtIndex(2);
is(gBrowser.selectedTab, testTab, "no third tab, so no change");
gBrowser.selectTabAtIndex(0);
is(gBrowser.selectedTab, pinned, "switch back to the pinned");
gBrowser.selectTabAtIndex(2);
is(gBrowser.selectedTab, pinned, "no third tab, so no change");
gBrowser.selectTabAtIndex(-1);
is(gBrowser.selectedTab, testTab, "last tab is the test tab");
gBrowser.tabContainer.advanceSelectedTab(1, true);
is(gBrowser.selectedTab, pinned, "wrapped around the end to pinned");
gBrowser.tabContainer.advanceSelectedTab(1, true);
is(gBrowser.selectedTab, testTab, "next to test tab");
gBrowser.tabContainer.advanceSelectedTab(1, true);
is(gBrowser.selectedTab, pinned, "next to pinned again");
gBrowser.tabContainer.advanceSelectedTab(-1, true);
is(gBrowser.selectedTab, testTab, "going backwards to last tab");
gBrowser.tabContainer.advanceSelectedTab(-1, true);
is(gBrowser.selectedTab, pinned, "next to pinned");
gBrowser.tabContainer.advanceSelectedTab(-1, true);
is(gBrowser.selectedTab, testTab, "next to test tab again");
// Try showing all tabs
gBrowser.showOnlyTheseTabs(Array.slice(gBrowser.tabs));
is(gBrowser.visibleTabs.length, 3, "all 3 tabs are visible again");
// Select the pinned tab and show the testTab to make sure selection updates
gBrowser.selectedTab = pinned;
gBrowser.showOnlyTheseTabs([testTab]);
is(gBrowser.tabs[1], origTab, "make sure origTab is in the middle");
is(origTab.hidden, true, "make sure it's hidden");
gBrowser.removeTab(pinned);
is(gBrowser.selectedTab, testTab, "making sure origTab was skipped");
is(gBrowser.visibleTabs.length, 1, "only testTab is there");
// Only show one of the non-pinned tabs (but testTab is selected)
gBrowser.showOnlyTheseTabs([origTab]);
is(gBrowser.visibleTabs.length, 2, "got 2 tabs");
// Now really only show one of the tabs
gBrowser.showOnlyTheseTabs([testTab]);
visible = gBrowser.visibleTabs;
is(visible.length, 1, "only the original tab is visible");
is(visible[0], testTab, "it's the original tab");
is(gBrowser.tabs.length, 2, "still have 2 open tabs");
// Close the last visible tab and make sure we still get a visible tab
gBrowser.removeTab(testTab);
is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
is(gBrowser.tabs.length, 1, "sanity check that it matches");
is(gBrowser.selectedTab, origTab, "got the orig tab");
is(origTab.hidden, false, "and it's not hidden -- visible!");
}

View File

@ -46,6 +46,9 @@ browser.jar:
content/browser/sanitizeDialog.css (content/sanitizeDialog.css)
* content/browser/tabbrowser.css (content/tabbrowser.css)
* content/browser/tabbrowser.xml (content/tabbrowser.xml)
content/browser/tabview.css (content/tabview/tabview.css)
* content/browser/tabview.js (content/tabview/tabview.js)
content/browser/tabview.html (content/tabview/tabview.html)
* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
* content/browser/utilityOverlay.js (content/utilityOverlay.js)
* content/browser/web-panels.js (content/web-panels.js)

View File

@ -1212,6 +1212,7 @@ SessionStoreService.prototype = {
if (aTab.pinned)
tabData.pinned = true;
tabData.hidden = aTab.hidden;
var disallow = [];
for (var i = 0; i < CAPABILITIES.length; i++)
@ -1938,6 +1939,11 @@ SessionStoreService.prototype = {
tabs.push(t < openTabCount ?
tabbrowser.tabs[t] :
tabbrowser.addTab("about:blank", {skipAnimation: true}));
// collapse all unselected tabs to prevent flickering when showing the
// tabs in the active group by TabView
if (!tabs[t].selected) {
tabs[t].hidden = true;
}
// when resuming at startup: add additionally requested pages to the end
if (!aOverwriteTabs && root._firstTabs) {
tabbrowser.moveTabTo(tabs[t], t);
@ -1947,6 +1953,7 @@ SessionStoreService.prototype = {
tabbrowser.pinTab(tabs[t]);
else
tabbrowser.unpinTab(tabs[t]);
tabs[t].hidden = winData.tabs[t].hidden;
}
// when overwriting tabs, remove all superflous ones
@ -2031,6 +2038,7 @@ SessionStoreService.prototype = {
tabbrowser.pinTab(tab);
else
tabbrowser.unpinTab(tab);
tab.hidden = tabData.hidden;
tabData._tabStillLoading = true;
if (!tabData.entries || tabData.entries.length == 0) {

View File

@ -12,6 +12,11 @@
<!ENTITY mainWindow.titlePrivateBrowsingSuffix "(Private Browsing)">
<!-- Tab context menu -->
<!ENTITY moveTabTo.label "Move This Tab To">
<!ENTITY moveTabTo.accesskey "M">
<!ENTITY createNewGroup.label "Create New Group">
<!ENTITY createNewGroup.accesskey "C">
<!ENTITY namedGroups.label "Named Groups">
<!ENTITY reloadTab.label "Reload Tab">
<!ENTITY reloadTab.accesskey "R">
<!ENTITY reloadAllTabs.label "Reload All Tabs">
@ -127,6 +132,8 @@
<!-- Toolbar items -->
<!ENTITY homeButton.label "Home">
<!ENTITY tabViewButton.label "Tab Sets">
<!ENTITY tabViewButton.tooltip "Open a visual tab interface">
<!ENTITY bookmarksButton.label "Bookmarks">
<!ENTITY bookmarksButton.tooltip "Display your bookmarks">
@ -233,6 +240,7 @@
<!ENTITY viewMenu.label "View">
<!ENTITY viewMenu.accesskey "V">
<!ENTITY showTabView.label "TabView">
<!ENTITY viewToolbarsMenu.label "Toolbars">
<!ENTITY viewToolbarsMenu.accesskey "T">
<!ENTITY viewSidebarMenu.label "Sidebar">

View File

@ -275,5 +275,8 @@ ctrlTab.showAll.label=;Show all #1 tabs
# Used as the bookmark name when saving a keyword for a search field.
addKeywordTitleAutoFill=Search %S
# TabView
tabView.title=%S Tab Sets
extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name=Default
extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description=The default theme.

View File

@ -563,6 +563,10 @@ toolbar[mode="full"] .toolbarbutton-menubutton-button {
list-style-image: url("moz-icon://stock/gtk-home?size=toolbar&state=disabled");
}
#tabview-button {
list-style-image: url(chrome://browser/skin/tabview/tabview.png);
}
#downloads-button {
-moz-image-region: rect(0px 24px 24px 0px);
}
@ -1519,3 +1523,12 @@ panel[dimmed="true"] {
opacity: 0.5;
}
/* Tab Context Menu */
#context_tabViewMenu {
list-style-image: url(chrome://browser/skin/tabview/tabview-16.png);
}
#context_tabViewMenuPopup > menuitem.group {
padding-left: 40px;
}

View File

@ -71,6 +71,13 @@ browser.jar:
skin/classic/browser/tabbrowser/progress.png (tabbrowser/progress.png)
skin/classic/browser/tabbrowser/progress-pulsing.png (tabbrowser/progress-pulsing.png)
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
skin/classic/browser/tabview/edit.png (tabview/edit.png)
skin/classic/browser/tabview/new-tab.png (tabview/new-tab.png)
skin/classic/browser/tabview/platform.css (tabview/platform.css)
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview-16.png (tabview/tabview-16.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

View File

@ -0,0 +1,7 @@
/* This file is for platform-specific CSS for TabView, and is loaded after the
platform-independent tabview.css, to allow overwriting.
*/
.close {
background: url("moz-icon://stock/gtk-close?size=menu") no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

View File

@ -544,6 +544,12 @@ toolbar[iconsize="small"][mode="icons"] #forward-button:-moz-locale-dir(rtl) {
-moz-margin-end: 3px;
}
/* tabview button */
#tabview-button {
list-style-image: url(chrome://browser/skin/tabview/tabview.png);
}
/* download manager button */
#downloads-button {
@ -2103,3 +2109,12 @@ panel[dimmed="true"] {
padding: 0 5px;
}
/* Tab Context Menu */
#context_tabViewMenu {
list-style-image: url(chrome://browser/skin/tabview/tabview-16.png);
}
#context_tabViewMenuPopup > menuitem.group {
padding-left: 40px;
}

View File

@ -112,6 +112,13 @@ browser.jar:
skin/classic/browser/tabbrowser/tabbrowser-tabs-bkgnd.png (tabbrowser/tabbrowser-tabs-bkgnd.png)
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
skin/classic/browser/tabbrowser/tab-bkgnd.png (tabbrowser/tab-bkgnd.png)
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
skin/classic/browser/tabview/edit.png (tabview/edit.png)
skin/classic/browser/tabview/new-tab.png (tabview/new-tab.png)
skin/classic/browser/tabview/platform.css (tabview/platform.css)
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview-16.png (tabview/tabview-16.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

View File

@ -0,0 +1,7 @@
/* This file is for platform-specific CSS for TabView, and is loaded after the
platform-independent tabview.css, to allow overwriting.
*/
.close {
background: url(chrome://global/skin/icons/closetab.png) no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

View File

@ -606,6 +606,12 @@ toolbar:not([iconsize="small"])[mode="icons"] #forward-button:not([disabled="tru
-moz-image-region: rect(0, 90px, 18px, 72px);
}
/* tabview button */
#tabview-button {
list-style-image: url(chrome://browser/skin/tabview/tabview.png);
}
/* download manager button */
#downloads-button {
@ -1800,3 +1806,12 @@ panel[dimmed="true"] {
opacity: 0.5;
}
/* Tab Context Menu */
#context_tabViewMenu {
list-style-image: url(chrome://browser/skin/tabview/tabview-16.png);
}
#context_tabViewMenuPopup > menuitem.group {
padding-left: 40px;
}

View File

@ -90,6 +90,13 @@ browser.jar:
skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png)
skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
skin/classic/browser/tabview/edit.png (tabview/edit.png)
skin/classic/browser/tabview/new-tab.png (tabview/new-tab.png)
skin/classic/browser/tabview/platform.css (tabview/platform.css)
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview-16.png (tabview/tabview-16.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png
@ -198,6 +205,13 @@ browser.jar:
skin/classic/aero/browser/tabbrowser/tab.png (tabbrowser/tab.png)
skin/classic/aero/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
skin/classic/aero/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
skin/classic/aero/browser/tabview/edit-light.png (tabview/edit-light.png)
skin/classic/aero/browser/tabview/edit.png (tabview/edit.png)
skin/classic/aero/browser/tabview/new-tab.png (tabview/new-tab.png)
skin/classic/aero/browser/tabview/platform.css (tabview/platform.css)
skin/classic/aero/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/aero/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/aero/browser/tabview/tabview-16.png (tabview/tabview-16.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/aero/browser/sync-16-throbber.png
skin/classic/aero/browser/sync-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

View File

@ -0,0 +1,53 @@
/* This file is for platform-specific CSS for TabView, and is loaded after the
platform-independent tabview.css, to allow overwriting.
*/
.close {
background: url(chrome://global/skin/icons/Close.gif) no-repeat;
}
#bg {
background: -moz-linear-gradient(top,#F1F5FB,#EDF4FB);
position:absolute;
top:0;
left:0;
width: 100%;
height: 100%;
z-index: -999999;
}
.newTabButtonAlt{
position: absolute;
cursor: pointer;
z-index:99999;
border: none;
-moz-border-radius: 4px;
font-size: 50px;
line-height: 50px;
height: 57px !important;
width: 70px !important;
margin-top: 4px !important;
text-align: center;
background-color: #B9CDEB;
-moz-box-shadow: inset 0px 0px 5px rgba(0,0,0,.5), 0 1px 0 rgba(255,255,255,.3);
}
.newTabButtonAlt>span{
color: #D9E7FC;
text-shadow: 0px 0px 7px rgba(0,0,0,.4), 0 -1px 0 rgba(255,255,255,.6);
font-weight: bold;
}
.newTabGroup{
background-color: #D9E7FC;
border: none;
border-top: 1px solid rgba(127,127,127,.5);
-moz-border-radius: 0.1em;
-moz-box-shadow: inset 0px 5px 10px rgba(0,0,0,.1);
}
.inNewTabGroup .favicon{
background-color: #D9E7FC;
-moz-box-shadow: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B