author | Ian Gilman <ian@iangilman.com> |
Fri, 16 Jul 2010 17:27:01 -0700 | |
changeset 50134 | fb6807bddc718ad23856dac080428210b60966d1 |
parent 50133 | e26ed60039cdee43858d0231aefab89eae83547d |
child 50138 | 1057012b1e02ecd3507cf8b33cd17113d1f4fd74 |
push id | 15039 |
push user | edward.lee@engineering.uiuc.edu |
push date | Thu, 12 Aug 2010 19:47:36 +0000 |
treeherder | mozilla-central@5da28c582cc7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 2.0b2pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/tabcandy/app/groups.js +++ b/browser/base/content/tabcandy/app/groups.js @@ -385,17 +385,17 @@ window.Group.prototype = iQ.extend(new I // Function: isEmpty // Returns true if the tab group is empty and unnamed. isEmpty: function() { return !this._children.length && !this.getTitle(); }, // ---------- // Function: save - // Saves this group to persistant storage. + // Saves this group to persistent storage. save: function() { if (!this._inited) // too soon to save now return; var data = this.getStorageData(); if (Groups.groupStorageSanity(data)) Storage.saveGroup(Utils.getCurrentWindow(), data); }, @@ -1438,17 +1438,17 @@ window.Groups = { var result = this.nextID; this.nextID++; this.save(); return result; }, // ---------- // Function: getStorageData - // Returns an object for saving Groups state to persistant storage. + // Returns an object for saving Groups state to persistent storage. getStorageData: function() { var data = {nextID: this.nextID, groups: []}; this.groups.forEach(function(group) { data.groups.push(group.getStorageData()); }); return data; }, @@ -1538,17 +1538,17 @@ window.Groups = { this.save(); // for nextID }catch(e){ Utils.log("error in recons: "+e); } }, // ---------- // Function: groupStorageSanity - // Given persistant storage data for a group, returns true if it appears to not be damaged. + // Given persistent storage data for a group, returns true if it appears to not be damaged. groupStorageSanity: function(groupData) { // TODO: check everything var sane = true; if (!isRect(groupData.bounds)) { Utils.log('Groups.groupStorageSanity: bad bounds', groupData.bounds); sane = false; }
--- a/browser/base/content/tabcandy/app/infoitems.js +++ b/browser/base/content/tabcandy/app/infoitems.js @@ -149,17 +149,17 @@ window.InfoItem.prototype = iQ.extend(ne Utils.log(e); } return data; }, // ---------- // Function: save - // Saves this item to persistant storage. + // Saves this item to persistent storage. save: function() { try { if (!this._inited) // too soon to save now return; var data = this.getStorageData(); /* if (Groups.groupStorageSanity(data))
--- a/browser/base/content/tabcandy/app/tabitems.js +++ b/browser/base/content/tabcandy/app/tabitems.js @@ -173,48 +173,70 @@ window.TabItem = function(container, tab TabItems.reconnect(self); self.save(); }); }; window.TabItem.prototype = iQ.extend(new Item(), { // ---------- + // Function: getStorageData + // Get data to be used for persistent storage of this object. + // + // Parameters: + // getImageData - true to include thumbnail pixels (and page title as well); default false getStorageData: function(getImageData) { return { bounds: this.getBounds(), userSize: (isPoint(this.userSize) ? new Point(this.userSize) : null), url: this.tab.url, groupID: (this.parent ? this.parent.id : 0), imageData: (getImageData && this.tab.mirror.tabCanvas ? this.tab.mirror.tabCanvas.toImageData() : null), title: (getImageData && this.tab.raw.label ? this.tab.raw.label : null) }; }, // ---------- + // Function: save + // Store persistent for this object. + // + // Parameters: + // saveImageData - true to include thumbnail pixels (and page title as well); default false save: function(saveImageData) { try{ if (!this.tab || !this.tab.raw || !this.reconnected) // too soon/late to save return; var data = this.getStorageData(saveImageData); if (TabItems.storageSanity(data)) Storage.saveTab(this.tab.raw, data); }catch(e){ Utils.log("Error in saving tab value: "+e); } }, - // ---------- + // ---------- + // Function: getURL + // Returns the URL for the page represented by this tab. getURL: function() { return this.tab.url; }, // ---------- + // Function: setBounds + // Moves this item to the specified location and size. + // + // Parameters: + // rect - a <Rect> giving the new bounds + // immediately - true if it should not animate; default false + // options - an object with additional parameters, see below + // + // Possible options: + // force - true to always update the DOM even if the bounds haven't changed; default false setBounds: function(rect, immediately, options) { if (!isRect(rect)) { Utils.trace('TabItem.setBounds: rect is not a real rectangle!', rect); return; } if (!options) options = {}; @@ -349,40 +371,51 @@ window.TabItem.prototype = iQ.extend(new if (!this.parent && !this.tab.closed) this.setTrenches(rect); this.save(); }, // ---------- + // Function: inStack + // Returns true if this item is in a stacked group. inStack: function(){ return iQ(this.container).hasClass("stacked"); }, // ---------- + // Function: setZ + // Sets the z-index for this item. setZ: function(value) { this.zIndex = value; iQ(this.container).css({zIndex: value}); }, // ---------- + // Function: close + // Closes this item (actually closes the tab associated with it, which automatically + // closes the item. close: function() { this.tab.close(); // No need to explicitly delete the tab data, becasue sessionstore data // associated with the tab will automatically go away }, // ---------- + // Function: addClass + // Adds the specified CSS class to this item's container DOM element. addClass: function(className) { iQ(this.container).addClass(className); }, // ---------- + // Function: removeClass + // Removes the specified CSS class from this item's container DOM element. removeClass: function(className) { iQ(this.container).removeClass(className); }, // ---------- // Function: addOnClose // Accepts a callback that will be called when this item closes. // The referenceObject is used to facilitate removal if necessary. @@ -393,37 +426,44 @@ window.TabItem.prototype = iQ.extend(new // ---------- // Function: removeOnClose // Removes the close event callback associated with referenceObject. removeOnClose: function(referenceObject) { this.tab.mirror.removeSubscriber(referenceObject, "close"); }, // ---------- + // Function: setResizable + // If value is true, makes this item resizable, otherwise non-resizable. + // Shows/hides a visible resize handle as appropriate. setResizable: function(value){ var $resizer = iQ('.expander', this.container); this.resizeOptions.minWidth = TabItems.minTabWidth; this.resizeOptions.minHeight = TabItems.minTabWidth * (TabItems.tabHeight / TabItems.tabWidth); if (value) { $resizer.fadeIn(); this.resizable(true); } else { $resizer.fadeOut(); this.resizable(false); } }, // ---------- + // Function: makeActive + // Updates this item to visually indicate that it's active. makeActive: function(){ iQ(this.container).find("canvas").addClass("focus") }, // ---------- + // Function: makeDeactive + // Updates this item to visually indicate that it's not active. makeDeactive: function(){ iQ(this.container).find("canvas").removeClass("focus") }, // ---------- // Function: zoomIn // Allows you to select the tab and zoom in on it, thereby bringing you // to the tab in Firefox to interact with. @@ -586,16 +626,18 @@ window.TabItem.prototype = iQ.extend(new // Singleton for managing <TabItem>s window.TabItems = { minTabWidth: 40, tabWidth: 160, tabHeight: 120, fontSize: 9, // ---------- + // Function: init + // Sets the object up. init: function() { this.items = []; var self = this; window.TabMirror.customize(function(mirror) { var $div = iQ(mirror.el); var tab = mirror.tab; var item = new TabItem(mirror.el, tab); @@ -605,62 +647,79 @@ window.TabItems = { }); if (!self.reconnect(item)) Groups.newTab(item); }); }, // ---------- + // Function: register + // Adds the given <TabItem> to the master list. register: function(item) { + Utils.assert('item must be a TabItem', item && item.isAnItem); Utils.assert('only register once per item', this.items.indexOf(item) == -1); this.items.push(item); }, // ---------- + // Function: unregister + // Removes the given <TabItem> from the master list. unregister: function(item) { var index = this.items.indexOf(item); if (index != -1) this.items.splice(index, 1); }, // ---------- + // Function: getItems + // Returns a copy of the master array of <TabItem>s. getItems: function() { return Utils.copy(this.items); }, // ---------- // Function: getItemByTabElement // Given the DOM element that contains the tab's representation on screen, // returns the <TabItem> it belongs to. getItemByTabElement: function(tabElement) { return iQ(tabElement).data("tabItem"); }, // ---------- + // Function: saveAll + // Saves all open <TabItem>s. + // + // Parameters: + // saveImageData - true to include thumbnail pixels (and page title as well); default false saveAll: function(saveImageData) { var items = this.getItems(); items.forEach(function(item) { item.save(saveImageData); }); }, // ---------- + // Function: storageSanity + // Checks the specified data (as returned by TabItem.getStorageData or loaded from storage) + // and returns true if it looks valid. + // TODO: check everything storageSanity: function(data) { - // TODO: check everything var sane = true; if (!isRect(data.bounds)) { Utils.log('TabItems.storageSanity: bad bounds', data.bounds); sane = false; } return sane; }, // ---------- + // Function: reconnect + // Given a <TabItem>, attempts to load its persistent data from storage. reconnect: function(item) { var found = false; try{ Utils.assert('item', item); Utils.assert('item.tab', item.tab); if (item.reconnected)
--- a/browser/base/content/tabcandy/app/ui.js +++ b/browser/base/content/tabcandy/app/ui.js @@ -41,31 +41,37 @@ // ********** // Title: ui.js (function(){ window.Keys = {meta: false}; // ########## +// Class: Navbar +// Singleton for helping with the browser's nav bar. Navbar = { // ---------- + // Variable: urlBar + // The URL bar for the window. get urlBar() { var win = Utils.getCurrentWindow(); if (win) return win.gURLBar; return null; } }; // ########## // Class: Tabbar // Singleton for managing the tabbar of the browser. var Tabbar = { // ---------- + // Variable: el + // The tab bar's element. get el() { return window.Tabs[0].raw.parentNode; }, // ---------- // Function: getVisibleTabCount // Returns the number of tabs that are currently visible getVisibleTabCount: function(){ @@ -149,55 +155,74 @@ var Tabbar = { // Singleton top-level UI manager. TODO: Integrate with <UIClass>. window.Page = { startX: 30, startY: 70, closedLastVisibleTab: false, closedSelectedTabInTabCandy: false, stopZoomPreparation: false, + // ---------- + // Function: isTabCandyVisible + // Returns true if the TabCandy UI is currently shown. isTabCandyVisible: function(){ return (Utils.getCurrentWindow().document.getElementById("tab-candy-deck"). selectedIndex == 1); }, + // ---------- + // Function: hideChrome + // Hides the nav bar, tab bar, etc. hideChrome: function(){ var currentWin = Utils.getCurrentWindow(); currentWin.document.getElementById("tab-candy-deck").selectedIndex = 1; currentWin.gBrowser.updateTitlebar(); this._setActiveTitleColor(true); }, + // ---------- + // Function: showChrome + // Shows the nav bar, tab bar, etc. showChrome: function(){ var currentWin = Utils.getCurrentWindow(); var tabContainer = currentWin.gBrowser.tabContainer; currentWin.document.getElementById("tab-candy-deck").selectedIndex = 0; // set the close button on tab /* iQ.timeout(function() { // Marshal event from chrome thread to DOM thread */ tabContainer.adjustTabstrip(); /* }, 1); */ currentWin.gBrowser.updateTitlebar(); this._setActiveTitleColor(false); }, + // ---------- + // Function: _setActiveTitleColor + // Used on the Mac to make the title bar match the gradient in the rest of the + // TabCandy UI. + // + // Parameters: + // set - true for the special TabCandy color, false for the normal color. _setActiveTitleColor: function(set) { // Mac Only if (Utils.isMac()) { var mainWindow = Utils.getCurrentWindow().document.getElementById("main-window"); if (set) mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4"); else mainWindow.removeAttribute("activetitlebarcolor"); } }, + // ---------- + // Function: showTabCandy + // Zoom out of the current tab (if applicable) and show the TabCandy UI. showTabCandy: function() { var self = this; var currentTab = UI.currentTab; var item = null; if (currentTab && currentTab.mirror) item = TabItems.getItemByTabElement(currentTab.mirror.el); @@ -218,16 +243,19 @@ window.Page = { activeGroup.setTopChild(item); window.Groups.setActiveGroup(null); UI.resize(true); }); } }, + // ---------- + // Function: setupKeyHandlers + // Sets up the handlers for keyboard navigation. setupKeyHandlers: function(){ var self = this; iQ(window).keyup(function(e){ if (!e.metaKey) window.Keys.meta = false; }); iQ(window).keydown(function(e){ if (e.metaKey) window.Keys.meta = true; @@ -276,16 +304,18 @@ window.Page = { } if ((e.which == 27 || e.which == 13) && iQ(":focus").length == 0 ) if ( self.getActiveTab() ) self.getActiveTab().zoomIn(); }); }, // ---------- + // Function: init + // Starts this object. init: function() { var self = this; // When you click on the background/empty part of TabCandy, // we create a new group. var tabCandyContentDoc = Utils.getCurrentWindow().document.getElementById("tab-candy"). contentDocument; @@ -335,16 +365,18 @@ window.Page = { }); Tabs.onFocus(function() { self.tabOnFocus(this); }); }, // ---------- + // Function: tabOnFocus + // Called when the user switches from one tab to another outside of the TabCandy UI. tabOnFocus: function(tab) { var focusTab = tab; var currentTab = UI.currentTab; var currentWindow = Utils.getCurrentWindow(); var self = this; UI.currentTab = focusTab; // if the last visible tab has just been closed, don't show the chrome UI. @@ -402,16 +434,19 @@ window.Page = { // same. if (oldItem) oldItem.setZoomPrep(true); } }, 1); }, // ---------- + // Function: createGroupOnDrag + // Called in response to a mousedown in empty space in the TabCandy UI; + // creates a new group based on the user's drag. createGroupOnDrag: function(e){ /* e.preventDefault(); */ const minSize = 60; const minMinSize = 15; var startPos = {x:e.clientX, y:e.clientY} var phantom = iQ("<div>") .addClass('group phantom') @@ -779,16 +814,19 @@ UIClass.prototype = { this.initialized = true; this.save(); // for this.pageBounds } catch(e) { Utils.log(e); } }, // ---------- + // Function: setBrowserKeyHandler + // Overrides the browser's keys for navigating between tabs (outside of the TabCandy UI), + // so they do the right thing in respect to groups. setBrowserKeyHandler : function() { var self = this; var browser = Utils.getCurrentWindow().gBrowser; var tabbox = browser.mTabBox; browser.addEventListener("keypress", function(event) { var handled = false; // based on http://mxr.mozilla.org/mozilla1.9.2/source/toolkit/content/widgets/tabbox.xml#145 @@ -865,17 +903,24 @@ UIClass.prototype = { event.preventDefault(); } } } }, false); }, // ---------- + // Function: advanceSelectedTab + // Moves you to the next tab in the current group's tab strip (outside the TabCandy UI). + // + // Parameters: + // reverse - true to go to previous instead of next + // index - go to a particular tab; numerical value from 1 to 9 advanceSelectedTab : function(reverse, index) { + Utils.assert('reverse should be false when index exists', !index || !reverse); var tabbox = Utils.getCurrentWindow().gBrowser.mTabBox; var tabs = tabbox.tabs; var visibleTabs = []; var selectedIndex; for (var i = 0; i < tabs.childNodes.length ; i++) { var tab = tabs.childNodes[i]; if (!tab.collapsed) { @@ -906,16 +951,22 @@ UIClass.prototype = { (selectedIndex == (visibleTabs.length - 1)) ? visibleTabs[0] : visibleTabs[selectedIndex + 1]; } } } }, // ---------- + // Function: resize + // Update the TabCandy UI contents in response to a window size change. + // Won't do anything if it doesn't deem the resize necessary. + // + // Parameters: + // force - true to update even when "unnecessary"; default false resize: function(force) { if ( typeof(force) == "undefined" ) force = false; // If we are currently doing an animation or if TabCandy isn't focused // don't perform a resize. This resize really slows things down. var isAnimating = iQ.isAnimating(); if ( !force && ( isAnimating || !Page.isTabCandyVisible() ) ) { // TODO: should try again once the animation is done @@ -993,16 +1044,18 @@ UIClass.prototype = { pair.item.snap(); }); this.pageBounds = Items.getPageBounds(); this.save(); }, // ---------- + // Function: addDevMenu + // Fills out the "dev menu" in the TabCandy UI. addDevMenu: function() { try { var self = this; var $select = iQ('<select>') .css({ position: 'absolute', bottom: 5, @@ -1068,61 +1121,74 @@ UIClass.prototype = { .get(0); } } catch(e) { Utils.log(e); } }, // ----------- + // Function: reset + // Wipes all TabCandy storage and refreshes, giving you the "first-run" state. reset: function() { Storage.wipe(); location.href = ''; }, // ---------- + // Function: saveAll + // Saves all data associated with TabCandy. + // TODO: Save info items saveAll: function() { this.save(); Groups.saveAll(); TabItems.saveAll(); }, // ---------- + // Function: save + // Saves the data for this object to persistent storage save: function() { if (!this.initialized) return; var data = { pageBounds: this.pageBounds }; if (this.storageSanity(data)) Storage.saveUIData(Utils.getCurrentWindow(), data); }, // ---------- + // Function: storageSanity + // Given storage data for this object, returns true if it looks valid. storageSanity: function(data) { if (iQ.isEmptyObject(data)) return true; if (!isRect(data.pageBounds)) { Utils.log('UI.storageSanity: bad pageBounds', data.pageBounds); data.pageBounds = null; return false; } return true; }, // ---------- + // Function: saveVisibility + // Saves to storage whether the TabCandy UI is visible (as passed in). saveVisibility: function(isVisible) { Storage.saveVisibilityData(Utils.getCurrentWindow(), { visible: isVisible }); }, // ---------- + // Function: arrangeBySite + // Blows away all existing groups and organizes the tabs into new groups based on domain. arrangeBySite: function() { function putInGroup(set, key) { var group = Groups.getGroupWithTitle(key); if (group) { set.forEach(function(el) { group.add(el); }); } else @@ -1159,16 +1225,18 @@ UIClass.prototype = { } putInGroup(leftovers, 'mixed'); Groups.arrange(); }, // ---------- + // Function: newTab + // Opens a new tab with the given URL. newTab: function(url) { try { var group = Groups.getNewTabGroup(); if (group) group.newTab(url); else Tabs.open(url); } catch(e) {
--- a/browser/base/content/tabcandy/core/mirror.js +++ b/browser/base/content/tabcandy/core/mirror.js @@ -36,16 +36,18 @@ * * ***** END LICENSE BLOCK ***** */ // ********** // Title: mirror.js (function(){ +// ---------- +// Function: _isIFrame function _isIframe(doc){ var win = doc.defaultView; return win.parent != win; } // ########## // Class: TabCanvas // Takes care of the actual canvas for the tab thumbnail @@ -252,16 +254,18 @@ TabMirror.prototype = { self.link(tab); }); this.paintingPaused = 0; this.heartbeatIndex = 0; this._fireNextHeartbeat(); }, + // ---------- + // Function: _heartbeat _heartbeat: function() { try { /* Utils.log('heartbeat', this.paintingPaused); */ var now = Utils.getMilliseconds(); var count = Tabs.length; if (count && this.paintingPaused <= 0) { this.heartbeatIndex++; if (this.heartbeatIndex >= count) @@ -319,49 +323,61 @@ TabMirror.prototype = { } } catch(e) { Utils.error('heartbeat', e); } this._fireNextHeartbeat(); }, + // ---------- + // Function: _fireNextHeartbeat _fireNextHeartbeat: function() { var self = this; iQ.timeout(function() { self._heartbeat(); }, 100); }, + // ---------- + // Function: _customize _customize: function(func){ // pass // This gets set by add-ons/extensions to MirrorTab }, + // ---------- + // Function: _createEl _createEl: function(tab){ new Mirror(tab, this); // sets tab.mirror to itself }, + // ---------- + // Function: update update: function(tab){ this.link(tab); if (tab.mirror && tab.mirror.tabCanvas) tab.mirror.triggerPaint(); }, + // ---------- + // Function: link link: function(tab){ // Don't add duplicates if (tab.mirror) return false; // Add the tab to the page this._createEl(tab); return true; }, + // ---------- + // Function: unlink unlink: function(tab){ var mirror = tab.mirror; if (mirror) { mirror._sendToSubscribers("close"); var tabCanvas = mirror.tabCanvas; if (tabCanvas) tabCanvas.detach();
--- a/browser/base/content/tabcandy/core/tabs.js +++ b/browser/base/content/tabcandy/core/tabs.js @@ -46,73 +46,96 @@ const Ci = Components.interfaces; const Cu = Components.utils; const Cr = Components.results; // ########## // Class: XULApp // Singelton var XULApp = { appWindowType: "navigator:browser", + + // ---------- + // Function: tabStripForWindow tabStripForWindow: function(aWindow) { return aWindow.document.getElementById("content").mStrip; }, + + // ---------- + // Function: openTab openTab: function(aUrl, aInBackground) { var window = this.mostRecentAppWindow; var tabbrowser = window.getBrowser(); var tab = tabbrowser.addTab(aUrl); if (!aInBackground) tabbrowser.selectedTab = tab; }, + + // ---------- + // Function: getBrowserFromContentWindow getBrowserFromContentWindow: function(aMainWindow, aWindow) { var browsers = aMainWindow.gBrowser.browsers; for (var i = 0; i < browsers.length; i++) { if (browsers[i].contentWindow == aWindow) return browsers[i]; } return null; } }; // ########## // Class: Dictionary function Dictionary() { var keys = []; var values = []; + // ---------- + // Function: set this.set = function set(key, value) { var id = keys.indexOf(key); if (id == -1) { keys.push(key); values.push(value); } else values[id] = value; }; + // ---------- + // Function: get this.get = function get(key, defaultValue) { if (defaultValue === undefined) defaultValue = null; var id = keys.indexOf(key); if (id == -1) return defaultValue; return values[id]; }; + // ---------- + // Function: remove this.remove = function remove(key) { var id = keys.indexOf(key); if (id == -1) throw new Error("object not in dictionary: " + key); keys.splice(id, 1); values.splice(id, 1); }; var readOnlyKeys = new ImmutableArray(keys); var readOnlyValues = new ImmutableArray(values); + // ---------- + // Variable: keys this.__defineGetter__("keys", function() { return readOnlyKeys; }); + + // ---------- + // Variable: values this.__defineGetter__("values", function() { return readOnlyValues; }); + + // ---------- + // Variable: length this.__defineGetter__("length", function() { return keys.length; }); } // ########## // Class: ImmutableArray function ImmutableArray(baseArray) { var self = this; var UNSUPPORTED_MUTATOR_METHODS = ["pop", "push", "reverse", "shift", @@ -120,16 +143,18 @@ function ImmutableArray(baseArray) { UNSUPPORTED_MUTATOR_METHODS.forEach( function(methodName) { self[methodName] = function() { throw new Error("Mutator method '" + methodName + "()' is " + "unsupported on this object."); }; }); + // ---------- + // Function: toString self.toString = function() { return "[ImmutableArray]"; }; self.__proto__ = baseArray; } // ########## // Class: Extension // Singleton
--- a/browser/base/content/tabcandy/core/utils.js +++ b/browser/base/content/tabcandy/core/utils.js @@ -54,114 +54,147 @@ var consoleService = Cc["@mozilla.org/co // 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. window.Point = function(a, y) { if (isPoint(a)) { + // Variable: x this.x = a.x; + + // Variable: y this.y = a.y; } else { this.x = (Utils.isNumber(a) ? a : 0); this.y = (Utils.isNumber(y) ? y : 0); } }; // ---------- +// Function: isPoint +// Returns true if the given object (p) looks like a <Point>. +// Note that this is not an actual method of <Point>, but a global routine. window.isPoint = function(p) { return (p && Utils.isNumber(p.x) && Utils.isNumber(p.y)); }; window.Point.prototype = { // ---------- + // Function: distance + // Returns the distance from this point to the given <Point>. distance: function(point) { var ax = Math.abs(this.x - point.x); var ay = Math.abs(this.y - point.y); return Math.sqrt((ax * ax) + (ay * ay)); }, // ---------- + // Function: plus + // Returns a new point with the result of adding this point to the given <Point>. plus: function(point) { return new Point(this.x + point.x, this.y + point.y); } }; // ########## // Class: Rect -// A simple rectangle. +// 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. window.Rect = function(a, top, width, height) { // Note: perhaps 'a' should really be called 'rectOrLeft' if (isRect(a)) { + // Variable: left this.left = a.left; + + // Variable: top this.top = a.top; + + // Variable: width this.width = a.width; + + // Variable: height this.height = a.height; } else { this.left = a; this.top = top; this.width = width; this.height = height; } }; // ---------- +// Function: isRect +// Returns true if the given object (r) looks like a <Rect>. +// Note that this is not an actual method of <Rect>, but a global routine. window.isRect = function(r) { return (r && Utils.isNumber(r.left) && Utils.isNumber(r.top) && Utils.isNumber(r.width) && Utils.isNumber(r.height)); }; window.Rect.prototype = { // ---------- + // Variable: right get right() { return this.left + this.width; }, // ---------- set right(value) { this.width = value - this.left; }, // ---------- + // Variable: bottom get bottom() { return 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() { return new Range(this.left,this.right); }, // ---------- + // Variable: yRange + // Gives you a new <Range> for the vertical dimension. get yRange() { return 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; @@ -191,31 +224,39 @@ window.Rect.prototype = { 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. @@ -246,35 +287,41 @@ window.Rect.prototype = { 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(a) { return (a.left == this.left && a.top == this.top && a.width == this.width && a.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; }, // ---------- @@ -303,16 +350,19 @@ window.Range = function(min, max) { this.max = min.max; } else { this.min = min || 0; this.max = max || 0; } }; // ---------- +// Function: isRange +// Returns true if the given object (r) looks like a <Range>. +// Note that this is not an actual method of <Range>, but a global routine. window.isRange = function(r) { return (r && Utils.isNumber(r.min) && Utils.isNumber(r.max)); }; window.Range.prototype = { // Variable: extent @@ -490,38 +540,55 @@ var Utils = { return browserWin; } } return null; }, // ___ Logging - - log: function() { // pass as many arguments as you want, it'll print them all + + // ---------- + // 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); consoleService.logStringMessage(text); }, - error: function() { // pass as many arguments as you want, it'll print them all + // ---------- + // 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. + // TODO: Does this still work? + error: function() { var text = this.expandArgumentsForLog(arguments); Cu.reportError('tabcandy error: ' + text); }, - trace: function() { // pass as many arguments as you want, it'll print them all + // ---------- + // 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); if (typeof(printStackTrace) != 'function') this.log(text + ' trace: you need to include stacktrace.js'); else { var calls = printStackTrace(); calls.splice(0, 3); // Remove this call and the printStackTrace calls this.log('trace: ' + text + '\n' + calls.join('\n')); } }, + // ---------- + // Function: assert + // Prints a stack trace along with label (as a console message) if condition is false. assert: function(label, condition) { if (!condition) { var text; if (typeof(label) == 'undefined') text = 'badly formed assert'; else text = 'tabcandy assert: ' + label; @@ -529,16 +596,17 @@ var Utils = { var calls = printStackTrace(); text += '\n' + calls[3]; } this.trace(text); } }, + // ---------- // Function: assertThrow // Throws label as an exception if condition is false. assertThrow: function(label, condition) { if (!condition) { var text; if (typeof(label) == 'undefined') text = 'badly formed assert'; else @@ -549,16 +617,19 @@ var Utils = { calls.splice(0, 3); // Remove this call and the printStackTrace calls text += '\n' + calls.join('\n'); } throw text; } }, + // ---------- + // Function: expandObject + // Prints the given object to a string, including all of its properties. expandObject: function(obj) { var s = obj + ' = {'; for (prop in obj) { var value; try { value = obj[prop]; } catch(e) { value = '[!!error retrieving property]'; @@ -572,16 +643,19 @@ var Utils = { else s += value; s += ", "; } return s + '}'; }, + // ---------- + // Function: expandArgumentsForLog + // Expands all of the given args (an array) into a single string. expandArgumentsForLog: function(args) { var s = ''; var count = args.length; var a; for (a = 0; a < count; a++) { var arg = args[a]; if (typeof(arg) == 'object') arg = this.expandObject(arg); @@ -589,41 +663,52 @@ var Utils = { s += arg; if (a < count - 1) s += '; '; } return s; }, + // ---------- + // Funtion: testLogging + // Prints some test messages with the various logging methods. testLogging: function() { this.log('beginning logging test'); this.error('this is an error'); this.trace('this is a trace'); this.log(1, null, {'foo': 'hello', 'bar': 2}, 'whatever'); this.log('ending logging test'); }, - // ___ Event + // ___ Misc + + // ---------- + // Function: isRightClick + // Given a DOM mouse event, returns true if it was for the right mouse button. isRightClick: function(event) { if (event.which) return (event.which == 3); if (event.button) return (event.button == 2); return false; }, - // ___ Time + // ---------- + // Function: getMilliseconds + // Returns the total milliseconds on the system clock right now. getMilliseconds: function() { var date = new Date(); return date.getTime(); }, - // ___ Misc + // ---------- + // Function: isDOMElement + // Returns true if the given object is a DOM element. isDOMElement: function(object) { return (object && typeof(object.nodeType) != 'undefined' ? true : false); }, // ---------- // Function: isNumber // Returns true if the argument is a valid number. isNumber: function(n) { @@ -640,17 +725,19 @@ var Utils = { return iQ.extend([], value); return iQ.extend({}, value); } return value; }, - // ___ Is Mac + // ---------- + // Function: isMac + // Returns true if running on a Mac. isMac: function() { if (this._isMac == null) { var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]. getService(Components.interfaces.nsIXULRuntime); this._isMac = (xulRuntime.OS == "Darwin"); }