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();