browser/components/tabview/ui.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 // **********
     6 // Title: ui.js
     8 let Keys = { meta: false };
    10 // ##########
    11 // Class: UI
    12 // Singleton top-level UI manager.
    13 let UI = {
    14   // Variable: _frameInitialized
    15   // True if the Tab View UI frame has been initialized.
    16   _frameInitialized: false,
    18   // Variable: _pageBounds
    19   // Stores the page bounds.
    20   _pageBounds: null,
    22   // Variable: _closedLastVisibleTab
    23   // If true, the last visible tab has just been closed in the tab strip.
    24   _closedLastVisibleTab: false,
    26   // Variable: _closedSelectedTabInTabView
    27   // If true, a select tab has just been closed in TabView.
    28   _closedSelectedTabInTabView: false,
    30   // Variable: restoredClosedTab
    31   // If true, a closed tab has just been restored.
    32   restoredClosedTab: false,
    34   // Variable: _isChangingVisibility
    35   // Tracks whether we're currently in the process of showing/hiding the tabview.
    36   _isChangingVisibility: false,
    38   // Variable: _reorderTabItemsOnShow
    39   // Keeps track of the <GroupItem>s which their tab items' tabs have been moved
    40   // and re-orders the tab items when switching to TabView.
    41   _reorderTabItemsOnShow: [],
    43   // Variable: _reorderTabsOnHide
    44   // Keeps track of the <GroupItem>s which their tab items have been moved in
    45   // TabView UI and re-orders the tabs when switcing back to main browser.
    46   _reorderTabsOnHide: [],
    48   // Variable: _currentTab
    49   // Keeps track of which xul:tab we are currently on.
    50   // Used to facilitate zooming down from a previous tab.
    51   _currentTab: null,
    53   // Variable: _eventListeners
    54   // Keeps track of event listeners added to the AllTabs object.
    55   _eventListeners: {},
    57   // Variable: _cleanupFunctions
    58   // An array of functions to be called at uninit time
    59   _cleanupFunctions: [],
    61   // Constant: _maxInteractiveWait
    62   // If the UI is in the middle of an operation, this is the max amount of
    63   // milliseconds to wait between input events before we no longer consider
    64   // the operation interactive.
    65   _maxInteractiveWait: 250,
    67   // Variable: _storageBusy
    68   // Tells whether the storage is currently busy or not.
    69   _storageBusy: false,
    71   // Variable: isDOMWindowClosing
    72   // Tells wether the parent window is about to close
    73   isDOMWindowClosing: false,
    75   // Variable: _browserKeys
    76   // Used to keep track of allowed browser keys.
    77   _browserKeys: null,
    79   // Variable: _browserKeysWithShift
    80   // Used to keep track of allowed browser keys with Shift key combination.
    81   _browserKeysWithShift: null,
    83   // Variable: ignoreKeypressForSearch
    84   // Used to prevent keypress being handled after quitting search mode.
    85   ignoreKeypressForSearch: false,
    87   // Variable: _lastOpenedTab
    88   // Used to keep track of the last opened tab.
    89   _lastOpenedTab: null,
    91   // Variable: _originalSmoothScroll
    92   // Used to keep track of the tab strip smooth scroll value.
    93   _originalSmoothScroll: null,
    95   // ----------
    96   // Function: toString
    97   // Prints [UI] for debug use
    98   toString: function UI_toString() {
    99     return "[UI]";
   100   },
   102   // ----------
   103   // Function: init
   104   // Must be called after the object is created.
   105   init: function UI_init() {
   106     try {
   107       let self = this;
   109       // initialize the direction of the page
   110       this._initPageDirection();
   112       // ___ storage
   113       Storage.init();
   115       if (Storage.readWindowBusyState(gWindow))
   116         this.storageBusy();
   118       let data = Storage.readUIData(gWindow);
   119       this._storageSanity(data);
   120       this._pageBounds = data.pageBounds;
   122       // ___ search
   123       Search.init();
   125       Telemetry.init();
   127       // ___ currentTab
   128       this._currentTab = gBrowser.selectedTab;
   130       // ___ exit button
   131       iQ("#exit-button").click(function() {
   132         self.exit();
   133         self.blurAll();
   134       })
   135       .attr("title", tabviewString("button.exitTabGroups"));
   137       // When you click on the background/empty part of TabView,
   138       // we create a new groupItem.
   139       iQ(gTabViewFrame.contentDocument).mousedown(function(e) {
   140         if (iQ(":focus").length > 0) {
   141           iQ(":focus").each(function(element) {
   142             // don't fire blur event if the same input element is clicked.
   143             if (e.target != element && element.nodeName == "INPUT")
   144               element.blur();
   145           });
   146         }
   147         if (e.originalTarget.id == "content" &&
   148             Utils.isLeftClick(e) &&
   149             e.detail == 1) {
   150           self._createGroupItemOnDrag(e);
   151         }
   152       });
   154       iQ(gTabViewFrame.contentDocument).dblclick(function(e) {
   155         if (e.originalTarget.id != "content")
   156           return;
   158         // Create a group with one tab on double click
   159         let box =
   160           new Rect(e.clientX - Math.floor(TabItems.tabWidth/2),
   161                    e.clientY - Math.floor(TabItems.tabHeight/2),
   162                    TabItems.tabWidth, TabItems.tabHeight);
   163         box.inset(-30, -30);
   165         let opts = {immediately: true, bounds: box};
   166         let groupItem = new GroupItem([], opts);
   167         groupItem.newTab();
   169         gTabView.firstUseExperienced = true;
   170       });
   172       iQ(window).bind("unload", function() {
   173         self.uninit();
   174       });
   176       // ___ setup DOMWillOpenModalDialog message handler
   177       let mm = gWindow.messageManager;
   178       let callback = this._onDOMWillOpenModalDialog.bind(this);
   179       mm.addMessageListener("Panorama:DOMWillOpenModalDialog", callback);
   181       this._cleanupFunctions.push(function () {
   182         mm.removeMessageListener("Panorama:DOMWillOpenModalDialog", callback);
   183       });
   185       // ___ setup key handlers
   186       this._setTabViewFrameKeyHandlers();
   188       // ___ add tab action handlers
   189       this._addTabActionHandlers();
   191       // ___ groups
   192       GroupItems.init();
   193       GroupItems.pauseArrange();
   194       let hasGroupItemsData = GroupItems.load();
   196       // ___ tabs
   197       TabItems.init();
   198       TabItems.pausePainting();
   200       // ___ favicons
   201       FavIcons.init();
   203       if (!hasGroupItemsData)
   204         this.reset();
   206       // ___ resizing
   207       if (this._pageBounds)
   208         this._resize(true);
   209       else
   210         this._pageBounds = Items.getPageBounds();
   212       iQ(window).resize(function() {
   213         self._resize();
   214       });
   216       // ___ setup event listener to save canvas images
   217       let onWindowClosing = function () {
   218         gWindow.removeEventListener("SSWindowClosing", onWindowClosing, false);
   220         // XXX bug #635975 - don't unlink the tab if the dom window is closing.
   221         self.isDOMWindowClosing = true;
   223         if (self.isTabViewVisible())
   224           GroupItems.removeHiddenGroups();
   226         TabItems.saveAll();
   228         self._save();
   229       };
   231       gWindow.addEventListener("SSWindowClosing", onWindowClosing);
   232       this._cleanupFunctions.push(function () {
   233         gWindow.removeEventListener("SSWindowClosing", onWindowClosing);
   234       });
   236       // ___ load frame script
   237       let frameScript = "chrome://browser/content/tabview-content.js";
   238       gWindow.messageManager.loadFrameScript(frameScript, true);
   240       // ___ Done
   241       this._frameInitialized = true;
   242       this._save();
   244       // fire an iframe initialized event so everyone knows tab view is 
   245       // initialized.
   246       let event = document.createEvent("Events");
   247       event.initEvent("tabviewframeinitialized", true, false);
   248       dispatchEvent(event);
   249     } catch(e) {
   250       Utils.log(e);
   251     } finally {
   252       GroupItems.resumeArrange();
   253     }
   254   },
   256   // Function: uninit
   257   // Should be called when window is unloaded.
   258   uninit: function UI_uninit() {
   259     // call our cleanup functions
   260     this._cleanupFunctions.forEach(function(func) {
   261       func();
   262     });
   263     this._cleanupFunctions = [];
   265     // additional clean up
   266     TabItems.uninit();
   267     GroupItems.uninit();
   268     FavIcons.uninit();
   269     Storage.uninit();
   270     Telemetry.uninit();
   272     this._removeTabActionHandlers();
   273     this._currentTab = null;
   274     this._pageBounds = null;
   275     this._reorderTabItemsOnShow = null;
   276     this._reorderTabsOnHide = null;
   277     this._frameInitialized = false;
   278   },
   280   // Property: rtl
   281   // Returns true if we are in RTL mode, false otherwise
   282   rtl: false,
   284   // Function: reset
   285   // Resets the Panorama view to have just one group with all tabs
   286   reset: function UI_reset() {
   287     let padding = Trenches.defaultRadius;
   288     let welcomeWidth = 300;
   289     let pageBounds = Items.getPageBounds();
   290     pageBounds.inset(padding, padding);
   292     let $actions = iQ("#actions");
   293     if ($actions) {
   294       pageBounds.width -= $actions.width();
   295       if (UI.rtl)
   296         pageBounds.left += $actions.width() - padding;
   297     }
   299     // ___ make a fresh groupItem
   300     let box = new Rect(pageBounds);
   301     box.width = Math.min(box.width * 0.667,
   302                          pageBounds.width - (welcomeWidth + padding));
   303     box.height = box.height * 0.667;
   304     if (UI.rtl) {
   305       box.left = pageBounds.left + welcomeWidth + 2 * padding;
   306     }
   308     GroupItems.groupItems.forEach(function(group) {
   309       group.close();
   310     });
   312     let options = {
   313       bounds: box,
   314       immediately: true
   315     };
   316     let groupItem = new GroupItem([], options);
   317     let items = TabItems.getItems();
   318     items.forEach(function(item) {
   319       if (item.parent)
   320         item.parent.remove(item);
   321       groupItem.add(item, {immediately: true});
   322     });
   323     this.setActive(groupItem);
   324   },
   326   // ----------
   327   // Function: blurAll
   328   // Blurs any currently focused element
   329   blurAll: function UI_blurAll() {
   330     iQ(":focus").each(function(element) {
   331       element.blur();
   332     });
   333   },
   335   // ----------
   336   // Function: isIdle
   337   // Returns true if the last interaction was long enough ago to consider the
   338   // UI idle. Used to determine whether interactivity would be sacrificed if 
   339   // the CPU was to become busy.
   340   //
   341   isIdle: function UI_isIdle() {
   342     let time = Date.now();
   343     let maxEvent = Math.max(drag.lastMoveTime, resize.lastMoveTime);
   344     return (time - maxEvent) > this._maxInteractiveWait;
   345   },
   347   // ----------
   348   // Function: getActiveTab
   349   // Returns the currently active tab as a <TabItem>
   350   getActiveTab: function UI_getActiveTab() {
   351     return this._activeTab;
   352   },
   354   // ----------
   355   // Function: _setActiveTab
   356   // Sets the currently active tab. The idea of a focused tab is useful
   357   // for keyboard navigation and returning to the last zoomed-in tab.
   358   // Hitting return/esc brings you to the focused tab, and using the
   359   // arrow keys lets you navigate between open tabs.
   360   //
   361   // Parameters:
   362   //  - Takes a <TabItem>
   363   _setActiveTab: function UI__setActiveTab(tabItem) {
   364     if (tabItem == this._activeTab)
   365       return;
   367     if (this._activeTab) {
   368       this._activeTab.makeDeactive();
   369       this._activeTab.removeSubscriber("close", this._onActiveTabClosed);
   370     }
   372     this._activeTab = tabItem;
   374     if (this._activeTab) {
   375       this._activeTab.addSubscriber("close", this._onActiveTabClosed);
   376       this._activeTab.makeActive();
   377     }
   378   },
   380   // ----------
   381   // Function: _onActiveTabClosed
   382   // Handles when the currently active tab gets closed.
   383   //
   384   // Parameters:
   385   //  - the <TabItem> that is closed
   386   _onActiveTabClosed: function UI__onActiveTabClosed(tabItem){
   387     if (UI._activeTab == tabItem)
   388       UI._setActiveTab(null);
   389   },
   391   // ----------
   392   // Function: setActive
   393   // Sets the active tab item or group item
   394   // Parameters:
   395   //
   396   // options
   397   //  dontSetActiveTabInGroup bool for not setting active tab in group
   398   setActive: function UI_setActive(item, options) {
   399     Utils.assert(item, "item must be given");
   401     if (item.isATabItem) {
   402       if (item.parent)
   403         GroupItems.setActiveGroupItem(item.parent);
   404       if (!options || !options.dontSetActiveTabInGroup)
   405         this._setActiveTab(item);
   406     } else {
   407       GroupItems.setActiveGroupItem(item);
   408       if (!options || !options.dontSetActiveTabInGroup) {
   409         let activeTab = item.getActiveTab();
   410         if (activeTab)
   411           this._setActiveTab(activeTab);
   412       }
   413     }
   414   },
   416   // ----------
   417   // Function: clearActiveTab
   418   // Sets the active tab to 'null'.
   419   clearActiveTab: function UI_clearActiveTab() {
   420     this._setActiveTab(null);
   421   },
   423   // ----------
   424   // Function: isTabViewVisible
   425   // Returns true if the TabView UI is currently shown.
   426   isTabViewVisible: function UI_isTabViewVisible() {
   427     return gTabViewDeck.selectedPanel == gTabViewFrame;
   428   },
   430   // ---------
   431   // Function: _initPageDirection
   432   // Initializes the page base direction
   433   _initPageDirection: function UI__initPageDirection() {
   434     let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
   435                     getService(Ci.nsIXULChromeRegistry);
   436     let dir = chromeReg.isLocaleRTL("global");
   437     document.documentElement.setAttribute("dir", dir ? "rtl" : "ltr");
   438     this.rtl = dir;
   439   },
   441   // ----------
   442   // Function: showTabView
   443   // Shows TabView and hides the main browser UI.
   444   // Parameters:
   445   //   zoomOut - true for zoom out animation, false for nothing.
   446   showTabView: function UI_showTabView(zoomOut) {
   447     if (this.isTabViewVisible() || this._isChangingVisibility)
   448       return;
   450     this._isChangingVisibility = true;
   452     // store tab strip smooth scroll value and disable it.
   453     let tabStrip = gBrowser.tabContainer.mTabstrip;
   454     this._originalSmoothScroll = tabStrip.smoothScroll;
   455     tabStrip.smoothScroll = false;
   457     // initialize the direction of the page
   458     this._initPageDirection();
   460     var self = this;
   461     var currentTab = this._currentTab;
   463     this._reorderTabItemsOnShow.forEach(function(groupItem) {
   464       groupItem.reorderTabItemsBasedOnTabOrder();
   465     });
   466     this._reorderTabItemsOnShow = [];
   468 #ifdef XP_WIN
   469     // Restore the full height when showing TabView
   470     gTabViewFrame.style.marginTop = "";
   471 #endif
   472     gTabViewDeck.selectedPanel = gTabViewFrame;
   473     gWindow.TabsInTitlebar.allowedBy("tabview-open", false);
   474     gTabViewFrame.contentWindow.focus();
   476     gBrowser.updateTitlebar();
   477 #ifdef XP_MACOSX
   478     this.setTitlebarColors(true);
   479 #endif
   480     let event = document.createEvent("Events");
   481     event.initEvent("tabviewshown", true, false);
   483     Storage.saveVisibilityData(gWindow, "true");
   485     if (zoomOut && currentTab && currentTab._tabViewTabItem) {
   486       let item = currentTab._tabViewTabItem;
   487       // If there was a previous currentTab we want to animate
   488       // its thumbnail (canvas) for the zoom out.
   489       // Note that we start the animation on the chrome thread.
   491       // Zoom out!
   492       item.zoomOut(function() {
   493         if (!currentTab._tabViewTabItem) // if the tab's been destroyed
   494           item = null;
   496         self.setActive(item);
   498         self._resize(true);
   499         self._isChangingVisibility = false;
   500         dispatchEvent(event);
   502         // Flush pending updates
   503         GroupItems.flushAppTabUpdates();
   505         TabItems.resumePainting();
   506       });
   507     } else {
   508       if (!currentTab || !currentTab._tabViewTabItem)
   509         self.clearActiveTab();
   510       self._isChangingVisibility = false;
   511       dispatchEvent(event);
   513       // Flush pending updates
   514       GroupItems.flushAppTabUpdates();
   516       TabItems.resumePainting();
   517     }
   519     if (gTabView.firstUseExperienced)
   520       gTabView.enableSessionRestore();
   521   },
   523   // ----------
   524   // Function: hideTabView
   525   // Hides TabView and shows the main browser UI.
   526   hideTabView: function UI_hideTabView() {
   527     if (!this.isTabViewVisible() || this._isChangingVisibility)
   528       return;
   530     // another tab might be select if user decides to stay on a page when
   531     // a onclose confirmation prompts.
   532     GroupItems.removeHiddenGroups();
   534     // We need to set this after removing the hidden groups because doing so
   535     // might show prompts which will cause us to be called again, and we'd get
   536     // stuck if we prevent re-entrancy before doing that.
   537     this._isChangingVisibility = true;
   539     TabItems.pausePainting();
   541     this._reorderTabsOnHide.forEach(function(groupItem) {
   542       groupItem.reorderTabsBasedOnTabItemOrder();
   543     });
   544     this._reorderTabsOnHide = [];
   546 #ifdef XP_WIN
   547     // Push the top of TabView frame to behind the tabbrowser, so glass can show
   548     // XXX bug 586679: avoid shrinking the iframe and squishing iframe contents
   549     // as well as avoiding the flash of black as we animate out
   550     gTabViewFrame.style.marginTop = gBrowser.boxObject.y + "px";
   551 #endif
   552     gTabViewDeck.selectedPanel = gBrowserPanel;
   553     gWindow.TabsInTitlebar.allowedBy("tabview-open", true);
   554     gBrowser.selectedBrowser.focus();
   556     gBrowser.updateTitlebar();
   557     gBrowser.tabContainer.mTabstrip.smoothScroll = this._originalSmoothScroll;
   558 #ifdef XP_MACOSX
   559     this.setTitlebarColors(false);
   560 #endif
   561     Storage.saveVisibilityData(gWindow, "false");
   563     this._isChangingVisibility = false;
   565     let event = document.createEvent("Events");
   566     event.initEvent("tabviewhidden", true, false);
   567     dispatchEvent(event);
   568   },
   570 #ifdef XP_MACOSX
   571   // ----------
   572   // Function: setTitlebarColors
   573   // Used on the Mac to make the title bar match the gradient in the rest of the
   574   // TabView UI.
   575   //
   576   // Parameters:
   577   //   colors - (bool or object) true for the special TabView color, false for
   578   //         the normal color, and an object with "active" and "inactive"
   579   //         properties to specify directly.
   580   setTitlebarColors: function UI_setTitlebarColors(colors) {
   581     // Mac Only
   582     var mainWindow = gWindow.document.getElementById("main-window");
   583     if (colors === true) {
   584       mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
   585       mainWindow.setAttribute("inactivetitlebarcolor", "#EDEDED");
   586     } else if (colors && "active" in colors && "inactive" in colors) {
   587       mainWindow.setAttribute("activetitlebarcolor", colors.active);
   588       mainWindow.setAttribute("inactivetitlebarcolor", colors.inactive);
   589     } else {
   590       mainWindow.removeAttribute("activetitlebarcolor");
   591       mainWindow.removeAttribute("inactivetitlebarcolor");
   592     }
   593   },
   594 #endif
   596   // ----------
   597   // Function: storageBusy
   598   // Pauses the storage activity that conflicts with sessionstore updates.
   599   // Calls can be nested.
   600   storageBusy: function UI_storageBusy() {
   601     if (this._storageBusy)
   602       return;
   604     this._storageBusy = true;
   606     TabItems.pauseReconnecting();
   607     GroupItems.pauseAutoclose();
   608   },
   610   // ----------
   611   // Function: storageReady
   612   // Resumes the activity paused by storageBusy, and updates for any new group
   613   // information in sessionstore. Calls can be nested. 
   614   storageReady: function UI_storageReady() {
   615     if (!this._storageBusy)
   616       return;
   618     this._storageBusy = false;
   620     let hasGroupItemsData = GroupItems.load();
   621     if (!hasGroupItemsData)
   622       this.reset();
   624     TabItems.resumeReconnecting();
   625     GroupItems._updateTabBar();
   626     GroupItems.resumeAutoclose();
   627   },
   629   // ----------
   630   // Function: _addTabActionHandlers
   631   // Adds handlers to handle tab actions.
   632   _addTabActionHandlers: function UI__addTabActionHandlers() {
   633     var self = this;
   635     // session restore events
   636     function handleSSWindowStateBusy() {
   637       self.storageBusy();
   638     }
   640     function handleSSWindowStateReady() {
   641       self.storageReady();
   642     }
   644     gWindow.addEventListener("SSWindowStateBusy", handleSSWindowStateBusy, false);
   645     gWindow.addEventListener("SSWindowStateReady", handleSSWindowStateReady, false);
   647     this._cleanupFunctions.push(function() {
   648       gWindow.removeEventListener("SSWindowStateBusy", handleSSWindowStateBusy, false);
   649       gWindow.removeEventListener("SSWindowStateReady", handleSSWindowStateReady, false);
   650     });
   652     // TabOpen
   653     this._eventListeners.open = function (event) {
   654       let tab = event.target;
   656       // if it's an app tab, add it to all the group items
   657       if (tab.pinned)
   658         GroupItems.addAppTab(tab);
   659       else if (self.isTabViewVisible() && !self._storageBusyCount)
   660         self._lastOpenedTab = tab;
   661     };
   663     // TabClose
   664     this._eventListeners.close = function (event) {
   665       let tab = event.target;
   667       // if it's an app tab, remove it from all the group items
   668       if (tab.pinned)
   669         GroupItems.removeAppTab(tab);
   671       if (self.isTabViewVisible()) {
   672         // just closed the selected tab in the TabView interface.
   673         if (self._currentTab == tab)
   674           self._closedSelectedTabInTabView = true;
   675       } else {
   676         // If we're currently in the process of session store update,
   677         // we don't want to go to the Tab View UI. 
   678         if (self._storageBusy)
   679           return;
   681         // if not closing the last tab
   682         if (gBrowser.tabs.length > 1) {
   683           // Don't return to TabView if there are any app tabs
   684           for (let a = 0; a < gBrowser._numPinnedTabs; a++) {
   685             if (Utils.isValidXULTab(gBrowser.tabs[a]))
   686               return;
   687           }
   689           let groupItem = GroupItems.getActiveGroupItem();
   691           // 1) Only go back to the TabView tab when there you close the last
   692           // tab of a groupItem.
   693           let closingLastOfGroup = (groupItem && 
   694               groupItem._children.length == 1 && 
   695               groupItem._children[0].tab == tab);
   697           // 2) When a blank tab is active while restoring a closed tab the
   698           // blank tab gets removed. The active group is not closed as this is
   699           // where the restored tab goes. So do not show the TabView.
   700           let tabItem = tab && tab._tabViewTabItem;
   701           let closingBlankTabAfterRestore =
   702             (tabItem && tabItem.isRemovedAfterRestore);
   704           if (closingLastOfGroup && !closingBlankTabAfterRestore) {
   705             // for the tab focus event to pick up.
   706             self._closedLastVisibleTab = true;
   707             self.showTabView();
   708           }
   709         }
   710       }
   711     };
   713     // TabMove
   714     this._eventListeners.move = function (event) {
   715       let tab = event.target;
   717       if (GroupItems.groupItems.length > 0) {
   718         if (tab.pinned) {
   719           if (gBrowser._numPinnedTabs > 1)
   720             GroupItems.arrangeAppTab(tab);
   721         } else {
   722           let activeGroupItem = GroupItems.getActiveGroupItem();
   723           if (activeGroupItem)
   724             self.setReorderTabItemsOnShow(activeGroupItem);
   725         }
   726       }
   727     };
   729     // TabSelect
   730     this._eventListeners.select = function (event) {
   731       self.onTabSelect(event.target);
   732     };
   734     // TabPinned
   735     this._eventListeners.pinned = function (event) {
   736       let tab = event.target;
   738       TabItems.handleTabPin(tab);
   739       GroupItems.addAppTab(tab);
   740     };
   742     // TabUnpinned
   743     this._eventListeners.unpinned = function (event) {
   744       let tab = event.target;
   746       TabItems.handleTabUnpin(tab);
   747       GroupItems.removeAppTab(tab);
   749       let groupItem = tab._tabViewTabItem.parent;
   750       if (groupItem)
   751         self.setReorderTabItemsOnShow(groupItem);
   752     };
   754     // Actually register the above handlers
   755     for (let name in this._eventListeners)
   756       AllTabs.register(name, this._eventListeners[name]);
   757   },
   759   // ----------
   760   // Function: _removeTabActionHandlers
   761   // Removes handlers to handle tab actions.
   762   _removeTabActionHandlers: function UI__removeTabActionHandlers() {
   763     for (let name in this._eventListeners)
   764       AllTabs.unregister(name, this._eventListeners[name]);
   765   },
   767   // ----------
   768   // Function: goToTab
   769   // Selects the given xul:tab in the browser.
   770   goToTab: function UI_goToTab(xulTab) {
   771     // If it's not focused, the onFocus listener would handle it.
   772     if (xulTab.selected)
   773       this.onTabSelect(xulTab);
   774     else
   775       gBrowser.selectedTab = xulTab;
   776   },
   778   // ----------
   779   // Function: onTabSelect
   780   // Called when the user switches from one tab to another outside of the TabView UI.
   781   onTabSelect: function UI_onTabSelect(tab) {
   782     this._currentTab = tab;
   784     if (this.isTabViewVisible()) {
   785       // We want to zoom in if:
   786       // 1) we didn't just restore a tab via Ctrl+Shift+T
   787       // 2) the currently selected tab is the last created tab and has a tabItem
   788       if (!this.restoredClosedTab &&
   789           this._lastOpenedTab == tab && tab._tabViewTabItem) {
   790         tab._tabViewTabItem.zoomIn(true);
   791         this._lastOpenedTab = null;
   792         return;
   793       }
   794       if (this._closedLastVisibleTab ||
   795           (this._closedSelectedTabInTabView && !this.closedLastTabInTabView) ||
   796           this.restoredClosedTab) {
   797         if (this.restoredClosedTab) {
   798           // when the tab view UI is being displayed, update the thumb for the 
   799           // restored closed tab after the page load
   800           tab.linkedBrowser.addEventListener("load", function onLoad(event) {
   801             tab.linkedBrowser.removeEventListener("load", onLoad, true);
   802             TabItems._update(tab);
   803           }, true);
   804         }
   805         this._closedLastVisibleTab = false;
   806         this._closedSelectedTabInTabView = false;
   807         this.closedLastTabInTabView = false;
   808         this.restoredClosedTab = false;
   809         return;
   810       }
   811     }
   812     // reset these vars, just in case.
   813     this._closedLastVisibleTab = false;
   814     this._closedSelectedTabInTabView = false;
   815     this.closedLastTabInTabView = false;
   816     this.restoredClosedTab = false;
   817     this._lastOpenedTab = null;
   819     // if TabView is visible but we didn't just close the last tab or
   820     // selected tab, show chrome.
   821     if (this.isTabViewVisible()) {
   822       // Unhide the group of the tab the user is activating.
   823       if (tab && tab._tabViewTabItem && tab._tabViewTabItem.parent &&
   824           tab._tabViewTabItem.parent.hidden)
   825         tab._tabViewTabItem.parent._unhide({immediately: true});
   827       this.hideTabView();
   828     }
   830     // another tab might be selected when hideTabView() is invoked so a
   831     // validation is needed.
   832     if (this._currentTab != tab)
   833       return;
   835     let newItem = null;
   836     // update the tab bar for the new tab's group
   837     if (tab && tab._tabViewTabItem) {
   838       if (!TabItems.reconnectingPaused()) {
   839         newItem = tab._tabViewTabItem;
   840         GroupItems.updateActiveGroupItemAndTabBar(newItem);
   841       }
   842     } else {
   843       // No tabItem; must be an app tab. Base the tab bar on the current group.
   844       // If no current group, figure it out based on what's already in the tab
   845       // bar.
   846       if (!GroupItems.getActiveGroupItem()) {
   847         for (let a = 0; a < gBrowser.tabs.length; a++) {
   848           let theTab = gBrowser.tabs[a];
   849           if (!theTab.pinned) {
   850             let tabItem = theTab._tabViewTabItem;
   851             this.setActive(tabItem.parent);
   852             break;
   853           }
   854         }
   855       }
   857       if (GroupItems.getActiveGroupItem())
   858         GroupItems._updateTabBar();
   859     }
   860   },
   862   // ----------
   863   // Function: _onDOMWillOpenModalDialog
   864   // Called when a web page is about to show a modal dialog.
   865   _onDOMWillOpenModalDialog: function UI__onDOMWillOpenModalDialog(cx) {
   866     if (!this.isTabViewVisible())
   867       return;
   869     let index = gBrowser.browsers.indexOf(cx.target);
   870     if (index == -1)
   871       return;
   873     let tab = gBrowser.tabs[index];
   875     // When TabView is visible, we need to call onTabSelect to make sure that
   876     // TabView is hidden and that the correct group is activated. When a modal
   877     // dialog is shown for currently selected tab the onTabSelect event handler
   878     // is not called, so we need to do it.
   879     if (tab.selected && this._currentTab == tab)
   880       this.onTabSelect(tab);
   881   },
   883   // ----------
   884   // Function: setReorderTabsOnHide
   885   // Sets the groupItem which the tab items' tabs should be re-ordered when
   886   // switching to the main browser UI.
   887   // Parameters:
   888   //   groupItem - the groupItem which would be used for re-ordering tabs.
   889   setReorderTabsOnHide: function UI_setReorderTabsOnHide(groupItem) {
   890     if (this.isTabViewVisible()) {
   891       var index = this._reorderTabsOnHide.indexOf(groupItem);
   892       if (index == -1)
   893         this._reorderTabsOnHide.push(groupItem);
   894     }
   895   },
   897   // ----------
   898   // Function: setReorderTabItemsOnShow
   899   // Sets the groupItem which the tab items should be re-ordered when
   900   // switching to the tab view UI.
   901   // Parameters:
   902   //   groupItem - the groupItem which would be used for re-ordering tab items.
   903   setReorderTabItemsOnShow: function UI_setReorderTabItemsOnShow(groupItem) {
   904     if (!this.isTabViewVisible()) {
   905       var index = this._reorderTabItemsOnShow.indexOf(groupItem);
   906       if (index == -1)
   907         this._reorderTabItemsOnShow.push(groupItem);
   908     }
   909   },
   911   // ----------
   912   updateTabButton: function UI_updateTabButton() {
   913     let exitButton = document.getElementById("exit-button");
   914     let numberOfGroups = GroupItems.groupItems.length;
   916     exitButton.setAttribute("groups", numberOfGroups);
   917     gTabView.updateGroupNumberBroadcaster(numberOfGroups);
   918   },
   920   // ----------
   921   // Function: getClosestTab
   922   // Convenience function to get the next tab closest to the entered position
   923   getClosestTab: function UI_getClosestTab(tabCenter) {
   924     let cl = null;
   925     let clDist;
   926     TabItems.getItems().forEach(function (item) {
   927       if (!item.parent || item.parent.hidden)
   928         return;
   929       let testDist = tabCenter.distance(item.bounds.center());
   930       if (cl==null || testDist < clDist) {
   931         cl = item;
   932         clDist = testDist;
   933       }
   934     });
   935     return cl;
   936   },
   938   // ----------
   939   // Function: _setupBrowserKeys
   940   // Sets up the allowed browser keys using key elements.
   941   _setupBrowserKeys: function UI__setupKeyWhiteList() {
   942     let keys = {};
   944     [
   945 #ifdef XP_UNIX
   946       "quitApplication",
   947 #else
   948       "redo",
   949 #endif
   950 #ifdef XP_MACOSX
   951       "preferencesCmdMac", "minimizeWindow", "hideThisAppCmdMac",
   952 #endif
   953       "newNavigator", "newNavigatorTab", "undo", "cut", "copy", "paste", 
   954       "selectAll", "find"
   955     ].forEach(function(key) {
   956       let element = gWindow.document.getElementById("key_" + key);
   957       let code = element.getAttribute("key").toLocaleLowerCase().charCodeAt(0);
   958       keys[code] = key;
   959     });
   960     this._browserKeys = keys;
   962     keys = {};
   963     // The lower case letters are passed to processBrowserKeys() even with shift 
   964     // key when stimulating a key press using EventUtils.synthesizeKey() so need 
   965     // to handle both upper and lower cases here.
   966     [
   967 #ifdef XP_UNIX
   968       "redo",
   969 #endif
   970 #ifdef XP_MACOSX
   971       "fullScreen",
   972 #endif
   973       "closeWindow", "tabview", "undoCloseTab", "undoCloseWindow"
   974     ].forEach(function(key) {
   975       let element = gWindow.document.getElementById("key_" + key);
   976       let code = element.getAttribute("key").toLocaleLowerCase().charCodeAt(0);
   977       keys[code] = key;
   978     });
   979     this._browserKeysWithShift = keys;
   980   },
   982   // ----------
   983   // Function: _setTabViewFrameKeyHandlers
   984   // Sets up the key handlers for navigating between tabs within the TabView UI.
   985   _setTabViewFrameKeyHandlers: function UI__setTabViewFrameKeyHandlers() {
   986     let self = this;
   988     this._setupBrowserKeys();
   990     iQ(window).keyup(function(event) {
   991       if (!event.metaKey)
   992         Keys.meta = false;
   993     });
   995     iQ(window).keypress(function(event) {
   996       if (event.metaKey)
   997         Keys.meta = true;
   999       function processBrowserKeys(evt) {
  1000         // let any keys with alt to pass through
  1001         if (evt.altKey)
  1002           return;
  1004 #ifdef XP_MACOSX
  1005         if (evt.metaKey) {
  1006 #else
  1007         if (evt.ctrlKey) {
  1008 #endif
  1009           let preventDefault = true;
  1010           if (evt.shiftKey) {
  1011             // when a user presses ctrl+shift+key, upper case letter charCode 
  1012             // is passed to processBrowserKeys() so converting back to lower 
  1013             // case charCode before doing the check
  1014             let lowercaseCharCode =
  1015               String.fromCharCode(evt.charCode).toLocaleLowerCase().charCodeAt(0);
  1016             if (lowercaseCharCode in self._browserKeysWithShift) {
  1017               let key = self._browserKeysWithShift[lowercaseCharCode];
  1018               if (key == "tabview")
  1019                 self.exit();
  1020               else
  1021                 preventDefault = false;
  1023           } else {
  1024             if (evt.charCode in self._browserKeys) {
  1025               let key = self._browserKeys[evt.charCode];
  1026               if (key == "find")
  1027                 self.enableSearch();
  1028               else
  1029                 preventDefault = false;
  1032           if (preventDefault) {
  1033             evt.stopPropagation();
  1034             evt.preventDefault();
  1038       if ((iQ(":focus").length > 0 && iQ(":focus")[0].nodeName == "INPUT") ||
  1039           Search.isEnabled() || self.ignoreKeypressForSearch) {
  1040         self.ignoreKeypressForSearch = false;
  1041         processBrowserKeys(event);
  1042         return;
  1045       function getClosestTabBy(norm) {
  1046         if (!self.getActiveTab())
  1047           return null;
  1049         let activeTab = self.getActiveTab();
  1050         let activeTabGroup = activeTab.parent;
  1051         let myCenter = activeTab.bounds.center();
  1052         let match;
  1054         TabItems.getItems().forEach(function (item) {
  1055           if (!item.parent.hidden &&
  1056               (!activeTabGroup.expanded || activeTabGroup.id == item.parent.id)) {
  1057             let itemCenter = item.bounds.center();
  1059             if (norm(itemCenter, myCenter)) {
  1060               let itemDist = myCenter.distance(itemCenter);
  1061               if (!match || match[0] > itemDist)
  1062                 match = [itemDist, item];
  1065         });
  1067         return match && match[1];
  1070       let preventDefault = true;
  1071       let activeTab;
  1072       let activeGroupItem;
  1073       let norm = null;
  1074       switch (event.keyCode) {
  1075         case KeyEvent.DOM_VK_RIGHT:
  1076           norm = function(a, me){return a.x > me.x};
  1077           break;
  1078         case KeyEvent.DOM_VK_LEFT:
  1079           norm = function(a, me){return a.x < me.x};
  1080           break;
  1081         case KeyEvent.DOM_VK_DOWN:
  1082           norm = function(a, me){return a.y > me.y};
  1083           break;
  1084         case KeyEvent.DOM_VK_UP:
  1085           norm = function(a, me){return a.y < me.y}
  1086           break;
  1089       if (norm != null) {
  1090         let nextTab = getClosestTabBy(norm);
  1091         if (nextTab) {
  1092           if (nextTab.isStacked && !nextTab.parent.expanded)
  1093             nextTab = nextTab.parent.getChild(0);
  1094           self.setActive(nextTab);
  1096       } else {
  1097         switch(event.keyCode) {
  1098           case KeyEvent.DOM_VK_ESCAPE:
  1099             activeGroupItem = GroupItems.getActiveGroupItem();
  1100             if (activeGroupItem && activeGroupItem.expanded)
  1101               activeGroupItem.collapse();
  1102             else
  1103               self.exit();
  1104             break;
  1105           case KeyEvent.DOM_VK_RETURN:
  1106             activeGroupItem = GroupItems.getActiveGroupItem();
  1107             if (activeGroupItem) {
  1108               activeTab = self.getActiveTab();
  1110               if (!activeTab || activeTab.parent != activeGroupItem)
  1111                 activeTab = activeGroupItem.getActiveTab();
  1113               if (activeTab)
  1114                 activeTab.zoomIn();
  1115               else
  1116                 activeGroupItem.newTab();
  1118             break;
  1119           case KeyEvent.DOM_VK_TAB:
  1120             // tab/shift + tab to go to the next tab.
  1121             activeTab = self.getActiveTab();
  1122             if (activeTab) {
  1123               let tabItems = (activeTab.parent ? activeTab.parent.getChildren() :
  1124                               [activeTab]);
  1125               let length = tabItems.length;
  1126               let currentIndex = tabItems.indexOf(activeTab);
  1128               if (length > 1) {
  1129                 let newIndex;
  1130                 if (event.shiftKey) {
  1131                   if (currentIndex == 0)
  1132                     newIndex = (length - 1);
  1133                   else
  1134                     newIndex = (currentIndex - 1);
  1135                 } else {
  1136                   if (currentIndex == (length - 1))
  1137                     newIndex = 0;
  1138                   else
  1139                     newIndex = (currentIndex + 1);
  1141                 self.setActive(tabItems[newIndex]);
  1144             break;
  1145           default:
  1146             processBrowserKeys(event);
  1147             preventDefault = false;
  1149         if (preventDefault) {
  1150           event.stopPropagation();
  1151           event.preventDefault();
  1154     });
  1155   },
  1157   // ----------
  1158   // Function: enableSearch
  1159   // Enables the search feature.
  1160   enableSearch: function UI_enableSearch() {
  1161     if (!Search.isEnabled()) {
  1162       Search.ensureShown();
  1163       Search.switchToInMode();
  1165   },
  1167   // ----------
  1168   // Function: _createGroupItemOnDrag
  1169   // Called in response to a mousedown in empty space in the TabView UI;
  1170   // creates a new groupItem based on the user's drag.
  1171   _createGroupItemOnDrag: function UI__createGroupItemOnDrag(e) {
  1172     const minSize = 60;
  1173     const minMinSize = 15;
  1175     let lastActiveGroupItem = GroupItems.getActiveGroupItem();
  1177     var startPos = { x: e.clientX, y: e.clientY };
  1178     var phantom = iQ("<div>")
  1179       .addClass("groupItem phantom activeGroupItem dragRegion")
  1180       .css({
  1181         position: "absolute",
  1182         zIndex: -1,
  1183         cursor: "default"
  1184       })
  1185       .appendTo("body");
  1187     var item = { // a faux-Item
  1188       container: phantom,
  1189       isAFauxItem: true,
  1190       bounds: {},
  1191       getBounds: function FauxItem_getBounds() {
  1192         return this.container.bounds();
  1193       },
  1194       setBounds: function FauxItem_setBounds(bounds) {
  1195         this.container.css(bounds);
  1196       },
  1197       setZ: function FauxItem_setZ(z) {
  1198         // don't set a z-index because we want to force it to be low.
  1199       },
  1200       setOpacity: function FauxItem_setOpacity(opacity) {
  1201         this.container.css("opacity", opacity);
  1202       },
  1203       // we don't need to pushAway the phantom item at the end, because
  1204       // when we create a new GroupItem, it'll do the actual pushAway.
  1205       pushAway: function () {},
  1206     };
  1207     item.setBounds(new Rect(startPos.y, startPos.x, 0, 0));
  1209     var dragOutInfo = new Drag(item, e);
  1211     function updateSize(e) {
  1212       var box = new Rect();
  1213       box.left = Math.min(startPos.x, e.clientX);
  1214       box.right = Math.max(startPos.x, e.clientX);
  1215       box.top = Math.min(startPos.y, e.clientY);
  1216       box.bottom = Math.max(startPos.y, e.clientY);
  1217       item.setBounds(box);
  1219       // compute the stationaryCorner
  1220       var stationaryCorner = "";
  1222       if (startPos.y == box.top)
  1223         stationaryCorner += "top";
  1224       else
  1225         stationaryCorner += "bottom";
  1227       if (startPos.x == box.left)
  1228         stationaryCorner += "left";
  1229       else
  1230         stationaryCorner += "right";
  1232       dragOutInfo.snap(stationaryCorner, false, false); // null for ui, which we don't use anyway.
  1234       box = item.getBounds();
  1235       if (box.width > minMinSize && box.height > minMinSize &&
  1236          (box.width > minSize || box.height > minSize))
  1237         item.setOpacity(1);
  1238       else
  1239         item.setOpacity(0.7);
  1241       e.preventDefault();
  1244     let self = this;
  1245     function collapse() {
  1246       let center = phantom.bounds().center();
  1247       phantom.animate({
  1248         width: 0,
  1249         height: 0,
  1250         top: center.y,
  1251         left: center.x
  1252       }, {
  1253         duration: 300,
  1254         complete: function() {
  1255           phantom.remove();
  1257       });
  1258       self.setActive(lastActiveGroupItem);
  1261     function finalize(e) {
  1262       iQ(window).unbind("mousemove", updateSize);
  1263       item.container.removeClass("dragRegion");
  1264       dragOutInfo.stop();
  1265       let box = item.getBounds();
  1266       if (box.width > minMinSize && box.height > minMinSize &&
  1267          (box.width > minSize || box.height > minSize)) {
  1268         let opts = {bounds: item.getBounds(), focusTitle: true};
  1269         let groupItem = new GroupItem([], opts);
  1270         self.setActive(groupItem);
  1271         phantom.remove();
  1272         dragOutInfo = null;
  1273         gTabView.firstUseExperienced = true;
  1274       } else {
  1275         collapse();
  1279     iQ(window).mousemove(updateSize)
  1280     iQ(gWindow).one("mouseup", finalize);
  1281     e.preventDefault();
  1282     return false;
  1283   },
  1285   // ----------
  1286   // Function: _resize
  1287   // Update the TabView UI contents in response to a window size change.
  1288   // Won't do anything if it doesn't deem the resize necessary.
  1289   // Parameters:
  1290   //   force - true to update even when "unnecessary"; default false
  1291   _resize: function UI__resize(force) {
  1292     if (!this._pageBounds)
  1293       return;
  1295     // Here are reasons why we *won't* resize:
  1296     // 1. Panorama isn't visible (in which case we will resize when we do display)
  1297     // 2. the screen dimensions haven't changed
  1298     // 3. everything on the screen fits and nothing feels cramped
  1299     if (!force && !this.isTabViewVisible())
  1300       return;
  1302     let oldPageBounds = new Rect(this._pageBounds);
  1303     let newPageBounds = Items.getPageBounds();
  1304     if (newPageBounds.equals(oldPageBounds))
  1305       return;
  1307     if (!this.shouldResizeItems())
  1308       return;
  1310     var items = Items.getTopLevelItems();
  1312     // compute itemBounds: the union of all the top-level items' bounds.
  1313     var itemBounds = new Rect(this._pageBounds);
  1314     // We start with pageBounds so that we respect the empty space the user
  1315     // has left on the page.
  1316     itemBounds.width = 1;
  1317     itemBounds.height = 1;
  1318     items.forEach(function(item) {
  1319       var bounds = item.getBounds();
  1320       itemBounds = (itemBounds ? itemBounds.union(bounds) : new Rect(bounds));
  1321     });
  1323     if (newPageBounds.width < this._pageBounds.width &&
  1324         newPageBounds.width > itemBounds.width)
  1325       newPageBounds.width = this._pageBounds.width;
  1327     if (newPageBounds.height < this._pageBounds.height &&
  1328         newPageBounds.height > itemBounds.height)
  1329       newPageBounds.height = this._pageBounds.height;
  1331     var wScale;
  1332     var hScale;
  1333     if (Math.abs(newPageBounds.width - this._pageBounds.width)
  1334          > Math.abs(newPageBounds.height - this._pageBounds.height)) {
  1335       wScale = newPageBounds.width / this._pageBounds.width;
  1336       hScale = newPageBounds.height / itemBounds.height;
  1337     } else {
  1338       wScale = newPageBounds.width / itemBounds.width;
  1339       hScale = newPageBounds.height / this._pageBounds.height;
  1342     var scale = Math.min(hScale, wScale);
  1343     var self = this;
  1344     var pairs = [];
  1345     items.forEach(function(item) {
  1346       var bounds = item.getBounds();
  1347       bounds.left += (UI.rtl ? -1 : 1) * (newPageBounds.left - self._pageBounds.left);
  1348       bounds.left *= scale;
  1349       bounds.width *= scale;
  1351       bounds.top += newPageBounds.top - self._pageBounds.top;
  1352       bounds.top *= scale;
  1353       bounds.height *= scale;
  1355       pairs.push({
  1356         item: item,
  1357         bounds: bounds
  1358       });
  1359     });
  1361     Items.unsquish(pairs);
  1363     pairs.forEach(function(pair) {
  1364       pair.item.setBounds(pair.bounds, true);
  1365       pair.item.snap();
  1366     });
  1368     this._pageBounds = Items.getPageBounds();
  1369     this._save();
  1370   },
  1372   // ----------
  1373   // Function: shouldResizeItems
  1374   // Returns whether we should resize the items on the screen, based on whether
  1375   // the top-level items fit in the screen or not and whether they feel
  1376   // "cramped" or not.
  1377   // These computations may be done using cached values. The cache can be
  1378   // cleared with UI.clearShouldResizeItems().
  1379   shouldResizeItems: function UI_shouldResizeItems() {
  1380     let newPageBounds = Items.getPageBounds();
  1382     // If we don't have cached cached values...
  1383     if (this._minimalRect === undefined || this._feelsCramped === undefined) {
  1385       // Loop through every top-level Item for two operations:
  1386       // 1. check if it is feeling "cramped" due to squishing (a technical term),
  1387       // 2. union its bounds with the minimalRect
  1388       let feelsCramped = false;
  1389       let minimalRect = new Rect(0, 0, 1, 1);
  1391       Items.getTopLevelItems()
  1392         .forEach(function UI_shouldResizeItems_checkItem(item) {
  1393           let bounds = new Rect(item.getBounds());
  1394           feelsCramped = feelsCramped || (item.userSize &&
  1395             (item.userSize.x > bounds.width || item.userSize.y > bounds.height));
  1396           bounds.inset(-Trenches.defaultRadius, -Trenches.defaultRadius);
  1397           minimalRect = minimalRect.union(bounds);
  1398         });
  1400       // ensure the minimalRect extends to, but not beyond, the origin
  1401       minimalRect.left = 0;
  1402       minimalRect.top  = 0;
  1404       this._minimalRect = minimalRect;
  1405       this._feelsCramped = feelsCramped;
  1408     return this._minimalRect.width > newPageBounds.width ||
  1409       this._minimalRect.height > newPageBounds.height ||
  1410       this._feelsCramped;
  1411   },
  1413   // ----------
  1414   // Function: clearShouldResizeItems
  1415   // Clear the cache of whether we should resize the items on the Panorama
  1416   // screen, forcing a recomputation on the next UI.shouldResizeItems()
  1417   // call.
  1418   clearShouldResizeItems: function UI_clearShouldResizeItems() {
  1419     delete this._minimalRect;
  1420     delete this._feelsCramped;
  1421   },
  1423   // ----------
  1424   // Function: exit
  1425   // Exits TabView UI.
  1426   exit: function UI_exit() {
  1427     let self = this;
  1428     let zoomedIn = false;
  1430     if (Search.isEnabled()) {
  1431       let matcher = Search.createSearchTabMatcher();
  1432       let matches = matcher.matched();
  1434       if (matches.length > 0) {
  1435         matches[0].zoomIn();
  1436         zoomedIn = true;
  1438       Search.hide();
  1441     if (!zoomedIn) {
  1442       let unhiddenGroups = GroupItems.groupItems.filter(function(groupItem) {
  1443         return (!groupItem.hidden && groupItem.getChildren().length > 0);
  1444       });
  1445       // no pinned tabs and no visible groups: open a new group. open a blank
  1446       // tab and return
  1447       if (!unhiddenGroups.length) {
  1448         let emptyGroups = GroupItems.groupItems.filter(function (groupItem) {
  1449           return (!groupItem.hidden && !groupItem.getChildren().length);
  1450         });
  1451         let group = (emptyGroups.length ? emptyGroups[0] : GroupItems.newGroup());
  1452         if (!gBrowser._numPinnedTabs) {
  1453           group.newTab(null, { closedLastTab: true });
  1454           return;
  1458       // If there's an active TabItem, zoom into it. If not (for instance when the
  1459       // selected tab is an app tab), just go there.
  1460       let activeTabItem = this.getActiveTab();
  1461       if (!activeTabItem) {
  1462         let tabItem = gBrowser.selectedTab._tabViewTabItem;
  1463         if (tabItem) {
  1464           if (!tabItem.parent || !tabItem.parent.hidden) {
  1465             activeTabItem = tabItem;
  1466           } else { // set active tab item if there is at least one unhidden group
  1467             if (unhiddenGroups.length > 0)
  1468               activeTabItem = unhiddenGroups[0].getActiveTab();
  1473       if (activeTabItem) {
  1474         activeTabItem.zoomIn();
  1475       } else {
  1476         if (gBrowser._numPinnedTabs > 0) {
  1477           if (gBrowser.selectedTab.pinned) {
  1478             self.goToTab(gBrowser.selectedTab);
  1479           } else {
  1480             Array.some(gBrowser.tabs, function(tab) {
  1481               if (tab.pinned) {
  1482                 self.goToTab(tab);
  1483                 return true;
  1485               return false
  1486             });
  1491   },
  1493   // ----------
  1494   // Function: storageSanity
  1495   // Given storage data for this object, returns true if it looks valid.
  1496   _storageSanity: function UI__storageSanity(data) {
  1497     if (Utils.isEmptyObject(data))
  1498       return true;
  1500     if (!Utils.isRect(data.pageBounds)) {
  1501       Utils.log("UI.storageSanity: bad pageBounds", data.pageBounds);
  1502       data.pageBounds = null;
  1503       return false;
  1506     return true;
  1507   },
  1509   // ----------
  1510   // Function: _save
  1511   // Saves the data for this object to persistent storage
  1512   _save: function UI__save() {
  1513     if (!this._frameInitialized)
  1514       return;
  1516     var data = {
  1517       pageBounds: this._pageBounds
  1518     };
  1520     if (this._storageSanity(data))
  1521       Storage.saveUIData(gWindow, data);
  1522   },
  1524   // ----------
  1525   // Function: _saveAll
  1526   // Saves all data associated with TabView.
  1527   _saveAll: function UI__saveAll() {
  1528     this._save();
  1529     GroupItems.saveAll();
  1530     TabItems.saveAll();
  1531   },
  1533   // ----------
  1534   // Function: notifySessionRestoreEnabled
  1535   // Notify the user that session restore has been automatically enabled
  1536   // by showing a banner that expects no user interaction. It fades out after
  1537   // some seconds.
  1538   notifySessionRestoreEnabled: function UI_notifySessionRestoreEnabled() {
  1539     let brandBundle = gWindow.document.getElementById("bundle_brand");
  1540     let brandShortName = brandBundle.getString("brandShortName");
  1541     let notificationText = tabviewBundle.formatStringFromName(
  1542       "tabview.notification.sessionStore", [brandShortName], 1);
  1544     let banner = iQ("<div>")
  1545       .text(notificationText)
  1546       .addClass("banner")
  1547       .appendTo("body");
  1549     let onFadeOut = function () {
  1550       banner.remove();
  1551     };
  1553     let onFadeIn = function () {
  1554       setTimeout(function () {
  1555         banner.animate({opacity: 0}, {duration: 1500, complete: onFadeOut});
  1556       }, 5000);
  1557     };
  1559     banner.animate({opacity: 0.7}, {duration: 1500, complete: onFadeIn});
  1561 };
  1563 // ----------
  1564 UI.init();

mercurial