+ Finished documenting all of the app code, plus utils.js. Remaining: iq.js, mirror.js, tabs.js
authorIan Gilman <ian@iangilman.com>
Fri, 16 Jul 2010 17:27:01 -0700
changeset 50134 fb6807bddc718ad23856dac080428210b60966d1
parent 50133 e26ed60039cdee43858d0231aefab89eae83547d
child 50138 1057012b1e02ecd3507cf8b33cd17113d1f4fd74
push id15039
push useredward.lee@engineering.uiuc.edu
push dateThu, 12 Aug 2010 19:47:36 +0000
treeherdermozilla-central@5da28c582cc7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone2.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
+ Finished documenting all of the app code, plus utils.js. Remaining: iq.js, mirror.js, tabs.js
browser/base/content/tabcandy/app/groups.js
browser/base/content/tabcandy/app/infoitems.js
browser/base/content/tabcandy/app/tabitems.js
browser/base/content/tabcandy/app/ui.js
browser/base/content/tabcandy/core/mirror.js
browser/base/content/tabcandy/core/tabs.js
browser/base/content/tabcandy/core/utils.js
--- 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");
     }