browser/components/tabview/ui.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial