michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: let TabView = { michael@0: _deck: null, michael@0: _iframe: null, michael@0: _window: null, michael@0: _initialized: false, michael@0: _browserKeyHandlerInitialized: false, michael@0: _closedLastVisibleTabBeforeFrameInitialized: false, michael@0: _isFrameLoading: false, michael@0: _initFrameCallbacks: [], michael@0: PREF_BRANCH: "browser.panorama.", michael@0: PREF_FIRST_RUN: "browser.panorama.experienced_first_run", michael@0: PREF_STARTUP_PAGE: "browser.startup.page", michael@0: PREF_RESTORE_ENABLED_ONCE: "browser.panorama.session_restore_enabled_once", michael@0: GROUPS_IDENTIFIER: "tabview-groups", michael@0: VISIBILITY_IDENTIFIER: "tabview-visibility", michael@0: michael@0: // ---------- michael@0: get windowTitle() { michael@0: delete this.windowTitle; michael@0: let brandBundle = document.getElementById("bundle_brand"); michael@0: let brandShortName = brandBundle.getString("brandShortName"); michael@0: let title = gNavigatorBundle.getFormattedString("tabview.title", [brandShortName]); michael@0: return this.windowTitle = title; michael@0: }, michael@0: michael@0: // ---------- michael@0: get firstUseExperienced() { michael@0: let pref = this.PREF_FIRST_RUN; michael@0: if (Services.prefs.prefHasUserValue(pref)) michael@0: return Services.prefs.getBoolPref(pref); michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: // ---------- michael@0: set firstUseExperienced(val) { michael@0: Services.prefs.setBoolPref(this.PREF_FIRST_RUN, val); michael@0: }, michael@0: michael@0: // ---------- michael@0: get sessionRestoreEnabledOnce() { michael@0: let pref = this.PREF_RESTORE_ENABLED_ONCE; michael@0: if (Services.prefs.prefHasUserValue(pref)) michael@0: return Services.prefs.getBoolPref(pref); michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: // ---------- michael@0: set sessionRestoreEnabledOnce(val) { michael@0: Services.prefs.setBoolPref(this.PREF_RESTORE_ENABLED_ONCE, val); michael@0: }, michael@0: michael@0: // ---------- michael@0: init: function TabView_init() { michael@0: // disable the ToggleTabView command for popup windows michael@0: goSetCommandEnabled("Browser:ToggleTabView", window.toolbar.visible); michael@0: if (!window.toolbar.visible) michael@0: return; michael@0: michael@0: if (this._initialized) michael@0: return; michael@0: michael@0: if (this.firstUseExperienced) { michael@0: // ___ visibility michael@0: michael@0: let data = SessionStore.getWindowValue(window, this.VISIBILITY_IDENTIFIER); michael@0: if (data && data == "true") { michael@0: this.show(); michael@0: } else { michael@0: try { michael@0: data = SessionStore.getWindowValue(window, this.GROUPS_IDENTIFIER); michael@0: if (data) { michael@0: let parsedData = JSON.parse(data); michael@0: this.updateGroupNumberBroadcaster(parsedData.totalNumber || 1); michael@0: } michael@0: } catch (e) { } michael@0: michael@0: let self = this; michael@0: // if a tab is changed from hidden to unhidden and the iframe is not michael@0: // initialized, load the iframe and setup the tab. michael@0: this._tabShowEventListener = function(event) { michael@0: if (!self._window) michael@0: self._initFrame(function() { michael@0: self._window.UI.onTabSelect(gBrowser.selectedTab); michael@0: if (self._closedLastVisibleTabBeforeFrameInitialized) { michael@0: self._closedLastVisibleTabBeforeFrameInitialized = false; michael@0: self._window.UI.showTabView(false); michael@0: } michael@0: }); michael@0: }; michael@0: this._tabCloseEventListener = function(event) { michael@0: if (!self._window && gBrowser.visibleTabs.length == 0) michael@0: self._closedLastVisibleTabBeforeFrameInitialized = true; michael@0: }; michael@0: gBrowser.tabContainer.addEventListener( michael@0: "TabShow", this._tabShowEventListener, false); michael@0: gBrowser.tabContainer.addEventListener( michael@0: "TabClose", this._tabCloseEventListener, false); michael@0: michael@0: if (this._tabBrowserHasHiddenTabs()) { michael@0: this._setBrowserKeyHandlers(); michael@0: } else { michael@0: // for restoring last session and undoing recently closed window michael@0: this._SSWindowStateReadyListener = function (event) { michael@0: if (this._tabBrowserHasHiddenTabs()) michael@0: this._setBrowserKeyHandlers(); michael@0: }.bind(this); michael@0: window.addEventListener( michael@0: "SSWindowStateReady", this._SSWindowStateReadyListener, false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: Services.prefs.addObserver(this.PREF_BRANCH, this, false); michael@0: michael@0: this._initialized = true; michael@0: }, michael@0: michael@0: // ---------- michael@0: // Observes topic changes. michael@0: observe: function TabView_observe(subject, topic, data) { michael@0: if (data == this.PREF_FIRST_RUN && this.firstUseExperienced) { michael@0: this._addToolbarButton(); michael@0: this.enableSessionRestore(); michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: // Uninitializes TabView. michael@0: uninit: function TabView_uninit() { michael@0: if (!this._initialized) michael@0: return; michael@0: michael@0: Services.prefs.removeObserver(this.PREF_BRANCH, this); michael@0: michael@0: if (this._tabShowEventListener) michael@0: gBrowser.tabContainer.removeEventListener( michael@0: "TabShow", this._tabShowEventListener, false); michael@0: michael@0: if (this._tabCloseEventListener) michael@0: gBrowser.tabContainer.removeEventListener( michael@0: "TabClose", this._tabCloseEventListener, false); michael@0: michael@0: if (this._SSWindowStateReadyListener) michael@0: window.removeEventListener( michael@0: "SSWindowStateReady", this._SSWindowStateReadyListener, false); michael@0: michael@0: this._initialized = false; michael@0: michael@0: if (this._window) { michael@0: this._window = null; michael@0: } michael@0: michael@0: if (this._iframe) { michael@0: this._iframe.remove(); michael@0: this._iframe = null; michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: // Creates the frame and calls the callback once it's loaded. michael@0: // If the frame already exists, calls the callback immediately. michael@0: _initFrame: function TabView__initFrame(callback) { michael@0: let hasCallback = typeof callback == "function"; michael@0: michael@0: // prevent frame to be initialized for popup windows michael@0: if (!window.toolbar.visible) michael@0: return; michael@0: michael@0: if (this._window) { michael@0: if (hasCallback) michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: if (hasCallback) michael@0: this._initFrameCallbacks.push(callback); michael@0: michael@0: if (this._isFrameLoading) michael@0: return; michael@0: michael@0: this._isFrameLoading = true; michael@0: michael@0: TelemetryStopwatch.start("PANORAMA_INITIALIZATION_TIME_MS"); michael@0: michael@0: // ___ find the deck michael@0: this._deck = document.getElementById("tab-view-deck"); michael@0: michael@0: // ___ create the frame michael@0: this._iframe = document.createElement("iframe"); michael@0: this._iframe.id = "tab-view"; michael@0: this._iframe.setAttribute("transparent", "true"); michael@0: this._iframe.setAttribute("tooltip", "tab-view-tooltip"); michael@0: this._iframe.flex = 1; michael@0: michael@0: let self = this; michael@0: michael@0: window.addEventListener("tabviewframeinitialized", function onInit() { michael@0: window.removeEventListener("tabviewframeinitialized", onInit, false); michael@0: michael@0: TelemetryStopwatch.finish("PANORAMA_INITIALIZATION_TIME_MS"); michael@0: michael@0: self._isFrameLoading = false; michael@0: self._window = self._iframe.contentWindow; michael@0: self._setBrowserKeyHandlers(); michael@0: michael@0: if (self._tabShowEventListener) { michael@0: gBrowser.tabContainer.removeEventListener( michael@0: "TabShow", self._tabShowEventListener, false); michael@0: self._tabShowEventListener = null; michael@0: } michael@0: if (self._tabCloseEventListener) { michael@0: gBrowser.tabContainer.removeEventListener( michael@0: "TabClose", self._tabCloseEventListener, false); michael@0: self._tabCloseEventListener = null; michael@0: } michael@0: if (self._SSWindowStateReadyListener) { michael@0: window.removeEventListener( michael@0: "SSWindowStateReady", self._SSWindowStateReadyListener, false); michael@0: self._SSWindowStateReadyListener = null; michael@0: } michael@0: michael@0: self._initFrameCallbacks.forEach(function (cb) cb()); michael@0: self._initFrameCallbacks = []; michael@0: }, false); michael@0: michael@0: this._iframe.setAttribute("src", "chrome://browser/content/tabview.html"); michael@0: this._deck.appendChild(this._iframe); michael@0: michael@0: // ___ create tooltip michael@0: let tooltip = document.createElement("tooltip"); michael@0: tooltip.id = "tab-view-tooltip"; michael@0: tooltip.setAttribute("onpopupshowing", "return TabView.fillInTooltip(document.tooltipNode);"); michael@0: document.getElementById("mainPopupSet").appendChild(tooltip); michael@0: }, michael@0: michael@0: // ---------- michael@0: getContentWindow: function TabView_getContentWindow() { michael@0: return this._window; michael@0: }, michael@0: michael@0: // ---------- michael@0: isVisible: function TabView_isVisible() { michael@0: return (this._deck ? this._deck.selectedPanel == this._iframe : false); michael@0: }, michael@0: michael@0: // ---------- michael@0: show: function TabView_show() { michael@0: if (this.isVisible()) michael@0: return; michael@0: michael@0: let self = this; michael@0: this._initFrame(function() { michael@0: self._window.UI.showTabView(true); michael@0: }); michael@0: }, michael@0: michael@0: // ---------- michael@0: hide: function TabView_hide() { michael@0: if (this.isVisible() && this._window) { michael@0: this._window.UI.exit(); michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: toggle: function TabView_toggle() { michael@0: if (this.isVisible()) michael@0: this.hide(); michael@0: else michael@0: this.show(); michael@0: }, michael@0: michael@0: // ---------- michael@0: _tabBrowserHasHiddenTabs: function TabView_tabBrowserHasHiddenTabs() { michael@0: return (gBrowser.tabs.length - gBrowser.visibleTabs.length) > 0; michael@0: }, michael@0: michael@0: // ---------- michael@0: updateContextMenu: function TabView_updateContextMenu(tab, popup) { michael@0: let separator = document.getElementById("context_tabViewNamedGroups"); michael@0: let isEmpty = true; michael@0: michael@0: while (popup.firstChild && popup.firstChild != separator) michael@0: popup.removeChild(popup.firstChild); michael@0: michael@0: let self = this; michael@0: this._initFrame(function() { michael@0: let activeGroup = tab._tabViewTabItem.parent; michael@0: let groupItems = self._window.GroupItems.groupItems; michael@0: michael@0: groupItems.forEach(function(groupItem) { michael@0: // if group has title, it's not hidden and there is no active group or michael@0: // the active group id doesn't match the group id, a group menu item michael@0: // would be added. michael@0: if (!groupItem.hidden && michael@0: (groupItem.getTitle().trim() || groupItem.getChildren().length) && michael@0: (!activeGroup || activeGroup.id != groupItem.id)) { michael@0: let menuItem = self._createGroupMenuItem(groupItem); michael@0: popup.insertBefore(menuItem, separator); michael@0: isEmpty = false; michael@0: } michael@0: }); michael@0: separator.hidden = isEmpty; michael@0: }); michael@0: }, michael@0: michael@0: // ---------- michael@0: _createGroupMenuItem: function TabView__createGroupMenuItem(groupItem) { michael@0: let menuItem = document.createElement("menuitem"); michael@0: let title = groupItem.getTitle(); michael@0: michael@0: if (!title.trim()) { michael@0: let topChildLabel = groupItem.getTopChild().tab.label; michael@0: let childNum = groupItem.getChildren().length; michael@0: michael@0: if (childNum > 1) { michael@0: let num = childNum - 1; michael@0: title = michael@0: gNavigatorBundle.getString("tabview.moveToUnnamedGroup.label"); michael@0: title = PluralForm.get(num, title).replace("#1", topChildLabel).replace("#2", num); michael@0: } else { michael@0: title = topChildLabel; michael@0: } michael@0: } michael@0: michael@0: menuItem.setAttribute("label", title); michael@0: menuItem.setAttribute("tooltiptext", title); michael@0: menuItem.setAttribute("crop", "center"); michael@0: menuItem.setAttribute("class", "tabview-menuitem"); michael@0: menuItem.setAttribute( michael@0: "oncommand", michael@0: "TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')"); michael@0: michael@0: return menuItem; michael@0: }, michael@0: michael@0: // ---------- michael@0: moveTabTo: function TabView_moveTabTo(tab, groupItemId) { michael@0: if (this._window) { michael@0: this._window.GroupItems.moveTabToGroupItem(tab, groupItemId); michael@0: } else { michael@0: let self = this; michael@0: this._initFrame(function() { michael@0: self._window.GroupItems.moveTabToGroupItem(tab, groupItemId); michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: // Adds new key commands to the browser, for invoking the Tab Candy UI michael@0: // and for switching between groups of tabs when outside of the Tab Candy UI. michael@0: _setBrowserKeyHandlers: function TabView__setBrowserKeyHandlers() { michael@0: if (this._browserKeyHandlerInitialized) michael@0: return; michael@0: michael@0: this._browserKeyHandlerInitialized = true; michael@0: michael@0: let self = this; michael@0: window.addEventListener("keypress", function(event) { michael@0: if (self.isVisible() || !self._tabBrowserHasHiddenTabs()) michael@0: return; michael@0: michael@0: let charCode = event.charCode; michael@0: // Control (+ Shift) + ` michael@0: if (event.ctrlKey && !event.metaKey && !event.altKey && michael@0: (charCode == 96 || charCode == 126)) { michael@0: event.stopPropagation(); michael@0: event.preventDefault(); michael@0: michael@0: self._initFrame(function() { michael@0: let groupItems = self._window.GroupItems; michael@0: let tabItem = groupItems.getNextGroupItemTab(event.shiftKey); michael@0: if (!tabItem) michael@0: return; michael@0: michael@0: if (gBrowser.selectedTab.pinned) michael@0: groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true}); michael@0: else michael@0: gBrowser.selectedTab = tabItem.tab; michael@0: }); michael@0: } michael@0: }, true); michael@0: }, michael@0: michael@0: // ---------- michael@0: // Prepares the tab view for undo close tab. michael@0: prepareUndoCloseTab: function TabView_prepareUndoCloseTab(blankTabToRemove) { michael@0: if (this._window) { michael@0: this._window.UI.restoredClosedTab = true; michael@0: michael@0: if (blankTabToRemove && blankTabToRemove._tabViewTabItem) michael@0: blankTabToRemove._tabViewTabItem.isRemovedAfterRestore = true; michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: // Cleans up the tab view after undo close tab. michael@0: afterUndoCloseTab: function TabView_afterUndoCloseTab() { michael@0: if (this._window) michael@0: this._window.UI.restoredClosedTab = false; michael@0: }, michael@0: michael@0: // ---------- michael@0: // On move to group pop showing. michael@0: moveToGroupPopupShowing: function TabView_moveToGroupPopupShowing(event) { michael@0: // Update the context menu only if Panorama was already initialized or if michael@0: // there are hidden tabs. michael@0: let numHiddenTabs = gBrowser.tabs.length - gBrowser.visibleTabs.length; michael@0: if (this._window || numHiddenTabs > 0) michael@0: this.updateContextMenu(TabContextMenu.contextTab, event.target); michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: _addToolbarButton michael@0: // Adds the TabView button to the TabsToolbar. michael@0: _addToolbarButton: function TabView__addToolbarButton() { michael@0: let buttonId = "tabview-button"; michael@0: michael@0: if (CustomizableUI.getPlacementOfWidget(buttonId)) michael@0: return; michael@0: michael@0: let allTabsBtnPlacement = CustomizableUI.getPlacementOfWidget("alltabs-button"); michael@0: // allTabsBtnPlacement can never be null because the button isn't removable michael@0: let desiredPosition = allTabsBtnPlacement.position + 1; michael@0: CustomizableUI.addWidgetToArea(buttonId, "TabsToolbar", desiredPosition); michael@0: // NB: this is for backwards compatibility, and should be removed by michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=976041 michael@0: document.persist("TabsToolbar", "currentset"); michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: updateGroupNumberBroadcaster michael@0: // Updates the group number broadcaster. michael@0: updateGroupNumberBroadcaster: function TabView_updateGroupNumberBroadcaster(number) { michael@0: let groupsNumber = document.getElementById("tabviewGroupsNumber"); michael@0: groupsNumber.setAttribute("groups", number); michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: enableSessionRestore michael@0: // Enables automatic session restore when the browser is started. Does michael@0: // nothing if we already did that once in the past. michael@0: enableSessionRestore: function TabView_enableSessionRestore() { michael@0: if (!this._window || !this.firstUseExperienced) michael@0: return; michael@0: michael@0: // do nothing if we already enabled session restore once michael@0: if (this.sessionRestoreEnabledOnce) michael@0: return; michael@0: michael@0: this.sessionRestoreEnabledOnce = true; michael@0: michael@0: // enable session restore if necessary michael@0: if (Services.prefs.getIntPref(this.PREF_STARTUP_PAGE) != 3) { michael@0: Services.prefs.setIntPref(this.PREF_STARTUP_PAGE, 3); michael@0: michael@0: // show banner michael@0: this._window.UI.notifySessionRestoreEnabled(); michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: fillInTooltip michael@0: // Fills in the tooltip text. michael@0: fillInTooltip: function fillInTooltip(tipElement) { michael@0: let retVal = false; michael@0: let titleText = null; michael@0: let direction = tipElement.ownerDocument.dir; michael@0: michael@0: while (!titleText && tipElement) { michael@0: if (tipElement.nodeType == Node.ELEMENT_NODE) michael@0: titleText = tipElement.getAttribute("title"); michael@0: tipElement = tipElement.parentNode; michael@0: } michael@0: let tipNode = document.getElementById("tab-view-tooltip"); michael@0: tipNode.style.direction = direction; michael@0: michael@0: if (titleText) { michael@0: tipNode.setAttribute("label", titleText); michael@0: retVal = true; michael@0: } michael@0: michael@0: return retVal; michael@0: } michael@0: };