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/.
10 _browserKeyHandlerInitialized: false,
11 _closedLastVisibleTabBeforeFrameInitialized: false,
12 _isFrameLoading: false,
13 _initFrameCallbacks: [],
14 PREF_BRANCH: "browser.panorama.",
15 PREF_FIRST_RUN: "browser.panorama.experienced_first_run",
16 PREF_STARTUP_PAGE: "browser.startup.page",
17 PREF_RESTORE_ENABLED_ONCE: "browser.panorama.session_restore_enabled_once",
18 GROUPS_IDENTIFIER: "tabview-groups",
19 VISIBILITY_IDENTIFIER: "tabview-visibility",
23 delete this.windowTitle;
24 let brandBundle = document.getElementById("bundle_brand");
25 let brandShortName = brandBundle.getString("brandShortName");
26 let title = gNavigatorBundle.getFormattedString("tabview.title", [brandShortName]);
27 return this.windowTitle = title;
31 get firstUseExperienced() {
32 let pref = this.PREF_FIRST_RUN;
33 if (Services.prefs.prefHasUserValue(pref))
34 return Services.prefs.getBoolPref(pref);
40 set firstUseExperienced(val) {
41 Services.prefs.setBoolPref(this.PREF_FIRST_RUN, val);
45 get sessionRestoreEnabledOnce() {
46 let pref = this.PREF_RESTORE_ENABLED_ONCE;
47 if (Services.prefs.prefHasUserValue(pref))
48 return Services.prefs.getBoolPref(pref);
54 set sessionRestoreEnabledOnce(val) {
55 Services.prefs.setBoolPref(this.PREF_RESTORE_ENABLED_ONCE, val);
59 init: function TabView_init() {
60 // disable the ToggleTabView command for popup windows
61 goSetCommandEnabled("Browser:ToggleTabView", window.toolbar.visible);
62 if (!window.toolbar.visible)
65 if (this._initialized)
68 if (this.firstUseExperienced) {
71 let data = SessionStore.getWindowValue(window, this.VISIBILITY_IDENTIFIER);
72 if (data && data == "true") {
76 data = SessionStore.getWindowValue(window, this.GROUPS_IDENTIFIER);
78 let parsedData = JSON.parse(data);
79 this.updateGroupNumberBroadcaster(parsedData.totalNumber || 1);
84 // if a tab is changed from hidden to unhidden and the iframe is not
85 // initialized, load the iframe and setup the tab.
86 this._tabShowEventListener = function(event) {
88 self._initFrame(function() {
89 self._window.UI.onTabSelect(gBrowser.selectedTab);
90 if (self._closedLastVisibleTabBeforeFrameInitialized) {
91 self._closedLastVisibleTabBeforeFrameInitialized = false;
92 self._window.UI.showTabView(false);
96 this._tabCloseEventListener = function(event) {
97 if (!self._window && gBrowser.visibleTabs.length == 0)
98 self._closedLastVisibleTabBeforeFrameInitialized = true;
100 gBrowser.tabContainer.addEventListener(
101 "TabShow", this._tabShowEventListener, false);
102 gBrowser.tabContainer.addEventListener(
103 "TabClose", this._tabCloseEventListener, false);
105 if (this._tabBrowserHasHiddenTabs()) {
106 this._setBrowserKeyHandlers();
108 // for restoring last session and undoing recently closed window
109 this._SSWindowStateReadyListener = function (event) {
110 if (this._tabBrowserHasHiddenTabs())
111 this._setBrowserKeyHandlers();
113 window.addEventListener(
114 "SSWindowStateReady", this._SSWindowStateReadyListener, false);
119 Services.prefs.addObserver(this.PREF_BRANCH, this, false);
121 this._initialized = true;
125 // Observes topic changes.
126 observe: function TabView_observe(subject, topic, data) {
127 if (data == this.PREF_FIRST_RUN && this.firstUseExperienced) {
128 this._addToolbarButton();
129 this.enableSessionRestore();
134 // Uninitializes TabView.
135 uninit: function TabView_uninit() {
136 if (!this._initialized)
139 Services.prefs.removeObserver(this.PREF_BRANCH, this);
141 if (this._tabShowEventListener)
142 gBrowser.tabContainer.removeEventListener(
143 "TabShow", this._tabShowEventListener, false);
145 if (this._tabCloseEventListener)
146 gBrowser.tabContainer.removeEventListener(
147 "TabClose", this._tabCloseEventListener, false);
149 if (this._SSWindowStateReadyListener)
150 window.removeEventListener(
151 "SSWindowStateReady", this._SSWindowStateReadyListener, false);
153 this._initialized = false;
160 this._iframe.remove();
166 // Creates the frame and calls the callback once it's loaded.
167 // If the frame already exists, calls the callback immediately.
168 _initFrame: function TabView__initFrame(callback) {
169 let hasCallback = typeof callback == "function";
171 // prevent frame to be initialized for popup windows
172 if (!window.toolbar.visible)
182 this._initFrameCallbacks.push(callback);
184 if (this._isFrameLoading)
187 this._isFrameLoading = true;
189 TelemetryStopwatch.start("PANORAMA_INITIALIZATION_TIME_MS");
192 this._deck = document.getElementById("tab-view-deck");
194 // ___ create the frame
195 this._iframe = document.createElement("iframe");
196 this._iframe.id = "tab-view";
197 this._iframe.setAttribute("transparent", "true");
198 this._iframe.setAttribute("tooltip", "tab-view-tooltip");
199 this._iframe.flex = 1;
203 window.addEventListener("tabviewframeinitialized", function onInit() {
204 window.removeEventListener("tabviewframeinitialized", onInit, false);
206 TelemetryStopwatch.finish("PANORAMA_INITIALIZATION_TIME_MS");
208 self._isFrameLoading = false;
209 self._window = self._iframe.contentWindow;
210 self._setBrowserKeyHandlers();
212 if (self._tabShowEventListener) {
213 gBrowser.tabContainer.removeEventListener(
214 "TabShow", self._tabShowEventListener, false);
215 self._tabShowEventListener = null;
217 if (self._tabCloseEventListener) {
218 gBrowser.tabContainer.removeEventListener(
219 "TabClose", self._tabCloseEventListener, false);
220 self._tabCloseEventListener = null;
222 if (self._SSWindowStateReadyListener) {
223 window.removeEventListener(
224 "SSWindowStateReady", self._SSWindowStateReadyListener, false);
225 self._SSWindowStateReadyListener = null;
228 self._initFrameCallbacks.forEach(function (cb) cb());
229 self._initFrameCallbacks = [];
232 this._iframe.setAttribute("src", "chrome://browser/content/tabview.html");
233 this._deck.appendChild(this._iframe);
235 // ___ create tooltip
236 let tooltip = document.createElement("tooltip");
237 tooltip.id = "tab-view-tooltip";
238 tooltip.setAttribute("onpopupshowing", "return TabView.fillInTooltip(document.tooltipNode);");
239 document.getElementById("mainPopupSet").appendChild(tooltip);
243 getContentWindow: function TabView_getContentWindow() {
248 isVisible: function TabView_isVisible() {
249 return (this._deck ? this._deck.selectedPanel == this._iframe : false);
253 show: function TabView_show() {
254 if (this.isVisible())
258 this._initFrame(function() {
259 self._window.UI.showTabView(true);
264 hide: function TabView_hide() {
265 if (this.isVisible() && this._window) {
266 this._window.UI.exit();
271 toggle: function TabView_toggle() {
272 if (this.isVisible())
279 _tabBrowserHasHiddenTabs: function TabView_tabBrowserHasHiddenTabs() {
280 return (gBrowser.tabs.length - gBrowser.visibleTabs.length) > 0;
284 updateContextMenu: function TabView_updateContextMenu(tab, popup) {
285 let separator = document.getElementById("context_tabViewNamedGroups");
288 while (popup.firstChild && popup.firstChild != separator)
289 popup.removeChild(popup.firstChild);
292 this._initFrame(function() {
293 let activeGroup = tab._tabViewTabItem.parent;
294 let groupItems = self._window.GroupItems.groupItems;
296 groupItems.forEach(function(groupItem) {
297 // if group has title, it's not hidden and there is no active group or
298 // the active group id doesn't match the group id, a group menu item
300 if (!groupItem.hidden &&
301 (groupItem.getTitle().trim() || groupItem.getChildren().length) &&
302 (!activeGroup || activeGroup.id != groupItem.id)) {
303 let menuItem = self._createGroupMenuItem(groupItem);
304 popup.insertBefore(menuItem, separator);
308 separator.hidden = isEmpty;
313 _createGroupMenuItem: function TabView__createGroupMenuItem(groupItem) {
314 let menuItem = document.createElement("menuitem");
315 let title = groupItem.getTitle();
318 let topChildLabel = groupItem.getTopChild().tab.label;
319 let childNum = groupItem.getChildren().length;
322 let num = childNum - 1;
324 gNavigatorBundle.getString("tabview.moveToUnnamedGroup.label");
325 title = PluralForm.get(num, title).replace("#1", topChildLabel).replace("#2", num);
327 title = topChildLabel;
331 menuItem.setAttribute("label", title);
332 menuItem.setAttribute("tooltiptext", title);
333 menuItem.setAttribute("crop", "center");
334 menuItem.setAttribute("class", "tabview-menuitem");
335 menuItem.setAttribute(
337 "TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')");
343 moveTabTo: function TabView_moveTabTo(tab, groupItemId) {
345 this._window.GroupItems.moveTabToGroupItem(tab, groupItemId);
348 this._initFrame(function() {
349 self._window.GroupItems.moveTabToGroupItem(tab, groupItemId);
355 // Adds new key commands to the browser, for invoking the Tab Candy UI
356 // and for switching between groups of tabs when outside of the Tab Candy UI.
357 _setBrowserKeyHandlers: function TabView__setBrowserKeyHandlers() {
358 if (this._browserKeyHandlerInitialized)
361 this._browserKeyHandlerInitialized = true;
364 window.addEventListener("keypress", function(event) {
365 if (self.isVisible() || !self._tabBrowserHasHiddenTabs())
368 let charCode = event.charCode;
369 // Control (+ Shift) + `
370 if (event.ctrlKey && !event.metaKey && !event.altKey &&
371 (charCode == 96 || charCode == 126)) {
372 event.stopPropagation();
373 event.preventDefault();
375 self._initFrame(function() {
376 let groupItems = self._window.GroupItems;
377 let tabItem = groupItems.getNextGroupItemTab(event.shiftKey);
381 if (gBrowser.selectedTab.pinned)
382 groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true});
384 gBrowser.selectedTab = tabItem.tab;
391 // Prepares the tab view for undo close tab.
392 prepareUndoCloseTab: function TabView_prepareUndoCloseTab(blankTabToRemove) {
394 this._window.UI.restoredClosedTab = true;
396 if (blankTabToRemove && blankTabToRemove._tabViewTabItem)
397 blankTabToRemove._tabViewTabItem.isRemovedAfterRestore = true;
402 // Cleans up the tab view after undo close tab.
403 afterUndoCloseTab: function TabView_afterUndoCloseTab() {
405 this._window.UI.restoredClosedTab = false;
409 // On move to group pop showing.
410 moveToGroupPopupShowing: function TabView_moveToGroupPopupShowing(event) {
411 // Update the context menu only if Panorama was already initialized or if
412 // there are hidden tabs.
413 let numHiddenTabs = gBrowser.tabs.length - gBrowser.visibleTabs.length;
414 if (this._window || numHiddenTabs > 0)
415 this.updateContextMenu(TabContextMenu.contextTab, event.target);
419 // Function: _addToolbarButton
420 // Adds the TabView button to the TabsToolbar.
421 _addToolbarButton: function TabView__addToolbarButton() {
422 let buttonId = "tabview-button";
424 if (CustomizableUI.getPlacementOfWidget(buttonId))
427 let allTabsBtnPlacement = CustomizableUI.getPlacementOfWidget("alltabs-button");
428 // allTabsBtnPlacement can never be null because the button isn't removable
429 let desiredPosition = allTabsBtnPlacement.position + 1;
430 CustomizableUI.addWidgetToArea(buttonId, "TabsToolbar", desiredPosition);
431 // NB: this is for backwards compatibility, and should be removed by
432 // https://bugzilla.mozilla.org/show_bug.cgi?id=976041
433 document.persist("TabsToolbar", "currentset");
437 // Function: updateGroupNumberBroadcaster
438 // Updates the group number broadcaster.
439 updateGroupNumberBroadcaster: function TabView_updateGroupNumberBroadcaster(number) {
440 let groupsNumber = document.getElementById("tabviewGroupsNumber");
441 groupsNumber.setAttribute("groups", number);
445 // Function: enableSessionRestore
446 // Enables automatic session restore when the browser is started. Does
447 // nothing if we already did that once in the past.
448 enableSessionRestore: function TabView_enableSessionRestore() {
449 if (!this._window || !this.firstUseExperienced)
452 // do nothing if we already enabled session restore once
453 if (this.sessionRestoreEnabledOnce)
456 this.sessionRestoreEnabledOnce = true;
458 // enable session restore if necessary
459 if (Services.prefs.getIntPref(this.PREF_STARTUP_PAGE) != 3) {
460 Services.prefs.setIntPref(this.PREF_STARTUP_PAGE, 3);
463 this._window.UI.notifySessionRestoreEnabled();
468 // Function: fillInTooltip
469 // Fills in the tooltip text.
470 fillInTooltip: function fillInTooltip(tipElement) {
472 let titleText = null;
473 let direction = tipElement.ownerDocument.dir;
475 while (!titleText && tipElement) {
476 if (tipElement.nodeType == Node.ELEMENT_NODE)
477 titleText = tipElement.getAttribute("title");
478 tipElement = tipElement.parentNode;
480 let tipNode = document.getElementById("tab-view-tooltip");
481 tipNode.style.direction = direction;
484 tipNode.setAttribute("label", titleText);