browser/components/tabview/ui.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/tabview/ui.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1564 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +// **********
     1.9 +// Title: ui.js
    1.10 +
    1.11 +let Keys = { meta: false };
    1.12 +
    1.13 +// ##########
    1.14 +// Class: UI
    1.15 +// Singleton top-level UI manager.
    1.16 +let UI = {
    1.17 +  // Variable: _frameInitialized
    1.18 +  // True if the Tab View UI frame has been initialized.
    1.19 +  _frameInitialized: false,
    1.20 +
    1.21 +  // Variable: _pageBounds
    1.22 +  // Stores the page bounds.
    1.23 +  _pageBounds: null,
    1.24 +
    1.25 +  // Variable: _closedLastVisibleTab
    1.26 +  // If true, the last visible tab has just been closed in the tab strip.
    1.27 +  _closedLastVisibleTab: false,
    1.28 +
    1.29 +  // Variable: _closedSelectedTabInTabView
    1.30 +  // If true, a select tab has just been closed in TabView.
    1.31 +  _closedSelectedTabInTabView: false,
    1.32 +
    1.33 +  // Variable: restoredClosedTab
    1.34 +  // If true, a closed tab has just been restored.
    1.35 +  restoredClosedTab: false,
    1.36 +
    1.37 +  // Variable: _isChangingVisibility
    1.38 +  // Tracks whether we're currently in the process of showing/hiding the tabview.
    1.39 +  _isChangingVisibility: false,
    1.40 +
    1.41 +  // Variable: _reorderTabItemsOnShow
    1.42 +  // Keeps track of the <GroupItem>s which their tab items' tabs have been moved
    1.43 +  // and re-orders the tab items when switching to TabView.
    1.44 +  _reorderTabItemsOnShow: [],
    1.45 +
    1.46 +  // Variable: _reorderTabsOnHide
    1.47 +  // Keeps track of the <GroupItem>s which their tab items have been moved in
    1.48 +  // TabView UI and re-orders the tabs when switcing back to main browser.
    1.49 +  _reorderTabsOnHide: [],
    1.50 +
    1.51 +  // Variable: _currentTab
    1.52 +  // Keeps track of which xul:tab we are currently on.
    1.53 +  // Used to facilitate zooming down from a previous tab.
    1.54 +  _currentTab: null,
    1.55 +
    1.56 +  // Variable: _eventListeners
    1.57 +  // Keeps track of event listeners added to the AllTabs object.
    1.58 +  _eventListeners: {},
    1.59 +
    1.60 +  // Variable: _cleanupFunctions
    1.61 +  // An array of functions to be called at uninit time
    1.62 +  _cleanupFunctions: [],
    1.63 +  
    1.64 +  // Constant: _maxInteractiveWait
    1.65 +  // If the UI is in the middle of an operation, this is the max amount of
    1.66 +  // milliseconds to wait between input events before we no longer consider
    1.67 +  // the operation interactive.
    1.68 +  _maxInteractiveWait: 250,
    1.69 +
    1.70 +  // Variable: _storageBusy
    1.71 +  // Tells whether the storage is currently busy or not.
    1.72 +  _storageBusy: false,
    1.73 +
    1.74 +  // Variable: isDOMWindowClosing
    1.75 +  // Tells wether the parent window is about to close
    1.76 +  isDOMWindowClosing: false,
    1.77 +
    1.78 +  // Variable: _browserKeys
    1.79 +  // Used to keep track of allowed browser keys.
    1.80 +  _browserKeys: null,
    1.81 +
    1.82 +  // Variable: _browserKeysWithShift
    1.83 +  // Used to keep track of allowed browser keys with Shift key combination.
    1.84 +  _browserKeysWithShift: null,
    1.85 +
    1.86 +  // Variable: ignoreKeypressForSearch
    1.87 +  // Used to prevent keypress being handled after quitting search mode.
    1.88 +  ignoreKeypressForSearch: false,
    1.89 +
    1.90 +  // Variable: _lastOpenedTab
    1.91 +  // Used to keep track of the last opened tab.
    1.92 +  _lastOpenedTab: null,
    1.93 +
    1.94 +  // Variable: _originalSmoothScroll
    1.95 +  // Used to keep track of the tab strip smooth scroll value.
    1.96 +  _originalSmoothScroll: null,
    1.97 +
    1.98 +  // ----------
    1.99 +  // Function: toString
   1.100 +  // Prints [UI] for debug use
   1.101 +  toString: function UI_toString() {
   1.102 +    return "[UI]";
   1.103 +  },
   1.104 +
   1.105 +  // ----------
   1.106 +  // Function: init
   1.107 +  // Must be called after the object is created.
   1.108 +  init: function UI_init() {
   1.109 +    try {
   1.110 +      let self = this;
   1.111 +
   1.112 +      // initialize the direction of the page
   1.113 +      this._initPageDirection();
   1.114 +
   1.115 +      // ___ storage
   1.116 +      Storage.init();
   1.117 +
   1.118 +      if (Storage.readWindowBusyState(gWindow))
   1.119 +        this.storageBusy();
   1.120 +
   1.121 +      let data = Storage.readUIData(gWindow);
   1.122 +      this._storageSanity(data);
   1.123 +      this._pageBounds = data.pageBounds;
   1.124 +
   1.125 +      // ___ search
   1.126 +      Search.init();
   1.127 +
   1.128 +      Telemetry.init();
   1.129 +
   1.130 +      // ___ currentTab
   1.131 +      this._currentTab = gBrowser.selectedTab;
   1.132 +
   1.133 +      // ___ exit button
   1.134 +      iQ("#exit-button").click(function() {
   1.135 +        self.exit();
   1.136 +        self.blurAll();
   1.137 +      })
   1.138 +      .attr("title", tabviewString("button.exitTabGroups"));
   1.139 +
   1.140 +      // When you click on the background/empty part of TabView,
   1.141 +      // we create a new groupItem.
   1.142 +      iQ(gTabViewFrame.contentDocument).mousedown(function(e) {
   1.143 +        if (iQ(":focus").length > 0) {
   1.144 +          iQ(":focus").each(function(element) {
   1.145 +            // don't fire blur event if the same input element is clicked.
   1.146 +            if (e.target != element && element.nodeName == "INPUT")
   1.147 +              element.blur();
   1.148 +          });
   1.149 +        }
   1.150 +        if (e.originalTarget.id == "content" &&
   1.151 +            Utils.isLeftClick(e) &&
   1.152 +            e.detail == 1) {
   1.153 +          self._createGroupItemOnDrag(e);
   1.154 +        }
   1.155 +      });
   1.156 +
   1.157 +      iQ(gTabViewFrame.contentDocument).dblclick(function(e) {
   1.158 +        if (e.originalTarget.id != "content")
   1.159 +          return;
   1.160 +
   1.161 +        // Create a group with one tab on double click
   1.162 +        let box =
   1.163 +          new Rect(e.clientX - Math.floor(TabItems.tabWidth/2),
   1.164 +                   e.clientY - Math.floor(TabItems.tabHeight/2),
   1.165 +                   TabItems.tabWidth, TabItems.tabHeight);
   1.166 +        box.inset(-30, -30);
   1.167 +
   1.168 +        let opts = {immediately: true, bounds: box};
   1.169 +        let groupItem = new GroupItem([], opts);
   1.170 +        groupItem.newTab();
   1.171 +
   1.172 +        gTabView.firstUseExperienced = true;
   1.173 +      });
   1.174 +
   1.175 +      iQ(window).bind("unload", function() {
   1.176 +        self.uninit();
   1.177 +      });
   1.178 +
   1.179 +      // ___ setup DOMWillOpenModalDialog message handler
   1.180 +      let mm = gWindow.messageManager;
   1.181 +      let callback = this._onDOMWillOpenModalDialog.bind(this);
   1.182 +      mm.addMessageListener("Panorama:DOMWillOpenModalDialog", callback);
   1.183 +
   1.184 +      this._cleanupFunctions.push(function () {
   1.185 +        mm.removeMessageListener("Panorama:DOMWillOpenModalDialog", callback);
   1.186 +      });
   1.187 +
   1.188 +      // ___ setup key handlers
   1.189 +      this._setTabViewFrameKeyHandlers();
   1.190 +
   1.191 +      // ___ add tab action handlers
   1.192 +      this._addTabActionHandlers();
   1.193 +
   1.194 +      // ___ groups
   1.195 +      GroupItems.init();
   1.196 +      GroupItems.pauseArrange();
   1.197 +      let hasGroupItemsData = GroupItems.load();
   1.198 +
   1.199 +      // ___ tabs
   1.200 +      TabItems.init();
   1.201 +      TabItems.pausePainting();
   1.202 +
   1.203 +      // ___ favicons
   1.204 +      FavIcons.init();
   1.205 +
   1.206 +      if (!hasGroupItemsData)
   1.207 +        this.reset();
   1.208 +
   1.209 +      // ___ resizing
   1.210 +      if (this._pageBounds)
   1.211 +        this._resize(true);
   1.212 +      else
   1.213 +        this._pageBounds = Items.getPageBounds();
   1.214 +
   1.215 +      iQ(window).resize(function() {
   1.216 +        self._resize();
   1.217 +      });
   1.218 +
   1.219 +      // ___ setup event listener to save canvas images
   1.220 +      let onWindowClosing = function () {
   1.221 +        gWindow.removeEventListener("SSWindowClosing", onWindowClosing, false);
   1.222 +
   1.223 +        // XXX bug #635975 - don't unlink the tab if the dom window is closing.
   1.224 +        self.isDOMWindowClosing = true;
   1.225 +
   1.226 +        if (self.isTabViewVisible())
   1.227 +          GroupItems.removeHiddenGroups();
   1.228 +
   1.229 +        TabItems.saveAll();
   1.230 +
   1.231 +        self._save();
   1.232 +      };
   1.233 +
   1.234 +      gWindow.addEventListener("SSWindowClosing", onWindowClosing);
   1.235 +      this._cleanupFunctions.push(function () {
   1.236 +        gWindow.removeEventListener("SSWindowClosing", onWindowClosing);
   1.237 +      });
   1.238 +
   1.239 +      // ___ load frame script
   1.240 +      let frameScript = "chrome://browser/content/tabview-content.js";
   1.241 +      gWindow.messageManager.loadFrameScript(frameScript, true);
   1.242 +
   1.243 +      // ___ Done
   1.244 +      this._frameInitialized = true;
   1.245 +      this._save();
   1.246 +
   1.247 +      // fire an iframe initialized event so everyone knows tab view is 
   1.248 +      // initialized.
   1.249 +      let event = document.createEvent("Events");
   1.250 +      event.initEvent("tabviewframeinitialized", true, false);
   1.251 +      dispatchEvent(event);
   1.252 +    } catch(e) {
   1.253 +      Utils.log(e);
   1.254 +    } finally {
   1.255 +      GroupItems.resumeArrange();
   1.256 +    }
   1.257 +  },
   1.258 +
   1.259 +  // Function: uninit
   1.260 +  // Should be called when window is unloaded.
   1.261 +  uninit: function UI_uninit() {
   1.262 +    // call our cleanup functions
   1.263 +    this._cleanupFunctions.forEach(function(func) {
   1.264 +      func();
   1.265 +    });
   1.266 +    this._cleanupFunctions = [];
   1.267 +
   1.268 +    // additional clean up
   1.269 +    TabItems.uninit();
   1.270 +    GroupItems.uninit();
   1.271 +    FavIcons.uninit();
   1.272 +    Storage.uninit();
   1.273 +    Telemetry.uninit();
   1.274 +
   1.275 +    this._removeTabActionHandlers();
   1.276 +    this._currentTab = null;
   1.277 +    this._pageBounds = null;
   1.278 +    this._reorderTabItemsOnShow = null;
   1.279 +    this._reorderTabsOnHide = null;
   1.280 +    this._frameInitialized = false;
   1.281 +  },
   1.282 +
   1.283 +  // Property: rtl
   1.284 +  // Returns true if we are in RTL mode, false otherwise
   1.285 +  rtl: false,
   1.286 +
   1.287 +  // Function: reset
   1.288 +  // Resets the Panorama view to have just one group with all tabs
   1.289 +  reset: function UI_reset() {
   1.290 +    let padding = Trenches.defaultRadius;
   1.291 +    let welcomeWidth = 300;
   1.292 +    let pageBounds = Items.getPageBounds();
   1.293 +    pageBounds.inset(padding, padding);
   1.294 +
   1.295 +    let $actions = iQ("#actions");
   1.296 +    if ($actions) {
   1.297 +      pageBounds.width -= $actions.width();
   1.298 +      if (UI.rtl)
   1.299 +        pageBounds.left += $actions.width() - padding;
   1.300 +    }
   1.301 +
   1.302 +    // ___ make a fresh groupItem
   1.303 +    let box = new Rect(pageBounds);
   1.304 +    box.width = Math.min(box.width * 0.667,
   1.305 +                         pageBounds.width - (welcomeWidth + padding));
   1.306 +    box.height = box.height * 0.667;
   1.307 +    if (UI.rtl) {
   1.308 +      box.left = pageBounds.left + welcomeWidth + 2 * padding;
   1.309 +    }
   1.310 +
   1.311 +    GroupItems.groupItems.forEach(function(group) {
   1.312 +      group.close();
   1.313 +    });
   1.314 +    
   1.315 +    let options = {
   1.316 +      bounds: box,
   1.317 +      immediately: true
   1.318 +    };
   1.319 +    let groupItem = new GroupItem([], options);
   1.320 +    let items = TabItems.getItems();
   1.321 +    items.forEach(function(item) {
   1.322 +      if (item.parent)
   1.323 +        item.parent.remove(item);
   1.324 +      groupItem.add(item, {immediately: true});
   1.325 +    });
   1.326 +    this.setActive(groupItem);
   1.327 +  },
   1.328 +
   1.329 +  // ----------
   1.330 +  // Function: blurAll
   1.331 +  // Blurs any currently focused element
   1.332 +  blurAll: function UI_blurAll() {
   1.333 +    iQ(":focus").each(function(element) {
   1.334 +      element.blur();
   1.335 +    });
   1.336 +  },
   1.337 +
   1.338 +  // ----------
   1.339 +  // Function: isIdle
   1.340 +  // Returns true if the last interaction was long enough ago to consider the
   1.341 +  // UI idle. Used to determine whether interactivity would be sacrificed if 
   1.342 +  // the CPU was to become busy.
   1.343 +  //
   1.344 +  isIdle: function UI_isIdle() {
   1.345 +    let time = Date.now();
   1.346 +    let maxEvent = Math.max(drag.lastMoveTime, resize.lastMoveTime);
   1.347 +    return (time - maxEvent) > this._maxInteractiveWait;
   1.348 +  },
   1.349 +
   1.350 +  // ----------
   1.351 +  // Function: getActiveTab
   1.352 +  // Returns the currently active tab as a <TabItem>
   1.353 +  getActiveTab: function UI_getActiveTab() {
   1.354 +    return this._activeTab;
   1.355 +  },
   1.356 +
   1.357 +  // ----------
   1.358 +  // Function: _setActiveTab
   1.359 +  // Sets the currently active tab. The idea of a focused tab is useful
   1.360 +  // for keyboard navigation and returning to the last zoomed-in tab.
   1.361 +  // Hitting return/esc brings you to the focused tab, and using the
   1.362 +  // arrow keys lets you navigate between open tabs.
   1.363 +  //
   1.364 +  // Parameters:
   1.365 +  //  - Takes a <TabItem>
   1.366 +  _setActiveTab: function UI__setActiveTab(tabItem) {
   1.367 +    if (tabItem == this._activeTab)
   1.368 +      return;
   1.369 +
   1.370 +    if (this._activeTab) {
   1.371 +      this._activeTab.makeDeactive();
   1.372 +      this._activeTab.removeSubscriber("close", this._onActiveTabClosed);
   1.373 +    }
   1.374 +
   1.375 +    this._activeTab = tabItem;
   1.376 +
   1.377 +    if (this._activeTab) {
   1.378 +      this._activeTab.addSubscriber("close", this._onActiveTabClosed);
   1.379 +      this._activeTab.makeActive();
   1.380 +    }
   1.381 +  },
   1.382 +
   1.383 +  // ----------
   1.384 +  // Function: _onActiveTabClosed
   1.385 +  // Handles when the currently active tab gets closed.
   1.386 +  //
   1.387 +  // Parameters:
   1.388 +  //  - the <TabItem> that is closed
   1.389 +  _onActiveTabClosed: function UI__onActiveTabClosed(tabItem){
   1.390 +    if (UI._activeTab == tabItem)
   1.391 +      UI._setActiveTab(null);
   1.392 +  },
   1.393 +
   1.394 +  // ----------
   1.395 +  // Function: setActive
   1.396 +  // Sets the active tab item or group item
   1.397 +  // Parameters:
   1.398 +  //
   1.399 +  // options
   1.400 +  //  dontSetActiveTabInGroup bool for not setting active tab in group
   1.401 +  setActive: function UI_setActive(item, options) {
   1.402 +    Utils.assert(item, "item must be given");
   1.403 +
   1.404 +    if (item.isATabItem) {
   1.405 +      if (item.parent)
   1.406 +        GroupItems.setActiveGroupItem(item.parent);
   1.407 +      if (!options || !options.dontSetActiveTabInGroup)
   1.408 +        this._setActiveTab(item);
   1.409 +    } else {
   1.410 +      GroupItems.setActiveGroupItem(item);
   1.411 +      if (!options || !options.dontSetActiveTabInGroup) {
   1.412 +        let activeTab = item.getActiveTab();
   1.413 +        if (activeTab)
   1.414 +          this._setActiveTab(activeTab);
   1.415 +      }
   1.416 +    }
   1.417 +  },
   1.418 +
   1.419 +  // ----------
   1.420 +  // Function: clearActiveTab
   1.421 +  // Sets the active tab to 'null'.
   1.422 +  clearActiveTab: function UI_clearActiveTab() {
   1.423 +    this._setActiveTab(null);
   1.424 +  },
   1.425 +
   1.426 +  // ----------
   1.427 +  // Function: isTabViewVisible
   1.428 +  // Returns true if the TabView UI is currently shown.
   1.429 +  isTabViewVisible: function UI_isTabViewVisible() {
   1.430 +    return gTabViewDeck.selectedPanel == gTabViewFrame;
   1.431 +  },
   1.432 +
   1.433 +  // ---------
   1.434 +  // Function: _initPageDirection
   1.435 +  // Initializes the page base direction
   1.436 +  _initPageDirection: function UI__initPageDirection() {
   1.437 +    let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
   1.438 +                    getService(Ci.nsIXULChromeRegistry);
   1.439 +    let dir = chromeReg.isLocaleRTL("global");
   1.440 +    document.documentElement.setAttribute("dir", dir ? "rtl" : "ltr");
   1.441 +    this.rtl = dir;
   1.442 +  },
   1.443 +
   1.444 +  // ----------
   1.445 +  // Function: showTabView
   1.446 +  // Shows TabView and hides the main browser UI.
   1.447 +  // Parameters:
   1.448 +  //   zoomOut - true for zoom out animation, false for nothing.
   1.449 +  showTabView: function UI_showTabView(zoomOut) {
   1.450 +    if (this.isTabViewVisible() || this._isChangingVisibility)
   1.451 +      return;
   1.452 +
   1.453 +    this._isChangingVisibility = true;
   1.454 +
   1.455 +    // store tab strip smooth scroll value and disable it.
   1.456 +    let tabStrip = gBrowser.tabContainer.mTabstrip;
   1.457 +    this._originalSmoothScroll = tabStrip.smoothScroll;
   1.458 +    tabStrip.smoothScroll = false;
   1.459 +
   1.460 +    // initialize the direction of the page
   1.461 +    this._initPageDirection();
   1.462 +
   1.463 +    var self = this;
   1.464 +    var currentTab = this._currentTab;
   1.465 +
   1.466 +    this._reorderTabItemsOnShow.forEach(function(groupItem) {
   1.467 +      groupItem.reorderTabItemsBasedOnTabOrder();
   1.468 +    });
   1.469 +    this._reorderTabItemsOnShow = [];
   1.470 +
   1.471 +#ifdef XP_WIN
   1.472 +    // Restore the full height when showing TabView
   1.473 +    gTabViewFrame.style.marginTop = "";
   1.474 +#endif
   1.475 +    gTabViewDeck.selectedPanel = gTabViewFrame;
   1.476 +    gWindow.TabsInTitlebar.allowedBy("tabview-open", false);
   1.477 +    gTabViewFrame.contentWindow.focus();
   1.478 +
   1.479 +    gBrowser.updateTitlebar();
   1.480 +#ifdef XP_MACOSX
   1.481 +    this.setTitlebarColors(true);
   1.482 +#endif
   1.483 +    let event = document.createEvent("Events");
   1.484 +    event.initEvent("tabviewshown", true, false);
   1.485 +
   1.486 +    Storage.saveVisibilityData(gWindow, "true");
   1.487 +
   1.488 +    if (zoomOut && currentTab && currentTab._tabViewTabItem) {
   1.489 +      let item = currentTab._tabViewTabItem;
   1.490 +      // If there was a previous currentTab we want to animate
   1.491 +      // its thumbnail (canvas) for the zoom out.
   1.492 +      // Note that we start the animation on the chrome thread.
   1.493 +
   1.494 +      // Zoom out!
   1.495 +      item.zoomOut(function() {
   1.496 +        if (!currentTab._tabViewTabItem) // if the tab's been destroyed
   1.497 +          item = null;
   1.498 +
   1.499 +        self.setActive(item);
   1.500 +
   1.501 +        self._resize(true);
   1.502 +        self._isChangingVisibility = false;
   1.503 +        dispatchEvent(event);
   1.504 +
   1.505 +        // Flush pending updates
   1.506 +        GroupItems.flushAppTabUpdates();
   1.507 +
   1.508 +        TabItems.resumePainting();
   1.509 +      });
   1.510 +    } else {
   1.511 +      if (!currentTab || !currentTab._tabViewTabItem)
   1.512 +        self.clearActiveTab();
   1.513 +      self._isChangingVisibility = false;
   1.514 +      dispatchEvent(event);
   1.515 +
   1.516 +      // Flush pending updates
   1.517 +      GroupItems.flushAppTabUpdates();
   1.518 +
   1.519 +      TabItems.resumePainting();
   1.520 +    }
   1.521 +
   1.522 +    if (gTabView.firstUseExperienced)
   1.523 +      gTabView.enableSessionRestore();
   1.524 +  },
   1.525 +
   1.526 +  // ----------
   1.527 +  // Function: hideTabView
   1.528 +  // Hides TabView and shows the main browser UI.
   1.529 +  hideTabView: function UI_hideTabView() {
   1.530 +    if (!this.isTabViewVisible() || this._isChangingVisibility)
   1.531 +      return;
   1.532 +
   1.533 +    // another tab might be select if user decides to stay on a page when
   1.534 +    // a onclose confirmation prompts.
   1.535 +    GroupItems.removeHiddenGroups();
   1.536 +
   1.537 +    // We need to set this after removing the hidden groups because doing so
   1.538 +    // might show prompts which will cause us to be called again, and we'd get
   1.539 +    // stuck if we prevent re-entrancy before doing that.
   1.540 +    this._isChangingVisibility = true;
   1.541 +
   1.542 +    TabItems.pausePainting();
   1.543 +
   1.544 +    this._reorderTabsOnHide.forEach(function(groupItem) {
   1.545 +      groupItem.reorderTabsBasedOnTabItemOrder();
   1.546 +    });
   1.547 +    this._reorderTabsOnHide = [];
   1.548 +
   1.549 +#ifdef XP_WIN
   1.550 +    // Push the top of TabView frame to behind the tabbrowser, so glass can show
   1.551 +    // XXX bug 586679: avoid shrinking the iframe and squishing iframe contents
   1.552 +    // as well as avoiding the flash of black as we animate out
   1.553 +    gTabViewFrame.style.marginTop = gBrowser.boxObject.y + "px";
   1.554 +#endif
   1.555 +    gTabViewDeck.selectedPanel = gBrowserPanel;
   1.556 +    gWindow.TabsInTitlebar.allowedBy("tabview-open", true);
   1.557 +    gBrowser.selectedBrowser.focus();
   1.558 +
   1.559 +    gBrowser.updateTitlebar();
   1.560 +    gBrowser.tabContainer.mTabstrip.smoothScroll = this._originalSmoothScroll;
   1.561 +#ifdef XP_MACOSX
   1.562 +    this.setTitlebarColors(false);
   1.563 +#endif
   1.564 +    Storage.saveVisibilityData(gWindow, "false");
   1.565 +
   1.566 +    this._isChangingVisibility = false;
   1.567 +
   1.568 +    let event = document.createEvent("Events");
   1.569 +    event.initEvent("tabviewhidden", true, false);
   1.570 +    dispatchEvent(event);
   1.571 +  },
   1.572 +
   1.573 +#ifdef XP_MACOSX
   1.574 +  // ----------
   1.575 +  // Function: setTitlebarColors
   1.576 +  // Used on the Mac to make the title bar match the gradient in the rest of the
   1.577 +  // TabView UI.
   1.578 +  //
   1.579 +  // Parameters:
   1.580 +  //   colors - (bool or object) true for the special TabView color, false for
   1.581 +  //         the normal color, and an object with "active" and "inactive"
   1.582 +  //         properties to specify directly.
   1.583 +  setTitlebarColors: function UI_setTitlebarColors(colors) {
   1.584 +    // Mac Only
   1.585 +    var mainWindow = gWindow.document.getElementById("main-window");
   1.586 +    if (colors === true) {
   1.587 +      mainWindow.setAttribute("activetitlebarcolor", "#C4C4C4");
   1.588 +      mainWindow.setAttribute("inactivetitlebarcolor", "#EDEDED");
   1.589 +    } else if (colors && "active" in colors && "inactive" in colors) {
   1.590 +      mainWindow.setAttribute("activetitlebarcolor", colors.active);
   1.591 +      mainWindow.setAttribute("inactivetitlebarcolor", colors.inactive);
   1.592 +    } else {
   1.593 +      mainWindow.removeAttribute("activetitlebarcolor");
   1.594 +      mainWindow.removeAttribute("inactivetitlebarcolor");
   1.595 +    }
   1.596 +  },
   1.597 +#endif
   1.598 +
   1.599 +  // ----------
   1.600 +  // Function: storageBusy
   1.601 +  // Pauses the storage activity that conflicts with sessionstore updates.
   1.602 +  // Calls can be nested.
   1.603 +  storageBusy: function UI_storageBusy() {
   1.604 +    if (this._storageBusy)
   1.605 +      return;
   1.606 +
   1.607 +    this._storageBusy = true;
   1.608 +
   1.609 +    TabItems.pauseReconnecting();
   1.610 +    GroupItems.pauseAutoclose();
   1.611 +  },
   1.612 +  
   1.613 +  // ----------
   1.614 +  // Function: storageReady
   1.615 +  // Resumes the activity paused by storageBusy, and updates for any new group
   1.616 +  // information in sessionstore. Calls can be nested. 
   1.617 +  storageReady: function UI_storageReady() {
   1.618 +    if (!this._storageBusy)
   1.619 +      return;
   1.620 +
   1.621 +    this._storageBusy = false;
   1.622 +
   1.623 +    let hasGroupItemsData = GroupItems.load();
   1.624 +    if (!hasGroupItemsData)
   1.625 +      this.reset();
   1.626 +
   1.627 +    TabItems.resumeReconnecting();
   1.628 +    GroupItems._updateTabBar();
   1.629 +    GroupItems.resumeAutoclose();
   1.630 +  },
   1.631 +
   1.632 +  // ----------
   1.633 +  // Function: _addTabActionHandlers
   1.634 +  // Adds handlers to handle tab actions.
   1.635 +  _addTabActionHandlers: function UI__addTabActionHandlers() {
   1.636 +    var self = this;
   1.637 +
   1.638 +    // session restore events
   1.639 +    function handleSSWindowStateBusy() {
   1.640 +      self.storageBusy();
   1.641 +    }
   1.642 +    
   1.643 +    function handleSSWindowStateReady() {
   1.644 +      self.storageReady();
   1.645 +    }
   1.646 +    
   1.647 +    gWindow.addEventListener("SSWindowStateBusy", handleSSWindowStateBusy, false);
   1.648 +    gWindow.addEventListener("SSWindowStateReady", handleSSWindowStateReady, false);
   1.649 +
   1.650 +    this._cleanupFunctions.push(function() {
   1.651 +      gWindow.removeEventListener("SSWindowStateBusy", handleSSWindowStateBusy, false);
   1.652 +      gWindow.removeEventListener("SSWindowStateReady", handleSSWindowStateReady, false);
   1.653 +    });
   1.654 +
   1.655 +    // TabOpen
   1.656 +    this._eventListeners.open = function (event) {
   1.657 +      let tab = event.target;
   1.658 +
   1.659 +      // if it's an app tab, add it to all the group items
   1.660 +      if (tab.pinned)
   1.661 +        GroupItems.addAppTab(tab);
   1.662 +      else if (self.isTabViewVisible() && !self._storageBusyCount)
   1.663 +        self._lastOpenedTab = tab;
   1.664 +    };
   1.665 +    
   1.666 +    // TabClose
   1.667 +    this._eventListeners.close = function (event) {
   1.668 +      let tab = event.target;
   1.669 +
   1.670 +      // if it's an app tab, remove it from all the group items
   1.671 +      if (tab.pinned)
   1.672 +        GroupItems.removeAppTab(tab);
   1.673 +        
   1.674 +      if (self.isTabViewVisible()) {
   1.675 +        // just closed the selected tab in the TabView interface.
   1.676 +        if (self._currentTab == tab)
   1.677 +          self._closedSelectedTabInTabView = true;
   1.678 +      } else {
   1.679 +        // If we're currently in the process of session store update,
   1.680 +        // we don't want to go to the Tab View UI. 
   1.681 +        if (self._storageBusy)
   1.682 +          return;
   1.683 +
   1.684 +        // if not closing the last tab
   1.685 +        if (gBrowser.tabs.length > 1) {
   1.686 +          // Don't return to TabView if there are any app tabs
   1.687 +          for (let a = 0; a < gBrowser._numPinnedTabs; a++) {
   1.688 +            if (Utils.isValidXULTab(gBrowser.tabs[a]))
   1.689 +              return;
   1.690 +          }
   1.691 +
   1.692 +          let groupItem = GroupItems.getActiveGroupItem();
   1.693 +
   1.694 +          // 1) Only go back to the TabView tab when there you close the last
   1.695 +          // tab of a groupItem.
   1.696 +          let closingLastOfGroup = (groupItem && 
   1.697 +              groupItem._children.length == 1 && 
   1.698 +              groupItem._children[0].tab == tab);
   1.699 +
   1.700 +          // 2) When a blank tab is active while restoring a closed tab the
   1.701 +          // blank tab gets removed. The active group is not closed as this is
   1.702 +          // where the restored tab goes. So do not show the TabView.
   1.703 +          let tabItem = tab && tab._tabViewTabItem;
   1.704 +          let closingBlankTabAfterRestore =
   1.705 +            (tabItem && tabItem.isRemovedAfterRestore);
   1.706 +
   1.707 +          if (closingLastOfGroup && !closingBlankTabAfterRestore) {
   1.708 +            // for the tab focus event to pick up.
   1.709 +            self._closedLastVisibleTab = true;
   1.710 +            self.showTabView();
   1.711 +          }
   1.712 +        }
   1.713 +      }
   1.714 +    };
   1.715 +
   1.716 +    // TabMove
   1.717 +    this._eventListeners.move = function (event) {
   1.718 +      let tab = event.target;
   1.719 +
   1.720 +      if (GroupItems.groupItems.length > 0) {
   1.721 +        if (tab.pinned) {
   1.722 +          if (gBrowser._numPinnedTabs > 1)
   1.723 +            GroupItems.arrangeAppTab(tab);
   1.724 +        } else {
   1.725 +          let activeGroupItem = GroupItems.getActiveGroupItem();
   1.726 +          if (activeGroupItem)
   1.727 +            self.setReorderTabItemsOnShow(activeGroupItem);
   1.728 +        }
   1.729 +      }
   1.730 +    };
   1.731 +
   1.732 +    // TabSelect
   1.733 +    this._eventListeners.select = function (event) {
   1.734 +      self.onTabSelect(event.target);
   1.735 +    };
   1.736 +
   1.737 +    // TabPinned
   1.738 +    this._eventListeners.pinned = function (event) {
   1.739 +      let tab = event.target;
   1.740 +
   1.741 +      TabItems.handleTabPin(tab);
   1.742 +      GroupItems.addAppTab(tab);
   1.743 +    };
   1.744 +
   1.745 +    // TabUnpinned
   1.746 +    this._eventListeners.unpinned = function (event) {
   1.747 +      let tab = event.target;
   1.748 +
   1.749 +      TabItems.handleTabUnpin(tab);
   1.750 +      GroupItems.removeAppTab(tab);
   1.751 +
   1.752 +      let groupItem = tab._tabViewTabItem.parent;
   1.753 +      if (groupItem)
   1.754 +        self.setReorderTabItemsOnShow(groupItem);
   1.755 +    };
   1.756 +
   1.757 +    // Actually register the above handlers
   1.758 +    for (let name in this._eventListeners)
   1.759 +      AllTabs.register(name, this._eventListeners[name]);
   1.760 +  },
   1.761 +
   1.762 +  // ----------
   1.763 +  // Function: _removeTabActionHandlers
   1.764 +  // Removes handlers to handle tab actions.
   1.765 +  _removeTabActionHandlers: function UI__removeTabActionHandlers() {
   1.766 +    for (let name in this._eventListeners)
   1.767 +      AllTabs.unregister(name, this._eventListeners[name]);
   1.768 +  },
   1.769 +
   1.770 +  // ----------
   1.771 +  // Function: goToTab
   1.772 +  // Selects the given xul:tab in the browser.
   1.773 +  goToTab: function UI_goToTab(xulTab) {
   1.774 +    // If it's not focused, the onFocus listener would handle it.
   1.775 +    if (xulTab.selected)
   1.776 +      this.onTabSelect(xulTab);
   1.777 +    else
   1.778 +      gBrowser.selectedTab = xulTab;
   1.779 +  },
   1.780 +
   1.781 +  // ----------
   1.782 +  // Function: onTabSelect
   1.783 +  // Called when the user switches from one tab to another outside of the TabView UI.
   1.784 +  onTabSelect: function UI_onTabSelect(tab) {
   1.785 +    this._currentTab = tab;
   1.786 +
   1.787 +    if (this.isTabViewVisible()) {
   1.788 +      // We want to zoom in if:
   1.789 +      // 1) we didn't just restore a tab via Ctrl+Shift+T
   1.790 +      // 2) the currently selected tab is the last created tab and has a tabItem
   1.791 +      if (!this.restoredClosedTab &&
   1.792 +          this._lastOpenedTab == tab && tab._tabViewTabItem) {
   1.793 +        tab._tabViewTabItem.zoomIn(true);
   1.794 +        this._lastOpenedTab = null;
   1.795 +        return;
   1.796 +      }
   1.797 +      if (this._closedLastVisibleTab ||
   1.798 +          (this._closedSelectedTabInTabView && !this.closedLastTabInTabView) ||
   1.799 +          this.restoredClosedTab) {
   1.800 +        if (this.restoredClosedTab) {
   1.801 +          // when the tab view UI is being displayed, update the thumb for the 
   1.802 +          // restored closed tab after the page load
   1.803 +          tab.linkedBrowser.addEventListener("load", function onLoad(event) {
   1.804 +            tab.linkedBrowser.removeEventListener("load", onLoad, true);
   1.805 +            TabItems._update(tab);
   1.806 +          }, true);
   1.807 +        }
   1.808 +        this._closedLastVisibleTab = false;
   1.809 +        this._closedSelectedTabInTabView = false;
   1.810 +        this.closedLastTabInTabView = false;
   1.811 +        this.restoredClosedTab = false;
   1.812 +        return;
   1.813 +      }
   1.814 +    }
   1.815 +    // reset these vars, just in case.
   1.816 +    this._closedLastVisibleTab = false;
   1.817 +    this._closedSelectedTabInTabView = false;
   1.818 +    this.closedLastTabInTabView = false;
   1.819 +    this.restoredClosedTab = false;
   1.820 +    this._lastOpenedTab = null;
   1.821 +
   1.822 +    // if TabView is visible but we didn't just close the last tab or
   1.823 +    // selected tab, show chrome.
   1.824 +    if (this.isTabViewVisible()) {
   1.825 +      // Unhide the group of the tab the user is activating.
   1.826 +      if (tab && tab._tabViewTabItem && tab._tabViewTabItem.parent &&
   1.827 +          tab._tabViewTabItem.parent.hidden)
   1.828 +        tab._tabViewTabItem.parent._unhide({immediately: true});
   1.829 +
   1.830 +      this.hideTabView();
   1.831 +    }
   1.832 +
   1.833 +    // another tab might be selected when hideTabView() is invoked so a
   1.834 +    // validation is needed.
   1.835 +    if (this._currentTab != tab)
   1.836 +      return;
   1.837 +
   1.838 +    let newItem = null;
   1.839 +    // update the tab bar for the new tab's group
   1.840 +    if (tab && tab._tabViewTabItem) {
   1.841 +      if (!TabItems.reconnectingPaused()) {
   1.842 +        newItem = tab._tabViewTabItem;
   1.843 +        GroupItems.updateActiveGroupItemAndTabBar(newItem);
   1.844 +      }
   1.845 +    } else {
   1.846 +      // No tabItem; must be an app tab. Base the tab bar on the current group.
   1.847 +      // If no current group, figure it out based on what's already in the tab
   1.848 +      // bar.
   1.849 +      if (!GroupItems.getActiveGroupItem()) {
   1.850 +        for (let a = 0; a < gBrowser.tabs.length; a++) {
   1.851 +          let theTab = gBrowser.tabs[a];
   1.852 +          if (!theTab.pinned) {
   1.853 +            let tabItem = theTab._tabViewTabItem;
   1.854 +            this.setActive(tabItem.parent);
   1.855 +            break;
   1.856 +          }
   1.857 +        }
   1.858 +      }
   1.859 +
   1.860 +      if (GroupItems.getActiveGroupItem())
   1.861 +        GroupItems._updateTabBar();
   1.862 +    }
   1.863 +  },
   1.864 +
   1.865 +  // ----------
   1.866 +  // Function: _onDOMWillOpenModalDialog
   1.867 +  // Called when a web page is about to show a modal dialog.
   1.868 +  _onDOMWillOpenModalDialog: function UI__onDOMWillOpenModalDialog(cx) {
   1.869 +    if (!this.isTabViewVisible())
   1.870 +      return;
   1.871 +
   1.872 +    let index = gBrowser.browsers.indexOf(cx.target);
   1.873 +    if (index == -1)
   1.874 +      return;
   1.875 +
   1.876 +    let tab = gBrowser.tabs[index];
   1.877 +
   1.878 +    // When TabView is visible, we need to call onTabSelect to make sure that
   1.879 +    // TabView is hidden and that the correct group is activated. When a modal
   1.880 +    // dialog is shown for currently selected tab the onTabSelect event handler
   1.881 +    // is not called, so we need to do it.
   1.882 +    if (tab.selected && this._currentTab == tab)
   1.883 +      this.onTabSelect(tab);
   1.884 +  },
   1.885 +
   1.886 +  // ----------
   1.887 +  // Function: setReorderTabsOnHide
   1.888 +  // Sets the groupItem which the tab items' tabs should be re-ordered when
   1.889 +  // switching to the main browser UI.
   1.890 +  // Parameters:
   1.891 +  //   groupItem - the groupItem which would be used for re-ordering tabs.
   1.892 +  setReorderTabsOnHide: function UI_setReorderTabsOnHide(groupItem) {
   1.893 +    if (this.isTabViewVisible()) {
   1.894 +      var index = this._reorderTabsOnHide.indexOf(groupItem);
   1.895 +      if (index == -1)
   1.896 +        this._reorderTabsOnHide.push(groupItem);
   1.897 +    }
   1.898 +  },
   1.899 +
   1.900 +  // ----------
   1.901 +  // Function: setReorderTabItemsOnShow
   1.902 +  // Sets the groupItem which the tab items should be re-ordered when
   1.903 +  // switching to the tab view UI.
   1.904 +  // Parameters:
   1.905 +  //   groupItem - the groupItem which would be used for re-ordering tab items.
   1.906 +  setReorderTabItemsOnShow: function UI_setReorderTabItemsOnShow(groupItem) {
   1.907 +    if (!this.isTabViewVisible()) {
   1.908 +      var index = this._reorderTabItemsOnShow.indexOf(groupItem);
   1.909 +      if (index == -1)
   1.910 +        this._reorderTabItemsOnShow.push(groupItem);
   1.911 +    }
   1.912 +  },
   1.913 +  
   1.914 +  // ----------
   1.915 +  updateTabButton: function UI_updateTabButton() {
   1.916 +    let exitButton = document.getElementById("exit-button");
   1.917 +    let numberOfGroups = GroupItems.groupItems.length;
   1.918 +
   1.919 +    exitButton.setAttribute("groups", numberOfGroups);
   1.920 +    gTabView.updateGroupNumberBroadcaster(numberOfGroups);
   1.921 +  },
   1.922 +
   1.923 +  // ----------
   1.924 +  // Function: getClosestTab
   1.925 +  // Convenience function to get the next tab closest to the entered position
   1.926 +  getClosestTab: function UI_getClosestTab(tabCenter) {
   1.927 +    let cl = null;
   1.928 +    let clDist;
   1.929 +    TabItems.getItems().forEach(function (item) {
   1.930 +      if (!item.parent || item.parent.hidden)
   1.931 +        return;
   1.932 +      let testDist = tabCenter.distance(item.bounds.center());
   1.933 +      if (cl==null || testDist < clDist) {
   1.934 +        cl = item;
   1.935 +        clDist = testDist;
   1.936 +      }
   1.937 +    });
   1.938 +    return cl;
   1.939 +  },
   1.940 +
   1.941 +  // ----------
   1.942 +  // Function: _setupBrowserKeys
   1.943 +  // Sets up the allowed browser keys using key elements.
   1.944 +  _setupBrowserKeys: function UI__setupKeyWhiteList() {
   1.945 +    let keys = {};
   1.946 +
   1.947 +    [
   1.948 +#ifdef XP_UNIX
   1.949 +      "quitApplication",
   1.950 +#else
   1.951 +      "redo",
   1.952 +#endif
   1.953 +#ifdef XP_MACOSX
   1.954 +      "preferencesCmdMac", "minimizeWindow", "hideThisAppCmdMac",
   1.955 +#endif
   1.956 +      "newNavigator", "newNavigatorTab", "undo", "cut", "copy", "paste", 
   1.957 +      "selectAll", "find"
   1.958 +    ].forEach(function(key) {
   1.959 +      let element = gWindow.document.getElementById("key_" + key);
   1.960 +      let code = element.getAttribute("key").toLocaleLowerCase().charCodeAt(0);
   1.961 +      keys[code] = key;
   1.962 +    });
   1.963 +    this._browserKeys = keys;
   1.964 +
   1.965 +    keys = {};
   1.966 +    // The lower case letters are passed to processBrowserKeys() even with shift 
   1.967 +    // key when stimulating a key press using EventUtils.synthesizeKey() so need 
   1.968 +    // to handle both upper and lower cases here.
   1.969 +    [
   1.970 +#ifdef XP_UNIX
   1.971 +      "redo",
   1.972 +#endif
   1.973 +#ifdef XP_MACOSX
   1.974 +      "fullScreen",
   1.975 +#endif
   1.976 +      "closeWindow", "tabview", "undoCloseTab", "undoCloseWindow"
   1.977 +    ].forEach(function(key) {
   1.978 +      let element = gWindow.document.getElementById("key_" + key);
   1.979 +      let code = element.getAttribute("key").toLocaleLowerCase().charCodeAt(0);
   1.980 +      keys[code] = key;
   1.981 +    });
   1.982 +    this._browserKeysWithShift = keys;
   1.983 +  },
   1.984 +
   1.985 +  // ----------
   1.986 +  // Function: _setTabViewFrameKeyHandlers
   1.987 +  // Sets up the key handlers for navigating between tabs within the TabView UI.
   1.988 +  _setTabViewFrameKeyHandlers: function UI__setTabViewFrameKeyHandlers() {
   1.989 +    let self = this;
   1.990 +
   1.991 +    this._setupBrowserKeys();
   1.992 +
   1.993 +    iQ(window).keyup(function(event) {
   1.994 +      if (!event.metaKey)
   1.995 +        Keys.meta = false;
   1.996 +    });
   1.997 +
   1.998 +    iQ(window).keypress(function(event) {
   1.999 +      if (event.metaKey)
  1.1000 +        Keys.meta = true;
  1.1001 +
  1.1002 +      function processBrowserKeys(evt) {
  1.1003 +        // let any keys with alt to pass through
  1.1004 +        if (evt.altKey)
  1.1005 +          return;
  1.1006 +
  1.1007 +#ifdef XP_MACOSX
  1.1008 +        if (evt.metaKey) {
  1.1009 +#else
  1.1010 +        if (evt.ctrlKey) {
  1.1011 +#endif
  1.1012 +          let preventDefault = true;
  1.1013 +          if (evt.shiftKey) {
  1.1014 +            // when a user presses ctrl+shift+key, upper case letter charCode 
  1.1015 +            // is passed to processBrowserKeys() so converting back to lower 
  1.1016 +            // case charCode before doing the check
  1.1017 +            let lowercaseCharCode =
  1.1018 +              String.fromCharCode(evt.charCode).toLocaleLowerCase().charCodeAt(0);
  1.1019 +            if (lowercaseCharCode in self._browserKeysWithShift) {
  1.1020 +              let key = self._browserKeysWithShift[lowercaseCharCode];
  1.1021 +              if (key == "tabview")
  1.1022 +                self.exit();
  1.1023 +              else
  1.1024 +                preventDefault = false;
  1.1025 +            }
  1.1026 +          } else {
  1.1027 +            if (evt.charCode in self._browserKeys) {
  1.1028 +              let key = self._browserKeys[evt.charCode];
  1.1029 +              if (key == "find")
  1.1030 +                self.enableSearch();
  1.1031 +              else
  1.1032 +                preventDefault = false;
  1.1033 +            }
  1.1034 +          }
  1.1035 +          if (preventDefault) {
  1.1036 +            evt.stopPropagation();
  1.1037 +            evt.preventDefault();
  1.1038 +          }
  1.1039 +        }
  1.1040 +      }
  1.1041 +      if ((iQ(":focus").length > 0 && iQ(":focus")[0].nodeName == "INPUT") ||
  1.1042 +          Search.isEnabled() || self.ignoreKeypressForSearch) {
  1.1043 +        self.ignoreKeypressForSearch = false;
  1.1044 +        processBrowserKeys(event);
  1.1045 +        return;
  1.1046 +      }
  1.1047 +
  1.1048 +      function getClosestTabBy(norm) {
  1.1049 +        if (!self.getActiveTab())
  1.1050 +          return null;
  1.1051 +
  1.1052 +        let activeTab = self.getActiveTab();
  1.1053 +        let activeTabGroup = activeTab.parent;
  1.1054 +        let myCenter = activeTab.bounds.center();
  1.1055 +        let match;
  1.1056 +
  1.1057 +        TabItems.getItems().forEach(function (item) {
  1.1058 +          if (!item.parent.hidden &&
  1.1059 +              (!activeTabGroup.expanded || activeTabGroup.id == item.parent.id)) {
  1.1060 +            let itemCenter = item.bounds.center();
  1.1061 +
  1.1062 +            if (norm(itemCenter, myCenter)) {
  1.1063 +              let itemDist = myCenter.distance(itemCenter);
  1.1064 +              if (!match || match[0] > itemDist)
  1.1065 +                match = [itemDist, item];
  1.1066 +            }
  1.1067 +          }
  1.1068 +        });
  1.1069 +
  1.1070 +        return match && match[1];
  1.1071 +      }
  1.1072 +
  1.1073 +      let preventDefault = true;
  1.1074 +      let activeTab;
  1.1075 +      let activeGroupItem;
  1.1076 +      let norm = null;
  1.1077 +      switch (event.keyCode) {
  1.1078 +        case KeyEvent.DOM_VK_RIGHT:
  1.1079 +          norm = function(a, me){return a.x > me.x};
  1.1080 +          break;
  1.1081 +        case KeyEvent.DOM_VK_LEFT:
  1.1082 +          norm = function(a, me){return a.x < me.x};
  1.1083 +          break;
  1.1084 +        case KeyEvent.DOM_VK_DOWN:
  1.1085 +          norm = function(a, me){return a.y > me.y};
  1.1086 +          break;
  1.1087 +        case KeyEvent.DOM_VK_UP:
  1.1088 +          norm = function(a, me){return a.y < me.y}
  1.1089 +          break;
  1.1090 +      }
  1.1091 +
  1.1092 +      if (norm != null) {
  1.1093 +        let nextTab = getClosestTabBy(norm);
  1.1094 +        if (nextTab) {
  1.1095 +          if (nextTab.isStacked && !nextTab.parent.expanded)
  1.1096 +            nextTab = nextTab.parent.getChild(0);
  1.1097 +          self.setActive(nextTab);
  1.1098 +        }
  1.1099 +      } else {
  1.1100 +        switch(event.keyCode) {
  1.1101 +          case KeyEvent.DOM_VK_ESCAPE:
  1.1102 +            activeGroupItem = GroupItems.getActiveGroupItem();
  1.1103 +            if (activeGroupItem && activeGroupItem.expanded)
  1.1104 +              activeGroupItem.collapse();
  1.1105 +            else
  1.1106 +              self.exit();
  1.1107 +            break;
  1.1108 +          case KeyEvent.DOM_VK_RETURN:
  1.1109 +            activeGroupItem = GroupItems.getActiveGroupItem();
  1.1110 +            if (activeGroupItem) {
  1.1111 +              activeTab = self.getActiveTab();
  1.1112 +
  1.1113 +              if (!activeTab || activeTab.parent != activeGroupItem)
  1.1114 +                activeTab = activeGroupItem.getActiveTab();
  1.1115 +
  1.1116 +              if (activeTab)
  1.1117 +                activeTab.zoomIn();
  1.1118 +              else
  1.1119 +                activeGroupItem.newTab();
  1.1120 +            }
  1.1121 +            break;
  1.1122 +          case KeyEvent.DOM_VK_TAB:
  1.1123 +            // tab/shift + tab to go to the next tab.
  1.1124 +            activeTab = self.getActiveTab();
  1.1125 +            if (activeTab) {
  1.1126 +              let tabItems = (activeTab.parent ? activeTab.parent.getChildren() :
  1.1127 +                              [activeTab]);
  1.1128 +              let length = tabItems.length;
  1.1129 +              let currentIndex = tabItems.indexOf(activeTab);
  1.1130 +
  1.1131 +              if (length > 1) {
  1.1132 +                let newIndex;
  1.1133 +                if (event.shiftKey) {
  1.1134 +                  if (currentIndex == 0)
  1.1135 +                    newIndex = (length - 1);
  1.1136 +                  else
  1.1137 +                    newIndex = (currentIndex - 1);
  1.1138 +                } else {
  1.1139 +                  if (currentIndex == (length - 1))
  1.1140 +                    newIndex = 0;
  1.1141 +                  else
  1.1142 +                    newIndex = (currentIndex + 1);
  1.1143 +                }
  1.1144 +                self.setActive(tabItems[newIndex]);
  1.1145 +              }
  1.1146 +            }
  1.1147 +            break;
  1.1148 +          default:
  1.1149 +            processBrowserKeys(event);
  1.1150 +            preventDefault = false;
  1.1151 +        }
  1.1152 +        if (preventDefault) {
  1.1153 +          event.stopPropagation();
  1.1154 +          event.preventDefault();
  1.1155 +        }
  1.1156 +      }
  1.1157 +    });
  1.1158 +  },
  1.1159 +
  1.1160 +  // ----------
  1.1161 +  // Function: enableSearch
  1.1162 +  // Enables the search feature.
  1.1163 +  enableSearch: function UI_enableSearch() {
  1.1164 +    if (!Search.isEnabled()) {
  1.1165 +      Search.ensureShown();
  1.1166 +      Search.switchToInMode();
  1.1167 +    }
  1.1168 +  },
  1.1169 +
  1.1170 +  // ----------
  1.1171 +  // Function: _createGroupItemOnDrag
  1.1172 +  // Called in response to a mousedown in empty space in the TabView UI;
  1.1173 +  // creates a new groupItem based on the user's drag.
  1.1174 +  _createGroupItemOnDrag: function UI__createGroupItemOnDrag(e) {
  1.1175 +    const minSize = 60;
  1.1176 +    const minMinSize = 15;
  1.1177 +
  1.1178 +    let lastActiveGroupItem = GroupItems.getActiveGroupItem();
  1.1179 +
  1.1180 +    var startPos = { x: e.clientX, y: e.clientY };
  1.1181 +    var phantom = iQ("<div>")
  1.1182 +      .addClass("groupItem phantom activeGroupItem dragRegion")
  1.1183 +      .css({
  1.1184 +        position: "absolute",
  1.1185 +        zIndex: -1,
  1.1186 +        cursor: "default"
  1.1187 +      })
  1.1188 +      .appendTo("body");
  1.1189 +
  1.1190 +    var item = { // a faux-Item
  1.1191 +      container: phantom,
  1.1192 +      isAFauxItem: true,
  1.1193 +      bounds: {},
  1.1194 +      getBounds: function FauxItem_getBounds() {
  1.1195 +        return this.container.bounds();
  1.1196 +      },
  1.1197 +      setBounds: function FauxItem_setBounds(bounds) {
  1.1198 +        this.container.css(bounds);
  1.1199 +      },
  1.1200 +      setZ: function FauxItem_setZ(z) {
  1.1201 +        // don't set a z-index because we want to force it to be low.
  1.1202 +      },
  1.1203 +      setOpacity: function FauxItem_setOpacity(opacity) {
  1.1204 +        this.container.css("opacity", opacity);
  1.1205 +      },
  1.1206 +      // we don't need to pushAway the phantom item at the end, because
  1.1207 +      // when we create a new GroupItem, it'll do the actual pushAway.
  1.1208 +      pushAway: function () {},
  1.1209 +    };
  1.1210 +    item.setBounds(new Rect(startPos.y, startPos.x, 0, 0));
  1.1211 +
  1.1212 +    var dragOutInfo = new Drag(item, e);
  1.1213 +
  1.1214 +    function updateSize(e) {
  1.1215 +      var box = new Rect();
  1.1216 +      box.left = Math.min(startPos.x, e.clientX);
  1.1217 +      box.right = Math.max(startPos.x, e.clientX);
  1.1218 +      box.top = Math.min(startPos.y, e.clientY);
  1.1219 +      box.bottom = Math.max(startPos.y, e.clientY);
  1.1220 +      item.setBounds(box);
  1.1221 +
  1.1222 +      // compute the stationaryCorner
  1.1223 +      var stationaryCorner = "";
  1.1224 +
  1.1225 +      if (startPos.y == box.top)
  1.1226 +        stationaryCorner += "top";
  1.1227 +      else
  1.1228 +        stationaryCorner += "bottom";
  1.1229 +
  1.1230 +      if (startPos.x == box.left)
  1.1231 +        stationaryCorner += "left";
  1.1232 +      else
  1.1233 +        stationaryCorner += "right";
  1.1234 +
  1.1235 +      dragOutInfo.snap(stationaryCorner, false, false); // null for ui, which we don't use anyway.
  1.1236 +
  1.1237 +      box = item.getBounds();
  1.1238 +      if (box.width > minMinSize && box.height > minMinSize &&
  1.1239 +         (box.width > minSize || box.height > minSize))
  1.1240 +        item.setOpacity(1);
  1.1241 +      else
  1.1242 +        item.setOpacity(0.7);
  1.1243 +
  1.1244 +      e.preventDefault();
  1.1245 +    }
  1.1246 +
  1.1247 +    let self = this;
  1.1248 +    function collapse() {
  1.1249 +      let center = phantom.bounds().center();
  1.1250 +      phantom.animate({
  1.1251 +        width: 0,
  1.1252 +        height: 0,
  1.1253 +        top: center.y,
  1.1254 +        left: center.x
  1.1255 +      }, {
  1.1256 +        duration: 300,
  1.1257 +        complete: function() {
  1.1258 +          phantom.remove();
  1.1259 +        }
  1.1260 +      });
  1.1261 +      self.setActive(lastActiveGroupItem);
  1.1262 +    }
  1.1263 +
  1.1264 +    function finalize(e) {
  1.1265 +      iQ(window).unbind("mousemove", updateSize);
  1.1266 +      item.container.removeClass("dragRegion");
  1.1267 +      dragOutInfo.stop();
  1.1268 +      let box = item.getBounds();
  1.1269 +      if (box.width > minMinSize && box.height > minMinSize &&
  1.1270 +         (box.width > minSize || box.height > minSize)) {
  1.1271 +        let opts = {bounds: item.getBounds(), focusTitle: true};
  1.1272 +        let groupItem = new GroupItem([], opts);
  1.1273 +        self.setActive(groupItem);
  1.1274 +        phantom.remove();
  1.1275 +        dragOutInfo = null;
  1.1276 +        gTabView.firstUseExperienced = true;
  1.1277 +      } else {
  1.1278 +        collapse();
  1.1279 +      }
  1.1280 +    }
  1.1281 +
  1.1282 +    iQ(window).mousemove(updateSize)
  1.1283 +    iQ(gWindow).one("mouseup", finalize);
  1.1284 +    e.preventDefault();
  1.1285 +    return false;
  1.1286 +  },
  1.1287 +
  1.1288 +  // ----------
  1.1289 +  // Function: _resize
  1.1290 +  // Update the TabView UI contents in response to a window size change.
  1.1291 +  // Won't do anything if it doesn't deem the resize necessary.
  1.1292 +  // Parameters:
  1.1293 +  //   force - true to update even when "unnecessary"; default false
  1.1294 +  _resize: function UI__resize(force) {
  1.1295 +    if (!this._pageBounds)
  1.1296 +      return;
  1.1297 +
  1.1298 +    // Here are reasons why we *won't* resize:
  1.1299 +    // 1. Panorama isn't visible (in which case we will resize when we do display)
  1.1300 +    // 2. the screen dimensions haven't changed
  1.1301 +    // 3. everything on the screen fits and nothing feels cramped
  1.1302 +    if (!force && !this.isTabViewVisible())
  1.1303 +      return;
  1.1304 +
  1.1305 +    let oldPageBounds = new Rect(this._pageBounds);
  1.1306 +    let newPageBounds = Items.getPageBounds();
  1.1307 +    if (newPageBounds.equals(oldPageBounds))
  1.1308 +      return;
  1.1309 +
  1.1310 +    if (!this.shouldResizeItems())
  1.1311 +      return;
  1.1312 +
  1.1313 +    var items = Items.getTopLevelItems();
  1.1314 +
  1.1315 +    // compute itemBounds: the union of all the top-level items' bounds.
  1.1316 +    var itemBounds = new Rect(this._pageBounds);
  1.1317 +    // We start with pageBounds so that we respect the empty space the user
  1.1318 +    // has left on the page.
  1.1319 +    itemBounds.width = 1;
  1.1320 +    itemBounds.height = 1;
  1.1321 +    items.forEach(function(item) {
  1.1322 +      var bounds = item.getBounds();
  1.1323 +      itemBounds = (itemBounds ? itemBounds.union(bounds) : new Rect(bounds));
  1.1324 +    });
  1.1325 +
  1.1326 +    if (newPageBounds.width < this._pageBounds.width &&
  1.1327 +        newPageBounds.width > itemBounds.width)
  1.1328 +      newPageBounds.width = this._pageBounds.width;
  1.1329 +
  1.1330 +    if (newPageBounds.height < this._pageBounds.height &&
  1.1331 +        newPageBounds.height > itemBounds.height)
  1.1332 +      newPageBounds.height = this._pageBounds.height;
  1.1333 +
  1.1334 +    var wScale;
  1.1335 +    var hScale;
  1.1336 +    if (Math.abs(newPageBounds.width - this._pageBounds.width)
  1.1337 +         > Math.abs(newPageBounds.height - this._pageBounds.height)) {
  1.1338 +      wScale = newPageBounds.width / this._pageBounds.width;
  1.1339 +      hScale = newPageBounds.height / itemBounds.height;
  1.1340 +    } else {
  1.1341 +      wScale = newPageBounds.width / itemBounds.width;
  1.1342 +      hScale = newPageBounds.height / this._pageBounds.height;
  1.1343 +    }
  1.1344 +
  1.1345 +    var scale = Math.min(hScale, wScale);
  1.1346 +    var self = this;
  1.1347 +    var pairs = [];
  1.1348 +    items.forEach(function(item) {
  1.1349 +      var bounds = item.getBounds();
  1.1350 +      bounds.left += (UI.rtl ? -1 : 1) * (newPageBounds.left - self._pageBounds.left);
  1.1351 +      bounds.left *= scale;
  1.1352 +      bounds.width *= scale;
  1.1353 +
  1.1354 +      bounds.top += newPageBounds.top - self._pageBounds.top;
  1.1355 +      bounds.top *= scale;
  1.1356 +      bounds.height *= scale;
  1.1357 +
  1.1358 +      pairs.push({
  1.1359 +        item: item,
  1.1360 +        bounds: bounds
  1.1361 +      });
  1.1362 +    });
  1.1363 +
  1.1364 +    Items.unsquish(pairs);
  1.1365 +
  1.1366 +    pairs.forEach(function(pair) {
  1.1367 +      pair.item.setBounds(pair.bounds, true);
  1.1368 +      pair.item.snap();
  1.1369 +    });
  1.1370 +
  1.1371 +    this._pageBounds = Items.getPageBounds();
  1.1372 +    this._save();
  1.1373 +  },
  1.1374 +  
  1.1375 +  // ----------
  1.1376 +  // Function: shouldResizeItems
  1.1377 +  // Returns whether we should resize the items on the screen, based on whether
  1.1378 +  // the top-level items fit in the screen or not and whether they feel
  1.1379 +  // "cramped" or not.
  1.1380 +  // These computations may be done using cached values. The cache can be
  1.1381 +  // cleared with UI.clearShouldResizeItems().
  1.1382 +  shouldResizeItems: function UI_shouldResizeItems() {
  1.1383 +    let newPageBounds = Items.getPageBounds();
  1.1384 +    
  1.1385 +    // If we don't have cached cached values...
  1.1386 +    if (this._minimalRect === undefined || this._feelsCramped === undefined) {
  1.1387 +
  1.1388 +      // Loop through every top-level Item for two operations:
  1.1389 +      // 1. check if it is feeling "cramped" due to squishing (a technical term),
  1.1390 +      // 2. union its bounds with the minimalRect
  1.1391 +      let feelsCramped = false;
  1.1392 +      let minimalRect = new Rect(0, 0, 1, 1);
  1.1393 +      
  1.1394 +      Items.getTopLevelItems()
  1.1395 +        .forEach(function UI_shouldResizeItems_checkItem(item) {
  1.1396 +          let bounds = new Rect(item.getBounds());
  1.1397 +          feelsCramped = feelsCramped || (item.userSize &&
  1.1398 +            (item.userSize.x > bounds.width || item.userSize.y > bounds.height));
  1.1399 +          bounds.inset(-Trenches.defaultRadius, -Trenches.defaultRadius);
  1.1400 +          minimalRect = minimalRect.union(bounds);
  1.1401 +        });
  1.1402 +      
  1.1403 +      // ensure the minimalRect extends to, but not beyond, the origin
  1.1404 +      minimalRect.left = 0;
  1.1405 +      minimalRect.top  = 0;
  1.1406 +  
  1.1407 +      this._minimalRect = minimalRect;
  1.1408 +      this._feelsCramped = feelsCramped;
  1.1409 +    }
  1.1410 +
  1.1411 +    return this._minimalRect.width > newPageBounds.width ||
  1.1412 +      this._minimalRect.height > newPageBounds.height ||
  1.1413 +      this._feelsCramped;
  1.1414 +  },
  1.1415 +  
  1.1416 +  // ----------
  1.1417 +  // Function: clearShouldResizeItems
  1.1418 +  // Clear the cache of whether we should resize the items on the Panorama
  1.1419 +  // screen, forcing a recomputation on the next UI.shouldResizeItems()
  1.1420 +  // call.
  1.1421 +  clearShouldResizeItems: function UI_clearShouldResizeItems() {
  1.1422 +    delete this._minimalRect;
  1.1423 +    delete this._feelsCramped;
  1.1424 +  },
  1.1425 +
  1.1426 +  // ----------
  1.1427 +  // Function: exit
  1.1428 +  // Exits TabView UI.
  1.1429 +  exit: function UI_exit() {
  1.1430 +    let self = this;
  1.1431 +    let zoomedIn = false;
  1.1432 +
  1.1433 +    if (Search.isEnabled()) {
  1.1434 +      let matcher = Search.createSearchTabMatcher();
  1.1435 +      let matches = matcher.matched();
  1.1436 +
  1.1437 +      if (matches.length > 0) {
  1.1438 +        matches[0].zoomIn();
  1.1439 +        zoomedIn = true;
  1.1440 +      }
  1.1441 +      Search.hide();
  1.1442 +    }
  1.1443 +
  1.1444 +    if (!zoomedIn) {
  1.1445 +      let unhiddenGroups = GroupItems.groupItems.filter(function(groupItem) {
  1.1446 +        return (!groupItem.hidden && groupItem.getChildren().length > 0);
  1.1447 +      });
  1.1448 +      // no pinned tabs and no visible groups: open a new group. open a blank
  1.1449 +      // tab and return
  1.1450 +      if (!unhiddenGroups.length) {
  1.1451 +        let emptyGroups = GroupItems.groupItems.filter(function (groupItem) {
  1.1452 +          return (!groupItem.hidden && !groupItem.getChildren().length);
  1.1453 +        });
  1.1454 +        let group = (emptyGroups.length ? emptyGroups[0] : GroupItems.newGroup());
  1.1455 +        if (!gBrowser._numPinnedTabs) {
  1.1456 +          group.newTab(null, { closedLastTab: true });
  1.1457 +          return;
  1.1458 +        }
  1.1459 +      }
  1.1460 +
  1.1461 +      // If there's an active TabItem, zoom into it. If not (for instance when the
  1.1462 +      // selected tab is an app tab), just go there.
  1.1463 +      let activeTabItem = this.getActiveTab();
  1.1464 +      if (!activeTabItem) {
  1.1465 +        let tabItem = gBrowser.selectedTab._tabViewTabItem;
  1.1466 +        if (tabItem) {
  1.1467 +          if (!tabItem.parent || !tabItem.parent.hidden) {
  1.1468 +            activeTabItem = tabItem;
  1.1469 +          } else { // set active tab item if there is at least one unhidden group
  1.1470 +            if (unhiddenGroups.length > 0)
  1.1471 +              activeTabItem = unhiddenGroups[0].getActiveTab();
  1.1472 +          }
  1.1473 +        }
  1.1474 +      }
  1.1475 +
  1.1476 +      if (activeTabItem) {
  1.1477 +        activeTabItem.zoomIn();
  1.1478 +      } else {
  1.1479 +        if (gBrowser._numPinnedTabs > 0) {
  1.1480 +          if (gBrowser.selectedTab.pinned) {
  1.1481 +            self.goToTab(gBrowser.selectedTab);
  1.1482 +          } else {
  1.1483 +            Array.some(gBrowser.tabs, function(tab) {
  1.1484 +              if (tab.pinned) {
  1.1485 +                self.goToTab(tab);
  1.1486 +                return true;
  1.1487 +              }
  1.1488 +              return false
  1.1489 +            });
  1.1490 +          }
  1.1491 +        }
  1.1492 +      }
  1.1493 +    }
  1.1494 +  },
  1.1495 +
  1.1496 +  // ----------
  1.1497 +  // Function: storageSanity
  1.1498 +  // Given storage data for this object, returns true if it looks valid.
  1.1499 +  _storageSanity: function UI__storageSanity(data) {
  1.1500 +    if (Utils.isEmptyObject(data))
  1.1501 +      return true;
  1.1502 +
  1.1503 +    if (!Utils.isRect(data.pageBounds)) {
  1.1504 +      Utils.log("UI.storageSanity: bad pageBounds", data.pageBounds);
  1.1505 +      data.pageBounds = null;
  1.1506 +      return false;
  1.1507 +    }
  1.1508 +
  1.1509 +    return true;
  1.1510 +  },
  1.1511 +
  1.1512 +  // ----------
  1.1513 +  // Function: _save
  1.1514 +  // Saves the data for this object to persistent storage
  1.1515 +  _save: function UI__save() {
  1.1516 +    if (!this._frameInitialized)
  1.1517 +      return;
  1.1518 +
  1.1519 +    var data = {
  1.1520 +      pageBounds: this._pageBounds
  1.1521 +    };
  1.1522 +
  1.1523 +    if (this._storageSanity(data))
  1.1524 +      Storage.saveUIData(gWindow, data);
  1.1525 +  },
  1.1526 +
  1.1527 +  // ----------
  1.1528 +  // Function: _saveAll
  1.1529 +  // Saves all data associated with TabView.
  1.1530 +  _saveAll: function UI__saveAll() {
  1.1531 +    this._save();
  1.1532 +    GroupItems.saveAll();
  1.1533 +    TabItems.saveAll();
  1.1534 +  },
  1.1535 +
  1.1536 +  // ----------
  1.1537 +  // Function: notifySessionRestoreEnabled
  1.1538 +  // Notify the user that session restore has been automatically enabled
  1.1539 +  // by showing a banner that expects no user interaction. It fades out after
  1.1540 +  // some seconds.
  1.1541 +  notifySessionRestoreEnabled: function UI_notifySessionRestoreEnabled() {
  1.1542 +    let brandBundle = gWindow.document.getElementById("bundle_brand");
  1.1543 +    let brandShortName = brandBundle.getString("brandShortName");
  1.1544 +    let notificationText = tabviewBundle.formatStringFromName(
  1.1545 +      "tabview.notification.sessionStore", [brandShortName], 1);
  1.1546 +
  1.1547 +    let banner = iQ("<div>")
  1.1548 +      .text(notificationText)
  1.1549 +      .addClass("banner")
  1.1550 +      .appendTo("body");
  1.1551 +
  1.1552 +    let onFadeOut = function () {
  1.1553 +      banner.remove();
  1.1554 +    };
  1.1555 +
  1.1556 +    let onFadeIn = function () {
  1.1557 +      setTimeout(function () {
  1.1558 +        banner.animate({opacity: 0}, {duration: 1500, complete: onFadeOut});
  1.1559 +      }, 5000);
  1.1560 +    };
  1.1561 +
  1.1562 +    banner.animate({opacity: 0.7}, {duration: 1500, complete: onFadeIn});
  1.1563 +  }
  1.1564 +};
  1.1565 +
  1.1566 +// ----------
  1.1567 +UI.init();

mercurial