/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Tree Style Tab. * * The Initial Developer of the Original Code is SHIMODA Hiroshi. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): SHIMODA Hiroshi * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ******/ const EXPORTED_SYMBOLS = ['TreeStyleTabBrowser']; const Cc = Components.classes; const Ci = Components.interfaces; // Components.utils.import('resource://treestyletab-modules/rap.js'); // rap(); Components.utils.import('resource://treestyletab-modules/window.js'); function TreeStyleTabBrowser(aWindowService, aTabBrowser) { this.windowService = aWindowService; this.window = aWindowService.window; this.document = aWindowService.document; this.mTabBrowser = aTabBrowser; aTabBrowser.treeStyleTab = this; this.tabVisibilityChangedTabs = []; this.updateTabsIndentWithDelayTabs = []; } TreeStyleTabBrowser.prototype = { __proto__ : TreeStyleTabWindow.prototype, kMENUITEM_RELOADSUBTREE : 'context-item-reloadTabSubtree', kMENUITEM_RELOADCHILDREN : 'context-item-reloadDescendantTabs', kMENUITEM_REMOVESUBTREE : 'context-item-removeTabSubtree', kMENUITEM_REMOVECHILDREN : 'context-item-removeDescendantTabs', kMENUITEM_REMOVEALLTABSBUT : 'context-item-removeAllTabsButThisTree', kMENUITEM_COLLAPSEEXPAND_SEPARATOR : 'context-separator-collapseExpandAll', kMENUITEM_COLLAPSE : 'context-item-collapseAllSubtree', kMENUITEM_EXPAND : 'context-item-expandAllSubtree', kMENUITEM_AUTOHIDE_SEPARATOR : 'context-separator-toggleAutoHide', kMENUITEM_AUTOHIDE : 'context-item-toggleAutoHide', kMENUITEM_FIXED : 'context-item-toggleFixed', kMENUITEM_BOOKMARKSUBTREE : 'context-item-bookmarkTabSubtree', mTabBrowser : null, indent : -1, indentProp : 'margin', indentTarget : 'left', indentCSSProp : 'margin-left', collapseTarget : 'top', collapseCSSProp : 'margin-top', screenPositionProp : 'screenY', sizeProp : 'height', invertedScreenPositionProp : 'screenX', invertedSizeProp : 'width', startProp : 'top', endProp : 'bottom', maxTreeLevelPhisical : false, /* elements */ get browser() { return this.mTabBrowser; }, get container() { if (!this._container) { this._container = this.document.getElementById('appcontent'); } return this._container; }, _container : null, get scrollBox() { return ( // Tab Mix Plus this.getTreePref('compatibility.TMP') && this.document.getAnonymousElementByAttribute(this.mTabBrowser.mTabContainer, 'class', 'tabs-frame') ) || this.mTabBrowser.mTabContainer.mTabstrip; }, get scrollBoxObject() { var node = this.scrollBox; if (node._scrollbox) node = node._scrollbox; return (node.scrollBoxObject || node.boxObject) .QueryInterface(Ci.nsIScrollBoxObject); // for Tab Mix Plus (ensure scrollbox-ed) }, get splitter() { var d = this.document; return d.getAnonymousElementByAttribute(this.mTabBrowser, 'class', this.kSPLITTER) || d.getAnonymousElementByAttribute(this.mTabBrowser, 'id', 'tabkit-splitter'); // Tab Kit }, get tabStripPlaceHolder() { return this._tabStripPlaceHolder; }, set tabStripPlaceHolder(value) { return (this._tabStripPlaceHolder = value); }, get tabTooltip() { return this.document.getElementById('tabbrowser-tab-tooltip') || // Firefox 4.0- this.evaluateXPath('descendant::xul:tooltip', this.mTabBrowser.mStrip, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue; // -Firefox 3.6 }, /* properties */ get maxTreeLevel() { return this._maxTreeLevel; }, set maxTreeLevel(aValue) { this._maxTreeLevel = aValue; this.setTabbrowserAttribute(this.kMAX_LEVEL, this._maxTreeLevel || '0'); this.enableSubtreeIndent = this._maxTreeLevel != 0; return aValue; }, _maxTreeLevel : -1, get enableSubtreeIndent() { return this._enableSubtreeIndent; }, set enableSubtreeIndent(aValue) { this._enableSubtreeIndent = aValue; this.setTabbrowserAttribute(this.kINDENTED, this._enableSubtreeIndent ? 'true' : null); return aValue; }, _enableSubtreeIndent : true, get allowSubtreeCollapseExpand() { return this._allowSubtreeCollapseExpand; }, set allowSubtreeCollapseExpand(aValue) { this._allowSubtreeCollapseExpand = aValue; this.setTabbrowserAttribute(this.kALLOW_COLLAPSE, this._allowSubtreeCollapseExpand ? 'true' : null); return aValue; }, _allowSubtreeCollapseExpand : true, get hideAlltabsButton() /* legacy feature for Firefox 3.6 or olders */ { return this._hideAlltabsButton; }, set hideAlltabsButton(aValue) { this._hideAlltabsButton = aValue; this.setTabbrowserAttribute(this.kHIDE_ALLTABS, this._hideAlltabsButton ? 'true' : null); return aValue; }, _hideAlltabsButton : true, get fixed() { var orient = this.isVertical ? 'vertical' : 'horizontal' ; if (!this.windowService.preInitialized) return this.getTreePref('tabbar.fixed.'+orient); var b = this.mTabBrowser; if (!b) return false; return b.getAttribute(this.kFIXED+'-'+orient) == 'true'; }, set fixed(aValue) { this.setTabbrowserAttribute(this.kFIXED, aValue || null, this.mTabBrowser); return aValue; }, get isFixed() // for backward compatibility { return this.fixed; }, get position() /* PUBLIC API */ { return ( // Don't touch to the element before it is initialized by XBL constructor. (this.windowService.preInitialized && this.browser.getAttribute(this.kTABBAR_POSITION)) || this.utils.position ); }, set position(aValue) { var position = String(aValue).toLowerCase(); if (!position || !/^(top|bottom|left|right)$/.test(position)) position = 'top'; if (position == this.position) return position; if ('UndoTabService' in this.window && this.window.UndoTabService.isUndoable()) { var current = this.position; var self = this; this.window.UndoTabService.doOperation( function() { self._changeTabbarPosition(position); }, { label : self.treeBundle.getString('undo_changeTabbarPosition_label'), name : 'treestyletab-changeTabbarPosition-private', data : { oldPosition : current, newPosition : position, target : self.mTabBrowser.id } } ); } else { this._changeTabbarPosition(position); } return position; }, _changeTabbarPosition : function TSTBrowser_changeTabbarPosition(aNewPosition) { var oldPosition = this.position; this.fireTabbarPositionEvent(true, oldPosition, aNewPosition); this.initTabbar(aNewPosition, oldPosition); this.reinitAllTabs(); var self = this; this.Deferred.next(function() { self.checkTabsIndentOverflow(); self.fireTabbarPositionEvent(false, oldPosition, aNewPosition); }); }, /* status getters */ get isVertical() { if (!this.windowService.preInitialized) return ['left', 'right'].indexOf(this.position) > -1; var b = this.mTabBrowser; if (!b) return false; if (b.hasAttribute(this.kMODE)) return b.getAttribute(this.kMODE) == 'vertical'; var box = this.scrollBox || b.mTabContainer ; return (box.getAttribute('orient') || this.window.getComputedStyle(box, '').getPropertyValue('-moz-box-orient')) == 'vertical'; }, get isFloating() { return this._tabStripPlaceHolder; }, get ownerToolbar() { return this.evaluateXPath( 'ancestor-or-self::xul:toolbar[1]', this.mTabBrowser.tabContainer, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; }, get canStackTabs() { return ( this.isGecko2 && !this.isVertical && this.canCollapseSubtree() && this.getTreePref('stackCollapsedTabs') ); }, /* utils */ /* get tab contents */ getTabLabel : function TSTBrowser_getTabLabel(aTab) { var d = this.document; var label = d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-stack') || // Mac OS X ( // Tab Mix Plus this.getTreePref('compatibility.TMP') && d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-container') ) || d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text tab-label') || // Firefox 4.0- d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text'); // Firefox 3.5 - Firefox 3.6 return label; }, getTabClosebox : function TSTBrowser_getTabClosebox(aTab) { var d = this.document; var close = ( // Tab Mix Plus this.getTreePref('compatibility.TMP') && d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button always-right') ) || d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button'); return close; }, isTabInViewport : function TSTBrowser_isTabInViewport(aTab) { if (!this.windowService.preInitialized || !aTab) return false; if (aTab.getAttribute('pinned') == 'true') return true; var tabBox = aTab.boxObject; var barBox = this.scrollBox.boxObject; var xOffset = this.getXOffsetOfTab(aTab); var yOffset = this.getYOffsetOfTab(aTab); return (tabBox.screenX + xOffset >= barBox.screenX && tabBox.screenX + xOffset + tabBox.width <= barBox.screenX + barBox.width && tabBox.screenY + yOffset >= barBox.screenY && tabBox.screenY + yOffset + tabBox.height <= barBox.screenY + barBox.height); }, isMultiRow : function TSTBrowser_isMultiRow() { var w = this.window; return ('tabberwocky' in w && this.getTreePref('compatibility.Tabberwocky')) ? (this.getPref('tabberwocky.multirow') && !this.isVertical) : ('TabmixTabbar' in w && this.getTreePref('compatibility.TMP')) ? w.TabmixTabbar.isMultiRow : false ; }, positionPinnedTabs : function TSTBrowser_positionPinnedTabs(aWidth, aHeight, aJustNow) { var b = this.mTabBrowser; var tabbar = b.tabContainer; if ( !tabbar || !tabbar._positionPinnedTabs || !tabbar.boxObject.width ) return; if (!this.isVertical) { this.resetPinnedTabs(); b.mTabContainer._positionPinnedTabs(); return; } var tabbarPlaceHolderWidth = this._tabStripPlaceHolder.boxObject.width; var tabbarWidth = this.tabStrip.boxObject.width; var maxWidth = tabbarPlaceHolderWidth || tabbarWidth; var count = this.pinnedTabsCount; var width = Math.min(maxWidth, aWidth || this.window.TreeStyleTabService.pinnedTabWidth); if (width < 0) width = maxWidth; else width = Math.max(this.MIN_PINNED_TAB_WIDTH, width); var height = Math.max(this.MIN_PINNED_TAB_HEIGHT, aHeight || this.window.TreeStyleTabService.pinnedTabHeight); var maxCol = Math.floor(maxWidth / width); var maxRow = Math.ceil(count / maxCol); var col = 0; var row = 0; var faviconized = width <= this.MIN_PINNED_TAB_WIDTH; /** * Hacks for Firefox 9 or olders. * In a box with "direction: rtr", we have to position tabs * by margin-right, because the basic position becomes * "top-right" instead of "top-left". */ var needToFixOrigin = !this.isGecko10OrLater; var needToInvertDirection = needToFixOrigin && this.position == 'left' && b.getAttribute(this.kINVERT_SCROLLBAR) == 'true'; var remainder = maxWidth - (maxCol * width); var shrunkenOffset = ((needToInvertDirection || this.position == 'right') && tabbarPlaceHolderWidth) ? tabbarWidth - tabbarPlaceHolderWidth : 0 ; var removeFaviconizedClassPattern = new RegExp('\\s+'+this.kFAVICONIZED, 'g'); tabbar.style.MozMarginStart = ''; tabbar.style.setProperty('margin-top', (height * maxRow)+'px', 'important'); for (let i = 0; i < count; i++) { let item = tabbar.childNodes[i]; let style = item.style; style.MozMarginStart = ''; let transitionStyleBackup = style.transition || style.MozTransition || ''; if (aJustNow) style.MozTransition = style.transition = 'none'; let className = item.className.replace(removeFaviconizedClassPattern, ''); if (faviconized) className += ' '+this.kFAVICONIZED; if (className != item.className) item.className = className; style.width = width+'px'; if (needToInvertDirection) { let margin = (width * (maxCol - col - 1)) + remainder + shrunkenOffset; style.setProperty('margin-right', margin+'px', 'important'); style.marginLeft = style.left = style.right = ''; } else { style.setProperty('margin-left', ((width * col) + shrunkenOffset)+'px', 'important'); if (needToFixOrigin) { /** * Hack for Firefox 9 or olders. In "rtl" box, the origin * is wrongly set to the edge of the root element. So, we * must not set specific left/right. */ style.left = style.right = ''; } else { style.left = '0'; style.right = 'auto'; } style.marginRight = ''; } style.setProperty('margin-top', (- height * (maxRow - row))+'px', 'important'); style.top = style.bottom = ''; if (aJustNow) this.Deferred.next(function() { // "transition" must be cleared after the reflow. style.MozTransition = style.transition = transitionStyleBackup; }); col++; if (col >= maxCol) { col = 0; row++; } } }, positionPinnedTabsWithDelay : function TSTBrowser_positionPinnedTabsWithDelay() { if (this._positionPinnedTabsWithDelayTimer) return; var args = Array.slice(arguments); var lastArgs = this._positionPinnedTabsWithDelayTimerArgs || [null, null, false]; lastArgs[0] = lastArgs[0] || args[0]; lastArgs[1] = lastArgs[1] || args[1]; lastArgs[2] = lastArgs[2] || args[2]; this._positionPinnedTabsWithDelayTimerArgs = lastArgs; this._positionPinnedTabsWithDelayTimer = this.window.setTimeout(function(aSelf) { aSelf.Deferred.next(function() { // do with delay again, after Firefox's reposition was completely finished. aSelf.positionPinnedTabs.apply(aSelf, aSelf._positionPinnedTabsWithDelayTimerArgs); }); aSelf._positionPinnedTabsWithDelayTimer = null; aSelf._positionPinnedTabsWithDelayTimerArgs = null; }, 0, this); }, resetPinnedTabs : function TSTBrowser_resetPinnedTabs() { var b = this.mTabBrowser; var tabbar = b.tabContainer; tabbar.style.MozMarginStart = tabbar.style.marginTop = ''; for (var i = 0, count = this.pinnedTabsCount; i < count; i++) { let style = tabbar.childNodes[i].style; style.width = style.left = style.right = style.MozMarginStart = style.marginLeft = style.marginRight = style.marginTop = ''; } }, updateTabsZIndex : function TSTBrowser_updateTabsZIndex(aStacked) { var tabs = this.getTabsArray(this.mTabBrowser); var count = tabs.length; tabs.forEach( aStacked ? function(aTab, aIndex) { aTab.style.zIndex = count * 1000 - aIndex; } : function(aTab, aIndex) { aTab.style.zIndex = ''; } ); }, fixTooNarrowTabbar : function TSTBrowser_fixTooNarrowTabbar() { if (!this.isFloating) return; /** * The tab bar can become smaller than the actual size of the * floating tab bar, and then, we cannot resize tab bar by * dragging anymore. To avoid this problem, we have to enlarge * the tab bar larger than the floating tab bar. */ if (this.isVertical) { let key = this.autoHide.expanded ? 'tabbar.width' : 'tabbar.shrunkenWidth' ; let width = this.getTreePref(key); let minWidth = this.scrollBox.boxObject.width if (minWidth > width) { this.setPrefForActiveWindow(function() { this.setTreePref(key, minWidth); this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); }); } } else { let height = this.getTreePref('tabbar.height'); let minHeight = this.scrollBox.boxObject.height if (minHeight > height) { this.setPrefForActiveWindow(function() { this.setTreePref('tabbar.height', minHeight); this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); }); } } }, /* initialize */ init : function TSTBrowser_init() { // rap('browser/init start'); this.stopRendering(); var w = this.window; var d = this.document; var b = this.mTabBrowser; b.tabContainer.treeStyleTab = this; if (b.tabContainer.parentNode.localName == 'toolbar') b.tabContainer.parentNode.classList.add(this.kTABBAR_TOOLBAR); this.tabsHash = {}; this.tabVisibilityChangedTabs = []; this._updateFloatingTabbarReason = 0; this.internallyTabMovingCount = 0; this.subTreeMovingCount = 0; this.subTreeChildrenMovingCount = 0; this._treeViewEnabled = true; this._initTabbrowserExtraContents(); let position = this.position; this.fireTabbarPositionEvent(this.kEVENT_TYPE_TABBAR_POSITION_CHANGING, 'top', position); /* PUBLIC API */ this.setTabbrowserAttribute(this.kFIXED+'-horizontal', this.getTreePref('tabbar.fixed.horizontal') ? 'true' : null, b); this.setTabbrowserAttribute(this.kFIXED+'-vertical', this.getTreePref('tabbar.fixed.vertical') ? 'true' : null, b); /** * has its custom background color for itself, but it * prevents to make transparent background of the vertical tab bar. * So, I re-define the background color of content area for * es via dynamically generated stylesheet. * See: * https://bugzilla.mozilla.org/show_bug.cgi?id=558585 * http://hg.mozilla.org/mozilla-central/rev/e90bdd97d168 */ if (b.style.backgroundColor && this.isFloating) { let color = b.style.backgroundColor; let pi = d.createProcessingInstruction( 'xml-stylesheet', 'type="text/css" href="data:text/css,'+encodeURIComponent( tabpanels > notificationbox { background-color: %COLOR%; } ]]>.toString().replace(/%COLOR%/, color) )+'"' ); d.insertBefore(pi, d.documentElement); b.style.backgroundColor = ''; } this.initTabbar(null, this.kTABBAR_TOP); w.addEventListener('resize', this, true); w.addEventListener('beforecustomization', this, true); w.addEventListener('aftercustomization', this, false); w.addEventListener('customizationchange', this, false); w.addEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); w.addEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); b.addEventListener('nsDOMMultipleTabHandlerTabsClosing', this, false); w['piro.sakura.ne.jp'].tabsDragUtils.initTabBrowser(b); w.TreeStyleTabWindowHelper.initTabbrowserMethods(b); this._initTabbrowserContextMenu(); w.TreeStyleTabWindowHelper.updateTabDNDObserver(b); this.getAllTabsArray(b).forEach(this.initTab, this); this.onPrefChange('extensions.treestyletab.maxTreeLevel'); this.onPrefChange('extensions.treestyletab.tabbar.style'); this.onPrefChange('extensions.treestyletab.twisty.style'); this.onPrefChange('extensions.treestyletab.showBorderForFirstTab'); this.onPrefChange('extensions.treestyletab.tabbar.invertTabContents'); this.onPrefChange('extensions.treestyletab.tabbar.invertClosebox'); this.onPrefChange('extensions.treestyletab.tabbar.autoShow.mousemove'); this.onPrefChange('extensions.treestyletab.tabbar.invertScrollbar'); this.onPrefChange('extensions.treestyletab.tabbar.narrowScrollbar'); this.onPrefChange('extensions.treestyletab.animation.enabled'); this.ObserverService.addObserver(this, this.kTOPIC_INDENT_MODIFIED, false); this.ObserverService.addObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL, false); this.ObserverService.addObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY, false); this.ObserverService.addObserver(this, 'sessionstore-windows-restored', false); this.ObserverService.addObserver(this, 'sessionstore-browser-state-restored', false); this.ObserverService.addObserver(this, 'private-browsing-change-granted', false); this.ObserverService.addObserver(this, 'lightweight-theme-styling-update', false); this.addPrefListener(this); // Don't init these ovservers on this point to avoid needless initializations. // this.tabbarDNDObserver; // this.panelDNDObserver; this._readyToInitDNDObservers(); // Init autohide service only if it have to be activated. if (this.isAutoHide) this.autoHide; this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_INITIALIZE); this.fixTooNarrowTabbar(); this.fireTabbarPositionEvent(false, 'top', position); /* PUBLIC API */ this.startRendering(); // rap('browser/init end'); }, _initTabbrowserExtraContents : function TSTBrowser_initTabbrowserExtraContents() { var d = this.document; var b = this.mTabBrowser; var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER); if (!toggler) { toggler = d.createElement('spacer'); toggler.setAttribute('class', this.kTABBAR_TOGGLER); toggler.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 b.mTabBox.insertBefore(toggler, b.mTabBox.firstChild); if (b.mTabDropIndicatorBar == toggler) b.mTabDropIndicatorBar = d.getAnonymousElementByAttribute(b, 'class', 'tab-drop-indicator-bar'); } var placeHolder = d.getAnonymousElementByAttribute(b, 'anonid', 'strip'); if (!placeHolder) { placeHolder = d.createElement('hbox'); placeHolder.setAttribute('anonid', 'strip'); placeHolder.setAttribute('class', 'tabbrowser-strip '+this.kTABBAR_PLACEHOLDER); placeHolder.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 b.mTabBox.insertBefore(placeHolder, toggler.nextSibling); } this.tabStripPlaceHolder = (placeHolder != this.tabStrip) ? placeHolder : null ; }, _initTabbrowserContextMenu : function TSTBrowser_initTabbrowserContextMenu() { var w = this.window; var d = this.document; var b = this.mTabBrowser; var tabContextMenu = b.tabContextMenu || d.getAnonymousElementByAttribute(b, 'anonid', 'tabContextMenu'); tabContextMenu.addEventListener('popupshowing', this, false); if (!('MultipleTabService' in w)) { w.setTimeout(function(aSelf, aTabBrowser, aPopup) { let suffix = '-tabbrowser-'+(aTabBrowser.id || 'instance-'+parseInt(Math.random() * 65000)); [ aSelf.kMENUITEM_RELOADSUBTREE, aSelf.kMENUITEM_RELOADCHILDREN, aSelf.kMENUITEM_REMOVESUBTREE, aSelf.kMENUITEM_REMOVECHILDREN, aSelf.kMENUITEM_REMOVEALLTABSBUT, aSelf.kMENUITEM_COLLAPSEEXPAND_SEPARATOR, aSelf.kMENUITEM_COLLAPSE, aSelf.kMENUITEM_EXPAND, aSelf.kMENUITEM_AUTOHIDE_SEPARATOR, aSelf.kMENUITEM_AUTOHIDE, aSelf.kMENUITEM_FIXED, aSelf.kMENUITEM_BOOKMARKSUBTREE ].forEach(function(aID) { let item = d.getElementById(aID).cloneNode(true); item.setAttribute('id', item.getAttribute('id')+suffix); let refNode = void(0); let insertAfter = item.getAttribute('multipletab-insertafter'); if (insertAfter) { try { eval('refNode = ('+insertAfter+').nextSibling'); } catch(e) { } } let insertBefore = item.getAttribute('multipletab-insertbefore'); if (refNode === void(0) && insertBefore) { try { eval('refNode = '+insertBefore); } catch(e) { } } aPopup.insertBefore(item, refNode || null); }); tabContextMenu = null; }, 0, this, b, tabContextMenu); } var removeTabItem = d.getAnonymousElementByAttribute(b, 'id', 'context_closeTab'); if (removeTabItem) { removeTabItem.setAttribute( 'oncommand', removeTabItem.getAttribute('oncommand').replace( /(tabbrowser\.removeTab\(([^\)]+)\))/, 'if (tabbrowser.treeStyleTab.warnAboutClosingTabSubtreeOf($2)) $1' ) ); } }, _readyToInitDNDObservers : function TSTBrowser_readyToInitDNDObservers() { var w = this.window; this._DNDObserversInitialized = false; w.addEventListener('mouseover', this, true); w.addEventListener('dragover', this, true); }, _initDNDObservers : function TSTBrowser_initDNDObservers() { if (this._DNDObserversInitialized) return; this.tabbarDNDObserver; this.panelDNDObserver; var w = this.window; w.addEventListener('mouseover', this, true); w.addEventListener('dragover', this, true); this._DNDObserversInitialized = true; }, initTab : function TSTBrowser_initTab(aTab) { if (!aTab.hasAttribute(this.kID)) { let id = this.getTabValue(aTab, this.kID) || this.makeNewId(); aTab.setAttribute(this.kID, id); aTab.setAttribute(this.kSUBTREE_COLLAPSED, true); aTab.setAttribute(this.kALLOW_COLLAPSE, true); let self = this; this.Deferred.next(function() { if (!self.getTabValue(aTab, self.kID)) { self.setTabValue(aTab, self.kID, id); if (!(id in self.tabsHash)) self.tabsHash[id] = aTab; } }); if (!(id in this.tabsHash)) this.tabsHash[id] = aTab; } aTab.__treestyletab__linkedTabBrowser = this.mTabBrowser; this.initTabAttributes(aTab); this.initTabContents(aTab); aTab.setAttribute(this.kNEST, 0); }, isTabInitialized : function TSTBrowser_isTabInitialized(aTab) { return aTab.getAttribute(this.kID); }, ensureTabInitialized : function TSTBrowser_ensureTabInitialized(aTab) { if (!aTab || this.isTabInitialized(aTab)) return; this.initTab(aTab); }, initTabAttributes : function TSTBrowser_initTabAttributes(aTab) { var pos = this.position; if (pos == 'left' || pos == 'right') { aTab.setAttribute('align', 'stretch'); aTab.removeAttribute('maxwidth'); aTab.removeAttribute('minwidth'); aTab.removeAttribute('width'); aTab.removeAttribute('flex'); aTab.maxWidth = 65000; aTab.minWidth = 0; if (this.getTreePref('compatibility.TMP')) aTab.setAttribute('dir', 'ltr'); // Tab Mix Plus } else { aTab.removeAttribute('align'); aTab.removeAttribute('maxwidth'); aTab.removeAttribute('minwidth'); if (!this.isGecko2) { // Firefox 3.6 or older aTab.setAttribute('maxwidth', 250); aTab.setAttribute('minwidth', this.mTabBrowser.mTabContainer.mTabMinWidth); aTab.setAttribute('width', '0'); aTab.maxWidth = 250; aTab.minWidth = this.mTabBrowser.mTabContainer.mTabMinWidth; aTab.setAttribute('flex', 100); } if (this.getTreePref('compatibility.TMP')) aTab.removeAttribute('dir'); // Tab Mix Plus } }, initTabContents : function TSTBrowser_initTabContents(aTab) { var d = this.document; var icon = d.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon'); var twisty = d.getAnonymousElementByAttribute(aTab, 'class', this.kTWISTY); if (icon && !twisty) { twisty = d.createElement('image'); twisty.setAttribute('class', this.kTWISTY); let container = d.createElement('hbox'); container.setAttribute('class', this.kTWISTY_CONTAINER); container.appendChild(twisty); icon.appendChild(container); let marker = d.createElement('image'); marker.setAttribute('class', this.kDROP_MARKER); container = d.createElement('hbox'); container.setAttribute('class', this.kDROP_MARKER_CONTAINER); container.appendChild(marker); icon.appendChild(container); } var label = this.getTabLabel(aTab); var counter = d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER); if (label && label.parentNode != aTab && !counter) { counter = d.createElement('hbox'); counter.setAttribute('class', this.kCOUNTER_CONTAINER); let startParen = counter.appendChild(d.createElement('label')); startParen.setAttribute('class', this.kCOUNTER_PAREN); startParen.setAttribute('value', '('); let counterLabel = counter.appendChild(d.createElement('label')); counterLabel.setAttribute('class', this.kCOUNTER); counterLabel.setAttribute('value', '0'); let endParen = counter.appendChild(d.createElement('label')); endParen.setAttribute('class', this.kCOUNTER_PAREN); endParen.setAttribute('value', ')'); /** XXX * Insertion before an anonymous element breaks its "xbl:inherits". * For example, "xbl:inherits" of the closebox in a tab (Tab Mix Plus * defines it) doesn't work. So, I don't use insertBefore(). * Instead, the counter will be rearranged by "ordinal" attribute * given by initTabContentsOrder(). */ // label.parentNode.insertBefore(counter, label.nextSibling); label.parentNode.appendChild(counter); } var tabContentBox = d.getAnonymousElementByAttribute(aTab, 'class', 'tab-content'); if (tabContentBox && (tabContentBox.firstChild.className || '').indexOf('tab-image-') > -1) { // Set stretched only if the tabFx2Compatible.xml is applied. // Tab Mix Plus overrides the binding so icons are wrongly stretched. tabContentBox.setAttribute('align', this.isVertical ? 'stretch' : 'center' ); } this.initTabContentsOrder(aTab); }, initTabContentsOrder : function TSTBrowser_initTabContentsOrder(aTab) { var d = this.document; var label = this.getTabLabel(aTab); var close = this.getTabClosebox(aTab); var inverted = this.mTabBrowser.getAttribute(this.kTAB_CONTENTS_INVERTED) == 'true'; var nodesContainer = d.getAnonymousElementByAttribute(aTab, 'class', 'tab-content') || aTab; var nodes = Array.slice(d.getAnonymousNodes(nodesContainer) || nodesContainer.childNodes); // reset order nodes.forEach(function(aNode, aIndex) { aNode.setAttribute('ordinal', aIndex); }, this); // rearrange top-level contents nodes.splice(nodes.indexOf(close), 1); if (inverted) { if (this.mTabBrowser.getAttribute(this.kCLOSEBOX_INVERTED) == 'true') nodes.splice(nodes.indexOf(label.parentNode)+1, 0, close); else nodes.splice(nodes.indexOf(label.parentNode), 0, close); } else { if (this.mTabBrowser.getAttribute(this.kCLOSEBOX_INVERTED) == 'true') nodes.splice(nodes.indexOf(label.parentNode), 0, close); else nodes.splice(nodes.indexOf(label.parentNode)+1, 0, close); } var count = nodes.length; Array.slice(nodes).reverse() .forEach(function(aNode, aIndex) { aNode.setAttribute('ordinal', (count - aIndex + 1) * 100); }, this); // rearrange contents in "tab-image-middle" nodes = Array.slice(label.parentNode.childNodes); if (inverted) nodes.reverse(); var counter = d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER); if (counter) { nodes.splice(nodes.indexOf(counter), 1); nodes.splice(nodes.indexOf(label)+1, 0, counter); } count = nodes.length; nodes.reverse().forEach(function(aNode, aIndex) { if (aNode.getAttribute('class') == 'informationaltab-thumbnail-container') return; aNode.setAttribute('ordinal', (count - aIndex + 1) * 100); }, this); }, updateInvertedTabContentsOrder : function TSTBrowser_updateInvertedTabContentsOrder(aTarget) { if (!this.getTreePref('tabbar.invertTabContents')) return; var self = this; this.Deferred.next(function() { var b = self.mTabBrowser; var tabs = !aTarget ? [b.selectedTab] : (aTarget instanceof Ci.nsIDOMElement) ? [aTarget] : (typeof aTarget == 'object' && 'length' in aTarget) ? Array.slice(aTarget) : self.getAllTabsArray(b); tabs.forEach(function(aTab) { this.initTabContentsOrder(aTab); }, self); }); }, initTabbar : function TSTBrowser_initTabbar(aNewPosition, aOldPosition) { var d = this.document; var b = this.mTabBrowser; if (aNewPosition && typeof aNewPosition == 'string') aNewPosition = this.getPositionFlag(aNewPosition); if (aOldPosition && typeof aOldPosition == 'string') aOldPosition = this.getPositionFlag(aOldPosition); this._startListenTabbarEvents(); this.window.TreeStyleTabWindowHelper.initTabbarMethods(b); this.stopRendering(); var pos = aNewPosition || this.getPositionFlag(this.position); if (b.getAttribute('id') != 'content' && !this.getTreePref('tabbar.position.subbrowser.enabled')) { pos = this.kTABBAR_TOP; } aOldPosition = aOldPosition || pos; // We have to use CSS property hack instead, because the stopRendering() // doesn't effect on the first time of startup. // * This hack works in a "stop"-"start" pair, so, people never see the side effect. // * This hack works only when "ordinal" properties are modified. // So, this is just for the case: "right" or "bottom" tab bar on the startup. if ( pos != aOldPosition && ( ((pos & this.kTABBAR_REGULAR) && (aOldPosition & this.kTABBAR_INVERTED)) || ((pos & this.kTABBAR_INVERTED) && (aOldPosition & this.kTABBAR_REGULAR)) ) ) b.style.visibility = 'hidden'; var strip = this.tabStrip; var placeHolder = this.tabStripPlaceHolder || strip; var splitter = this._ensureNewSplitter(); var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER); var indicator = b.mTabDropIndicatorBar || b.tabContainer._tabDropIndicator; // Tab Mix Plus var scrollFrame, newTabBox, tabBarMode; if (this.getTreePref('compatibility.TMP')) { scrollFrame = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-frame') || d.getAnonymousElementByAttribute(b.mTabContainer, 'anonid', 'scroll-tabs-frame'); newTabBox = d.getAnonymousElementByAttribute(b.mTabContainer, 'id', 'tabs-newbutton-box'); let newTabButton = d.getElementById('new-tab-button'); if (newTabButton && newTabButton.parentNode == b.tabContainer._container) newTabBox = newTabButton; tabBarMode = this.getPref('extensions.tabmix.tabBarMode'); } // All-in-One Sidebar var toolboxContainer = d.getAnonymousElementByAttribute(strip, 'anonid', 'aiostbx-toolbox-tableft'); if (toolboxContainer) toolboxContainer = toolboxContainer.parentNode; var scrollInnerBox = b.mTabContainer.mTabstrip._scrollbox ? d.getAnonymousNodes(b.mTabContainer.mTabstrip._scrollbox)[0] : scrollFrame; // Tab Mix Plus this.removeTabbrowserAttribute(this.kRESIZING, b); this.removeTabStripAttribute('width'); b.mPanelContainer.removeAttribute('width'); var delayedPostProcess; if (pos & this.kTABBAR_VERTICAL) { this.collapseTarget = 'top'; this.screenPositionProp = 'screenY'; this.sizeProp = 'height'; this.invertedScreenPositionProp = 'screenX'; this.invertedSizeProp = 'width'; this.startProp = 'top'; this.endProp = 'bottom'; b.mTabBox.orient = splitter.orient = 'horizontal'; strip.orient = placeHolder.orient = toggler.orient = b.mTabContainer.orient = b.mTabContainer.mTabstrip.orient = b.mTabContainer.mTabstrip.parentNode.orient = 'vertical'; b.mTabContainer.setAttribute('align', 'stretch'); // for Mac OS X if (scrollInnerBox) scrollInnerBox.removeAttribute('flex'); if (this.getTreePref('compatibility.TMP') && scrollFrame) { // Tab Mix Plus d.getAnonymousNodes(scrollFrame)[0].removeAttribute('flex'); scrollFrame.parentNode.orient = scrollFrame.orient = 'vertical'; if (newTabBox) newTabBox.orient = 'horizontal'; if (tabBarMode == 2) this.setPref('extensions.tabmix.tabBarMode', 1); } if (toolboxContainer) toolboxContainer.orient = 'vertical'; this.setTabbrowserAttribute(this.kMODE, 'vertical'); let width = this.maxTabbarWidth(this.getTreePref('tabbar.width'), b); this.setTabStripAttribute('width', width); this.removeTabStripAttribute('height'); b.mPanelContainer.removeAttribute('height'); if (strip.localName == 'toolbar') { Array.forEach(strip.childNodes, function(aNode) { if (aNode.localName == 'tabs') return; if (aNode.hasAttribute('flex')) aNode.setAttribute('treestyletab-backup-flex', aNode.getAttribute('flex')); aNode.removeAttribute('flex'); }, this); } if (pos == this.kTABBAR_RIGHT) { this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'right'); if (this.getTreePref('tabbar.invertTab')) { this.setTabbrowserAttribute(this.kTAB_INVERTED, 'true'); this.indentTarget = 'right'; } else { this.removeTabbrowserAttribute(this.kTAB_INVERTED); this.indentTarget = 'left'; } delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { /* in Firefox 3, the width of the rightside tab bar unexpectedly becomes 0 on the startup. so, we have to set the width again. */ aSelf.setTabStripAttribute('width', width); if (!aSelf.isFloating) indicator.setAttribute('ordinal', 1); aSelf.setTabStripAttribute('ordinal', 30); aSplitter.setAttribute('ordinal', 20); aToggler.setAttribute('ordinal', 40); aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); aSplitter.setAttribute('collapse', 'after'); }; } else { this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'left'); this.removeTabbrowserAttribute(this.kTAB_INVERTED); this.indentTarget = 'left'; delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { if (!aSelf.isFloating) indicator.setAttribute('ordinal', 1); aSelf.setTabStripAttribute('ordinal', 10); aSplitter.setAttribute('ordinal', 20); aToggler.setAttribute('ordinal', 5); aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); aSplitter.setAttribute('collapse', 'before'); }; } } else { this.collapseTarget = 'left'; this.screenPositionProp = 'screenX'; this.sizeProp = 'width'; this.invertedScreenPositionProp = 'screenY'; this.invertedSizeProp = 'height'; this.startProp = 'left'; this.endProp = 'right'; b.mTabBox.orient = splitter.orient = 'vertical'; strip.orient = placeHolder.orient = toggler.orient = b.mTabContainer.orient = b.mTabContainer.mTabstrip.orient = b.mTabContainer.mTabstrip.parentNode.orient = 'horizontal'; b.mTabContainer.removeAttribute('align'); // for Mac OS X if (scrollInnerBox) scrollInnerBox.setAttribute('flex', 1); if (this.getTreePref('compatibility.TMP') && scrollFrame) { // Tab Mix Plus d.getAnonymousNodes(scrollFrame)[0].setAttribute('flex', 1); scrollFrame.parentNode.orient = scrollFrame.orient = 'horizontal'; if (newTabBox) newTabBox.orient = 'vertical'; } if (toolboxContainer) toolboxContainer.orient = 'horizontal'; this.setTabbrowserAttribute(this.kMODE, this.getTreePref('tabbar.multirow') ? 'multirow' : 'horizontal'); this.removeTabbrowserAttribute(this.kTAB_INVERTED); if (strip.localName == 'toolbar') { Array.forEach(strip.childNodes, function(aNode) { if (aNode.localName == 'tabs') return; var flex = aNode.hasAttribute('treestyletab-backup-flex'); if (!flex) return; aNode.setAttribute('flex', flex); aNode.removeAttribute('treestyletab-backup-flex'); }, this); } if (pos == this.kTABBAR_BOTTOM) { this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'bottom'); this.indentTarget = 'bottom'; delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { if (!aSelf.isFloating) indicator.setAttribute('ordinal', 1); aSelf.setTabStripAttribute('ordinal', 30); aSplitter.setAttribute('ordinal', 20); aToggler.setAttribute('ordinal', 40); aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); }; } else { this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'top'); this.indentTarget = 'top'; delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) { if (!aSelf.isFloating) indicator.setAttribute('ordinal', 1); aSelf.setTabStripAttribute('ordinal', 10); aSplitter.setAttribute('ordinal', 20); aToggler.setAttribute('ordinal', 5); aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); }; } } var tabs = this.getAllTabsArray(b); tabs.forEach(function(aTab) { aTab.style.removeProperty(this.indentCSSProp); aTab.style.removeProperty(this.collapseCSSProp); }, this); this.indentProp = this.getTreePref('indent.property'); this.indentCSSProp = this.indentProp+'-'+this.indentTarget; this.collapseCSSProp = 'margin-'+this.collapseTarget; tabs.forEach(function(aTab) { this.updateTabCollapsed(aTab, aTab.getAttribute(this.kCOLLAPSED) == 'true', true); }, this); this.updateTabbarState(false); var self = this; this.Deferred.next(function() { delayedPostProcess(self, b, splitter, toggler); self.updateTabbarOverflow(); self.updateAllTabsButton(b); delayedPostProcess = null; self.mTabBrowser.style.visibility = ''; var event = d.createEvent('Events'); event.initEvent(self.kEVENT_TYPE_TABBAR_INITIALIZED, true, false); self.mTabBrowser.dispatchEvent(event); self.startRendering(); }); pos = null; scrollFrame = null; newTabBox = null tabBarMode = null; toolboxContainer = null; scrollInnerBox = null; scrollInnerBox = null; }, _startListenTabbarEvents : function TSTBrowser_startListenTabbarEvents() { var b = this.mTabBrowser; var tabContainer = b.mTabContainer; tabContainer.addEventListener('TabOpen', this, true); tabContainer.addEventListener('TabClose', this, true); tabContainer.addEventListener('TabMove', this, true); tabContainer.addEventListener('TabShow', this, true); tabContainer.addEventListener('TabHide', this, true); tabContainer.addEventListener('SSTabRestoring', this, true); tabContainer.addEventListener('SSTabRestored', this, true); tabContainer.addEventListener('TabPinned', this, true); tabContainer.addEventListener('TabUnpinned', this, true); if (!this.isGecko2 && 'tabutils' in this.window) tabContainer.addEventListener('DOMAttrModified', this, true); // Tab Utilities tabContainer.addEventListener('mouseover', this, true); tabContainer.addEventListener('dblclick', this, true); tabContainer.addEventListener('select', this, true); tabContainer.addEventListener('scroll', this, true); var strip = this.tabStrip; strip.addEventListener('MozMouseHittest', this, true); // to block default behaviors of the tab bar strip.addEventListener('mousedown', this, true); strip.addEventListener('click', this, true); this.scrollBox.addEventListener('overflow', this, true); this.scrollBox.addEventListener('underflow', this, true); this.tabTooltip.addEventListener('popupshowing', this, true); }, _ensureNewSplitter : function TSTBrowser__ensureNewSplitter() { var d = this.document; var splitter = this.splitter; // We always have to re-create splitter, because its "collapse" // behavior becomes broken by repositioning of the tab bar. if (splitter) { try { splitter.removeEventListener('mousedown', this.windowService, false); splitter.removeEventListener('mouseup', this.windowService, false); splitter.removeEventListener('dblclick', this.windowService, false); } catch(e) { } let oldSplitter = splitter; splitter = oldSplitter.cloneNode(true); oldSplitter.parentNode.removeChild(oldSplitter); } else { splitter = d.createElement('splitter'); splitter.setAttribute('state', 'open'); splitter.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 splitter.appendChild(d.createElement('grippy')); } var splitterClass = splitter.getAttribute('class') || ''; if (splitterClass.indexOf(this.kSPLITTER) < 0) splitterClass += (splitterClass ? ' ' : '' ) + this.kSPLITTER; splitter.setAttribute('class', splitterClass); splitter.addEventListener('mousedown', this.windowService, false); splitter.addEventListener('mouseup', this.windowService, false); splitter.addEventListener('dblclick', this.windowService, false); var ref = this.mTabBrowser.mPanelContainer; ref.parentNode.insertBefore(splitter, ref); return splitter; }, fireTabbarPositionEvent : function TSTBrowser_fireTabbarPositionEvent(aChanging, aOldPosition, aNewPosition) { if (aOldPosition == aNewPosition) return false; var type = aChanging ? this.kEVENT_TYPE_TABBAR_POSITION_CHANGING : this.kEVENT_TYPE_TABBAR_POSITION_CHANGED; var data = { oldPosition : aOldPosition, newPosition : aNewPosition }; /* PUBLIC API */ this.fireDataContainerEvent(type, this.mTabBrowser, true, false, data); // for backward compatibility this.fireDataContainerEvent(type.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); return true; }, updateTabbarState : function TSTBrowser_updateTabbarState(aCancelable) { if (!this._fireTabbarStateChangingEvent() && aCancelable) return; this.stopRendering(); var w = this.window; var d = this.document; var b = this.mTabBrowser; var orient; var toggleTabsOnTop = d.getElementById('cmd_ToggleTabsOnTop'); var TabsOnTop = 'TabsOnTop' in w ? w.TabsOnTop : null ; if (this.isVertical) { orient = 'vertical'; this.fixed = this.fixed; // ensure set to the current orient if (toggleTabsOnTop) toggleTabsOnTop.setAttribute('disabled', true); } else { orient = 'horizontal'; if (this.fixed) { this.fixed = true; // ensure set to the current orient if (!this.isMultiRow()) { this.removeTabStripAttribute('height'); b.mPanelContainer.removeAttribute('height'); } // remove ordinal for "tabs on top" https://bugzilla.mozilla.org/show_bug.cgi?id=544815 if (this.isFloating && this.position == 'top') { this.removeTabStripAttribute('ordinal'); if (TabsOnTop) { // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=555987 TabsOnTop.enabled = !TabsOnTop.enabled; this.Deferred.next(function() { TabsOnTop.enabled = !TabsOnTop.enabled; }); } } } else { this.fixed = false; // ensure set to the current orient this.setTabStripAttribute('height', this.maxTabbarHeight(this.getTreePref('tabbar.height'), b)); } if (toggleTabsOnTop) { if (this.position == 'top') toggleTabsOnTop.removeAttribute('disabled'); else toggleTabsOnTop.setAttribute('disabled', true); } } if (TabsOnTop) { let tabsWasOnTop = TabsOnTop.enabled; TabsOnTop.enabled = TabsOnTop.enabled && this.position == 'top' && this.fixed; if (tabsWasOnTop && !TabsOnTop.enabled) this.setTreePref('tabsOnTopShouldBeRestored', true); } var self = this; this.Deferred.next(function() { self.updateFloatingTabbar(self.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); self._fireTabbarStateChangedEvent(); self.startRendering(); }); this.allowSubtreeCollapseExpand = this.getTreePref('allowSubtreeCollapseExpand.'+orient) ; this.maxTreeLevel = this.getTreePref('maxTreeLevel.'+orient); this.setTabbrowserAttribute(this.kALLOW_STACK, this.canStackTabs ? 'true' : null); this.updateTabsZIndex(this.canStackTabs); if (!this.ownerToolbar) /* legacy feature for Firefox 3.6 or olders */ this.hideAlltabsButton = this.getTreePref('tabbar.hideAlltabsButton.'+orient); if (this.maxTreeLevelPhisical) this.promoteTooDeepLevelTabs(); this.updateAllTabsIndent(); }, _fireTabbarStateChangingEvent : function TSTBrowser_fireTabbarStateChangingEvent() { var b = this.mTabBrowser; var orient = this.isVertical ? 'vertical' : 'horizontal' ; var oldState = { fixed : this.fixed, maxTreeLevel : this.maxTreeLevel, indented : this.maxTreeLevel != 0, canCollapse : b.getAttribute(this.kALLOW_COLLAPSE) == 'true' }; if (!this.ownerToolbar) { /* legacy feature for Firefox 3.6 or olders */ oldState.alltabsButton = b.getAttribute(this.kHIDE_ALLTABS) != 'true'; oldState.allTabsButton = oldState.alltabsButton; } var newState = { fixed : this.getTreePref('tabbar.fixed.'+orient), maxTreeLevel : this.getTreePref('maxTreeLevel.'+orient), indented : this.getTreePref('maxTreeLevel.'+orient) != 0, canCollapse : this.getTreePref('allowSubtreeCollapseExpand.'+orient) }; if (!this.ownerToolbar) { /* legacy feature for Firefox 3.6 or olders */ newState.alltabsButton = !this.getTreePref('tabbar.hideAlltabsButton.'+orient); newState.allTabsButton = newState.alltabsButton; } if (oldState.fixed == newState.fixed && oldState.maxTreeLevel == newState.maxTreeLevel && oldState.indented == newState.indented && oldState.canCollapse == newState.canCollapse && oldState.alltabsButton == newState.alltabsButton) return false; var data = { oldState : oldState, newState : newState }; /* PUBLIC API */ this.fireDataContainerEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING, this.mTabBrowser, true, false, data); // for backward compatibility this.fireDataContainerEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); return true; }, _fireTabbarStateChangedEvent : function TSTBrowser_fireTabbarStateChangedEvent() { var b = this.mTabBrowser; var state = { fixed : this.fixed, maxTreeLevel : this.maxTreeLevel, indented : this.maxTreeLevel != 0, canCollapse : b.getAttribute(this.kALLOW_COLLAPSE) == 'true' }; if (!this.ownerToolbar) { /* legacy feature for Firefox 3.6 or olders */ state.alltabsButton = b.getAttribute(this.kHIDE_ALLTABS) != 'true'; state.allTabsButton = state.alltabsButton; } var data = { state : state }; /* PUBLIC API */ this.fireDataContainerEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this.mTabBrowser, true, false, data); // for backward compatibility this.fireDataContainerEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data); return true; }, updateFloatingTabbar : function TSTBrowser_updateFloatingTabbar(aReason) { // this method is just for Firefox 4.0 or later if (!this.isFloating) return; var w = this.window; if (this._updateFloatingTabbarTimer) { w.clearTimeout(this._updateFloatingTabbarTimer); this._updateFloatingTabbarTimer = null; } this._updateFloatingTabbarReason |= aReason; if (this._updateFloatingTabbarReason & this.kTABBAR_UPDATE_NOW) { this._updateFloatingTabbarInternal(this._updateFloatingTabbarReason); this._updateFloatingTabbarReason = 0; } else { this._updateFloatingTabbarTimer = w.setTimeout(function(aSelf) { aSelf._updateFloatingTabbarTimer = null; aSelf._updateFloatingTabbarInternal(aSelf._updateFloatingTabbarReason) aSelf._updateFloatingTabbarReason = 0; }, 0, this); } }, _updateFloatingTabbarInternal : function TSTBrowser_updateFloatingTabbarInternal(aReason) { aReason = aReason || this.kTABBAR_UPDATE_BY_UNKNOWN_REASON; var d = this.document; var splitter = this.splitter; if (splitter.collapsed || splitter.getAttribute('state') != 'collapsed') { this._tabStripPlaceHolder.collapsed = splitter.collapsed = (this.getPref('browser.tabs.autoHide') && this.getTabsArray(this.mTabBrowser).length == 1); } var strip = this.tabStrip; var collapsed = splitter.collapsed ? strip.collapsed : splitter.getAttribute('state') == 'collapsed' ; var stripStyle = strip.style; var tabContainerBox = this.getTabContainerBox(this.mTabBrowser); var statusPanel = d.getElementById('statusbar-display'); var statusPanelStyle = statusPanel ? statusPanel.style : null ; var pos = this.position; if (pos != 'top' || this.mTabBrowser.getAttribute(this.kFIXED) != 'true') { strip.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 if ( this.autoHide.enabled && this.autoHide.expanded && (aReason & this.kTABBAR_UPDATE_SYNC_TO_PLACEHOLDER) && this.autoHide.mode == this.autoHide.kMODE_SHRINK ) this.autoHide.hide(); let box = this._tabStripPlaceHolder.boxObject; let root = d.documentElement.boxObject; let realWidth = parseInt(this._tabStripPlaceHolder.getAttribute('width') || box.width); let realHeight = parseInt(this._tabStripPlaceHolder.getAttribute('height') || box.height); let width = (this.autoHide.expanded && this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ? this.maxTabbarWidth(this.getTreePref('tabbar.width')) : 0 ) || realWidth; let height = (this.autoHide.expanded && !this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ? this.maxTabbarHeight(this.getTreePref('tabbar.height')) : 0 ) || realHeight; let yOffset = pos == 'bottom' ? height - realHeight : 0 ; stripStyle.top = (box.screenY - root.screenY + root.y - yOffset)+'px'; stripStyle.left = pos == 'right' ? '' : (box.screenX - root.screenX + root.x)+'px'; stripStyle.right = pos != 'right' ? '' : ((root.screenX + root.width) - (box.screenX + box.width))+'px'; stripStyle.width = (tabContainerBox.width = width)+'px'; stripStyle.height = (tabContainerBox.height = height)+'px'; this._updateFloatingTabbarResizer({ width : width, realWidth : realWidth, height : height, realHeight : realHeight }); strip.collapsed = tabContainerBox.collapsed = collapsed; if (statusPanel && this.getTreePref('repositionStatusPanel')) { let offsetParentBox = this.utils.findOffsetParent(statusPanel).boxObject; let contentBox = this.mTabBrowser.mPanelContainer.boxObject; let chromeMargins = (d.documentElement.getAttribute('chromemargin') || '0,0,0,0').split(','); chromeMargins = chromeMargins.map(function(aMargin) { return parseInt(aMargin); }); statusPanelStyle.marginTop = (pos == 'bottom') ? '-moz-calc(0px - ' + (offsetParentBox.height - contentBox.height + chromeMargins[2]) + 'px - 3em)' : '' ; statusPanelStyle.marginLeft = (contentBox.screenX - offsetParentBox.screenX + chromeMargins[3])+'px'; statusPanelStyle.marginRight = ((offsetParentBox.screenX + offsetParentBox.width) - (contentBox.screenX + contentBox.width) + chromeMargins[1])+'px'; statusPanelStyle.maxWidth = this.isVertical ? parseInt(contentBox.width / 2)+'px' : '' ; statusPanel.__treestyletab__repositioned = true; } this.mTabBrowser.tabContainer.setAttribute('context', this.mTabBrowser.tabContextMenu.id); } else { strip.collapsed = tabContainerBox.collapsed = collapsed; stripStyle.top = stripStyle.left = stripStyle.right = stripStyle.width = stripStyle.height = ''; if ( statusPanel && ( this.getTreePref('repositionStatusPanel') || statusPanel.__treestyletab__repositioned ) ) { statusPanelStyle.marginTop = statusPanelStyle.marginLeft = statusPanelStyle.marginRight = statusPanelStyle.maxWidth = ''; statusPanel.__treestyletab__repositioned = false; } strip.removeAttribute('layer'); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468 this.mTabBrowser.tabContainer.removeAttribute('context'); } if (tabContainerBox.boxObject.width) this.positionPinnedTabs(null, null, aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE); else this.positionPinnedTabsWithDelay(null, null, aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE); }, _updateFloatingTabbarResizer : function TSTBrowser_updateFloatingTabbarResizer(aSize) { var d = this.document; var width = aSize.width; var realWidth = this.autoHide.mode == this.autoHide.kMODE_HIDE ? 0 : aSize.realWidth ; var height = aSize.height; var realHeight = this.autoHide.mode == this.autoHide.kMODE_HIDE ? 0 : aSize.realHeight ; var pos = this.position; var vertical = this.isVertical; var splitter = d.getElementById('treestyletab-tabbar-resizer-splitter'); if (!splitter) { let box = d.createElement('box'); box.setAttribute('id', 'treestyletab-tabbar-resizer-box'); splitter = d.createElement('splitter'); splitter.setAttribute('id', 'treestyletab-tabbar-resizer-splitter'); splitter.setAttribute('class', this.kSPLITTER); splitter.setAttribute('onmousedown', 'TreeStyleTabService.handleEvent(event);'); splitter.setAttribute('onmouseup', 'TreeStyleTabService.handleEvent(event);'); splitter.setAttribute('ondblclick', 'TreeStyleTabService.handleEvent(event);'); box.appendChild(splitter); this.tabStrip.appendChild(box); } var box = splitter.parentNode; box.orient = splitter.orient = vertical ? 'horizontal' : 'vertical' ; box.width = (width - realWidth) || width; box.height = (height - realHeight) || height; var boxStyle = box.style; boxStyle.top = pos == 'top' ? realHeight+'px' : '' ; boxStyle.right = pos == 'right' ? realWidth+'px' : '' ; boxStyle.left = pos == 'left' ? realWidth+'px' : '' ; boxStyle.bottom = pos == 'bottom' ? realHeight+'px' : '' ; if (vertical) { splitter.removeAttribute('width'); splitter.setAttribute('height', height); } else { splitter.setAttribute('width', width); splitter.removeAttribute('height'); } var splitterWidth = splitter.boxObject.width; var splitterHeight = splitter.boxObject.height; var splitterStyle = splitter.style; splitterStyle.marginTop = pos == 'bottom' ? (-splitterHeight)+'px' : vertical ? '0' : box.height+'px' ; splitterStyle.marginRight = pos == 'left' ? (-splitterWidth)+'px' : !vertical ? '0' : box.width+'px' ; splitterStyle.marginLeft = pos == 'right' ? (-splitterWidth)+'px' : !vertical ? '0' : box.width+'px' ; splitterStyle.marginBottom = pos == 'top' ? (-splitterHeight)+'px' : vertical ? '0' : box.height+'px' ; }, updateTabbarOverflow : function TSTBrowser_updateTabbarOverflow() { var d = this.document; var b = this.mTabBrowser; b.mTabContainer.removeAttribute('overflow'); // Firefox 4.0 var container = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container'); if (!container) { if (this.ownerToolbar) container = b.mTabContainer; else return; } container.removeAttribute('overflow'); var scrollBox = this.scrollBox; this.window.setTimeout(function() { scrollBox = d.getAnonymousElementByAttribute(scrollBox, 'anonid', 'scrollbox'); if (scrollBox) scrollBox = d.getAnonymousNodes(scrollBox)[0]; if ( scrollBox && ( scrollBox.boxObject.width > container.boxObject.width || scrollBox.boxObject.height > container.boxObject.height ) ) { b.mTabContainer.setAttribute('overflow', true); // Firefox 4.0 container.setAttribute('overflow', true); } else { b.mTabContainer.removeAttribute('overflow'); // Firefox 4.0 container.removeAttribute('overflow'); } }, 100); }, reinitAllTabs : function TSTBrowser_reinitAllTabs(aSouldUpdateCount) { var tabs = this.getAllTabsArray(this.mTabBrowser); tabs.forEach(function(aTab) { this.initTabAttributes(aTab); this.initTabContents(aTab); if (aSouldUpdateCount) this.updateTabsCount(aTab); }, this); }, destroy : function TSTBrowser_destroy() { this.saveTreeStructure(); this.animationManager.removeTask(this.smoothScrollTask); this.autoHide.destroy(); delete this._autoHide; this._initDNDObservers(); // ensure initialized this.tabbarDNDObserver.destroy(); delete this._tabbarDNDObserver; this.panelDNDObserver.destroy(); delete this._panelDNDObserver; var w = this.window; var d = this.document; var b = this.mTabBrowser; delete b.tabContainer.treeStyleTab; this.getAllTabsArray(b).forEach(function(aTab) { this.stopTabIndentAnimation(aTab); this.stopTabCollapseAnimation(aTab); this.destroyTab(aTab); }, this); this._endListenTabbarEvents(); w.removeEventListener('resize', this, true); w.removeEventListener('beforecustomization', this, true); w.removeEventListener('aftercustomization', this, false); w.removeEventListener('customizationchange', this, false); w.removeEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED, this, false); w.removeEventListener(this.kEVENT_TYPE_PRINT_PREVIEW_EXITED, this, false); b.removeEventListener('nsDOMMultipleTabHandlerTabsClosing', this, false); w['piro.sakura.ne.jp'].tabsDragUtils.destroyTabBrowser(b); var tabContextMenu = b.tabContextMenu || d.getAnonymousElementByAttribute(b, 'anonid', 'tabContextMenu'); tabContextMenu.removeEventListener('popupshowing', this, false); if (this.tabbarCanvas) { this.tabbarCanvas.parentNode.removeChild(this.tabbarCanvas); this.tabbarCanvas = null; } this.ObserverService.removeObserver(this, this.kTOPIC_INDENT_MODIFIED); this.ObserverService.removeObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL); this.ObserverService.removeObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY); this.ObserverService.removeObserver(this, 'sessionstore-windows-restored'); this.ObserverService.removeObserver(this, 'sessionstore-browser-state-restored'); this.ObserverService.removeObserver(this, 'private-browsing-change-granted'); this.ObserverService.removeObserver(this, 'lightweight-theme-styling-update'); this.removePrefListener(this); delete this.windowService; delete this.window; delete this.document; delete this.mTabBrowser.treeStyleTab; delete this.mTabBrowser; }, destroyTab : function TSTBrowser_destroyTab(aTab) { var id = aTab.getAttribute(this.kID); if (id in this.tabsHash) delete this.tabsHash[id]; delete aTab.__treestyletab__linkedTabBrowser; }, _endListenTabbarEvents : function TSTBrowser_endListenTabbarEvents() { var b = this.mTabBrowser; var tabContainer = b.mTabContainer; tabContainer.removeEventListener('TabOpen', this, true); tabContainer.removeEventListener('TabClose', this, true); tabContainer.removeEventListener('TabMove', this, true); tabContainer.removeEventListener('TabShow', this, true); tabContainer.removeEventListener('TabHide', this, true); tabContainer.removeEventListener('SSTabRestoring', this, true); tabContainer.removeEventListener('SSTabRestored', this, true); tabContainer.removeEventListener('TabPinned', this, true); tabContainer.removeEventListener('TabUnpinned', this, true); if (!this.ownerToolbar && 'tabutils' in this.window) b.mTabContainer.removeEventListener('DOMAttrModified', this, true); // Tab Utilites tabContainer.removeEventListener('mouseover', this, true); tabContainer.removeEventListener('dblclick', this, true); tabContainer.removeEventListener('select', this, true); tabContainer.removeEventListener('scroll', this, true); var strip = this.tabStrip; strip.removeEventListener('MozMouseHittest', this, true); strip.removeEventListener('mousedown', this, true); strip.removeEventListener('click', this, true); this.scrollBox.removeEventListener('overflow', this, true); this.scrollBox.removeEventListener('underflow', this, true); this.tabTooltip.removeEventListener('popupshowing', this, true); }, saveCurrentState : function TSTBrowser_saveCurrentState() { this.autoHide.saveCurrentState(); var b = this.mTabBrowser; var floatingBox = this.getTabStrip(b).boxObject; var fixedBox = (this.tabStripPlaceHolder || this.getTabStrip(b)).boxObject; var prefs = { 'tabbar.fixed.horizontal' : b.getAttribute(this.kFIXED+'-horizontal') == 'true', 'tabbar.fixed.vertical' : b.getAttribute(this.kFIXED+'-vertical') == 'true', 'tabbar.width' : this.isVertical && this.autoHide.expanded && floatingBox.width ? floatingBox.width : void(0), 'tabbar.shrunkenWidth' : this.isVertical && !this.autoHide.expanded && fixedBox.width ? fixedBox.width : void(0), 'tabbar.height' : !this.isVertical && this.autoHide.expanded && floatingBox.height ? floatingBox.height : void(0) }; for (var i in prefs) { if (prefs[i] !== void(0) && this.getTreePref(i) != prefs[i]) this.setTreePref(i, prefs[i]); } this.position = this.position; }, /* toolbar customization on Firefox 4 or later */ syncDestroyTabbar : function TSTBrowser_syncDestroyTabbar() { this.stopRendering(); this._lastTabbarPositionBeforeDestroyed = this.position; if (this.position != 'top') { let self = this; this.doAndWaitDOMEvent( this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, this.window, 100, function() { self.position = 'top'; } ); } this.maxTreeLevel = 0; this.hideAlltabsButton = false; /* legacy feature for Firefox 3.6 or olders */ this.fixed = true; this._lastTreeViewEnabledBeforeDestroyed = this.treeViewEnabled; this.treeViewEnabled = false; var tabbar = this.mTabBrowser.tabContainer; tabbar.removeAttribute('width'); tabbar.removeAttribute('height'); tabbar.removeAttribute('ordinal'); this.removeTabStripAttribute('width'); this.removeTabStripAttribute('height'); this.removeTabStripAttribute('ordinal'); this.removeTabStripAttribute('orient'); var toolbar = this.ownerToolbar; this.destroyTabStrip(toolbar); toolbar.classList.add(this.kTABBAR_TOOLBAR_READY); this._endListenTabbarEvents(); this.tabbarDNDObserver.endListenEvents(); this.window.setTimeout(function(aSelf) { aSelf.updateCustomizedTabsToolbar(); }, 100, this); this.startRendering(); }, destroyTabStrip : function TSTBrowser_destroyTabStrip(aTabStrip) { aTabStrip.classList.remove(this.kTABBAR_TOOLBAR); aTabStrip.style.top = aTabStrip.style.left = aTabStrip.style.width = aTabStrip.style.height = ''; aTabStrip.removeAttribute('height'); aTabStrip.removeAttribute('width'); aTabStrip.removeAttribute('ordinal'); aTabStrip.removeAttribute('orient'); }, syncReinitTabbar : function TSTBrowser_syncReinitTabbar() { this.stopRendering(); this.ownerToolbar.classList.add(this.kTABBAR_TOOLBAR); this.ownerToolbar.classList.remove(this.kTABBAR_TOOLBAR_READY); Array.slice(this.document.querySelectorAll('.'+this.kTABBAR_TOOLBAR_READY_POPUP)) .forEach(function(aPanel) { this.safeRemovePopup(aPanel); }, this); var position = this._lastTabbarPositionBeforeDestroyed || this.position; delete this._lastTabbarPositionBeforeDestroyed; var self = this; this.doAndWaitDOMEvent( this.kEVENT_TYPE_TABBAR_INITIALIZED, this.window, 100, function() { self.initTabbar(position, 'top'); } ); this.reinitAllTabs(true); this.tabbarDNDObserver.startListenEvents(); this.treeViewEnabled = this._lastTreeViewEnabledBeforeDestroyed; delete this._lastTreeViewEnabledBeforeDestroyed; this.startRendering(); }, updateCustomizedTabsToolbar : function TSTBrowser_updateCustomizedTabsToolbar() { var d = this.document; var newToolbar = this.ownerToolbar; newToolbar.classList.add(this.kTABBAR_TOOLBAR_READY); var oldToolbar = d.querySelector('.'+this.kTABBAR_TOOLBAR_READY); if (oldToolbar == newToolbar) return; if (oldToolbar && oldToolbar != newToolbar) { this.safeRemovePopup(d.getElementById(oldToolbar.id+'-'+this.kTABBAR_TOOLBAR_READY_POPUP)); oldToolbar.classList.remove(this.kTABBAR_TOOLBAR_READY); } var id = newToolbar.id+'-'+this.kTABBAR_TOOLBAR_READY_POPUP; var panel = d.getElementById(id); if (!panel) { panel = d.createElement('panel'); panel.setAttribute('id', id); panel.setAttribute('class', this.kTABBAR_TOOLBAR_READY_POPUP); panel.setAttribute('noautohide', true); panel.setAttribute('onmouseover', 'this.hidePopup()'); panel.setAttribute('ondragover', 'this.hidePopup()'); panel.appendChild(d.createElement('label')); let position = this._lastTabbarPositionBeforeDestroyed || this.position; let label = this.treeBundle.getString('toolbarCustomizing_tabbar_'+(position == 'left' || position == 'right' ? 'vertical' : 'horizontal' )); panel.firstChild.appendChild(d.createTextNode(label)); d.getElementById('mainPopupSet').appendChild(panel); } panel.openPopup(newToolbar, 'end_after', 0, 0, false, false); }, safeRemovePopup : function TSTBrowser_safeRemovePopup(aPopup) { if (!aPopup) return; if (aPopup.state == 'open') { aPopup.addEventListener('popuphidden', function(aEvent) { aPopup.removeEventListener(aEvent.type, arguments.callee, false); aPopup.parentNode.removeChild(aPopup); }, false); aPopup.hidePopup(); } else { aPopup.parentNode.removeChild(aPopup); } }, /* nsIObserver */ domains : [ 'extensions.treestyletab.', 'browser.tabs.closeButtons', 'browser.tabs.closeWindowWithLastTab', 'browser.tabs.autoHide', 'browser.tabs.animate' ], observe : function TSTBrowser_observe(aSubject, aTopic, aData) { switch (aTopic) { case this.kTOPIC_INDENT_MODIFIED: if (this.indent > -1) this.updateAllTabsIndent(); return; case this.kTOPIC_COLLAPSE_EXPAND_ALL: if (!aSubject || aSubject == this.window) { aData = String(aData); this.collapseExpandAllSubtree( aData.indexOf('collapse') > -1, aData.indexOf('now') > -1 ); } return; case 'sessionstore-windows-restored': case 'sessionstore-browser-state-restored': return this.onWindowStateRestored(); case 'private-browsing-change-granted': this.collapseExpandAllSubtree(false, true); this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PRIVATE_BROWSING); return; case 'lightweight-theme-styling-update': return this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE); case this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY: return this.treeViewEnabled = (aData != 'false'); case 'nsPref:changed': return this.onPrefChange(aData); default: return; } }, onPrefChange : function TSTBrowser_onPrefChange(aPrefName) { var b = this.mTabBrowser; var value = this.getPref(aPrefName); var tabContainer = b.mTabContainer; var tabs = this.getAllTabsArray(b); switch (aPrefName) { case 'extensions.treestyletab.tabbar.position': if (this.shouldApplyNewPref) this.position = value; return; case 'extensions.treestyletab.tabbar.invertTab': case 'extensions.treestyletab.tabbar.multirow': this.initTabbar(); this.updateAllTabsIndent(); tabs.forEach(function(aTab) { this.initTabContents(aTab); }, this); return; case 'extensions.treestyletab.tabbar.invertTabContents': this.setTabbrowserAttribute(this.kTAB_CONTENTS_INVERTED, value); tabs.forEach(function(aTab) { this.initTabContents(aTab); }, this); return; case 'extensions.treestyletab.tabbar.invertClosebox': this.setTabbrowserAttribute(this.kCLOSEBOX_INVERTED, value); tabs.forEach(function(aTab) { this.initTabContents(aTab); }, this); return; case 'extensions.treestyletab.tabbar.style': case 'extensions.treestyletab.tabbar.style.aero': this.setTabbarStyle(this.getTreePref('tabbar.style')); value = this.getTreePref('twisty.style'); if (value != 'auto') return; case 'extensions.treestyletab.twisty.style': return this.setTwistyStyle(value); case 'extensions.treestyletab.showBorderForFirstTab': return this.setTabbrowserAttribute(this.kFIRSTTAB_BORDER, value); case 'extensions.treestyletab.tabbar.fixed.horizontal': if (!this.shouldApplyNewPref) return; this.setTabbrowserAttribute(this.kFIXED+'-horizontal', value ? 'true' : null, b); case 'extensions.treestyletab.maxTreeLevel.horizontal': case 'extensions.treestyletab.allowSubtreeCollapseExpand.horizontal': case 'extensions.treestyletab.tabbar.hideAlltabsButton.horizontal': /* legacy feature for Firefox 3.6 or olders */ if (!this.isVertical) this.updateTabbarState(true); return; case 'extensions.treestyletab.tabbar.fixed.vertical': if (!this.shouldApplyNewPref) return; this.setTabbrowserAttribute(this.kFIXED+'-vertical', value ? 'true' : null, b); case 'extensions.treestyletab.maxTreeLevel.vertical': case 'extensions.treestyletab.allowSubtreeCollapseExpand.vertical': case 'extensions.treestyletab.tabbar.hideAlltabsButton.vertical': /* legacy feature for Firefox 3.6 or olders */ if (this.isVertical) this.updateTabbarState(true); return; case 'extensions.treestyletab.tabbar.width': case 'extensions.treestyletab.tabbar.shrunkenWidth': if (!this.shouldApplyNewPref) return; if (!this.autoHide.isResizing && this.isVertical) { this.removeTabStripAttribute('width'); if (this.isFloating) { this.setTabStripAttribute('width', this.autoHide.placeHolderWidthFromMode); this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE); } else { this.setTabStripAttribute('width', this.autoHide.widthFromMode); } } this.checkTabsIndentOverflow(); return; case 'extensions.treestyletab.tabbar.height': if (!this.shouldApplyNewPref) return; this._horizontalTabMaxIndentBase = 0; this.checkTabsIndentOverflow(); return; case 'extensions.treestyletab.tabbar.autoShow.mousemove': let (toggler = this.document.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER)) { if (toggler) { if (value) toggler.removeAttribute('hidden'); else toggler.setAttribute('hidden', true); } } return; case 'extensions.treestyletab.tabbar.invertScrollbar': this.setTabbrowserAttribute(this.kINVERT_SCROLLBAR, value); this.positionPinnedTabs(); return; case 'extensions.treestyletab.tabbar.narrowScrollbar': return this.setTabbrowserAttribute(this.kNARROW_SCROLLBAR, value); case 'extensions.treestyletab.maxTreeLevel.phisical': if (this.maxTreeLevelPhisical = value) this.promoteTooDeepLevelTabs(); return; case 'browser.tabs.animate': case 'extensions.treestyletab.animation.enabled': this.setTabbrowserAttribute(this.kANIMATION_ENABLED, ( this.getPref('extensions.treestyletab.animation.enabled') && (this.getPref('browser.tabs.animate') !== false) ) ? 'true' : null ); return; case 'browser.tabs.closeButtons': case 'browser.tabs.closeWindowWithLastTab': return this.updateInvertedTabContentsOrder(true); case 'browser.tabs.autoHide': if (this.getTabsArray(this.mTabBrowser).length == 1) this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); return; case 'extensions.treestyletab.tabbar.autoHide.mode': case 'extensions.treestyletab.tabbar.autoHide.mode.fullscreen': return this.autoHide; // ensure initialized case 'extensions.treestyletab.pinnedTab.width': case 'extensions.treestyletab.pinnedTab.height': return this.positionPinnedTabsWithDelay(); default: return; } }, setTabbarStyle : function TSTBrowser_setTabbarStyle(aStyle) { if (/^(default|plain|flat|mixed|vertigo|metal|sidebar)(-aero)?$/.test(aStyle)) aStyle = aStyle.toLowerCase(); if (aStyle.indexOf('default') == 0) { // old name (for compatibility) this.setTreePref('tabbar.style', aStyle = aStyle.replace('default', 'plain')); } else if (// dropshadow is available only on Firefox 3.5 or later. aStyle.indexOf('mixed') == 0 && this.Comparator.compare(this.XULAppInfo.version, '3.5') < 0 ) { this.setTreePref('tabbar.style', aStyle = aStyle.replace('mixed', 'flat')); } if (aStyle) { let additionalValues = []; if (/^(plain|flat|mixed|vertigo)$/.test(aStyle)) additionalValues.push('square'); if (/^(plain|flat|mixed)$/.test(aStyle)) additionalValues.push('border'); if (/^(flat|mixed)$/.test(aStyle)) additionalValues.push('color'); if (/^(plain|mixed)$/.test(aStyle)) additionalValues.push('shadow'); if (this.getTreePref('tabbar.style.aero')) additionalValues.push('aero'); if (additionalValues.length) aStyle = additionalValues.join(' ')+' '+aStyle; this.setTabbrowserAttribute(this.kSTYLE, aStyle); } else { this.removeTabbrowserAttribute(this.kSTYLE); } }, setTwistyStyle : function TSTBrowser_setTwistyStyle(aStyle) { if (aStyle != 'auto') { this.setTabbrowserAttribute(this.kTWISTY_STYLE, aStyle); return; } aStyle = 'modern-black'; if (this.getTreePref('tabbar.style') == 'sidebar') { aStyle = 'osx'; } else if ( this.getPref('extensions.informationaltab.thumbnail.enabled') && this.getPref('extensions.informationaltab.thumbnail.position') < 100 ) { let self = this; this.extensions.isAvailable('informationaltab@piro.sakura.ne.jp', { ok : function() { aStyle = 'retro'; self.setTabbrowserAttribute(self.kTWISTY_STYLE, aStyle); }, ng : function() { self.setTabbrowserAttribute(self.kTWISTY_STYLE, aStyle); } }); return; } this.setTabbrowserAttribute(this.kTWISTY_STYLE, aStyle); }, onWindowStateRestored : function TSTBrowser_onWindowStateRestored() { if (!this.window.__SS_tabsToRestore) return; }, saveTreeStructureWithDelay : function TSTBrowser_saveTreeStructureWithDelay() { if (this.restoringTree || this.saveTreeStructureWithDelayTimer) return; this.saveTreeStructureWithDelayTimer = this.window.setTimeout(function(aSelf) { aSelf.saveTreeStructureWithDelayTimer = null; aSelf.saveTreeStructure(); }, this.getPref('browser.sessionstore.interval'), this); }, saveTreeStructureWithDelayTimer : null, saveTreeStructure : function TSTBrowser_saveTreeStructure() { if (!this.getTreePref('restoreTreeOnStartup')) return; var treeStructures = JSON.parse(this.SessionStore.getWindowValue(this.window, this.kSTRUCTURE) || '{}'); var id = this.mTabBrowser.getAttribute('id'); var tabs = this.getAllTabsArray(this.mTabBrowser); treeStructures[id] = { tree : this.getTreeStructureFromTabs(tabs), collapsed : tabs.filter(function(aTab) { return this.isCollapsed(aTab); }, this).map(function(aTab) { return aTab._tPos; }), treeCollapsed : tabs.filter(function(aTab) { return this.isSubtreeCollapsed(aTab); }, this).map(function(aTab) { return aTab._tPos; }) }; this.SessionStore.setWindowValue(this.window, this.kSTRUCTURE, JSON.stringify(treeStructures)) }, restoreTreeStructure : function TSTBrowser_restoreTreeStructure() { if (!this.getTreePref('restoreTreeOnStartup')) return; this.restoreTreeStructure(); var treeStructures = JSON.parse(this.SessionStore.getWindowValue(this.window, this.kSTRUCTURE) || '{}'); var id = this.mTabBrowser.getAttribute('id'); var treeStructure = id in treeStructures ? treeStructures[id] : null ; if ( !treeStructure || !treeStructure.tree || !treeStructure.tree.length ) return; this.applyTreeStructureToTabBrowser(this.mTabBrowser, treeStructure.tree); this.getAllTabsArray(this.mTabBrowser) .forEach(function(aTab, aIndex) { if (treeStructure.treeCollapsed && treeStructure.treeCollapsed.indexOf(aIndex) > -1) aTab.setAttribute(this.kSUBTREE_COLLAPSED, true); if (treeStructure.collapsed && treeStructure.collapsed.indexOf(aIndex) > -1) this.collapseExpandTab(aTab, true, true); aTab.removeAttribute(this.kID); aTab.removeAttribute(this.kPARENT); aTab.removeAttribute(this.kCHILDREN); }, this); }, /* DOM Event Handling */ handleEvent : function TSTBrowser_handleEvent(aEvent) { switch (aEvent.type) { case 'TabOpen': return this.onTabAdded(aEvent); case 'TabClose': return this.onTabRemoved(aEvent); case 'TabMove': return this.onTabMove(aEvent); case 'TabShow': case 'TabHide': return this.onTabVisibilityChanged(aEvent); case 'SSTabRestoring': return this.onTabRestoring(aEvent); case 'SSTabRestored': return this.onTabRestored(aEvent); case 'TabPinned': return this.onPinTab(aEvent.originalTarget); case 'TabUnpinned': return this.onUnpinTab(aEvent.originalTarget); case 'DOMAttrModified': return this.onDOMAttrModified(aEvent); case 'select': return this.onTabSelect(aEvent); case 'click': return this.onClick(aEvent); case 'dblclick': return this.onDblClick(aEvent); case 'MozMouseHittest': // to block default behaviors of the tab bar return this.onMozMouseHittest(aEvent); case 'mousedown': return this.onMouseDown(aEvent); case 'scroll': return this.onScroll(aEvent); case 'popupshowing': return this.onPopupShowing(aEvent) case 'mouseover': this._initDNDObservers(); let (tab = aEvent.target) { if (tab.__treestyletab__twistyHoverTimer) this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer); if (this.isEventFiredOnTwisty(aEvent)) tab.__treestyletab__twistyHoverTimer = this.window.setTimeout(function(aSelf) { tab.setAttribute(aSelf.kTWISTY_HOVER, true); }, 0, this); else tab.removeAttribute(this.kTWISTY_HOVER); } return; case 'dragover': return this._initDNDObservers(); case 'overflow': case 'underflow': return this.onTabbarOverflow(aEvent); case 'resize': return this.onResize(aEvent); // toolbar customizing on Firefox 4 or later case 'beforecustomization': this.toolbarCustomizing = true; return this.syncDestroyTabbar(); case 'aftercustomization': // Ignore it, because 'aftercustomization' fired not // following to 'beforecustomization' is invalid. // Personal Titlebar addon (or others) fires a fake // event on its startup process. if (!this.toolbarCustomizing) return; this.toolbarCustomizing = false; return this.syncReinitTabbar(); case 'customizationchange': return this.updateCustomizedTabsToolbar(); case this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED: return this.onTreeStyleTabPrintPreviewEntered(aEvent); case this.kEVENT_TYPE_PRINT_PREVIEW_EXITED: return this.onTreeStyleTabPrintPreviewExited(aEvent); case 'nsDOMMultipleTabHandlerTabsClosing': if (!this.onTabsRemoving(aEvent)) aEvent.preventDefault(); return; } }, lastScrollX : -1, lastScrollY : -1, restoreLastScrollPosition : function TSTBrowser_restoreLastScrollPosition() { if (this.lastScrollX < 0 || this.lastScrollY < 0) return; var lastX = this.lastScrollX; var lastY = this.lastScrollY; this.clearLastScrollPosition(); if (!this.smoothScrollTask && !this.scrollBox._smoothScrollTimer) { // don't restore scroll position if another scroll is already running. let x = {}, y = {}; let scrollBoxObject = this.scrollBoxObject; scrollBoxObject.getPosition(x, y); if (x.value != lastX || y.value != lastY) scrollBoxObject.scrollTo(lastX, lastY); } }, clearLastScrollPosition : function TSTBrowser_clearLastScrollPosition() { this.lastScrollX = -1; this.lastScrollY = -1; }, updateLastScrollPosition : function TSTBrowser_updateLastScrollPosition() { if (!this.isVertical) return; var x = {}, y = {}; var scrollBoxObject = this.scrollBoxObject; if (!scrollBoxObject) return; scrollBoxObject.getPosition(x, y); this.lastScrollX = x.value; this.lastScrollY = y.value; }, onTabAdded : function TSTBrowser_onTabAdded(aEvent, aTab) { var tab = aTab || aEvent.originalTarget; var b = this.mTabBrowser; if (this.isTabInitialized(tab)) return false; this.initTab(tab); var hasStructure = this.treeStructure && this.treeStructure.length; var pareintIndexInTree = hasStructure ? this.treeStructure.shift() : 0 ; var lastRelatedTab = b._lastRelatedTab; if (this.readiedToAttachNewTab) { if (pareintIndexInTree < 0) { // there is no parent, so this is a new parent! this.parentTab = tab.getAttribute(this.kID); } let parent = this.getTabById(this.parentTab); if (parent) { let tabs = [parent].concat(this.getDescendantTabs(parent)); parent = pareintIndexInTree < tabs.length ? tabs[pareintIndexInTree] : parent ; } if (parent) { this.attachTabTo(tab, parent, { dontExpand : this.shouldExpandAllTree }); } let refTab; let newIndex = -1; if (hasStructure) { } else if (this.insertBefore && (refTab = this.getTabById(this.insertBefore))) { newIndex = refTab._tPos; } else if ( parent && this.getTreePref('insertNewChildAt') == this.kINSERT_FISRT && (this.multipleCount == 0 || this._addedCountInThisLoop == 0) ) { /* •¡”‚ÌŽqƒ^ƒu‚ðˆê‹C‚ÉŠJ‚­ê‡Aʼn‚ÉŠJ‚¢‚½ƒ^ƒu‚¾‚¯‚ð Žqƒ^ƒu‚Ìʼn‚̈ʒu‚É‘}“ü‚µA‘±‚­ƒ^ƒu‚Íuʼn‚ÌŠJ‚¢‚½ƒ^ƒuv‚Æ uŒ³Xʼn‚ÌŽq‚¾‚Á‚½ƒ^ƒuv‚Æ‚ÌŠÔ‚É‘}“ü‚µ‚Ä‚¢‚­ */ newIndex = parent._tPos + 1; if (refTab = this.getFirstChildTab(parent)) this.insertBefore = refTab.getAttribute(this.kID); } if (newIndex > -1) { if (newIndex > tab._tPos) newIndex--; this.internallyTabMovingCount++; b.moveTabTo(tab, newIndex); this.internallyTabMovingCount--; } if (this.shouldExpandAllTree) this.collapseExpandSubtree(parent, false); } this._addedCountInThisLoop++; if (!this._addedCountClearTimer) { this._addedCountClearTimer = this.window.setTimeout(function(aSelf) { aSelf._addedCountInThisLoop = 0; aSelf._addedCountClearTimer = null; }, 0, this); } if (!this.readiedToAttachMultiple) { this.stopToOpenChildTab(b); } else { this.multipleCount++; } if (this.animationEnabled) { this.updateTabCollapsed(tab, true, true); this.updateTabCollapsed(tab, false, this.restoringTree); } var prev = this.getPreviousSiblingTab(tab); if (prev) { this.setTabValue(tab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); this.setTabValue(prev, this.kINSERT_BEFORE, tab.getAttribute(this.kID)); } var next = this.getNextSiblingTab(tab); if (next) { this.setTabValue(tab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); this.setTabValue(next, this.kINSERT_AFTER, tab.getAttribute(this.kID)); } if (this.scrollToNewTabMode > 0) this.scrollToTab(tab, this.scrollToNewTabMode < 2); if (this.getPref('browser.tabs.autoHide')) this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); if (this.canStackTabs) this.updateTabsZIndex(true); // if there is only one tab and new another tab is opened, // closebox appearance is possibly changed. var tabs = this.getTabsArray(b); if (tabs.length == 2) this.updateInvertedTabContentsOrder(tabs); /** * gBrowser.adthis._changeTabbarPosition(position); dTab() resets gBrowser._lastRelatedTab.owner * when a new background tab is opened from the current tab, * but it will fail with TST because gBrowser.moveTab() (called * by TST) clears gBrowser._lastRelatedTab. * So, we have to restore gBrowser._lastRelatedTab manually. */ b._lastRelatedTab = lastRelatedTab; return true; }, _addedCountInThisLoop : 0, _addedCountClearTimer : null, _checkRestoringWindowTimerOnTabAdded : null, onTabRemoved : function TSTBrowser_onTabRemoved(aEvent) { var tab = aEvent.originalTarget; var d = this.document; var b = this.mTabBrowser; tab.setAttribute(this.kREMOVED, true); this.stopTabIndentAnimation(tab); this.stopTabCollapseAnimation(tab); var closeParentBehavior = this.getCloseParentBehaviorForTab(tab); var collapsed = this.isCollapsed(tab); if (collapsed) this.stopRendering(); var backupAttributes = {}; if (this.hasChildTabs(tab)) backupAttributes[this.kCHILDREN] = this.getTabValue(tab, this.kCHILDREN); var subtreeCollapsed = this.isSubtreeCollapsed(tab); if ( closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN || subtreeCollapsed ) { let tabs = this.getDescendantTabs(tab); if (this.fireTabSubtreeClosingEvent(tab, tabs)) { if (subtreeCollapsed) this.stopRendering(); this.markAsClosedSet([tab].concat(tabs)); tabs.reverse().forEach(function(aTab) { b.removeTab(aTab, { animate : true }); }, this); this.fireTabSubtreeClosedEvent(b, tab, tabs); if (subtreeCollapsed) this.startRendering(); } } var toBeClosedSibling = !this.hasChildTabs(tab) ? this._reserveCloseNeedlessGroupTabSibling(tab) : null ; var firstChild = this.getFirstChildTab(tab); var parentTab = this.getParentTab(tab); var nextFocusedTab = null; var prev = this.getPreviousSiblingTab(tab); var next = this.getNextSiblingTab(tab); if (prev) { this.setTabValue(tab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); if (next) this.setTabValue(prev, this.kINSERT_BEFORE, next.getAttribute(this.kID)); else this.deleteTabValue(prev, this.kINSERT_BEFORE); } if (next) { this.setTabValue(tab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); if (prev) this.setTabValue(next, this.kINSERT_AFTER, prev.getAttribute(this.kID)); else this.deleteTabValue(next, this.kINSERT_AFTER); } var indentModifiedTabs = []; if (firstChild) { let children = this.getChildTabs(tab); indentModifiedTabs = indentModifiedTabs.concat( closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD ? [children[0]] : children ); this.partAllChildren(tab, { behavior : closeParentBehavior, dontUpdateIndent : true }); if (closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN || closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD) nextFocusedTab = firstChild; } if (parentTab) { let firstSibling = this.getFirstChildTab(parentTab); let lastSibling = this.getLastChildTab(parentTab); if (tab == lastSibling && !nextFocusedTab) { if (tab == firstSibling) { // there is only one child nextFocusedTab = parentTab; } else { // previous sibling tab nextFocusedTab = this.getPreviousSiblingTab(tab); } } let ancestors = [], ancestor = parentTab; do { ancestors.push(ancestor.getAttribute(this.kID)); if (!next && (next = this.getNextSiblingTab(ancestor))) backupAttributes[this.kINSERT_BEFORE] = next.getAttribute(this.kID); } while (ancestor = this.getParentTab(ancestor)); backupAttributes[this.kANCESTOR] = ancestors.join('|'); let shouldCloseParentTab = ( this.isGroupTab(parentTab) && this.getDescendantTabs(parentTab).length == 1 ); if (shouldCloseParentTab && nextFocusedTab == parentTab) nextFocusedTab = this.getNextFocusedTab(parentTab); this.partTab(tab, { dontUpdateIndent : true }); if (shouldCloseParentTab) { this.Deferred.next(function() { if (parentTab.parentNode) b.removeTab(parentTab, { animate : true }); parentTab = null; b = null; }); } } else if (!nextFocusedTab) { nextFocusedTab = this.getNextFocusedTab(tab); } if (indentModifiedTabs.length) this.updateTabsIndentWithDelay(indentModifiedTabs); this.checkTabsIndentOverflow(); for (var i in backupAttributes) { this.setTabValue(tab, i, backupAttributes[i]); } if (b.selectedTab == tab) { if (nextFocusedTab && nextFocusedTab == toBeClosedSibling) nextFocusedTab = this.getFirstChildTab(nextFocusedTab); if ( nextFocusedTab && !nextFocusedTab.hidden ) { let event = d.createEvent('Events'); event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB, true, true); let canFocus = tab.dispatchEvent(event); // for backward compatibility event = d.createEvent('Events'); event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB.replace(/^nsDOM/, ''), true, true); canFocus = canFocus && tab.dispatchEvent(event); if (canFocus) { this._focusChangedByCurrentTabRemove = true; b.selectedTab = nextFocusedTab; } } } this.updateLastScrollPosition(); this.destroyTab(tab); if (tab.getAttribute('pinned') == 'true') this.positionPinnedTabsWithDelay(); if (this.getPref('browser.tabs.autoHide')) this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR); if (this.canStackTabs) this.updateTabsZIndex(true); if (collapsed) this.startRendering(); }, _reserveCloseNeedlessGroupTabSibling : function TSTBrowser_reserveCloseNeedlessGroupTabSibling(aTab) { if (!aTab) return null; var parent = this.getParentTab(aTab); var siblings = this.getSiblingTabs(aTab); var groupTabs = siblings.filter(function(aTab) { return this.isGroupTab(aTab); }, this); var groupTab = ( groupTabs.length == 1 && siblings.length == 1 && this.hasChildTabs(groupTabs[0]) ) ? groupTabs[0] : null ; if (groupTab) { this.window.setTimeout(function(aSelf, aGroupTab) { aSelf.getTabBrowserFromChild(aGroupTab).removeTab(aGroupTab, { animate : true }); }, 0, this, groupTab); return groupTab; } return null; }, getNextFocusedTab : function TSTBrowser_getNextFocusedTab(aTab) { return this.getNextSiblingTab(aTab) || this.getPreviousVisibleTab(aTab); }, onTabsRemoving : function TSTBrowser_onTabsRemoving(aEvent) { var tabs = aEvent.tabs || aEvent.getData('tabs'); var b = this.getTabBrowserFromChild(tabs[0]); var trees = this.splitTabsToSubtrees(tabs); if (trees.some(function(aTabs) { return aTabs.length > 1 && !this.fireTabSubtreeClosingEvent(aTabs[0], aTabs); }, this)) return false; trees.forEach(function(aTabs) { this.markAsClosedSet(aTabs); }, this); var self = this; this.Deferred.next(function() { trees.forEach(function(aTabs) { self.fireTabSubtreeClosedEvent(b, aTabs[0], aTabs); }); }); return true; }, onTabMove : function TSTBrowser_onTabMove(aEvent) { var tab = aEvent.originalTarget; var b = this.mTabBrowser; // When the tab was moved before TabOpen event is fired, we have to update manually. var newlyOpened = !this.isTabInitialized(tab) && this.onTabAdded(null, tab); // twisty vanished after the tab is moved!! this.initTabContents(tab); if (this.hasChildTabs(tab) && !this.subTreeMovingCount) { this.moveTabSubtreeTo(tab, tab._tPos); } var parentTab = this.getParentTab(tab); if (parentTab && !this.subTreeChildrenMovingCount) { this.updateChildrenArray(parentTab); } this.updateTabsCount(tab, true); var prev = this.getPreviousSiblingTab(tab); var next = this.getNextSiblingTab(tab); if (prev) { this.setTabValue(prev, this.kINSERT_BEFORE, tab.getAttribute(this.kID)); this.setTabValue(tab, this.kINSERT_AFTER, prev.getAttribute(this.kID)); } else this.deleteTabValue(tab, this.kINSERT_AFTER); if (next) { this.setTabValue(next, this.kINSERT_AFTER, tab.getAttribute(this.kID)); this.setTabValue(tab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); } else this.deleteTabValue(tab, this.kINSERT_BEFORE); var old = aEvent.detail; if (old > tab._tPos) old--; var tabs = this.getAllTabsArray(b); old = tabs[old]; prev = this.getPreviousSiblingTab(old); next = this.getNextSiblingTab(old); if (prev) { this.setTabValue(prev, this.kINSERT_BEFORE, old.getAttribute(this.kID)); this.setTabValue(old, this.kINSERT_AFTER, prev.getAttribute(this.kID)); } else this.deleteTabValue(old, this.kINSERT_AFTER); if (next) { this.setTabValue(next, this.kINSERT_AFTER, old.getAttribute(this.kID)); this.setTabValue(old, this.kINSERT_BEFORE, next.getAttribute(this.kID)); } else this.deleteTabValue(old, this.kINSERT_BEFORE); this.positionPinnedTabsWithDelay(); if (this.canStackTabs) this.updateTabsZIndex(true); if ( this.subTreeMovingCount || this.internallyTabMovingCount || // We don't have to fixup tree structure for a NEW TAB // which has already been structured. (newlyOpened && this.getParentTab(tab)) ) return; this.attachTabFromPosition(tab, aEvent.detail); this.rearrangeTabViewItems(tab); }, attachTabFromPosition : function TSTBrowser_attachTabFromPosition(aTab, aOldPosition) { var parent = this.getParentTab(aTab); if (aOldPosition === void(0)) aOldPosition = aTab._tPos; var pos = this.getChildIndex(aTab, parent); var oldPos = this.getChildIndex(this.getAllTabsArray(this.mTabBrowser)[aOldPosition], parent); var delta; if (pos == oldPos) { // no move? return; } else if (pos < 0 || oldPos < 0) { delta = 2; } else { delta = Math.abs(pos - oldPos); } var prevTab = this.getPreviousTab(aTab); var nextTab = this.getNextTab(aTab); var tabs = this.getDescendantTabs(aTab); if (tabs.length) { nextTab = this.getNextTab(tabs[tabs.length-1]); } var prevParent = this.getParentTab(prevTab); var nextParent = this.getParentTab(nextTab); var prevLevel = prevTab ? Number(prevTab.getAttribute(this.kNEST)) : -1 ; var nextLevel = nextTab ? Number(nextTab.getAttribute(this.kNEST)) : -1 ; var newParent; if (!prevTab) { // moved to topmost position newParent = null; } else if (!nextTab) { // moved to last position newParent = (delta > 1) ? prevParent : parent ; } else if (prevParent == nextParent) { // moved into existing tree newParent = prevParent; } else if (prevLevel > nextLevel) { // moved to end of existing tree if (this.mTabBrowser.selectedTab != aTab) { // maybe newly opened tab newParent = prevParent; } else { // maybe drag and drop var realDelta = Math.abs(aTab._tPos - aOldPosition); newParent = realDelta < 2 ? prevParent : (parent || nextParent) ; } } else if (prevLevel < nextLevel) { // moved to first child position of existing tree newParent = parent || nextParent; } if (newParent != parent) { if (newParent) { if (newParent.hidden == aTab.hidden) this.attachTabTo(aTab, newParent, { insertBefore : nextTab }); } else { this.partTab(aTab); } } }, updateChildrenArray : function TSTBrowser_updateChildrenArray(aTab) { var children = this.getChildTabs(aTab); children.sort(this.sortTabsByOrder); this.setTabValue( aTab, this.kCHILDREN, children .map(function(aItem) { return aItem.getAttribute(this.kID); }, this) .join('|') ); }, // for TabView (Panorama aka Tab Candy) rearrangeTabViewItems : function TSTBrowser_rearrangeTabViewItems(aTab) { if ( !aTab.tabItem || !aTab.tabItem.parent || !aTab.tabItem.parent.reorderTabItemsBasedOnTabOrder ) return; aTab.tabItem.parent.reorderTabItemsBasedOnTabOrder(); }, // for TabView (Panorama aka Tab Candy) onTabVisibilityChanged : function TSTBrowser_onTabVisibilityChanged(aEvent) { this.updateInvertedTabContentsOrder(aEvent.originalTarget); if (this.tabVisibilityChangedTimer) { this.window.clearTimeout(this.tabVisibilityChangedTimer); this.tabVisibilityChangedTimer = null; } this.tabVisibilityChangedTabs.push(aEvent.originalTarget); this.tabVisibilityChangedTimer = this.window.setTimeout(function(aSelf) { aSelf.tabVisibilityChangedTimer = null; var tabs = aSelf.tabVisibilityChangedTabs; aSelf.tabVisibilityChangedTabs = []; aSelf.updateTreeByTabVisibility(tabs); }, 0, this); }, tabVisibilityChangedTimer : null, updateTreeByTabVisibility : function TSTBrowser_updateTreeByTabVisibility(aChangedTabs) { this.internallyTabMovingCount++; var allTabs = this.getAllTabsArray(this.mTabBrowser); var normalTabs = allTabs.filter(function(aTab) { return !aTab.hasAttribute('pinned'); }); aChangedTabs = aChangedTabs || normalTabs; var shownTabs = aChangedTabs.filter(function(aTab) { return !aTab.hidden; }); var movingTabToAnotherGroup = !shownTabs.length; var switchingGroup = !movingTabToAnotherGroup; var lastIndex = allTabs.length - 1; var lastMovedTab; normalTabs = normalTabs.slice(0).reverse(); for each (let tab in normalTabs) { let parent = this.getParentTab(tab); let attached = false; if (parent && (tab.hidden != parent.hidden)) { let ancestor = parent; let lastNextTab = null; while (ancestor = this.getParentTab(ancestor)) { if (ancestor.hidden == tab.hidden) { this.attachTabTo(tab, ancestor, { dontMove : true, insertBefore : lastNextTab }); attached = true; break; } lastNextTab = this.getNextSiblingTab(ancestor); } if (!attached) { this.collapseExpandTab(tab, false, true); this.partTab(tab); } } if (aChangedTabs.indexOf(tab) < 0) continue; if ( switchingGroup && !tab.hidden && !attached && !parent ) { let prev = this.getPreviousTab(tab); let next = this.getNextTab(tab); if ( (prev && aChangedTabs.indexOf(prev) < 0 && !prev.hidden) || (next && aChangedTabs.indexOf(next) < 0 && !next.hidden) ) this.attachTabFromPosition(tab, lastIndex); } if (movingTabToAnotherGroup && tab.hidden) { let index = lastMovedTab ? lastMovedTab._tPos - 1 : lastIndex ; this.mTabBrowser.moveTabTo(tab, index); lastMovedTab = tab; } } this.internallyTabMovingCount--; }, onTabRestoring : function TSTBrowser_onTabRestoring(aEvent) { this.restoreStructure(aEvent.originalTarget); /** * Updating of the counter which is used to know how many tabs were * restored in a time. */ this.windowService.restoringCount++; /** * By nsSessionStore.js, the next "SSTabRestoring" event will be fined * with "window.setTimeout()" following this "SSTabRestoring" event. * So, we have to do "setTimeout()" twice, instead of "Deferred.next()". */ var self = this; this.window.setTimeout(function() { /** * On this timing, the next "SSTabRestoring" is not fired yet. * We only register the countdown task for the next event loop. */ self.windowService.Deferred.next(function() { /** * On this timing, the next "SSTabRestoring" was fired. * Now we can decrement the counter. */ self.windowService.restoringCount--; }); }, 0); if (!aEvent.originalTarget.selected && this.mTabBrowser.currentURI.spec == 'about:sessionrestore') { let frame = this.mTabBrowser.contentWindow; frame = frame.wrappedJSObject || frame; let tree = frame.document.getElementById('tabList'); let data = frame.gTreeData; if (tree && data) { let item = data[tree.currentIndex]; this.window.setTimeout(function(aSelf, aTab, aTitle, aParent) { if (aTab.label== aTitle) aSelf.attachTabTo(aTab, aParent); }, 0, this, aEvent.originalTarget, item.label, this.mTabBrowser.selectedTab); } } }, RESTORED_TREE_COLLAPSED_STATE_LAST_STATE : -1, RESTORED_TREE_COLLAPSED_STATE_COLLAPSED : 0, RESTORED_TREE_COLLAPSED_STATE_EXPANDED : 1, restoreStructure : function TSTBrowser_restoreStructure(aTab) { var [id, mayBeDuplicated] = this._restoreTabId(aTab); var children = this.getTabValue(aTab, this.kCHILDREN); if (!mayBeDuplicated || aTab.hasAttribute(this.kCHILDREN)) { // for safety this.partAllChildren(aTab, { dontUpdateIndent : true, dontAnimate : this.windowService.restoringTree }); } var closeSetId = this._restoreCloseSetId(aTab, mayBeDuplicated); this.setTabValue(aTab, this.kID, id); this.tabsHash[id] = aTab; if (closeSetId) this.restoreClosedSet(closeSetId, aTab); var isSubtreeCollapsed = this._restoreSubtreeCollapsedState(aTab); var childTabs = this._restoreChildTabsRelation(aTab, children, mayBeDuplicated); this._restoreTabPositionAndIndent(aTab, childTabs, mayBeDuplicated); if (isSubtreeCollapsed) this.collapseExpandSubtree(aTab, isSubtreeCollapsed); if (mayBeDuplicated) this.clearRedirectionTable(); }, _restoreTabId : function TSTBrowser_restoreTabId(aTab) { var id = this.getTabValue(aTab, this.kID); var mayBeDuplicated = false; aTab.setAttribute(this.kID_RESTORING, id); if (this.isTabDuplicated(aTab)) { mayBeDuplicated = true; /** * If the tab has its ID as the attribute, then we should use it * instead of redirected ID, because the tab has been possibly * attached to another tab. */ id = aTab.getAttribute(this.kID) || this.redirectId(id); } aTab.removeAttribute(this.kID_RESTORING); return [id, mayBeDuplicated]; }, _restoreCloseSetId : function TSTBrowser_restoreCloseSetId(aTab, aMayBeDuplicated) { var closeSetId = null; if (!aMayBeDuplicated) { closeSetId = this.getTabValue(aTab, this.kCLOSED_SET_ID); /** * If the tab is not a duplicated but it has a parent, then, * it is wrongly attacched by tab moving on restoring. * Restoring the old ID (the next statement) breaks the children * list of the temporary parent and causes many problems. * So, to prevent these problems, I part the tab from the temporary * parent manually. * If the ID stored in the session equals to the value of the * attribute stored in the element itself, then don't reset the * tab, because the restoring session is got from the tab itself. * ( like SS.setTabState(tab, SS.getTabState(tab)) ) */ if (this.getTabValue(aTab, this.kID) != aTab.getAttribute(this.kID)) this.resetTab(aTab, false); } this.deleteTabValue(aTab, this.kCLOSED_SET_ID); return closeSetId; }, _restoreSubtreeCollapsedState : function TSTBrowser_restoreSubtreeCollapsedState(aTab) { var shouldCollapse = this.getTreePref('collapseExpandSubtree.sessionRestore'); var isSubtreeCollapsed = ( this.windowService.restoringTree && ( shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_LAST_STATE ? (this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true') : shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_COLLAPSED ) ); this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, isSubtreeCollapsed); return isSubtreeCollapsed; }, _restoreChildTabsRelation : function TSTBrowser_restoreChildTabsRelation(aTab, aChildrenList, aMayBeDuplicated) { var childTabs = []; if (!aChildrenList) return childTabs; aTab.removeAttribute(this.kCHILDREN); aChildrenList = aChildrenList.split('|'); if (aMayBeDuplicated) aChildrenList = aChildrenList.map(function(aChild) { return this.redirectId(aChild); }, this); var restoringMultipleTabs = this.windowService.restoringTree; aChildrenList.forEach(function(aChildTab) { if (aChildTab && (aChildTab = this.getTabById(aChildTab))) { this.attachTabTo(aChildTab, aTab, { dontExpand : restoringMultipleTabs, dontUpdateIndent : true, dontAnimate : restoringMultipleTabs }); childTabs.push(aChildTab); } }, this); aChildrenList = aChildrenList.join('|'); if (aTab.getAttribute(this.kCHILDREN) == aChildrenList) aTab.removeAttribute(this.kCHILDREN_RESTORING); else aTab.setAttribute(this.kCHILDREN_RESTORING, aChildrenList); return childTabs; }, _restoreTabPositionAndIndent : function TSTBrowser_restoreTabPositionAndIndent(aTab, aChildTabs, aMayBeDuplicated) { var restoringMultipleTabs = this.windowService.restoringTree; var position = this._prepareInsertionPosition(aTab, aMayBeDuplicated); var parent = position.parent; if (parent) { aTab.removeAttribute(this.kPARENT); parent = this.getTabById(parent); if (parent) { this.attachTabTo(aTab, parent, { dontExpand : restoringMultipleTabs, insertBefore : position.next, dontUpdateIndent : true, dontAnimate : restoringMultipleTabs }); this.updateTabsIndent([aTab], undefined, restoringMultipleTabs); this.checkTabsIndentOverflow(); if (parent.getAttribute(this.kCHILDREN_RESTORING)) this.correctChildTabsOrderWithDelay(parent); } else { this.deleteTabValue(aTab, this.kPARENT); } } else { if (aChildTabs.length) { this.updateTabsIndent(aChildTabs, undefined, restoringMultipleTabs); this.checkTabsIndentOverflow(); } this._restoreTabPosition(aTab, position.next); } }, _prepareInsertionPosition : function TSTBrowser_prepareInsertionPosition(aTab, aMayBeDuplicated) { var next = this.getTabValue(aTab, this.kINSERT_BEFORE); if (next && aMayBeDuplicated) next = this.redirectId(next); next = this.getTabById(next); if (!next) { let prev = this.getTabValue(aTab, this.kINSERT_AFTER); if (prev && aMayBeDuplicated) prev = this.redirectId(prev); prev = this.getTabById(prev); next = this.getNextSiblingTab(prev); } var ancestors = (this.getTabValue(aTab, this.kANCESTOR) || this.getTabValue(aTab, this.kPARENT)).split('|'); var parent = null; for (let i in ancestors) { if (aMayBeDuplicated) ancestors[i] = this.redirectId(ancestors[i]); parent = this.getTabById(ancestors[i]); if (parent) { parent = ancestors[i]; break; } } this.deleteTabValue(aTab, this.kANCESTOR); /** * If the tab is a duplicated and the tab has already been * attached, then reuse current status based on attributes. * (Note, if the tab is not a duplicated tab, all attributes * have been cleared.) */ if (!parent) { parent = aTab.getAttribute(this.kPARENT); if (parent && !next) next = this.getNextSiblingTab(aTab); } return { parent : parent, next : next }; }, _restoreTabPosition : function TSTBrowser_restoreTabPosition(aTab, aNextTab) { if (!aNextTab) aNextTab = this.getNextTab(aTab); var parentOfNext = this.getParentTab(aNextTab); var newPos = -1; if (parentOfNext) { let descendants = this.getDescendantTabs(parentOfNext); newPos = descendants[descendants.length-1]._tPos; } else if (aNextTab) { newPos = aNextTab._tPos; if (newPos > aTab._tPos) newPos--; } if (newPos > -1) this.mTabBrowser.moveTabTo(aTab, newPos); }, correctChildTabsOrderWithDelay : function TSTBrowser_correctChildTabsOrderWithDelay(aTab) { if (aTab.correctChildTabsOrderWithDelayTimer) this.window.clearTimeout(aTab.correctChildTabsOrderWithDelayTimer); aTab.correctChildTabsOrderWithDelayTimer = this.window.setTimeout(function(aSelf) { aSelf.correctChildTabsOrder(aTab); }, 10, this); }, correctChildTabsOrder : function TSTBrowser_correctChildTabsOrder(aTab) { var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING); if (!restoringChildren) return; var children = aTab.getAttribute(this.kCHILDREN); if (restoringChildren != children) { var restoringChildrenIDs = restoringChildren.split('|'); restoringChildrenIDs.reverse().forEach(function(aChild, aIndex) { aChild = this.getTabById(aChild); if (!aChild) return; let nextTab = aIndex > 0 ? this.getTabById(restoringChildrenIDs[aIndex-1]) : this.getNextSiblingTab(aTab) ; if (nextTab == this.getNextSiblingTab(aChild)) return; let newPos = -1; if (nextTab) { newPos = nextTab._tPos; if (newPos > aChild._tPos) newPos--; } if (newPos > -1) this.moveTabSubtreeTo(aChild, newPos); }, this); children = aTab.getAttribute(this.kCHILDREN); } if (restoringChildren == children) aTab.removeAttribute(this.kCHILDREN_RESTORING); aTab.correctChildTabsOrderWithDelayTimer = null; }, redirectId : function TSTBrowser_redirectId(aId) { if (!(aId in this._redirectionTable)) this._redirectionTable[aId] = this.makeNewId(); return this._redirectionTable[aId]; }, _redirectionTable : {}, clearRedirectionTable : function TSTBrowser_clearRedirectionTable() { if (this._clearRedirectionTableTimer) { this.window.clearTimeout(this._clearRedirectionTableTimer); this._clearRedirectionTableTimer = null; } this._clearRedirectionTableTimer = this.window.setTimeout(function(aSelf) { aSelf._redirectionTable = {}; }, 1000, this); }, _clearRedirectionTableTimer : null, restoreClosedSet : function TSTBrowser_restoreClosedSet(aId, aRestoredTab) { var behavior = this.undoCloseTabSetBehavior; if ( aRestoredTab.__treestyletab__restoredByUndoCloseTab || !this.browser.__treestyletab__readyToUndoCloseTab || this.windowService.useTMPSessionAPI || this._restoringClosedSet || !(behavior & this.kUNDO_CLOSE_SET || behavior & this.kUNDO_ASK) ) return; var items = this.evalInSandbox('('+this.SessionStore.getClosedTabData(this.window)+')'); var indexes = []; items.forEach(function(aItem, aIndex) { if (aItem.state.extData && aItem.state.extData[this.kCLOSED_SET_ID] && aItem.state.extData[this.kCLOSED_SET_ID] == aId) indexes.push(aIndex); }, this); var count = parseInt(aId.split('::')[1]); if ( !indexes.length || ( indexes.length+1 < count && behavior & this.kUNDO_CLOSE_FULL_SET ) ) return; if (behavior & this.kUNDO_ASK) { let self = this; aRestoredTab.addEventListener('SSTabRestored', function(aEvent) { aRestoredTab.removeEventListener(aEvent.type, arguments.callee, false); self.askUndoCloseTabSetBehavior(aRestoredTab, indexes.length) .next(function(aBehavior) { if (aBehavior & self.kUNDO_CLOSE_SET) self.doRestoreClosedSet(aRestoredTab, indexes); }); }, false); } else if (behavior & this.kUNDO_CLOSE_SET) { this.doRestoreClosedSet(aRestoredTab, indexes); } }, doRestoreClosedSet : function TSTBrowser_doRestoreClosedSet(aRestoredTab, aIndexes) { if (!this.window.PlacesUIUtils._confirmOpenInTabs(aIndexes.length)) return; this._restoringClosedSet = true; this.stopRendering(); this.windowService.restoringTree = true; var offset = 0; aIndexes.forEach(function(aIndex) { undoCloseTab(aIndex - (offset++)); }); this.window.setTimeout(function(aSelf, aNextFocused) { aSelf.mTabBrowser.selectedTab = aNextFocused; }, 0, this, aRestoredTab || aSelf.mTabBrowser.selectedTab); this.startRendering(); this._restoringClosedSet = false; }, _restoringClosedSet : false, onTabRestored : function TSTBrowser_onTabRestored(aEvent) { delete aEvent.originalTarget.__treestyletab__restoredByUndoCloseTab; }, onPinTab : function TSTBrowser_onPinTab(aTab) { var parentTab = this.getParentTab(aTab); if (!parentTab) { this.collapseExpandSubtree(aTab, false); } else { /** * This logic should work for any pinned tabs, but, now we have * no way to know previous positions of children correctly when * the pinned tab has no parent... */ let b = this.browser; this.internallyTabMovingCount++; this.getChildTabs(aTab).reverse().forEach(function(aChildTab) { /** * Children of the newly pinned tab are possibly * moved to the top of the tab bar, by TabMove event * from the newly pinned tab. So, we have to * reposition unexpectedly moved children. */ if (aChildTab._tPos < parentTab._tPos) b.moveTabTo(aChildTab, parentTab._tPos); }, this); this.internallyTabMovingCount--; } this.partAllChildren(aTab, { behavior : this.getCloseParentBehaviorForTab( aTab, this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD ) }); this.partTab(aTab); this.collapseExpandTab(aTab, false); if (this.isVertical) this.positionPinnedTabsWithDelay(); }, onUnpinTab : function TSTBrowser_onUnpinTab(aTab) { var style = aTab.style; style.marginLeft = style.marginRight = style.marginTop = ''; this.updateInvertedTabContentsOrder(aTab); if (this.isVertical) this.positionPinnedTabsWithDelay(); }, onDOMAttrModified : function TSTBrowser_onDOMAttrModified(aEvent) { switch (aEvent.attrName) { case 'pinned': let (tab = aEvent.originalTarget) { if (tab.localName != 'tab') return; if (aEvent.newValue == 'true') this.onPinTab(tab); else this.onUnpinTab(tab); } return; default: return; } }, onTabSelect : function TSTBrowser_onTabSelect(aEvent) { var b = this.mTabBrowser; var tab = b.selectedTab if ( /** * .previewTab() focuses to the tab internally, * so we should ignore this event if it is fired from previewTab(). */ b._previewMode || /** * Ignore selected tabs which is being closed. For example, * when a collapsed tree is closed, Firefox unexpectedly gives * focus to a collapsed child in the tree. */ (b._removingTabs && b._removingTabs.indexOf(tab) > -1) ) return; if (this.isCollapsed(tab)) { if (this.getTreePref('autoExpandSubtreeOnCollapsedChildFocused')) { let parentTab = tab; while (parentTab = this.getParentTab(parentTab)) { this.collapseExpandSubtree(parentTab, false); } this.collapseExpandTreesIntelligentlyWithDelayFor(tab); } else { b.selectedTab = this.getRootTab(tab); } } else if ( this.getTreePref('autoCollapseExpandSubtreeOnSelect') && ( !this._focusChangedByCurrentTabRemove || this.getTreePref('autoCollapseExpandSubtreeOnSelect.onCurrentTabRemove') ) ) { if (!this.hasChildTabs(tab) || !this.isSubtreeCollapsed(tab)) tab = null; if ( this._focusChangedByShortcut && this.accelKeyPressed && !this.getTreePref('autoCollapseExpandSubtreeOnSelect.whileFocusMovingByShortcut') ) { this.windowService.expandTreeAfterKeyReleased(tab); } else { this.collapseExpandTreesIntelligentlyWithDelayFor(tab); } } this._focusChangedByCurrentTabRemove = false; this._focusChangedByShortcut = false; this.updateInvertedTabContentsOrder(); if (!this.isTabInViewport(tab)) { this.scrollToTab(tab); aEvent.stopPropagation(); } }, handleAdvanceSelectedTab : function TSTBrowser_handleAdvanceSelectedTab(aDir, aWrap, aTabbar) { this._focusChangedByShortcut = this.windowService.accelKeyPressed; if (!this.canCollapseSubtree(aTabbar.selectedItem) || this.getTreePref('focusMode') != this.kFOCUS_VISIBLE) return false; var nextTab = (aDir < 0) ? this.getPreviousVisibleTab(aTabbar.selectedItem) : this.getNextVisibleTab(aTabbar.selectedItem) ; if (!nextTab && aWrap) { nextTab = this.evaluateXPath( 'child::xul:tab[not(@'+this.kCOLLAPSED+'="true")]['+ (aDir < 0 ? 'last()' : '1' )+ ']', aTabbar, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; } if (nextTab && nextTab != aTabbar.selectedItem) aTabbar._selectNewTab(nextTab, aDir, aWrap); return true; }, onTabClick : function TSTBrowser_onTabClick(aEvent, aTab) { aTab = aTab || this.getTabFromEvent(aEvent); if (aEvent.button == 1) { if (!this.warnAboutClosingTabSubtreeOf(aTab)) { aEvent.preventDefault(); aEvent.stopPropagation(); } return; } if (aEvent.button != 0) return; if (this.isEventFiredOnTwisty(aEvent)) { if (this.hasChildTabs(aTab) && this.canCollapseSubtree(aTab)) { this.collapseExpandSubtree(aTab, aTab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); aEvent.preventDefault(); aEvent.stopPropagation(); } return; } if (this.isEventFiredOnClosebox(aEvent)) { if (!this.warnAboutClosingTabSubtreeOf(aTab)) { aEvent.preventDefault(); aEvent.stopPropagation(); } return; } }, getTabFromTabbarEvent : function TSTBrowser_getTabFromTabbarEvent(aEvent) { if ( !this.shouldDetectClickOnIndentSpaces || !this.getAncestorTabbarFromEvent(aEvent) || this.isEventFiredOnClickable(aEvent) || this.getSplitterFromEvent(aEvent) ) return null; var tab = null; var clickedPoint = aEvent[this.screenPositionProp]; this.getTabsArray(this.mTabBrowser).some(function(aTab) { var box = aTab.boxObject; if (box[this.screenPositionProp] > clickedPoint || box[this.screenPositionProp] + box[this.sizeProp] < clickedPoint) { return false; } tab = aTab; return true; }, this); return tab; }, onClick : function TSTBrowser_onClick(aEvent) { if ( aEvent.target.ownerDocument != this.document || aEvent.button != 0 || this.isAccelKeyPressed(aEvent) ) return; var tab = this.getTabFromEvent(aEvent); if (tab) { this.onTabClick(aEvent, tab); } else { // click on indented space on the tab bar tab = this.getTabFromTabbarEvent(aEvent); if (tab) this.mTabBrowser.selectedTab = tab; } }, onDblClick : function TSTBrowser_onDblClick(aEvent) { let tab = this.getTabFromEvent(aEvent); if (tab && this.hasChildTabs(tab) && this.getTreePref('collapseExpandSubtree.dblclick')) { this.collapseExpandSubtree(tab, tab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); aEvent.preventDefault(); aEvent.stopPropagation(); } }, onMozMouseHittest : function TSTBrowser_onMozMouseHittest(aEvent) { // block default behaviors of the tab bar (dragging => window move, etc.) if ( !this.getTabFromEvent(aEvent) && !this.isEventFiredOnClickable(aEvent) && ( this.position != 'top' || aEvent.shiftKey || this.tabbarDNDObserver.canDragTabbar(aEvent) ) ) aEvent.stopPropagation(); }, onMouseDown : function TSTBrowser_onMouseDown(aEvent) { if ( aEvent.button == 0 && this.isEventFiredOnTwisty(aEvent) ) { // prevent to select the tab for clicking on twisty aEvent.stopPropagation(); // prevent to focus to the tab element itself aEvent.preventDefault(); } else { this.onMozMouseHittest(aEvent); } }, onScroll : function TSTBrowser_onScroll(aEvent) { // restore scroll position when a tab is closed. this.restoreLastScrollPosition(); }, onTabbarOverflow : function TSTBrowser_onTabbarOverflow(aEvent) { var tabs = this.mTabBrowser.mTabContainer; var horizontal = tabs.orient == 'horizontal'; if (horizontal) return; aEvent.stopPropagation(); this.positionPinnedTabsWithDelay(); if (aEvent.detail == 1) return; if (aEvent.type == 'overflow') { tabs.setAttribute('overflow', 'true'); this.scrollBoxObject.ensureElementIsVisible(tabs.selectedItem); } else { tabs.removeAttribute('overflow'); } }, onResize : function TSTBrowser_onResize(aEvent) { if ( !aEvent.originalTarget || !(aEvent.originalTarget instanceof Ci.nsIDOMWindow) ) return; this.mTabBrowser.mTabContainer.adjustTabstrip(); this.updateInvertedTabContentsOrder(true); this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_WINDOW_RESIZE); }, onPopupShowing : function TSTBrowser_onPopupShowing(aEvent) { if (aEvent.target.localName == 'tooltip') this.handleTooltip(aEvent); else if (aEvent.target == aEvent.currentTarget) this.initTabContextMenu(aEvent); }, handleTooltip : function TSTBrowser_handleTooltip(aEvent) { var tab = this.document.tooltipNode; if (tab.localName != 'tab') return; var label; var collapsed = this.isSubtreeCollapsed(tab); var base = parseInt(tab.getAttribute(this.kNEST) || 0); var descendant = this.getDescendantTabs(tab); var indentPart = ' '; var tree = (this.getTreePref('tooltip.includeChildren') && descendant.length) ? [tab].concat(descendant) .map(function(aTab) { let label = aTab.getAttribute('label'); let indent = ''; let nest = parseInt(aTab.getAttribute(this.kNEST) || 0) - base; for (let i = 0; i < nest; i++) { indent += indentPart; } return this.treeBundle.getFormattedString('tooltip.item.label', [label, indent]); }, this) .join('\n') : null ; if ('mOverCloseButton' in tab && tab.mOverCloseButton) { if (descendant.length && (collapsed || this.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN)) { label = this.treeBundle.getString('tooltip.closeTree'); } } else if (tab.getAttribute(this.kTWISTY_HOVER) == 'true') { let key = collapsed ? 'tooltip.expandSubtree' : 'tooltip.collapseSubtree' ; label = tree || tab.getAttribute('label'); label = label ? this.treeBundle.getFormattedString(key+'.labeled', [label]) : this.treeBundle.getString(key) ; } else if (collapsed) { label = tree; } if (label) { aEvent.target.setAttribute('label', label); aEvent.stopPropagation(); } }, initTabContextMenu : function TSTBrowser_initTabContextMenu(aEvent) { var b = this.mTabBrowser; var sep, items = {}; [ this.kMENUITEM_RELOADSUBTREE, this.kMENUITEM_RELOADCHILDREN, this.kMENUITEM_REMOVESUBTREE, this.kMENUITEM_REMOVECHILDREN, this.kMENUITEM_REMOVEALLTABSBUT, this.kMENUITEM_COLLAPSE, this.kMENUITEM_EXPAND, this.kMENUITEM_AUTOHIDE, this.kMENUITEM_FIXED, this.kMENUITEM_BOOKMARKSUBTREE ].forEach(function(aID) { let item = this.evaluateXPath( 'descendant::xul:*[starts-with(@id, "'+aID+'")]', aEvent.currentTarget, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; if (!item) return; items[aID] = item; if (this.getTreePref('show.'+aID)) item.removeAttribute('hidden'); else item.setAttribute('hidden', true); switch (aID) { case this.kMENUITEM_RELOADSUBTREE: case this.kMENUITEM_RELOADCHILDREN: case this.kMENUITEM_REMOVESUBTREE: case this.kMENUITEM_REMOVECHILDREN: case this.kMENUITEM_REMOVEALLTABSBUT: case this.kMENUITEM_COLLAPSE: case this.kMENUITEM_EXPAND: case this.kMENUITEM_BOOKMARKSUBTREE: this.showHideSubtreeMenuItem(item, [b.mContextTab]); break; default: break; } }, this); // collapse/expand all sep = this.evaluateXPath( 'descendant::xul:menuseparator[starts-with(@id, "'+this.kMENUITEM_COLLAPSEEXPAND_SEPARATOR+'")]', aEvent.currentTarget, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; let collapseItem = items[this.kMENUITEM_COLLAPSE]; let expandItem = items[this.kMENUITEM_EXPAND]; if (this.canCollapseSubtree(b) && this.evaluateXPath( 'child::xul:tab[@'+this.kCHILDREN+']', b.mTabContainer ).snapshotLength) { if (collapseItem) { if (this.evaluateXPath( 'child::xul:tab[@'+this.kCHILDREN+' and not(@'+this.kSUBTREE_COLLAPSED+'="true")]', b.mTabContainer ).snapshotLength) collapseItem.removeAttribute('disabled'); else collapseItem.setAttribute('disabled', true); } if (expandItem) { if (this.evaluateXPath( 'child::xul:tab[@'+this.kCHILDREN+' and @'+this.kSUBTREE_COLLAPSED+'="true"]', b.mTabContainer ).snapshotLength) expandItem.removeAttribute('disabled'); else expandItem.setAttribute('disabled', true); } } else { if (collapseItem) collapseItem.setAttribute('hidden', true); if (expandItem) expandItem.setAttribute('hidden', true); } if (sep) { if ( (!collapseItem || collapseItem.getAttribute('hidden') == 'true') && (!expandItem || expandItem.getAttribute('hidden') == 'true') ) { sep.setAttribute('hidden', true); } else { sep.removeAttribute('hidden'); } } // close all tabs but this tree let removeAllTabsBut = items[this.kMENUITEM_REMOVEALLTABSBUT]; if (removeAllTabsBut) { let rootTabs = this.visibleRootTabs; if (rootTabs.length == 1 && rootTabs[0] == b.mContextTab) removeAllTabsBut.setAttribute('disabled', true); else removeAllTabsBut.removeAttribute('disabled'); } // auto hide let autohide = items[this.kMENUITEM_AUTOHIDE]; if (autohide) this.autoHide.updateMenuItem(autohide); // fix let fixedPref; let fixedLabel; if (this.isVertical) { fixedPref = b.getAttribute(this.kFIXED+'-vertical') == 'true'; fixedLabel = 'label-vertical'; } else { fixedPref = b.getAttribute(this.kFIXED+'-horizontal') == 'true'; fixedLabel = 'label-horizontal'; } let fixed = items[this.kMENUITEM_FIXED]; if (fixed) { fixed.setAttribute('label', fixed.getAttribute(fixedLabel)); if (fixedPref) fixed.setAttribute('checked', true); else fixed.removeAttribute('checked'); } sep = this.evaluateXPath( 'descendant::xul:menuseparator[starts-with(@id, "'+this.kMENUITEM_AUTOHIDE_SEPARATOR+'")]', aEvent.currentTarget, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; if (sep) { if ( (autohide && autohide.getAttribute('hidden') != 'true') || (fixed && fixed.getAttribute('hidden') != 'true') ) { sep.removeAttribute('hidden'); } else { sep.setAttribute('hidden', true); } } }, onTabsOnTopSyncCommand : function TSTBrowser_onTabsOnTopSyncCommand(aEnabled) { if ( !aEnabled || this.position != 'top' || this.fixed ) return; var self = this; this.Deferred .next(function() { self.windowService.toggleFixed(self.mTabBrowser); }) .next(function() { if (self.window.TabsOnTop.enabled != aEnabled) self.window.TabsOnTop.enabled = aEnabled; }); }, onTreeStyleTabPrintPreviewEntered : function TSTBrowser_onTreeStyleTabPrintPreviewEntered(aEvent) { this.setTabbrowserAttribute(this.kPRINT_PREVIEW, true); }, onTreeStyleTabPrintPreviewExited : function TSTBrowser_onTreeStyleTabPrintPreviewExited(aEvent) { this.removeTabbrowserAttribute(this.kPRINT_PREVIEW); }, /* commands */ /* reset */ resetTab : function TSTBrowser_resetTab(aTab, aPartChildren) { if (aPartChildren) this.partAllChildren(aTab, { dontUpdateIndent : true, dontAnimate : true }); this.partTab(aTab, { dontUpdateIndent : true, dontAnimate : true }); /* reset attributes before restoring */ aTab.removeAttribute(this.kID); aTab.removeAttribute(this.kPARENT); aTab.removeAttribute(this.kCHILDREN); aTab.removeAttribute(this.kSUBTREE_COLLAPSED); aTab.removeAttribute(this.kCOLLAPSED); aTab.removeAttribute(this.kCOLLAPSED_DONE); aTab.removeAttribute(this.kNEST); this.updateTabsIndent([aTab], undefined, true); }, resetAllTabs : function TSTBrowser_resetAllTabs(aPartChildren) { this.getAllTabsArray(this.mTabBrowser).forEach(function(aTab) { this.resetTab(aTab, aPartChildren); }, this); }, resetTabbarSize : function TSTBrowser_resetTabbarSize() { if (this.isVertical) { this.clearTreePref('tabbar.shrunkenWidth'); this.clearTreePref('tabbar.width'); } else { this.clearTreePref('tabbar.height'); if (this.isFloating) { let tabContainerBox = this.getTabContainerBox(this.mTabBrowser); tabContainerBox.removeAttribute('height'); this._tabStripPlaceHolder.height = tabContainerBox.boxObject.height; } } this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_RESET); }, get treeViewEnabled() /* PUBLIC API */ { return this._treeViewEnabled; }, set treeViewEnabled(aValue) { this._treeViewEnabled = !!aValue; if (this._treeViewEnabled) { if (this._lastAllowSubtreeCollapseExpand) this.allowSubtreeCollapseExpand = true; delete this._lastAllowSubtreeCollapseExpand; this.getAllTabsArray(this.browser).forEach(function(aTab) { if (aTab._TSTLastSubtreeCollapsed) this.collapseExpandSubtree(aTab, true, true); delete aTab._TSTLastSubtreeCollapsed; this.updateTabIndent(aTab, 0, true); }, this); this.updateTabsIndent(this.rootTabs, undefined, true); } else { this.getAllTabsArray(this.browser).forEach(function(aTab) { this.updateTabIndent(aTab, 0, true); aTab._TSTLastSubtreeCollapsed = this.isSubtreeCollapsed(aTab); this.collapseExpandSubtree(aTab, false, true); }, this); this._lastAllowSubtreeCollapseExpand = this.allowSubtreeCollapseExpand; this.allowSubtreeCollapseExpand = false; } return aValue; }, // _treeViewEnabled : true, /* attach/part */ attachTabTo : function TSTBrowser_attachTabTo(aChild, aParent, aInfo) /* PUBLIC API */ { aInfo = aInfo || {}; if (aParent && this.maxTreeLevelPhisical && this.maxTreeLevel > -1) { let level = parseInt(aParent.getAttribute(this.kNEST) || 0) + 1; while (aParent && level > this.maxTreeLevel) { level--; aParent = this.getParentTab(aParent); } } var currentParent; if ( !aChild || !aParent || aChild == aParent || (currentParent = this.getParentTab(aChild)) == aParent || aChild.getAttribute('pinned') == 'true' || aParent.getAttribute('pinned') == 'true' ) { this.fireAttachedEvent(aChild, aParent); return; } currentParent = aParent; do { if (currentParent != aChild) continue; // this.fireAttachedEvent(aChild, aParent); return; } while (currentParent = this.getParentTab(currentParent)); shouldInheritIndent = ( !currentParent || (currentParent.getAttribute(this.kNEST) == aParent.getAttribute(this.kNEST)) ); this.ensureTabInitialized(aChild); this.ensureTabInitialized(aParent); if (!aInfo) aInfo = {}; var id = aChild.getAttribute(this.kID); this.partTab(aChild, { dontUpdateIndent : true }); var children = aParent.getAttribute(this.kCHILDREN) .split('|').filter(function(aId) { return this.getTabById(aId); }, this); var newIndex; var oldIndex = children.indexOf(id); if (oldIndex > -1) children.splice(oldIndex, 1); var insertBefore = aInfo.insertBefore || (aInfo.dontMove ? this.getNextTab(aChild) : null ); var beforeTab = insertBefore ? insertBefore.getAttribute(this.kID) : null ; var beforeIndex; if (beforeTab && (beforeIndex = children.indexOf(beforeTab)) > -1) { children.splice(beforeIndex, 0, id); newIndex = insertBefore._tPos; } else { children.push(id); if (aInfo.dontMove && children.length > 1) { children = children .map(this.getTabById, this) .sort(this.sortTabsByOrder) .map(function(aTab) { return aTab.getAttribute(this.kID); }, this); } let refTab = aParent; let descendant = this.getDescendantTabs(aParent); if (descendant.length) { let lastDescendant = descendant[descendant.length-1]; /** * The last descendant tab can be temporarilly moved * upper than the root parent tab, in some cases. * (the parent tab is pinned, etc.) */ if (!refTab || lastDescendant._tPos > refTab._tPos) refTab = lastDescendant; } newIndex = refTab._tPos+1; } this.setTabValue(aParent, this.kCHILDREN, children.join('|')); this.setTabValue(aChild, this.kPARENT, aParent.getAttribute(this.kID)); this.updateTabsCount(aParent); if (shouldInheritIndent && !aInfo.dontUpdateIndent) this.inheritTabIndent(aChild, aParent); if (!aInfo.dontMove) { if (newIndex > aChild._tPos) newIndex--; this.moveTabSubtreeTo(aChild, newIndex); } if (!aInfo.dontExpand) { if (this.getTreePref('autoCollapseExpandSubtreeOnSelect')) { if (this.shouldTabAutoExpanded(aParent)) this.collapseExpandTreesIntelligentlyFor(aParent); let p = aParent; do { if (this.shouldTabAutoExpanded(p)) this.collapseExpandSubtree(p, false, aInfo.dontAnimate); } while (p = this.getParentTab(p)); } else if (this.shouldTabAutoExpanded(aParent)) { if (this.getTreePref('autoExpandSubtreeOnAppendChild')) { let p = aParent; do { if (this.shouldTabAutoExpanded(p)) this.collapseExpandSubtree(p, false, aInfo.dontAnimate); } while (p = this.getParentTab(p)); } else this.collapseExpandTab(aChild, true, aInfo.dontAnimate); } if (this.isCollapsed(aParent)) this.collapseExpandTab(aChild, true, aInfo.dontAnimate); } else if (this.shouldTabAutoExpanded(aParent) || this.isCollapsed(aParent)) { this.collapseExpandTab(aChild, true, aInfo.dontAnimate); } if (!aInfo.dontUpdateIndent) { this.updateTabsIndent([aChild], undefined, aInfo.dontAnimate); this.checkTabsIndentOverflow(); } this.promoteTooDeepLevelTabs(aChild); this.saveTreeStructureWithDelay(); this.fireAttachedEvent(aChild, aParent); }, fireAttachedEvent : function TSTBrowser_fireAttachedEvent(aChild, aParent) { var data = { parentTab : aParent }; /* PUBLIC API */ this.fireDataContainerEvent(this.kEVENT_TYPE_ATTACHED, aChild, true, false, data); // for backward compatibility this.fireDataContainerEvent(this.kEVENT_TYPE_ATTACHED.replace(/^nsDOM/, ''), aChild, true, false, data); }, shouldTabAutoExpanded : function TSTBrowser_shouldTabAutoExpanded(aTab) { return this.hasChildTabs(aTab) && this.isSubtreeCollapsed(aTab); }, partTab : function TSTBrowser_partTab(aChild, aInfo) /* PUBLIC API */ { if (!aChild) return; if (!aInfo) aInfo = {}; var parentTab = this.getParentTab(aChild); if (!parentTab) return; var id = aChild.getAttribute(this.kID); this.setTabValue( parentTab, this.kCHILDREN, parentTab.getAttribute(this.kCHILDREN) .split('|') .filter(function(aId) { return this.getTabById(aId) && aId != id; }, this).join('|') ); this.deleteTabValue(aChild, this.kPARENT); if (!this.hasChildTabs(parentTab)) this.setTabValue(parentTab, this.kSUBTREE_COLLAPSED, true); this.updateTabsCount(parentTab); if (!aInfo.dontUpdateIndent) { this.updateTabsIndent([aChild], undefined, aInfo.dontAnimate); this.checkTabsIndentOverflow(); } this.saveTreeStructureWithDelay(); var data = { parentTab : parentTab }; /* PUBLIC API */ this.fireDataContainerEvent(this.kEVENT_TYPE_DETACHED, aChild, true, false, data); // for backward compatibility this.fireDataContainerEvent(this.kEVENT_TYPE_DETACHED.replace(/^nsDOM/, ''), aChild, true, false, data); if (this.isGroupTab(parentTab) && !this.hasChildTabs(parentTab)) { this.window.setTimeout(function(aTabBrowser) { if (parentTab.parentNode) aTabBrowser.removeTab(parentTab, { animate : true }); parentTab = null; }, 0, this.getTabBrowserFromChild(parentTab)); } }, detachTab : function TSTBrowser_detachTab(aChild, aInfo) // alias (unstable API!) { return this.partTab(aChild, aInfo); }, partAllChildren : function TSTBrowser_partAllChildren(aTab, aInfo) { aInfo = aInfo || {}; if (!('behavior' in aInfo)) aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN; if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN) aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD; var b = this.mTabBrowser; var parentTab = this.getParentTab(aTab); var children = this.getChildTabs(aTab); if ( this.isGroupTab(aTab) && this.getTabsArray(b).filter(function(aTab) { return !b._removingTabs || b._removingTabs.indexOf(aTab) < 0; }).length == children.length ) { aInfo.behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN; aInfo.dontUpdateIndent = false; } var insertBefore = null; if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN && !this.getTreePref('closeParentBehavior.moveDetachedTabsToBottom')) { insertBefore = this.getNextSiblingTab(this.getRootTab(aTab)); } children.forEach(( aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN ? function(aTab) { this.partTab(aTab, aInfo); this.moveTabSubtreeTo(aTab, insertBefore ? insertBefore._tPos - 1 : this.getLastTab(b)._tPos ); } : aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD ? function(aTab, aIndex) { this.partTab(aTab, aInfo); if (aIndex == 0) { if (parentTab) { this.attachTabTo(aTab, parentTab, { __proto__ : aInfo, dontExpand : true, dontMove : true }); } this.collapseExpandSubtree(aTab, false); this.deleteTabValue(aTab, this.kSUBTREE_COLLAPSED); } else { this.attachTabTo(aTab, children[0], { __proto__ : aInfo, dontExpand : true, dontMove : true }); } } : aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN && parentTab ? function(aTab) { this.attachTabTo(aTab, parentTab, { __proto__ : aInfo, dontExpand : true, dontMove : true }); } : // aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN ? function(aTab) { this.partTab(aTab, aInfo); } ), this); }, partTabs : function TSTBrowser_partTabs(aTabs) { var aTabs = Array.slice(aTabs); for each (let tab in aTabs) { if (aTabs.indexOf(this.getParentTab(tab)) > -1) continue; this.partAllChildren(tab, { behavior : this.getCloseParentBehaviorForTab( tab, this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD ) }); } }, getCloseParentBehaviorForTab : function TSTBrowser_getCloseParentBehaviorForTab(aTab, aDefaultBehavior) { var closeParentBehavior = this.getTreePref('closeParentBehavior'); var closeRootBehavior = this.getTreePref('closeRootBehavior'); var parentTab = this.getParentTab(aTab); var behavior = aDefaultBehavior ? aDefaultBehavior : (!parentTab && closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN) ? closeRootBehavior : closeParentBehavior ; if (behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD && parentTab && this.getChildTabs(parentTab).length == 1) behavior = this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN; return behavior; }, updateTabsIndent : function TSTBrowser_updateTabsIndent(aTabs, aLevel, aJustNow) { if (!aTabs || !aTabs.length || !this._treeViewEnabled) return; if (aLevel === void(0)) aLevel = this.getAncestorTabs(aTabs[0]).length; var b = this.mTabBrowser; var margin = this.indent < 0 ? this.baseIndent : this.indent ; var indent = (this.maxTreeLevel < 0 ? aLevel : Math.min(aLevel, this.maxTreeLevel) ) * margin; var multirow = this.isMultiRow(); if (multirow) { let maxIndent = parseInt(aTabs[0].boxObject.height / 2); indent = Math.min(aLevel * 3, maxIndent); } Array.slice(aTabs).forEach(function(aTab) { if (!aTab.parentNode) return; // ignore removed tabs this.updateTabIndent(aTab, indent, aJustNow); aTab.setAttribute(this.kNEST, aLevel); this.updateCanCollapseSubtree(aTab, aLevel); this.updateTabsIndent(this.getChildTabs(aTab), aLevel+1, aJustNow); }, this); }, updateTabsIndentWithDelay : function TSTBrowser_updateTabsIndentWithDelay(aTabs) { if (this.updateTabsIndentWithDelayTimer) this.window.clearTimeout(this.updateTabsIndentWithDelayTimer); this.updateTabsIndentWithDelayTabs = this.updateTabsIndentWithDelayTabs.concat(aTabs); this.updateTabsIndentWithDelayTimer = this.window.setTimeout(function(aSelf) { var tabs = []; aSelf.updateTabsIndentWithDelayTabs.forEach(function(aTab) { if (tabs.indexOf(aTab) < 0 && aTab.parentNode) tabs.push(aTab); }); aSelf.updateTabsIndentWithDelayTabs = []; aSelf.updateTabsIndent(tabs); aSelf.window.clearTimeout(aSelf.updateTabsIndentWithDelayTimer); aSelf.updateTabsIndentWithDelayTimer = null; tabs = null; }, 0, this); }, updateTabsIndentWithDelayTimer : null, updateTabIndent : function TSTBrowser_updateTabIndent(aTab, aIndent, aJustNow) { this.stopTabIndentAnimation(aTab); if (aTab.hasAttribute('pinned')) return; if (!this.enableSubtreeIndent) aIndent = 0; if (this.isMultiRow()) { let colors = '-moz-border-'+this.indentTarget+'-colors:'+(function() { var retVal = []; for (var i = 1; i < aIndent; i++) { retVal.push('transparent'); } retVal.push('ThreeDShadow'); return retVal.length == 1 ? 'none' : retVal.join(' ') ; })()+' !important;'; Array.slice(this.document.getAnonymousNodes(aTab)).forEach(function(aBox) { if (aBox.nodeType != Node.ELEMENT_NODE) return; aBox.setAttribute( 'style', aBox.getAttribute('style').replace(/(-moz-)?border-(top|bottom)(-[^:]*)?.*:[^;]+;?/g, '') + '; border-'+this.indentTarget+': solid transparent '+aIndent+'px !important;'+colors ); }, this); return; } if ( !this.animationEnabled || aJustNow || this.indentDuration < 1 || this.isCollapsed(aTab) ) { aTab.style.setProperty(this.indentCSSProp, aIndent+'px', 'important'); return; } var self = this; var CSSTransitionEnabled = ('Transition' in aTab.style || 'MozTransition' in aTab.style); if (CSSTransitionEnabled) { aTab.__treestyletab__updateTabIndentTask = function(aTime, aBeginning, aChange, aDuration) { aTab.style.setProperty(self.indentCSSProp, aIndent+'px', 'important'); return true; }; this.animationManager.addTask( aTab.__treestyletab__updateTabIndentTask, 0, 0, 1, this.window ); return; } var startIndent = this.getPropertyPixelValue(aTab, this.indentCSSProp); var delta = aIndent - startIndent; var radian = 90 * Math.PI / 180; aTab.__treestyletab__updateTabIndentTask = function(aTime, aBeginning, aChange, aDuration) { var indent, finished; if (aTime >= aDuration) { delete aTab.__treestyletab__updateTabIndentTask; indent = aIndent; finished = true; } else { indent = startIndent + (delta * Math.sin(aTime / aDuration * radian)); finished = false; } aTab.style.setProperty(self.indentCSSProp, indent+'px', 'important'); if (finished) { startIndent = null; delta = null; radian = null; self = null; aTab = null; } return finished; }; this.animationManager.addTask( aTab.__treestyletab__updateTabIndentTask, 0, 0, this.indentDuration, this.window ); }, stopTabIndentAnimation : function TSTBrowser_stopTabIndentAnimation(aTab) { this.animationManager.removeTask( aTab.__treestyletab__updateTabIndentTask ); }, inheritTabIndent : function TSTBrowser_inheritTabIndent(aNewTab, aExistingTab) { var indent = this.getPropertyPixelValue(aExistingTab, this.indentCSSProp); if (indent) aNewTab.style.setProperty(this.indentCSSProp, indent+'px', 'important'); else aNewTab.style.removeProperty(this.indentCSSProp); }, updateAllTabsIndent : function TSTBrowser_updateAllTabsIndent(aJustNow) { this.updateTabsIndent(this.rootTabs, 0, aJustNow); // this.checkTabsIndentOverflow(); }, checkTabsIndentOverflow : function TSTBrowser_checkTabsIndentOverflow() { if (this.checkTabsIndentOverflowTimer) { this.window.clearTimeout(this.checkTabsIndentOverflowTimer); this.checkTabsIndentOverflowTimer = null; } this.checkTabsIndentOverflowTimer = this.window.setTimeout(function(aSelf) { aSelf.checkTabsIndentOverflowCallback(); }, 100, this); }, checkTabsIndentOverflowTimer : null, checkTabsIndentOverflowCallback : function TSTBrowser_checkTabsIndentOverflowCallback() { if (!this.getTreePref('indent.autoShrink')) { this.indent = -1; return; } var b = this.mTabBrowser; var tabs = this.getArrayFromXPathResult(this.evaluateXPath( 'child::xul:tab[@'+this.kNEST+' and not(@'+this.kNEST+'="0" or @'+this.kNEST+'="")]', b.mTabContainer )); if (!tabs.length) return; var self = this; tabs.sort(function(aA, aB) { return Number(aA.getAttribute(self.kNEST)) - Number(aB.getAttribute(self.kNEST)); }); var nest = tabs[tabs.length-1].getAttribute(this.kNEST); if (this.maxTreeLevel > -1) nest = Math.min(nest, this.maxTreeLevel); if (!nest) return; var oldIndent = this.indent; var indent = (oldIndent < 0 ? this.baseIndent : oldIndent ) * nest; var maxIndentBase = Math.min( this.getFirstNormalTab(b).boxObject[this.invertedSizeProp], b.mTabContainer.boxObject[this.invertedSizeProp] ); if (!this.isVertical) { if (this._horizontalTabMaxIndentBase) maxIndentBase = this._horizontalTabMaxIndentBase; else this._horizontalTabMaxIndentBase = maxIndentBase; } var maxIndent = maxIndentBase * (this.isVertical ? 0.33 : 0.5 ); var indentUnit = Math.max(Math.floor(maxIndent / nest), this.getTreePref('indent.min')); if (indent > maxIndent) { this.indent = indentUnit; } else { this.indent = -1; if ((this.baseIndent * nest) > maxIndent) this.indent = indentUnit; } if (oldIndent != this.indent) { this.updateAllTabsIndent(); } }, _horizontalTabMaxIndentBase : 0, updateCanCollapseSubtree : function TSTBrowser_updateCanCollapseSubtree(aTab, aLevel) { if ( !aLevel || this.maxTreeLevel < 0 || this.maxTreeLevel > aLevel ) { aTab.setAttribute(this.kALLOW_COLLAPSE, true); this.collapseExpandSubtree(aTab, this.isSubtreeCollapsed(aTab)); } else { this.collapseExpandSubtree(aTab, false); aTab.removeAttribute(this.kALLOW_COLLAPSE); } }, updateTabsCount : function TSTBrowser_updateTabsCount(aTab, aDontUpdateAncestor) { var count = this.document.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER); if (count) { count.setAttribute('value', this.getDescendantTabs(aTab).length); } if (!aDontUpdateAncestor) { let parent = this.getParentTab(aTab); if (parent) this.updateTabsCount(parent); } }, promoteTooDeepLevelTabs : function TSTBrowser_promoteTooDeepLevelTabs(aParent) { if (this.maxTreeLevel < 0 || !this.maxTreeLevelPhisical) return; var tabs = aParent ? this.getDescendantTabs(aParent) : this.getAllTabsArray(this.mTabBrowser) ; tabs.forEach(function(aTab) { var level = parseInt(aTab.getAttribute(this.kNEST) || 0); if (level <= this.maxTreeLevel) return; var parent = this.getParentTab(aTab); var newParent = this.getParentTab(parent); if (this.maxTreeLevel == 0 || !newParent) { this.partTab(aTab); } else { let nextSibling = this.getNextTab(aTab); this.attachTabTo(aTab, newParent, { dontMove : true, insertBefore : nextSibling }); } }, this); }, /* move */ moveTabSubtreeTo : function TSTBrowser_moveTabSubtreeTo(aTab, aIndex) { if (!aTab) return; var b = this.mTabBrowser; this.subTreeMovingCount++; this.internallyTabMovingCount++; b.moveTabTo(aTab, aIndex); this.internallyTabMovingCount--; this.subTreeChildrenMovingCount++; this.internallyTabMovingCount++; this.getDescendantTabs(aTab).forEach(function(aDescendantTab, aIndex) { b.moveTabTo(aDescendantTab, aTab._tPos + aIndex + (aTab._tPos < aDescendantTab._tPos ? 1 : 0 )); }, this); this.internallyTabMovingCount--; this.subTreeChildrenMovingCount--; this.subTreeMovingCount--; }, moveTabSubTreeTo : function() { return this.moveTabSubtreeTo.apply(this, arguments); }, // obsolete, for backward compatibility moveTabLevel : function TSTBrowser_moveTabLevel(aEvent) { var b = this.mTabBrowser; var parentTab = this.getParentTab(b.mCurrentTab); if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) { let prevTab = this.getPreviousSiblingTab(b.mCurrentTab); if ((!parentTab && prevTab) || (parentTab && b.mCurrentTab != this.getFirstChildTab(parentTab))) { this.attachTabTo(b.mCurrentTab, prevTab); b.mCurrentTab.focus(); return true; } } else if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT && parentTab) { let grandParent = this.getParentTab(parentTab); if (grandParent) { this.attachTabTo(b.mCurrentTab, grandParent, { insertBefore : this.getNextSiblingTab(parentTab) }); b.mCurrentTab.focus(); return true; } else { let nextTab = this.getNextSiblingTab(parentTab); this.partTab(b.mCurrentTab); this.internallyTabMovingCount++; if (nextTab) { b.moveTabTo(b.mCurrentTab, nextTab._tPos - 1); } else { b.moveTabTo(b.mCurrentTab, this.getLastTab(b)._tPos); } this.internallyTabMovingCount--; b.mCurrentTab.focus(); return true; } } return false; }, /** * Imports tabs from another window with their tree structure. * aOptions is an optional hash which can have two properties: * * duplicate (boolean) * * insertBefore (nsIDOMElement) */ importTabs : function TSTBrowser_importTabs(aTabs, aInsertBefore) /* PUBLIC API */ { return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); }, duplicateTabs : function TSTBrowser_duplicateTabs(aTabs, aInsertBefore) /* PUBLIC API */ { return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore, duplicate : true }); }, moveTabs : function TSTBrowser_importTabs(aTabs, aInsertBefore) /* PUBLIC API */ { return this.moveTabsInternal(aTabs, { insertBefore : aInsertBefore }); }, moveTabsInternal : function TSTBrowser_moveTabsInternal(aTabs, aOptions) { aOptions = aOptions || {}; var targetBrowser = this.mTabBrowser; var sourceWindow = aTabs[0].ownerDocument.defaultView; var sourceBrowser = sourceWindow.TreeStyleTabService.getTabBrowserFromChild(aTabs[0]); var sourceService = sourceBrowser.treeStyleTab; // prevent Multiple Tab Handler feature targetBrowser.duplicatingSelectedTabs = true; targetBrowser.movingSelectedTabs = true; var shouldClose = ( !aOptions.duplicate && sourceService.getAllTabsArray(sourceBrowser).length == aTabs.length ); var newTabs = []; var treeStructure = sourceService.getTreeStructureFromTabs(aTabs); // Firefox fails to "move" collapsed tabs. So, expand them first // and collapse them after they are moved. var collapsedStates = sourceService.forceExpandTabs(aTabs);; var shouldResetSelection = ( aTabs.every(function(aTab) { return aTab.getAttribute('multiselected') == 'true'; }) && (sourceService != this || aOptions.duplicate) ); var tabs = this.getTabsArray(targetBrowser); var lastTabIndex = tabs[tabs.length -1]._tPos; for (let i in aTabs) { let tab = aTabs[i]; if (shouldResetSelection) { if ('MultipleTabService' in sourceWindow) sourceWindow.MultipleTabService.setSelection(tab, false); else tab.removeAttribute('multiselected'); } if (aOptions.duplicate) { tab = this.duplicateTabAsOrphan(tab); newTabs.push(tab); } else if (sourceService != this) { tab = this.importTab(tab); newTabs.push(tab); } if (shouldResetSelection) { if ('MultipleTabService' in sourceWindow) sourceWindow.MultipleTabService.setSelection(tab, true); else tab.setAttribute('multiselected', true); } lastTabIndex++; let newIndex = aOptions.insertBefore ? aOptions.insertBefore._tPos : lastTabIndex ; if (aOptions.insertBefore && newIndex > tab._tPos) newIndex--; this.internallyTabMovingCount++; targetBrowser.moveTabTo(tab, newIndex); this.collapseExpandTab(tab, false, true); this.internallyTabMovingCount--; } if (shouldClose) sourceService.closeOwner(sourceBrowser); if (newTabs.length) this.applyTreeStructureToTabs( newTabs, treeStructure, collapsedStates.map(function(aCollapsed) { return !aCollapsed }) ); for (let i = collapsedStates.length - 1; i > -1; i--) { sourceService.collapseExpandSubtree(aTabs[i], collapsedStates[i], true); } // Multiple Tab Handler targetBrowser.movingSelectedTabs = false; targetBrowser.duplicatingSelectedTabs = false; return newTabs; }, importTab : function TSTBrowser_importTab(aTab) { var newTab = this.mTabBrowser.addTab(); newTab.linkedBrowser.stop(); newTab.linkedBrowser.docShell; this.mTabBrowser.swapBrowsersAndCloseOther(newTab, aTab); this.mTabBrowser.setTabTitle(newTab); return newTab; }, duplicateTabAsOrphan : function TSTBrowser_duplicateTabAsOrphan(aTab) { var newTab = this.mTabBrowser.duplicateTab(aTab); this.deleteTabValue(newTab, this.kCHILDREN); this.deleteTabValue(newTab, this.kPARENT); return newTab; }, closeOwner : function TSTBrowser_closeOwner(aTabOwner) { var w = aTabOwner.ownerDocument.defaultView; if (!w) return; if ('SplitBrowser' in w) { if ('getSubBrowserFromChild' in w.SplitBrowser) { var subbrowser = w.SplitBrowser.getSubBrowserFromChild(aTabOwner); if (subbrowser) { subbrowser.close(); return; } } if (w.SplitBrowser.browsers.length) return; } w.close(); }, /* collapse/expand */ collapseExpandSubtree : function TSTBrowser_collapseExpandSubtree(aTab, aCollapse, aJustNow) /* PUBLIC API */ { if (!aTab) return; if (this.isSubtreeCollapsed(aTab) == aCollapse) return; var b = this.mTabBrowser; this.doingCollapseExpand = true; this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, aCollapse); this.getChildTabs(aTab).forEach(function(aTab) { this.collapseExpandTab(aTab, aCollapse, aJustNow); }, this); if (!aCollapse) this.scrollToTabSubtree(aTab); this.saveTreeStructureWithDelay(); this.doingCollapseExpand = false; }, collapseExpandTab : function TSTBrowser_collapseExpandTab(aTab, aCollapse, aJustNow) { if (!aTab || !this.getParentTab(aTab)) return; this.setTabValue(aTab, this.kCOLLAPSED, aCollapse); this.updateTabCollapsed(aTab, aCollapse, aJustNow); var data = { collapsed : aCollapse }; /* PUBLIC API */ this.fireDataContainerEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, aTab, true, false, data); // for backward compatibility this.fireDataContainerEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED.replace(/^nsDOM/, ''), aTab, true, false, data); var b = this.mTabBrowser; var parent; if (aCollapse && aTab == b.selectedTab && (parent = this.getParentTab(aTab))) { var newSelection = parent; while (this.isCollapsed(parent)) { parent = this.getParentTab(parent); if (!parent) break; newSelection = parent; } b.selectedTab = newSelection; } if (!this.isSubtreeCollapsed(aTab)) { this.getChildTabs(aTab).forEach(function(aTab) { this.collapseExpandTab(aTab, aCollapse, aJustNow); }, this); } }, updateTabCollapsed : function TSTBrowser_updateTabCollapsed(aTab, aCollapsed, aJustNow) { this.stopTabCollapseAnimation(aTab); aTab.removeAttribute(this.kX_OFFSET); aTab.removeAttribute(this.kY_OFFSET); aTab.setAttribute(this.kCOLLAPSING_PHASE, aCollapsed ? this.kCOLLAPSING_PHASE_TO_BE_COLLAPSED : this.kCOLLAPSING_PHASE_TO_BE_EXPANDED ); var CSSTransitionEnabled = ('Transition' in aTab.style || 'MozTransition' in aTab.style); var maxMargin; var offsetAttr; var collapseProp = 'margin-'+this.collapseTarget; let (firstTab = this.getFirstNormalTab(this.mTabBrowser)) { if (this.isVertical) { maxMargin = firstTab.boxObject.height; offsetAttr = this.kY_OFFSET; if (firstTab.style.height) aTab.style.height = firstTab.style.height; } else { maxMargin = firstTab.boxObject.width; offsetAttr = this.kX_OFFSET; if (firstTab.style.width) aTab.style.width = firstTab.style.width; } } var startMargin, endMargin, startOpacity, endOpacity; if (aCollapsed) { startMargin = 0; endMargin = maxMargin; startOpacity = 1; endOpacity = 0; if (this.canStackTabs && this.getParentTab(aTab)) { endOpacity = 1; endMargin = this.kSTACKED_TAB_MARGIN; } } else { startMargin = maxMargin; endMargin = 0; startOpacity = 0; endOpacity = 1; if (this.canStackTabs && this.getParentTab(aTab)) { startOpacity = 1; startMargin = this.kSTACKED_TAB_MARGIN; } } if ( !this.animationEnabled || aJustNow || this.collapseDuration < 1 // || // !this.isVertical || // !this.canCollapseSubtree(this.getParentTab(aTab)) ) { if (aCollapsed) aTab.setAttribute(this.kCOLLAPSED_DONE, true); else aTab.removeAttribute(this.kCOLLAPSED_DONE); aTab.removeAttribute(this.kCOLLAPSING_PHASE); if (CSSTransitionEnabled) { aTab.style.setProperty(this.collapseCSSProp, endMargin ? '-'+endMargin+'px' : '', 'important'); if (endOpacity == 0) aTab.style.setProperty('opacity', endOpacity == 1 ? '' : endOpacity, 'important'); else aTab.style.removeProperty('opacity'); } else { aTab.style.removeProperty(this.collapseCSSProp); aTab.style.removeProperty('opacity'); } return; } var deltaMargin = endMargin - startMargin; var deltaOpacity = endOpacity - startOpacity; aTab.style.setProperty(this.collapseCSSProp, startMargin ? '-'+startMargin+'px' : '', 'important'); aTab.style.setProperty('opacity', startOpacity == 1 ? '' : startOpacity, 'important'); if (!aCollapsed) { aTab.setAttribute(offsetAttr, maxMargin); aTab.removeAttribute(this.kCOLLAPSED_DONE); } var radian = 90 * Math.PI / 180; var self = this; var firstFrame = true; aTab.__treestyletab__updateTabCollapsedTask = function(aTime, aBeginning, aChange, aDuration) { if (firstFrame && CSSTransitionEnabled) { aTab.style.setProperty(self.collapseCSSProp, endMargin ? '-'+endMargin+'px' : '', 'important'); aTab.style.setProperty('opacity', endOpacity == 1 ? '' : endOpacity, 'important'); } firstFrame = false; // If this is the last tab, negative scroll happens. // Then, we shouldn't do animation. var stopAnimation = false; var scrollBox = self.scrollBox; if (scrollBox) { if (scrollBox._scrollbox) scrollBox = scrollBox._scrollbox; if ('scrollTop' in scrollBox && (scrollBox.scrollTop < 0 || scrollBox.scrollLeft < 0)) { scrollBox.scrollTop = 0; scrollBox.scrollLeft = 0; stopAnimation = true; } } if (aTime >= aDuration || stopAnimation) { delete aTab.__treestyletab__updateTabCollapsedTask; if (aCollapsed) aTab.setAttribute(self.kCOLLAPSED_DONE, true); if (!CSSTransitionEnabled) { aTab.style.removeProperty(self.collapseCSSProp); aTab.style.removeProperty('opacity'); } aTab.removeAttribute(offsetAttr); aTab.removeAttribute(self.kCOLLAPSING_PHASE); maxMargin = null; offsetAttr = null; startMargin = null; endMargin = null; startOpacity = null; endOpacity = null; deltaMargin = null; deltaOpacity = null; collapseProp = null; radian = null; self = null; aTab = null; return true; } else { if (!CSSTransitionEnabled) { let power = Math.sin(aTime / aDuration * radian); let margin = startMargin + (deltaMargin * power); let opacity = startOpacity + (deltaOpacity * power); aTab.style.setProperty(self.collapseCSSProp, margin ? '-'+margin+'px' : '', 'important'); aTab.style.setProperty('opacity', opacity == 1 ? '' : opacity, 'important'); } aTab.setAttribute(offsetAttr, maxMargin); return false; } }; this.animationManager.addTask( aTab.__treestyletab__updateTabCollapsedTask, 0, 0, this.collapseDuration, this.window ); }, kOPACITY_RULE_REGEXP : /opacity\s*:[^;]+;?/, kSTACKED_TAB_MARGIN : 15, stopTabCollapseAnimation : function TSTBrowser_stopTabCollapseAnimation(aTab) { this.animationManager.removeTask( aTab.__treestyletab__updateTabCollapsedTask ); }, collapseExpandTreesIntelligentlyFor : function TSTBrowser_collapseExpandTreesIntelligentlyFor(aTab, aJustNow) { if (!aTab || this.doingCollapseExpand || !this.canCollapseSubtree(aTab)) return; var b = this.mTabBrowser; var sameParentTab = this.getParentTab(aTab); var expandedParentTabs = [ aTab.getAttribute(this.kID) ]; var parentTab = aTab; while (parentTab = this.getParentTab(parentTab)) { expandedParentTabs.push(parentTab.getAttribute(this.kID)); } expandedParentTabs = expandedParentTabs.join('|'); var xpathResult = this.evaluateXPath( 'child::xul:tab[@'+this.kCHILDREN+' and not(@'+this.kCOLLAPSED+'="true") and not(@'+this.kSUBTREE_COLLAPSED+'="true") and @'+this.kID+' and not(contains("'+expandedParentTabs+'", @'+this.kID+'))]', b.mTabContainer ); var collapseTab; var dontCollapse; for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++) { dontCollapse = false; collapseTab = xpathResult.snapshotItem(i); parentTab = this.getParentTab(collapseTab); if (parentTab) { dontCollapse = true; if (!this.isSubtreeCollapsed(parentTab)) { do { if (expandedParentTabs.indexOf(parentTab.getAttribute(this.kID)) < 0) continue; dontCollapse = false; break; } while (parentTab = this.getParentTab(parentTab)); } } if (!dontCollapse) this.collapseExpandSubtree(collapseTab, true, aJustNow); } this.collapseExpandSubtree(aTab, false, aJustNow); }, collapseExpandTreesIntelligentlyWithDelayFor : function TSTBrowser_collapseExpandTreesIntelligentlyWithDelayFor(aTab) { if (this.doingCollapseExpand) return; if (this._cETIWDFTimer) this.window.clearTimeout(this._cETIWDFTimer); this._cETIWDFTimer = this.window.setTimeout(function(aSelf) { aSelf.window.clearTimeout(aSelf._cETIWDFTimer); aSelf._cETIWDFTimer = null; aSelf.collapseExpandTreesIntelligentlyFor(aTab); }, 0, this); }, _cETIWDFTimer : null, collapseExpandAllSubtree : function TSTBrowser_collapseExpandAllSubtree(aCollapse, aJustNow) { var xpathResult = this.evaluateXPath( 'child::xul:tab[@'+this.kID+' and @'+this.kCHILDREN+ ( aCollapse ? ' and not(@'+this.kSUBTREE_COLLAPSED+'="true")' : ' and @'+this.kSUBTREE_COLLAPSED+'="true"' )+ ']', this.mTabBrowser.mTabContainer ); for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++) { this.collapseExpandSubtree(xpathResult.snapshotItem(i), aCollapse, aJustNow); } }, /* scroll */ scrollTo : function TSTBrowser_scrollTo(aEndX, aEndY) { // Prevent to restore scroll position for "TabClose". // We override it. this.lastScrollX = -1; this.lastScrollY = -1; if (this.animationEnabled || this.smoothScrollEnabled) { this.smoothScrollTo(aEndX, aEndY); } else { try { this.scrollBoxObject.scrollTo(aEndX, aEndY); } catch(e) { } } }, smoothScrollTo : function TSTBrowser_smoothScrollTo(aEndX, aEndY, aDuration) { var b = this.mTabBrowser; this.animationManager.removeTask(this.smoothScrollTask); var scrollBoxObject = this.scrollBoxObject; var x = {}, y = {}; scrollBoxObject.getPosition(x, y); var startX = x.value; var startY = y.value; var deltaX = aEndX - startX; var deltaY = aEndY - startY; var arrowscrollbox = scrollBoxObject.element.parentNode; if ( arrowscrollbox && ( arrowscrollbox.localName != 'arrowscrollbox' || !('_isScrolling' in arrowscrollbox) ) ) arrowscrollbox = null; var radian = 90 * Math.PI / 180; var self = this; this.smoothScrollTask = function(aTime, aBeginning, aChange, aDuration) { var scrollBoxObject = self.scrollBoxObject; if (aTime >= aDuration) { scrollBoxObject.scrollTo(aEndX, aEndY); /** * When there is any expanding tab, we have to retry to scroll. * if the scroll box was expanded. */ let oldSize = self._getMaxScrollSize(scrollBoxObject); self.Deferred.next(function() { let newSize = self._getMaxScrollSize(scrollBoxObject); let lastTab = self.getLastVisibleTab(self.mTabBrowser); if ( // scroll size can be expanded by expanding tabs. oldSize[0] < newSize[0] || oldSize[1] < newSize[1] || // there are still animating tabs self.getXOffsetOfTab(lastTab) || self.getYOffsetOfTab(lastTab) || self.evaluateXPath( 'child::xul:tab[@'+self.kCOLLAPSING_PHASE+'="'+self.kCOLLAPSING_PHASE_TO_BE_EXPANDED+'"]', self.mTabBrowser.mTabContainer, Ci.nsIDOMXPathResult.BOOLEAN_TYPE ).booleanValue ) self.smoothScrollTo(aEndX, aEndY, parseInt(aDuration * 0.5)); self = null; scrollBoxObject = null; }); b = null; x = null; y = null; startX = null; startY = null; radian = null; self.smoothScrollTask = null; return true; } var power = Math.sin(aTime / aDuration * radian); var newX = startX + parseInt(deltaX * power); var newY = startY + parseInt(deltaY * power); scrollBoxObject.scrollTo(newX, newY); return false; }; this.animationManager.addTask( this.smoothScrollTask, 0, 0, this.smoothScrollDuration || aDuration, this.window ); }, _getMaxScrollSize : function(aScrollBoxObject) { var x = {}, y = {}; aScrollBoxObject.getPosition(x, y); var w = {}, h = {}; aScrollBoxObject.getScrolledSize(w, h); var maxX = Math.max(0, w.value - aScrollBoxObject.width); var maxY = Math.max(0, h.value - aScrollBoxObject.height); return [maxX, maxY]; }, smoothScrollTask : null, scrollToTab : function TSTBrowser_scrollToTab(aTab, aOnlyWhenCurrentTabIsInViewport) { if (!aTab || !aTab.parentNode || this.isTabInViewport(aTab)) return; var b = this.mTabBrowser; var scrollBoxObject = this.scrollBoxObject; var w = {}, h = {}; try { scrollBoxObject.getScrolledSize(w, h); } catch(e) { // Tab Mix Plus (or others) return; } var targetTabBox = aTab.boxObject; var baseTabBox = this.getFirstNormalTab(b).boxObject; var xOffset = this.getXOffsetOfTab(aTab); var yOffset = this.getYOffsetOfTab(aTab); var targetX = (aTab.boxObject.screenX + xOffset < scrollBoxObject.screenX) ? (targetTabBox.screenX + xOffset - baseTabBox.screenX) - (targetTabBox.width * 0.5) : (targetTabBox.screenX + xOffset - baseTabBox.screenX) - scrollBoxObject.width + (targetTabBox.width * 1.5) ; var targetY = (aTab.boxObject.screenY + yOffset < scrollBoxObject.screenY) ? (targetTabBox.screenY + yOffset - baseTabBox.screenY) - (targetTabBox.height * 0.5) : (targetTabBox.screenY + yOffset - baseTabBox.screenY) - scrollBoxObject.height + (targetTabBox.height * 1.5) ; if (aOnlyWhenCurrentTabIsInViewport && b.selectedTab != aTab) { let box = b.selectedTab.boxObject; if (targetTabBox.screenX - box.screenX + baseTabBox.width + xOffset > scrollBoxObject.width || targetTabBox.screenY - box.screenY + baseTabBox.height + yOffset > scrollBoxObject.height) return; } this.scrollTo(targetX, targetY); }, scrollToTabSubtree : function TSTBrowser_scrollToTabSubtree(aTab) { var b = this.mTabBrowser; var descendant = this.getDescendantTabs(aTab); var lastVisible = aTab; for (var i = descendant.length-1; i > -1; i--) { if (this.isCollapsed(descendant[i])) continue; lastVisible = descendant[i]; break; } if (this.isTabInViewport(aTab) && this.isTabInViewport(lastVisible)) { return; } var containerPosition = this.tabStrip.boxObject[this.screenPositionProp]; var containerSize = this.tabStrip.boxObject[this.sizeProp]; var parentPosition = aTab.boxObject[this.screenPositionProp]; var lastPosition = lastVisible.boxObject[this.screenPositionProp]; var tabSize = lastVisible.boxObject[this.sizeProp]; if (lastPosition - parentPosition + tabSize > containerSize - tabSize) { // out of screen var endPos = parentPosition - this.getFirstNormalTab(b).boxObject[this.screenPositionProp] - tabSize * 0.5; var endX = this.isVertical ? 0 : endPos ; var endY = this.isVertical ? endPos : 0 ; this.scrollTo(endX, endY); } else if (!this.isTabInViewport(aTab) && this.isTabInViewport(lastVisible)) { this.scrollToTab(aTab); } else if (this.isTabInViewport(aTab) && !this.isTabInViewport(lastVisible)) { this.scrollToTab(lastVisible); } else if (parentPosition < containerPosition) { this.scrollToTab(aTab); } else { this.scrollToTab(lastVisible); } }, /* sub modules */ get tabbarDNDObserver() { if (!this._tabbarDNDObserver) { let ns = {}; Components.utils.import('resource://treestyletab-modules/tabbarDNDObserver.js', ns); this._tabbarDNDObserver = new ns.TabbarDNDObserver(this.mTabBrowser); } return this._tabbarDNDObserver; }, get panelDNDObserver() { if (!this._panelDNDObserver) { let ns = {}; Components.utils.import('resource://treestyletab-modules/tabpanelDNDObserver.js', ns); this._panelDNDObserver = new ns.TabpanelDNDObserver(this.mTabBrowser); } return this._panelDNDObserver; }, /* proxying for window service */ _callWindowServiceMethod : function TSTBrowser_callWindowServiceMethod(aName, aArgs) { return this.windowService[aName].apply(this.windowService, aArgs); }, isPopupShown : function TSTBrowser_isPopupShown() { return this._callWindowServiceMethod('isPopupShown', arguments); }, updateTabsOnTop : function TSTBrowser_updateTabsOnTop() { return this._callWindowServiceMethod('updateTabsOnTop', arguments); }, registerTabFocusAllowance : function TSTBrowser_registerTabFocusAllowance() { return this._callWindowServiceMethod('registerTabFocusAllowance', arguments); }, isPopupShown : function TSTBrowser_isPopupShown() { return this._callWindowServiceMethod('isPopupShown', arguments); }, toggleAutoHide : function TSTBrowser_toggleAutoHide() { return this._callWindowServiceMethod('toggleAutoHide', arguments); }, /* show/hide tab bar */ get autoHide() { if (!this._autoHide) { let ns = {}; Components.utils.import('resource://treestyletab-modules/autoHide.js', ns); this._autoHide = new ns.AutoHideBrowser(this.mTabBrowser); } return this._autoHide; }, // for backward compatibility get tabbarShown() { return this.autoHide.expanded; }, set tabbarShown(aValue) { if (aValue) this.autoHide.show(); else this.autoHide.hide(); return aValue; }, get tabbarExpanded() { return this.autoHide.expanded; }, set tabbarExpanded(aValue) { return this.tabbarShown = aValue; }, get tabbarResizing() { return this.autoHide.isResizing; }, set tabbarResizing(aValue) { return this.autoHide.isResizing = aValue; }, get togglerSize() { return this.autoHide.togglerSize; }, set togglerSize(aValue) { return this.autoHide.togglerSize = aValue; }, get sensitiveArea() { return this.autoHide.sensitiveArea; }, set sensitiveArea(aValue) { return this.autoHide.sensitiveArea = aValue; }, get lastMouseDownTarget() { return this.autoHide.lastMouseDownTarget; }, set lastMouseDownTarget(aValue) { return this.autoHide.lastMouseDownTarget = aValue; }, get tabbarWidth() { return this.autoHide.width; }, set tabbarWidth(aValue) { return this.autoHide.widthwidth = aValue; }, get tabbarHeight() { return this.autoHide.height; }, set tabbarHeight(aValue) { return this.autoHide.height = aValue; }, get splitterWidth() { return this.autoHide.splitterWidth; }, get autoHideShown() { return this.autoHide.expanded; }, set autoHideShown(aValue) { return this.tabbarShown = aValue; }, get autoHideXOffset() { return this.autoHide.XOffset; }, get autoHideYOffset() { return this.autoHide.YOffset; }, get autoHideMode() { return this.autoHide.mode; }, set autoHideMode(aValue) { return this.autoHide.mode = aValue; }, updateAutoHideMode : function TSTBrowser_updateAutoHideMode() { this.autoHide.updateAutoHideMode(); }, showHideTabbarInternal : function TSTBrowser_showHideTabbarInternal(aReason) { this.autoHide.showHideInternal(aReason); }, showTabbar : function TSTBrowser_showTabbar(aReason) { this.autoHide.show(aReason); }, hideTabbar : function TSTBrowser_hideTabbar(aReason) { this.autoHide.hide(aReason); }, redrawContentArea : function TSTBrowser_redrawContentArea() { this.autoHide.redrawContentArea(); }, drawTabbarCanvas : function TSTBrowser_drawTabbarCanvas() { this.autoHide.drawBG(); }, get splitterBorderColor() { this.autoHide.splitterBorderColor; }, clearTabbarCanvas : function TSTBrowser_clearTabbarCanvas() { this.autoHide.clearBG(); }, updateTabbarTransparency : function TSTBrowser_updateTabbarTransparency() { this.autoHide.updateTransparency(); }, get autoHideEnabled() { return this.autoHide.enabled; }, set autoHideEnabled(aValue) { return this.autoHide.enabled = aValue; }, startAutoHide : function TSTBrowser_startAutoHide() { this.autoHide.start(); }, endAutoHide : function TSTBrowser_endAutoHide() { this.autoHide.end(); }, startAutoHideForFullScreen : function TSTBrowser_startAutoHideForFullScreen() { this.autoHide.startForFullScreen(); }, endAutoHideForFullScreen : function TSTBrowser_endAutoHideForFullScreen() { this.autoHide.endForFullScreen(); }, startListenMouseMove : function TSTBrowser_startListenMouseMove() { this.autoHide.startListenMouseMove(); }, endListenMouseMove : function TSTBrowser_endListenMouseMove() { this.autoHide.endListenMouseMove(); }, get shouldListenMouseMove() { return this.autoHide.shouldListenMouseMove; }, showHideTabbarOnMousemove : function TSTBrowser_showHideTabbarOnMousemove() { this.autoHide.showHideOnMousemove(); }, cancelShowHideTabbarOnMousemove : function TSTBrowser_cancelShowHideTabbarOnMousemove() { this.autoHide.cancelShowHideOnMousemove(); }, showTabbarForFeedback : function TSTBrowser_showTabbarForFeedback() { this.autoHide.showForFeedback(); }, delayedShowTabbarForFeedback : function TSTBrowser_delayedShowTabbarForFeedback() { this.autoHide.delayedShowForFeedback(); }, cancelHideTabbarForFeedback : function TSTBrowser_cancelHideTabbarForFeedback() { this.autoHide.cancelHideForFeedback(); } }; // rap('end of definition of browser');