1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/browser-tabview.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,490 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +let TabView = { 1.9 + _deck: null, 1.10 + _iframe: null, 1.11 + _window: null, 1.12 + _initialized: false, 1.13 + _browserKeyHandlerInitialized: false, 1.14 + _closedLastVisibleTabBeforeFrameInitialized: false, 1.15 + _isFrameLoading: false, 1.16 + _initFrameCallbacks: [], 1.17 + PREF_BRANCH: "browser.panorama.", 1.18 + PREF_FIRST_RUN: "browser.panorama.experienced_first_run", 1.19 + PREF_STARTUP_PAGE: "browser.startup.page", 1.20 + PREF_RESTORE_ENABLED_ONCE: "browser.panorama.session_restore_enabled_once", 1.21 + GROUPS_IDENTIFIER: "tabview-groups", 1.22 + VISIBILITY_IDENTIFIER: "tabview-visibility", 1.23 + 1.24 + // ---------- 1.25 + get windowTitle() { 1.26 + delete this.windowTitle; 1.27 + let brandBundle = document.getElementById("bundle_brand"); 1.28 + let brandShortName = brandBundle.getString("brandShortName"); 1.29 + let title = gNavigatorBundle.getFormattedString("tabview.title", [brandShortName]); 1.30 + return this.windowTitle = title; 1.31 + }, 1.32 + 1.33 + // ---------- 1.34 + get firstUseExperienced() { 1.35 + let pref = this.PREF_FIRST_RUN; 1.36 + if (Services.prefs.prefHasUserValue(pref)) 1.37 + return Services.prefs.getBoolPref(pref); 1.38 + 1.39 + return false; 1.40 + }, 1.41 + 1.42 + // ---------- 1.43 + set firstUseExperienced(val) { 1.44 + Services.prefs.setBoolPref(this.PREF_FIRST_RUN, val); 1.45 + }, 1.46 + 1.47 + // ---------- 1.48 + get sessionRestoreEnabledOnce() { 1.49 + let pref = this.PREF_RESTORE_ENABLED_ONCE; 1.50 + if (Services.prefs.prefHasUserValue(pref)) 1.51 + return Services.prefs.getBoolPref(pref); 1.52 + 1.53 + return false; 1.54 + }, 1.55 + 1.56 + // ---------- 1.57 + set sessionRestoreEnabledOnce(val) { 1.58 + Services.prefs.setBoolPref(this.PREF_RESTORE_ENABLED_ONCE, val); 1.59 + }, 1.60 + 1.61 + // ---------- 1.62 + init: function TabView_init() { 1.63 + // disable the ToggleTabView command for popup windows 1.64 + goSetCommandEnabled("Browser:ToggleTabView", window.toolbar.visible); 1.65 + if (!window.toolbar.visible) 1.66 + return; 1.67 + 1.68 + if (this._initialized) 1.69 + return; 1.70 + 1.71 + if (this.firstUseExperienced) { 1.72 + // ___ visibility 1.73 + 1.74 + let data = SessionStore.getWindowValue(window, this.VISIBILITY_IDENTIFIER); 1.75 + if (data && data == "true") { 1.76 + this.show(); 1.77 + } else { 1.78 + try { 1.79 + data = SessionStore.getWindowValue(window, this.GROUPS_IDENTIFIER); 1.80 + if (data) { 1.81 + let parsedData = JSON.parse(data); 1.82 + this.updateGroupNumberBroadcaster(parsedData.totalNumber || 1); 1.83 + } 1.84 + } catch (e) { } 1.85 + 1.86 + let self = this; 1.87 + // if a tab is changed from hidden to unhidden and the iframe is not 1.88 + // initialized, load the iframe and setup the tab. 1.89 + this._tabShowEventListener = function(event) { 1.90 + if (!self._window) 1.91 + self._initFrame(function() { 1.92 + self._window.UI.onTabSelect(gBrowser.selectedTab); 1.93 + if (self._closedLastVisibleTabBeforeFrameInitialized) { 1.94 + self._closedLastVisibleTabBeforeFrameInitialized = false; 1.95 + self._window.UI.showTabView(false); 1.96 + } 1.97 + }); 1.98 + }; 1.99 + this._tabCloseEventListener = function(event) { 1.100 + if (!self._window && gBrowser.visibleTabs.length == 0) 1.101 + self._closedLastVisibleTabBeforeFrameInitialized = true; 1.102 + }; 1.103 + gBrowser.tabContainer.addEventListener( 1.104 + "TabShow", this._tabShowEventListener, false); 1.105 + gBrowser.tabContainer.addEventListener( 1.106 + "TabClose", this._tabCloseEventListener, false); 1.107 + 1.108 + if (this._tabBrowserHasHiddenTabs()) { 1.109 + this._setBrowserKeyHandlers(); 1.110 + } else { 1.111 + // for restoring last session and undoing recently closed window 1.112 + this._SSWindowStateReadyListener = function (event) { 1.113 + if (this._tabBrowserHasHiddenTabs()) 1.114 + this._setBrowserKeyHandlers(); 1.115 + }.bind(this); 1.116 + window.addEventListener( 1.117 + "SSWindowStateReady", this._SSWindowStateReadyListener, false); 1.118 + } 1.119 + } 1.120 + } 1.121 + 1.122 + Services.prefs.addObserver(this.PREF_BRANCH, this, false); 1.123 + 1.124 + this._initialized = true; 1.125 + }, 1.126 + 1.127 + // ---------- 1.128 + // Observes topic changes. 1.129 + observe: function TabView_observe(subject, topic, data) { 1.130 + if (data == this.PREF_FIRST_RUN && this.firstUseExperienced) { 1.131 + this._addToolbarButton(); 1.132 + this.enableSessionRestore(); 1.133 + } 1.134 + }, 1.135 + 1.136 + // ---------- 1.137 + // Uninitializes TabView. 1.138 + uninit: function TabView_uninit() { 1.139 + if (!this._initialized) 1.140 + return; 1.141 + 1.142 + Services.prefs.removeObserver(this.PREF_BRANCH, this); 1.143 + 1.144 + if (this._tabShowEventListener) 1.145 + gBrowser.tabContainer.removeEventListener( 1.146 + "TabShow", this._tabShowEventListener, false); 1.147 + 1.148 + if (this._tabCloseEventListener) 1.149 + gBrowser.tabContainer.removeEventListener( 1.150 + "TabClose", this._tabCloseEventListener, false); 1.151 + 1.152 + if (this._SSWindowStateReadyListener) 1.153 + window.removeEventListener( 1.154 + "SSWindowStateReady", this._SSWindowStateReadyListener, false); 1.155 + 1.156 + this._initialized = false; 1.157 + 1.158 + if (this._window) { 1.159 + this._window = null; 1.160 + } 1.161 + 1.162 + if (this._iframe) { 1.163 + this._iframe.remove(); 1.164 + this._iframe = null; 1.165 + } 1.166 + }, 1.167 + 1.168 + // ---------- 1.169 + // Creates the frame and calls the callback once it's loaded. 1.170 + // If the frame already exists, calls the callback immediately. 1.171 + _initFrame: function TabView__initFrame(callback) { 1.172 + let hasCallback = typeof callback == "function"; 1.173 + 1.174 + // prevent frame to be initialized for popup windows 1.175 + if (!window.toolbar.visible) 1.176 + return; 1.177 + 1.178 + if (this._window) { 1.179 + if (hasCallback) 1.180 + callback(); 1.181 + return; 1.182 + } 1.183 + 1.184 + if (hasCallback) 1.185 + this._initFrameCallbacks.push(callback); 1.186 + 1.187 + if (this._isFrameLoading) 1.188 + return; 1.189 + 1.190 + this._isFrameLoading = true; 1.191 + 1.192 + TelemetryStopwatch.start("PANORAMA_INITIALIZATION_TIME_MS"); 1.193 + 1.194 + // ___ find the deck 1.195 + this._deck = document.getElementById("tab-view-deck"); 1.196 + 1.197 + // ___ create the frame 1.198 + this._iframe = document.createElement("iframe"); 1.199 + this._iframe.id = "tab-view"; 1.200 + this._iframe.setAttribute("transparent", "true"); 1.201 + this._iframe.setAttribute("tooltip", "tab-view-tooltip"); 1.202 + this._iframe.flex = 1; 1.203 + 1.204 + let self = this; 1.205 + 1.206 + window.addEventListener("tabviewframeinitialized", function onInit() { 1.207 + window.removeEventListener("tabviewframeinitialized", onInit, false); 1.208 + 1.209 + TelemetryStopwatch.finish("PANORAMA_INITIALIZATION_TIME_MS"); 1.210 + 1.211 + self._isFrameLoading = false; 1.212 + self._window = self._iframe.contentWindow; 1.213 + self._setBrowserKeyHandlers(); 1.214 + 1.215 + if (self._tabShowEventListener) { 1.216 + gBrowser.tabContainer.removeEventListener( 1.217 + "TabShow", self._tabShowEventListener, false); 1.218 + self._tabShowEventListener = null; 1.219 + } 1.220 + if (self._tabCloseEventListener) { 1.221 + gBrowser.tabContainer.removeEventListener( 1.222 + "TabClose", self._tabCloseEventListener, false); 1.223 + self._tabCloseEventListener = null; 1.224 + } 1.225 + if (self._SSWindowStateReadyListener) { 1.226 + window.removeEventListener( 1.227 + "SSWindowStateReady", self._SSWindowStateReadyListener, false); 1.228 + self._SSWindowStateReadyListener = null; 1.229 + } 1.230 + 1.231 + self._initFrameCallbacks.forEach(function (cb) cb()); 1.232 + self._initFrameCallbacks = []; 1.233 + }, false); 1.234 + 1.235 + this._iframe.setAttribute("src", "chrome://browser/content/tabview.html"); 1.236 + this._deck.appendChild(this._iframe); 1.237 + 1.238 + // ___ create tooltip 1.239 + let tooltip = document.createElement("tooltip"); 1.240 + tooltip.id = "tab-view-tooltip"; 1.241 + tooltip.setAttribute("onpopupshowing", "return TabView.fillInTooltip(document.tooltipNode);"); 1.242 + document.getElementById("mainPopupSet").appendChild(tooltip); 1.243 + }, 1.244 + 1.245 + // ---------- 1.246 + getContentWindow: function TabView_getContentWindow() { 1.247 + return this._window; 1.248 + }, 1.249 + 1.250 + // ---------- 1.251 + isVisible: function TabView_isVisible() { 1.252 + return (this._deck ? this._deck.selectedPanel == this._iframe : false); 1.253 + }, 1.254 + 1.255 + // ---------- 1.256 + show: function TabView_show() { 1.257 + if (this.isVisible()) 1.258 + return; 1.259 + 1.260 + let self = this; 1.261 + this._initFrame(function() { 1.262 + self._window.UI.showTabView(true); 1.263 + }); 1.264 + }, 1.265 + 1.266 + // ---------- 1.267 + hide: function TabView_hide() { 1.268 + if (this.isVisible() && this._window) { 1.269 + this._window.UI.exit(); 1.270 + } 1.271 + }, 1.272 + 1.273 + // ---------- 1.274 + toggle: function TabView_toggle() { 1.275 + if (this.isVisible()) 1.276 + this.hide(); 1.277 + else 1.278 + this.show(); 1.279 + }, 1.280 + 1.281 + // ---------- 1.282 + _tabBrowserHasHiddenTabs: function TabView_tabBrowserHasHiddenTabs() { 1.283 + return (gBrowser.tabs.length - gBrowser.visibleTabs.length) > 0; 1.284 + }, 1.285 + 1.286 + // ---------- 1.287 + updateContextMenu: function TabView_updateContextMenu(tab, popup) { 1.288 + let separator = document.getElementById("context_tabViewNamedGroups"); 1.289 + let isEmpty = true; 1.290 + 1.291 + while (popup.firstChild && popup.firstChild != separator) 1.292 + popup.removeChild(popup.firstChild); 1.293 + 1.294 + let self = this; 1.295 + this._initFrame(function() { 1.296 + let activeGroup = tab._tabViewTabItem.parent; 1.297 + let groupItems = self._window.GroupItems.groupItems; 1.298 + 1.299 + groupItems.forEach(function(groupItem) { 1.300 + // if group has title, it's not hidden and there is no active group or 1.301 + // the active group id doesn't match the group id, a group menu item 1.302 + // would be added. 1.303 + if (!groupItem.hidden && 1.304 + (groupItem.getTitle().trim() || groupItem.getChildren().length) && 1.305 + (!activeGroup || activeGroup.id != groupItem.id)) { 1.306 + let menuItem = self._createGroupMenuItem(groupItem); 1.307 + popup.insertBefore(menuItem, separator); 1.308 + isEmpty = false; 1.309 + } 1.310 + }); 1.311 + separator.hidden = isEmpty; 1.312 + }); 1.313 + }, 1.314 + 1.315 + // ---------- 1.316 + _createGroupMenuItem: function TabView__createGroupMenuItem(groupItem) { 1.317 + let menuItem = document.createElement("menuitem"); 1.318 + let title = groupItem.getTitle(); 1.319 + 1.320 + if (!title.trim()) { 1.321 + let topChildLabel = groupItem.getTopChild().tab.label; 1.322 + let childNum = groupItem.getChildren().length; 1.323 + 1.324 + if (childNum > 1) { 1.325 + let num = childNum - 1; 1.326 + title = 1.327 + gNavigatorBundle.getString("tabview.moveToUnnamedGroup.label"); 1.328 + title = PluralForm.get(num, title).replace("#1", topChildLabel).replace("#2", num); 1.329 + } else { 1.330 + title = topChildLabel; 1.331 + } 1.332 + } 1.333 + 1.334 + menuItem.setAttribute("label", title); 1.335 + menuItem.setAttribute("tooltiptext", title); 1.336 + menuItem.setAttribute("crop", "center"); 1.337 + menuItem.setAttribute("class", "tabview-menuitem"); 1.338 + menuItem.setAttribute( 1.339 + "oncommand", 1.340 + "TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')"); 1.341 + 1.342 + return menuItem; 1.343 + }, 1.344 + 1.345 + // ---------- 1.346 + moveTabTo: function TabView_moveTabTo(tab, groupItemId) { 1.347 + if (this._window) { 1.348 + this._window.GroupItems.moveTabToGroupItem(tab, groupItemId); 1.349 + } else { 1.350 + let self = this; 1.351 + this._initFrame(function() { 1.352 + self._window.GroupItems.moveTabToGroupItem(tab, groupItemId); 1.353 + }); 1.354 + } 1.355 + }, 1.356 + 1.357 + // ---------- 1.358 + // Adds new key commands to the browser, for invoking the Tab Candy UI 1.359 + // and for switching between groups of tabs when outside of the Tab Candy UI. 1.360 + _setBrowserKeyHandlers: function TabView__setBrowserKeyHandlers() { 1.361 + if (this._browserKeyHandlerInitialized) 1.362 + return; 1.363 + 1.364 + this._browserKeyHandlerInitialized = true; 1.365 + 1.366 + let self = this; 1.367 + window.addEventListener("keypress", function(event) { 1.368 + if (self.isVisible() || !self._tabBrowserHasHiddenTabs()) 1.369 + return; 1.370 + 1.371 + let charCode = event.charCode; 1.372 + // Control (+ Shift) + ` 1.373 + if (event.ctrlKey && !event.metaKey && !event.altKey && 1.374 + (charCode == 96 || charCode == 126)) { 1.375 + event.stopPropagation(); 1.376 + event.preventDefault(); 1.377 + 1.378 + self._initFrame(function() { 1.379 + let groupItems = self._window.GroupItems; 1.380 + let tabItem = groupItems.getNextGroupItemTab(event.shiftKey); 1.381 + if (!tabItem) 1.382 + return; 1.383 + 1.384 + if (gBrowser.selectedTab.pinned) 1.385 + groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true}); 1.386 + else 1.387 + gBrowser.selectedTab = tabItem.tab; 1.388 + }); 1.389 + } 1.390 + }, true); 1.391 + }, 1.392 + 1.393 + // ---------- 1.394 + // Prepares the tab view for undo close tab. 1.395 + prepareUndoCloseTab: function TabView_prepareUndoCloseTab(blankTabToRemove) { 1.396 + if (this._window) { 1.397 + this._window.UI.restoredClosedTab = true; 1.398 + 1.399 + if (blankTabToRemove && blankTabToRemove._tabViewTabItem) 1.400 + blankTabToRemove._tabViewTabItem.isRemovedAfterRestore = true; 1.401 + } 1.402 + }, 1.403 + 1.404 + // ---------- 1.405 + // Cleans up the tab view after undo close tab. 1.406 + afterUndoCloseTab: function TabView_afterUndoCloseTab() { 1.407 + if (this._window) 1.408 + this._window.UI.restoredClosedTab = false; 1.409 + }, 1.410 + 1.411 + // ---------- 1.412 + // On move to group pop showing. 1.413 + moveToGroupPopupShowing: function TabView_moveToGroupPopupShowing(event) { 1.414 + // Update the context menu only if Panorama was already initialized or if 1.415 + // there are hidden tabs. 1.416 + let numHiddenTabs = gBrowser.tabs.length - gBrowser.visibleTabs.length; 1.417 + if (this._window || numHiddenTabs > 0) 1.418 + this.updateContextMenu(TabContextMenu.contextTab, event.target); 1.419 + }, 1.420 + 1.421 + // ---------- 1.422 + // Function: _addToolbarButton 1.423 + // Adds the TabView button to the TabsToolbar. 1.424 + _addToolbarButton: function TabView__addToolbarButton() { 1.425 + let buttonId = "tabview-button"; 1.426 + 1.427 + if (CustomizableUI.getPlacementOfWidget(buttonId)) 1.428 + return; 1.429 + 1.430 + let allTabsBtnPlacement = CustomizableUI.getPlacementOfWidget("alltabs-button"); 1.431 + // allTabsBtnPlacement can never be null because the button isn't removable 1.432 + let desiredPosition = allTabsBtnPlacement.position + 1; 1.433 + CustomizableUI.addWidgetToArea(buttonId, "TabsToolbar", desiredPosition); 1.434 + // NB: this is for backwards compatibility, and should be removed by 1.435 + // https://bugzilla.mozilla.org/show_bug.cgi?id=976041 1.436 + document.persist("TabsToolbar", "currentset"); 1.437 + }, 1.438 + 1.439 + // ---------- 1.440 + // Function: updateGroupNumberBroadcaster 1.441 + // Updates the group number broadcaster. 1.442 + updateGroupNumberBroadcaster: function TabView_updateGroupNumberBroadcaster(number) { 1.443 + let groupsNumber = document.getElementById("tabviewGroupsNumber"); 1.444 + groupsNumber.setAttribute("groups", number); 1.445 + }, 1.446 + 1.447 + // ---------- 1.448 + // Function: enableSessionRestore 1.449 + // Enables automatic session restore when the browser is started. Does 1.450 + // nothing if we already did that once in the past. 1.451 + enableSessionRestore: function TabView_enableSessionRestore() { 1.452 + if (!this._window || !this.firstUseExperienced) 1.453 + return; 1.454 + 1.455 + // do nothing if we already enabled session restore once 1.456 + if (this.sessionRestoreEnabledOnce) 1.457 + return; 1.458 + 1.459 + this.sessionRestoreEnabledOnce = true; 1.460 + 1.461 + // enable session restore if necessary 1.462 + if (Services.prefs.getIntPref(this.PREF_STARTUP_PAGE) != 3) { 1.463 + Services.prefs.setIntPref(this.PREF_STARTUP_PAGE, 3); 1.464 + 1.465 + // show banner 1.466 + this._window.UI.notifySessionRestoreEnabled(); 1.467 + } 1.468 + }, 1.469 + 1.470 + // ---------- 1.471 + // Function: fillInTooltip 1.472 + // Fills in the tooltip text. 1.473 + fillInTooltip: function fillInTooltip(tipElement) { 1.474 + let retVal = false; 1.475 + let titleText = null; 1.476 + let direction = tipElement.ownerDocument.dir; 1.477 + 1.478 + while (!titleText && tipElement) { 1.479 + if (tipElement.nodeType == Node.ELEMENT_NODE) 1.480 + titleText = tipElement.getAttribute("title"); 1.481 + tipElement = tipElement.parentNode; 1.482 + } 1.483 + let tipNode = document.getElementById("tab-view-tooltip"); 1.484 + tipNode.style.direction = direction; 1.485 + 1.486 + if (titleText) { 1.487 + tipNode.setAttribute("label", titleText); 1.488 + retVal = true; 1.489 + } 1.490 + 1.491 + return retVal; 1.492 + } 1.493 +};