browser/components/tabview/ui.js

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

mercurial