treestyletab/modules/browser.js
Piro / YUKI Hiroshi daa27ee336 Clear temporary position information of the tab bar after it is permanently changed.
This seems to fix broken appearance issue around toolbar customization (on second try or later).
2015-06-13 00:16:21 +09:00

7054 lines
217 KiB
JavaScript

/* ***** 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 YUKI "Piro" Hiroshi.
* Portions created by the Initial Developer are Copyright (C) 2011-2015
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): YUKI "Piro" Hiroshi <piro.outsider.reflex@gmail.com>
* wanabe <https://github.com/wanabe>
* Tetsuharu OHZEKI <https://github.com/saneyuki>
*
* 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 DEBUG = false;
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Timer.jsm');
Cu.import('resource://treestyletab-modules/lib/inherit.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Promise', 'resource://gre/modules/Promise.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils');
XPCOMUtils.defineLazyModuleGetter(this, 'FullTooltipManager', 'resource://treestyletab-modules/fullTooltip.js');
XPCOMUtils.defineLazyModuleGetter(this, 'TabbarDNDObserver', 'resource://treestyletab-modules/tabbarDNDObserver.js');
XPCOMUtils.defineLazyModuleGetter(this, 'TabpanelDNDObserver', 'resource://treestyletab-modules/tabpanelDNDObserver.js');
XPCOMUtils.defineLazyModuleGetter(this, 'AutoHideBrowser', 'resource://treestyletab-modules/autoHide.js');
XPCOMUtils.defineLazyModuleGetter(this, 'ContentBridge', 'resource://treestyletab-modules/contentBridge.js');
XPCOMUtils.defineLazyModuleGetter(this, 'BrowserUIShowHideObserver', 'resource://treestyletab-modules/browserUIShowHideObserver.js');
XPCOMUtils.defineLazyModuleGetter(this, 'visuallyselectedTabs', 'resource://treestyletab-modules/lib/visuallyselectedTabs.jsm');
XPCOMUtils.defineLazyGetter(this, 'window', function() {
Cu.import('resource://treestyletab-modules/lib/namespace.jsm');
return getNamespaceFor('piro.sakura.ne.jp');
});
XPCOMUtils.defineLazyGetter(this, 'prefs', function() {
Cu.import('resource://treestyletab-modules/lib/prefs.js');
return window['piro.sakura.ne.jp'].prefs;
});
function wait(aMilliSeconds) {
return new Promise(function(aResolve, aReject) {
setTimeout(function() {
aResolve();
}, aMilliSeconds);
});
}
Cu.import('resource://treestyletab-modules/window.js');
function TreeStyleTabBrowser(aWindowService, aTabBrowser)
{
this.id = Date.now() + '-' + parseInt(Math.random() * 65000);
this.windowService = aWindowService;
this.window = aWindowService.window;
this.document = aWindowService.document;
this.mTabBrowser = aTabBrowser;
aTabBrowser.treeStyleTab = this;
this.tabVisibilityChangedTabs = [];
this.updateTabsIndentWithDelayTabs = [];
this.timers = {};
this.tabVisibilityChangedTabs = [];
this._updateFloatingTabbarReason = 0;
this.internallyTabMovingCount = 0;
this.subTreeMovingCount = 0;
this.subTreeChildrenMovingCount = 0;
this._treeViewEnabled = true;
}
TreeStyleTabBrowser.prototype = inherit(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',
kMENUITEM_CLOSE_TABS_TO_END : 'context_closeTabsToTheEnd',
mTabBrowser : null,
indent : -1,
indentProp : 'margin',
indentTarget : 'left',
indentCSSProp : 'margin-left',
collapseTarget : 'top',
collapseCSSProp : 'margin-top',
screenPositionProp : 'screenY',
offsetProp : 'offsetY',
translateFunction : 'translateY',
sizeProp : 'height',
invertedScreenPositionProp : 'screenX',
invertedSizeProp : 'width',
startProp : 'top',
endProp : 'bottom',
maxTreeLevelPhisical : false,
needRestoreTree : 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
utils.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;
var boxObject = (node.scrollBoxObject || node.boxObject);
try {
boxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject); // for Tab Mix Plus (ensure scrollbox-ed)
}
catch(e) {
// May not implement this interface e.g. after bug 979835
}
return boxObject;
},
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);
},
/* properties */
get tabbarWidth()
{
return this.autoHide.tabbarWidth;
},
set tabbarWidth(aValue)
{
return this.autoHide.tabbarWidth = aValue;
},
/* // this section will be used after autoHide feature is separated in the future...
get tabbarWidth()
{
var width = this.getWindowValue(this.kTABBAR_WIDTH);
return width === '' ?
utils.getTreePref('tabbar.width') :
parseInt(width);
},
set tabbarWidth(aValue)
{
this.setWindowValue(this.kTABBAR_WIDTH, aValue);
this.setPrefForActiveWindow(function() {
utils.setTreePref('tabbar.width', aValue);
});
return aValue;
},
*/
get tabbarHeight()
{
var height = this.getWindowValue(this.kTABBAR_HEIGHT);
return height === '' ?
utils.getTreePref('tabbar.height') :
parseInt(height);
},
set tabbarHeight(aValue)
{
this.setWindowValue(this.kTABBAR_HEIGHT, aValue);
this.setPrefForActiveWindow(function() {
utils.setTreePref('tabbar.height', aValue);
});
return aValue;
},
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 baseIndent() {
return this.isVertical ? this.baseIndentVertical : this.baseIndentHorizontal;
},
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 fixed()
{
var orient = this.isVertical ? 'vertical' : 'horizontal' ;
if (!this.windowService.preInitialized)
return utils.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;
},
set temporaryPosition(aValue)
{
var position = this.normalizeTabbarPosition(aValue);
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, true);
self._temporaryPosition = aValue;
},
{
label : utils.treeBundle.getString('undo_changeTabbarPosition_label'),
name : 'treestyletab-changeTabbarPosition-private',
data : {
oldPosition : current,
newPosition : position,
target : self.mTabBrowser.id
}
}
);
}
else {
this._changeTabbarPosition(position, true);
this._temporaryPosition = aValue;
}
return position;
},
_temporaryPosition : null,
get position() /* PUBLIC API */
{
if (this._temporaryPosition)
return this._temporaryPosition;
var lastPosition = this.getWindowValue(this.kTABBAR_POSITION);
if (lastPosition !== '')
return lastPosition;
return (
// Don't touch to the <tabbrowser/> element before it is initialized by XBL constructor.
(this.windowService.preInitialized && this.browser.getAttribute(this.kTABBAR_POSITION)) ||
this.base.position
);
},
set position(aValue)
{
var position = this.normalizeTabbarPosition(aValue);
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 : utils.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, aIsTemporaryChange)
{
if (this.timers['_changeTabbarPosition'])
clearTimeout(this.timers['_changeTabbarPosition']);
var oldPosition = this.position;
this.fireTabbarPositionEvent(true, oldPosition, aNewPosition);
this.initTabbar(aNewPosition, oldPosition, aIsTemporaryChange);
this.reinitAllTabs();
this.timers['_changeTabbarPosition'] = setTimeout((function() {
try {
this.checkTabsIndentOverflow();
this.fireTabbarPositionEvent(false, oldPosition, aNewPosition);
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers['_changeTabbarPosition'];
}).bind(this), 0);
},
/* 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 isVisible()
{
var bar = this.ownerToolbar;
var style = this.window.getComputedStyle(bar, '');
if (style.visibility != 'visible' || style.display == 'none')
return false;
var box = bar.boxObject;
return !!(box.width || box.height);
},
isFloating : true, // for backward compatibility (but this should be removed)
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.isVertical &&
this.canCollapseSubtree() &&
utils.getTreePref('stackCollapsedTabs')
);
},
get counterRole()
{
return this.isVertical ? this.counterRoleVertical : this.counterRoleHorizontal ;
},
get isDestroying()
{
return !this.mTabBrowser || !this.mTabBrowser.mTabContainer;
},
/* 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
utils.getTreePref('compatibility.TMP') &&
d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text-container')
) ||
d.getAnonymousElementByAttribute(aTab, 'class', 'tab-text tab-label');
return label;
},
getTabClosebox : function TSTBrowser_getTabClosebox(aTab)
{
var d = this.document;
var close = ( // Tab Mix Plus
utils.getTreePref('compatibility.TMP') &&
d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button always-right')
) ||
d.getAnonymousElementByAttribute(aTab, 'anonid', 'close-button') || // with Australis
d.getAnonymousElementByAttribute(aTab, 'class', 'tab-close-button');
return close;
},
getTabTwisty : function TSTBrowser_getTabTwisty(aTab)
{
return this.document.getAnonymousElementByAttribute(aTab, 'class', this.kTWISTY);
},
getTabTwistyAnchorNode : function TSTBrowser_getTabTwistyAnchorNode(aTab)
{
return this.document.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon') || // Tab Mix Plus
this.document.getAnonymousElementByAttribute(aTab, 'class', 'tab-throbber');
},
getTabFromTabbarEvent : function TSTBrowser_getTabFromTabbarEvent(aEvent)
{
if (
!this.shouldDetectClickOnIndentSpaces ||
!this.getAncestorTabbarFromEvent(aEvent) ||
this.isEventFiredOnClickable(aEvent) ||
this.getSplitterFromEvent(aEvent)
)
return null;
return this.getTabFromCoordinates(aEvent);
},
getTabFromCoordinates : function TSTBrowser_getTabFromCoordinates(aCoordinates, aTabs)
{
var tab = this.document.elementFromPoint(aCoordinates.clientX, aCoordinates.clientY);
if (tab && tab.localName == 'tab' && (!aTabs || aTabs.indexOf(tab) > -1))
return tab;
var positionCoordinate = aCoordinates[this.screenPositionProp];
var tabs = aTabs || this.getTabs(this.mTabBrowser);
if (!tabs.length ||
this.getTabActualScreenPosition(tabs[0]) > positionCoordinate ||
this.getTabActualScreenPosition(tabs[tabs.length-1]) < positionCoordinate)
return null;
var low = 0;
var high = tabs.length - 1;
while (low <= high) {
let middle = Math.floor((low + high) / 2);
let position = this.getTabActualScreenPosition(tabs[middle]);
if (position > positionCoordinate) {
high = middle - 1;
}
else if (position + tabs[middle].boxObject[this.sizeProp] < positionCoordinate) {
low = middle + 1;
}
else {
return tabs[middle];
}
}
return null;
/*
var tab = null;
this.getTabs(this.mTabBrowser).some(function(aTab) {
var box = aTab.boxObject;
if (box[this.screenPositionProp] > positionCoordinate ||
box[this.screenPositionProp] + box[this.sizeProp] < positionCoordinate) {
return false;
}
tab = aTab;
return true;
}, this);
return tab;
*/
},
getNextFocusedTab : function TSTBrowser_getNextFocusedTab(aTab)
{
return this.getNextSiblingTab(aTab) ||
this.getPreviousVisibleTab(aTab);
},
isTabInViewport : function TSTBrowser_isTabInViewport(aTab)
{
if (!this.windowService.preInitialized || !aTab)
return false;
if (aTab.getAttribute('pinned') == 'true')
return true;
var tabBox = this.getFutureBoxObject(aTab);
var barBox = this.scrollBox.boxObject;
if (this.isVertical)
return (
tabBox.screenY >= barBox.screenY &&
tabBox.screenY + tabBox.height <= barBox.screenY + barBox.height
);
else
return (
tabBox.screenX >= barBox.screenX &&
tabBox.screenX + tabBox.width <= barBox.screenX + barBox.width
);
},
isMultiRow : function TSTBrowser_isMultiRow()
{
var w = this.window;
return ('tabberwocky' in w && utils.getTreePref('compatibility.Tabberwocky')) ?
(prefs.getPref('tabberwocky.multirow') && !this.isVertical) :
('TabmixTabbar' in w && utils.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;
var count = this.pinnedTabsCount;
if (!this.isVertical || !count) {
this.resetPinnedTabs();
b.mTabContainer._positionPinnedTabs();
return;
}
var tabbarPlaceHolderWidth = this._tabStripPlaceHolder.boxObject.width;
var tabbarWidth = this.tabStrip.boxObject.width;
var maxWidth = tabbarPlaceHolderWidth || tabbarWidth;
var faviconized = utils.getTreePref('pinnedTab.faviconized');
var faviconizedSize = tabbar.childNodes[0].boxObject.height;
var width = faviconized ? faviconizedSize : maxWidth ;
var height = faviconizedSize;
var maxCol = Math.max(1, Math.floor(maxWidth / width));
var maxRow = Math.ceil(count / maxCol);
var col = 0;
var row = 0;
var baseX = this.tabStrip.boxObject.screenX - this.document.documentElement.boxObject.screenX;
var shrunkenOffset = (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.maxWidth = style.width = width+'px';
style.setProperty('margin-left', ((width * col) + shrunkenOffset)+'px', 'important');
style.left = baseX+'px';
style.right = 'auto';
style.marginRight = '';
style.setProperty('margin-top', (- height * (maxRow - row))+'px', 'important');
style.top = style.bottom = '';
if (aJustNow) {
let key = 'positionPinnedTabs_tab_'+parseInt(Math.random() * 65000);
// "transition" must be cleared after the reflow.
this.timers[key] = setTimeout((function() {
try {
style.MozTransition = style.transition = transitionStyleBackup;
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
}).bind(this), 0);
}
col++;
if (col >= maxCol) {
col = 0;
row++;
}
}
},
positionPinnedTabsWithDelay : function TSTBrowser_positionPinnedTabsWithDelay(...aArgs)
{
if (this.timers['positionPinnedTabsWithDelay'])
return;
var lastArgs = this.timers['positionPinnedTabsWithDelay'] ?
this.timers['positionPinnedTabsWithDelay'].__treestyletab__args :
[null, null, false] ;
lastArgs[0] = lastArgs[0] || aArgs[0];
lastArgs[1] = lastArgs[1] || aArgs[1];
lastArgs[2] = lastArgs[2] || aArgs[2];
var timer = setTimeout((function() {
setTimeout((function() {
try {
// do with delay again, after Firefox's reposition was completely finished.
this.positionPinnedTabs.apply(this, lastArgs);
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers['positionPinnedTabsWithDelay'];
}).bind(this), 0);
}).bind(this), 0);
this.timers['positionPinnedTabsWithDelay'] = {
timer : timer,
__treestyletab__args : lastArgs
};
},
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.maxWidth = style.width = style.left = style.right =
style.MozMarginStart = style.marginLeft = style.marginRight = style.marginTop = '';
}
},
updateTabsZIndex : function TSTBrowser_updateTabsZIndex(aStacked)
{
var tabs = this.getTabs(this.mTabBrowser);
var count = tabs.length;
for (let i = 0; i < count; i++)
{
let tab = tabs[i];
if (aStacked)
tab.style.zIndex = count * 1000 - i;
else
tab.style.zIndex = '';
}
},
fixTooNarrowTabbar : function TSTBrowser_fixTooNarrowTabbar()
{
/**
* 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 width = this.tabbarWidth;
let minWidth = Math.max(this.MIN_TABBAR_WIDTH, this.scrollBox.boxObject.width);
if (minWidth > width) {
this.setPrefForActiveWindow((function() {
this.tabbarWidth = minWidth;
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE);
}).bind(this));
}
}
else {
let height = this.tabbarHeight;
let minHeight = Math.max(this.MIN_TABBAR_HEIGHT, this.scrollBox.boxObject.height);
if (minHeight > height) {
this.setPrefForActiveWindow((function() {
this.tabbarHeight = minHeight;
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE);
}).bind(this));
}
}
},
/* initialize */
init : function TSTBrowser_init()
{
var w = this.window;
var d = this.document;
var b = this.mTabBrowser;
b.tabContainer.treeStyleTab = this;
visuallyselectedTabs(b);
this.tabsHash = {};
if (b.tabContainer.parentNode.localName == 'toolbar')
b.tabContainer.parentNode.classList.add(this.kTABBAR_TOOLBAR);
/**
* On secondary (and later) window, SSWindowStateBusy event can be fired
* before DOMContentLoad, on "domwindowopened".
*/
this.needRestoreTree = w.__treestyletab__WindowStateBusy || false;
delete w.__treestyletab__WindowStateBusy;
this._initTabbrowserExtraContents();
var position = this.position;
this.fireTabbarPositionEvent(this.kEVENT_TYPE_TABBAR_POSITION_CHANGING, 'top', position); /* PUBLIC API */
this.setTabbrowserAttribute(this.kFIXED+'-horizontal', utils.getTreePref('tabbar.fixed.horizontal') ? 'true' : null, b);
this.setTabbrowserAttribute(this.kFIXED+'-vertical', utils.getTreePref('tabbar.fixed.vertical') ? 'true' : null, b);
this.setTabStripAttribute(this.kTAB_STRIP_ELEMENT, true);
/**
* <tabbrowser> 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
* <notificationbox>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) {
let color = b.style.backgroundColor;
let pi = d.createProcessingInstruction(
'xml-stylesheet',
'type="text/css" href="data:text/css,'+encodeURIComponent(
('.tabbrowser-tabbox > tabpanels > notificationbox {\n' +
' background-color: %COLOR%;\n' +
'}').replace(/%COLOR%/, color)
)+'"'
);
d.insertBefore(pi, d.documentElement);
b.style.backgroundColor = '';
}
this.initTabbar(null, this.kTABBAR_TOP, true);
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);
w.addEventListener('tabviewframeinitialized', this, false);
w.addEventListener(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false);
w.addEventListener('SSWindowStateBusy', 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.getAllTabs(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('browser.tabs.animate');
Services.obs.addObserver(this, this.kTOPIC_INDENT_MODIFIED, false);
Services.obs.addObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL, false);
Services.obs.addObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY, false);
Services.obs.addObserver(this, 'lightweight-theme-styling-update', false);
prefs.addPrefListener(this);
// Don't init these ovservers on this point to avoid needless initializations.
// this.tabbarDNDObserver;
// this.panelDNDObserver;
this._readyToInitDNDObservers();
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_INITIALIZE);
this.fixTooNarrowTabbar();
this.fireTabbarPositionEvent(false, 'top', position); /* PUBLIC API */
if (this.timers['init'])
clearTimeout(this.timers['init']);
this.timers['init'] = setTimeout((function() {
try {
// This command is always enabled and the TabsOnTop can be enabled
// by <tabbrowser>.updateVisibility().
// So we have to reset TabsOnTop state on the startup.
var toggleTabsOnTop = d.getElementById('cmd_ToggleTabsOnTop');
var TabsOnTop = 'TabsOnTop' in w ? w.TabsOnTop : null ;
if (TabsOnTop && TabsOnTop.syncUI && toggleTabsOnTop && this.isVertical) {
toggleTabsOnTop.setAttribute('disabled', true);
if (TabsOnTop.enabled && TabsOnTop.toggle)
TabsOnTop.toggle();
}
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers['init'];
}).bind(this), 0);
},
_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(this.kTAB_STRIP_ELEMENT, true);
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(this.kTAB_STRIP_ELEMENT, true);
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 ;
if (this.tabStripPlaceHolder)
this.tabStripPlaceHolderBoxObserver = new BrowserUIShowHideObserver(this, this.tabStripPlaceHolder.parentNode);
},
_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));
let ids = [
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
];
for (let i = 0, maxi = ids.length; i < maxi; i++)
{
let id = ids[i];
let item = d.getElementById(id).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);
}
let closeTabsToEnd = d.getElementById(this.kMENUITEM_CLOSE_TABS_TO_END);
if (closeTabsToEnd) {
this._closeTabsToEnd_horizontalLabel = closeTabsToEnd.getAttribute('label');
this._closeTabsToEnd_horizontalAccesskey = closeTabsToEnd.getAttribute('accesskey');
}
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'
)
);
}
},
_initTooltipManager : function TSTBrowser_initTooltipManager()
{
if (this.tooltipManager)
return;
this.tooltipManager = new FullTooltipManager(this);
},
_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.removeEventListener('mouseover', this, true);
w.removeEventListener('dragover', this, true);
this._DNDObserversInitialized = true;
},
initTab : function TSTBrowser_initTab(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
if (!aTab.hasAttribute(this.kID)) {
let id = this.getTabValue(aTab, this.kID) || this.makeNewId();
aTab.setAttribute(this.kID, id);
aTab.setAttribute(this.kID_NEW, id);
aTab.setAttribute(this.kSUBTREE_COLLAPSED, true);
aTab.setAttribute(this.kALLOW_COLLAPSE, true);
let key = 'initTab_'+id;
if (this.timers[key])
clearTimeout(this.timers[key]);
this.timers[key] = setTimeout((function() {
try {
if (aTab.getAttribute(this.kID) == id) { // not changed by someone!
aTab.removeAttribute(this.kID_NEW);
if (!this.getTabValue(aTab, this.kID)) {
this.setTabValue(aTab, this.kID, id);
if (!(id in this.tabsHash))
this.tabsHash[id] = aTab;
}
}
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
}).bind(this), 0);
if (!(id in this.tabsHash))
this.tabsHash[id] = aTab;
}
else {
// if the tab is restored from session, it can be not-cached.
let id = aTab.getAttribute(this.kID);
if (!(id in this.tabsHash))
this.tabsHash[id] = aTab;
}
aTab.__treestyletab__linkedTabBrowser = this.mTabBrowser;
aTab.__treestyletab__restoreState = this.RESTORE_STATE_INITIAL;
if (utils.isTabNotRestoredYet(aTab))
aTab.linkedBrowser.__treestyletab__toBeRestored = true;
this.initTabAttributes(aTab);
this.initTabContents(aTab);
if (!aTab.hasAttribute(this.kNEST))
aTab.setAttribute(this.kNEST, 0);
aTab.__treestyletab__contentBridge = new ContentBridge(aTab, this.mTabBrowser);
this.autoHide.notifyStatusToTab(aTab);
},
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)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
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 (utils.getTreePref('compatibility.TMP'))
aTab.setAttribute('dir', 'ltr'); // Tab Mix Plus
}
else {
aTab.removeAttribute('align');
aTab.removeAttribute('maxwidth');
aTab.removeAttribute('minwidth');
if (utils.getTreePref('compatibility.TMP'))
aTab.removeAttribute('dir'); // Tab Mix Plus
}
},
initTabContents : function TSTBrowser_initTabContents(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var d = this.document;
var twisty = this.getTabTwisty(aTab);
var anchor = this.getTabTwistyAnchorNode(aTab);
if (anchor && !twisty) {
twisty = d.createElement('image');
twisty.setAttribute('class', this.kTWISTY);
anchor.parentNode.appendChild(twisty);
}
var label = this.getTabLabel(aTab);
var counter = d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER);
if (label && !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);
}
this.initTabContentsOrder(aTab, true);
},
initTabContentsOrder : function TSTBrowser_initTabContentsOrder(aTab, aForce)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var d = this.document;
var namedNodes = {
label : this.getTabLabel(aTab),
close : this.getTabClosebox(aTab),
twistyAnchor : this.getTabTwistyAnchorNode(aTab),
twisty : this.getTabTwisty(aTab),
counter : d.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER)
};
namedNodes.closeAnchor = namedNodes.label;
if (namedNodes.closeAnchor.parentNode != namedNodes.close.parentNode) {
let containerFinder = d.createRange();
containerFinder.selectNode(namedNodes.closeAnchor);
containerFinder.setEndAfter(namedNodes.close);
let container = containerFinder.commonAncestorContainer;
while (namedNodes.closeAnchor.parentNode != container)
{
namedNodes.closeAnchor = namedNodes.closeAnchor.parentNode;
}
while (namedNodes.close.parentNode != container)
{
namedNodes.close = namedNodes.close.parentNode;
}
}
namedNodes.counterAnchor = namedNodes.label;
var foundContainers = [];
var containers = [
namedNodes.twistyAnchor.parentNode,
namedNodes.label.parentNode,
namedNodes.counter.parentNode,
namedNodes.closeAnchor.parentNode
];
for (let i = 0, maxi = containers.length; i < maxi; i++)
{
let container = containers[i];
if (foundContainers.indexOf(container) > -1)
continue;
this.initTabContentsOrderInternal(container, namedNodes, aForce);
foundContainers.push(container);
}
},
initTabContentsOrderInternal : function TSTBrowser_initTabContentsOrderInternal(aContainer, aNamedNodes, aForce)
{
if (this.window.getComputedStyle(aContainer, '').getPropertyValue('-moz-box-orient') == 'vertical')
return;
var nodes = Array.slice(this.document.getAnonymousNodes(aContainer) || aContainer.childNodes);
// reset order at first!
for (let i = 0, maxi = nodes.length; i < maxi; i++)
{
let node = nodes[i];
if (node.getAttribute('class') == 'informationaltab-thumbnail-container')
continue;
node.setAttribute('ordinal', i);
}
// after that, rearrange contents
var index = nodes.indexOf(aNamedNodes.close);
if (index > -1) {
nodes.splice(index, 1);
if (this.mTabBrowser.getAttribute(this.kCLOSEBOX_INVERTED) == 'true')
nodes.splice(nodes.indexOf(aNamedNodes.closeAnchor), 0, aNamedNodes.close);
else
nodes.splice(nodes.indexOf(aNamedNodes.closeAnchor)+1, 0, aNamedNodes.close);
}
index = nodes.indexOf(aNamedNodes.twisty);
if (index > -1) {
nodes.splice(index, 1);
nodes.splice(nodes.indexOf(aNamedNodes.twistyAnchor), 0, aNamedNodes.twisty);
}
if (this.mTabBrowser.getAttribute(this.kTAB_CONTENTS_INVERTED) == 'true')
nodes.reverse();
// counter must rightside of the label!
index = nodes.indexOf(aNamedNodes.counter);
if (index > -1) {
nodes.splice(index, 1);
nodes.splice(nodes.indexOf(aNamedNodes.counterAnchor)+1, 0, aNamedNodes.counter);
}
var count = nodes.length;
nodes.reverse();
for (let i = 0, maxi = nodes.length; i < maxi; i++)
{
let node = nodes[i];
if (node.getAttribute('class') == 'informationaltab-thumbnail-container')
continue;
node.setAttribute('ordinal', (count - i + 1) * 100);
}
if (aForce) {
/**
* After the order of contents are changed dynamically,
* Gecko doesn't re-render them in the new order.
* Changing of "display" or "position" can fix this problem.
*/
let shouldHideTemporaryState = (
'TabmixTabbar' in this.window || // Tab Mix Plus
'InformationalTabService' in this.window // Informational Tab
);
for (let i = 0, maxi = nodes.length; i < maxi; i++)
{
let node = nodes[i];
if (shouldHideTemporaryState)
node.style.visibility = 'hidden';
node.style.position = 'fixed';
}
let key = 'initTabContentsOrderInternal_'+parseInt(Math.random() * 65000);
this.timers[key] = setTimeout((function() {
try {
for (let i = 0, maxi = nodes.length; i < maxi; i++)
{
let node = nodes[i];
node.style.position = '';
if (shouldHideTemporaryState)
node.style.visibility = '';
}
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
}).bind(this), 100);
}
},
updateInvertedTabContentsOrder : function TSTBrowser_updateInvertedTabContentsOrder(aTarget)
{
let key = 'updateInvertedTabContentsOrder_'+parseInt(Math.random() * 65000);
this.timers[key] = setTimeout((function() {
try {
var b = this.mTabBrowser;
var tabs = !aTarget ?
[b.selectedTab] :
(aTarget instanceof this.window.Element) ?
[aTarget] :
(typeof aTarget == 'object' && 'length' in aTarget) ?
Array.slice(aTarget) :
this.getAllTabs(b);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.initTabContentsOrder(tabs[i]);
}
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
}).bind(this), 0);
},
initTabbar : function TSTBrowser_initTabbar(aNewPosition, aOldPosition, aIsTemporaryChange)
{
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);
var pos = aNewPosition || this.getPositionFlag(this.position);
if (b.getAttribute('id') != 'content' &&
!utils.getTreePref('tabbar.position.subbrowser.enabled')) {
pos = this.kTABBAR_TOP;
}
if (!aIsTemporaryChange) {
let positionName = this.normalizeTabbarPosition(pos);
this.setWindowValue(this.kTABBAR_POSITION, positionName);
this.setPrefForActiveWindow(function() {
utils.setTreePref('tabbar.position', positionName);
});
}
aOldPosition = aOldPosition || pos;
var strip = this.tabStrip;
var placeHolder = this.tabStripPlaceHolder || strip;
var splitter = this._ensureNewSplitter();
var toggler = d.getAnonymousElementByAttribute(b, 'class', this.kTABBAR_TOGGLER);
// Tab Mix Plus
var scrollFrame, newTabBox, tabBarMode;
if (utils.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 = prefs.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.offsetProp = 'offsetY';
this.translateFunction = 'translateY';
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 (utils.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)
prefs.setPref('extensions.tabmix.tabBarMode', 1);
}
if (toolboxContainer)
toolboxContainer.orient = 'vertical';
this.setTabbrowserAttribute(this.kMODE, 'vertical');
//let width = this.maxTabbarWidth(this.tabbarWidth, b);
let width = this.maxTabbarWidth(this.autoHide.expandedWidth, b);
this.setTabStripAttribute('width', width);
this.removeTabStripAttribute('height');
b.mPanelContainer.removeAttribute('height');
if (strip.localName == 'toolbar') {
let nodes = strip.childNodes;
for (let i = 0, maxi = nodes.length; i < maxi; i++)
{
let node = nodes[i];
if (node.localName == 'tabs')
continue;
if (node.hasAttribute('flex'))
node.setAttribute('treestyletab-backup-flex', node.getAttribute('flex'));
node.removeAttribute('flex');
}
}
if (pos == this.kTABBAR_RIGHT) {
this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'right');
if (utils.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);
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) {
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.offsetProp = 'offsetX';
this.translateFunction = 'translateX';
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 (utils.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, utils.getTreePref('tabbar.multirow') ? 'multirow' : 'horizontal');
this.removeTabbrowserAttribute(this.kTAB_INVERTED);
if (strip.localName == 'toolbar') {
let nodes = strip.childNodes;
for (let i = 0, maxi = nodes.length; i < maxi; i++)
{
let node = nodes[i];
if (node.localName == 'tabs')
continue;
let flex = node.hasAttribute('treestyletab-backup-flex');
if (!flex)
continue;
node.setAttribute('flex', flex);
node.removeAttribute('treestyletab-backup-flex');
}
}
if (pos == this.kTABBAR_BOTTOM) {
this.setTabbrowserAttribute(this.kTABBAR_POSITION, 'bottom');
this.indentTarget = 'bottom';
delayedPostProcess = function(aSelf, aTabBrowser, aSplitter, aToggler) {
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) {
aSelf.setTabStripAttribute('ordinal', 10);
aSplitter.setAttribute('ordinal', 20);
aToggler.setAttribute('ordinal', 5);
aTabBrowser.mPanelContainer.setAttribute('ordinal', 30);
};
}
}
var tabs = this.getAllTabs(b);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
tab.style.removeProperty(this.indentCSSProp);
tab.style.removeProperty(this.collapseCSSProp);
}
this.indentProp = utils.getTreePref('indent.property');
this.indentCSSProp = this.indentProp+'-'+this.indentTarget;
this.collapseCSSProp = 'margin-'+this.collapseTarget;
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
this.updateTabCollapsed(tab, tab.getAttribute(this.kCOLLAPSED) == 'true', true);
}
// for updateTabbarOverflow(), we should reset the "overflow" now.
b.mTabContainer.removeAttribute('overflow');
{
let container = this.document.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container');
if (container)
container.removeAttribute('overflow');
}
this.updateTabbarState(false);
if (this.timers['initTabbar'])
clearTimeout(this.timers['initTabbar']);
this.timers['initTabbar'] = setTimeout((function() {
try {
delayedPostProcess(this, b, splitter, toggler);
this.updateTabbarOverflow();
this.updateAllTabsButton(b);
this.updateAllTabsCount();
delayedPostProcess = null;
this.mTabBrowser.style.visibility = '';
var event = d.createEvent('Events');
event.initEvent(this.kEVENT_TYPE_TABBAR_INITIALIZED, true, false);
this.mTabBrowser.dispatchEvent(event);
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers['initTabbar'];
}).bind(this), 0);
pos = null;
scrollFrame = null;
newTabBox = null
tabBarMode = null;
toolboxContainer = null;
scrollInnerBox = null;
scrollInnerBox = null;
return new Promise((function(aResolve, aReject) {
var onInitialized = (function() {
this.mTabBrowser.removeEventListener(this.kEVENT_TYPE_TABBAR_INITIALIZED, onInitialized, false);
if (!aIsTemporaryChange)
delete this._temporaryPosition;
aResolve();
}).bind(this);
this.mTabBrowser.addEventListener(this.kEVENT_TYPE_TABBAR_INITIALIZED, onInitialized, false);
}).bind(this));
},
_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);
tabContainer.addEventListener('mouseover', this, true);
tabContainer.addEventListener('mouseout', 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);
},
_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(this.kTAB_STRIP_ELEMENT, true);
splitter.setAttribute('state', 'open');
splitter.setAttribute('layer', true); // https://bugzilla.mozilla.org/show_bug.cgi?id=590468
let grippy = d.createElement('grippy')
grippy.setAttribute(this.kTAB_STRIP_ELEMENT, true);
// Workaround for bugs:
// * https://github.com/piroor/treestyletab/issues/593
// * https://github.com/piroor/treestyletab/issues/783
// When you click the grippy...
// 1. The grippy changes "state" of the splitter from "collapsed"
// to "open".
// 2. The splitter changes visibility of the place holder.
// 3. BrowserUIShowHideObserver detects the change of place
// holder's visibility and triggers updateFloatingTabbar().
// 4. updateFloatingTabbar() copies the visibility of the
// actual tab bar to the place holder. However, the tab bar
// is still collapsed.
// 5. As the result, the place holder becomes collapsed and
// the splitter disappear.
// So, we have to turn the actual tab bar visible manually
// when the grippy is clicked.
let tabContainer = this.mTabBrowser.tabContainer;
grippy.addEventListener('click', function() {
tabContainer.ownerDocument.defaultView.setTimeout(function() {
var visible = grippy.getAttribute('state') != 'collapsed';
if (visible != tabContainer.visible)
tabContainer.visible = visible;
}, 0);
}, true);
splitter.appendChild(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.fireCustomEvent(type, this.mTabBrowser, true, false, data);
// for backward compatibility
this.fireCustomEvent(type.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data);
return true;
},
updateTabbarState : function TSTBrowser_updateTabbarState(aCancelable)
{
if (!this._fireTabbarStateChangingEvent() && aCancelable)
return;
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.position == 'top') {
this.removeTabStripAttribute('ordinal');
if (TabsOnTop && !this.windowService.isPopupWindow &&
this.windowService.initialized) {
let currentState = TabsOnTop.enabled;
let originalState = utils.getTreePref('tabsOnTop.originalState');
if (originalState !== null &&
currentState != originalState &&
this.windowService.tabsOnTopChangingByUI &&
!this.windowService.changingTabsOnTop)
utils.setTreePref('tabsOnTop.originalState', currentState);
// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=555987
// This should be done when the value of the "ordinal" attribute
// is modified dynamically. So, we don' have to do it before
// the browser window is completely initialized.
TabsOnTop.enabled = !currentState;
if (this.timers['updateTabbarState_TabsOnTop'])
clearTimeout(this.timers['updateTabbarState_TabsOnTop']);
this.timers['updateTabbarState_TabsOnTop'] = setTimeout((function() {
try {
TabsOnTop.enabled = currentState;
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers['updateTabbarState_TabsOnTop'];
}).bind(this), 0);
}
}
}
else {
this.fixed = false; // ensure set to the current orient
this.setTabStripAttribute('height', this.maxTabbarHeight(this.tabbarHeight, b));
}
if (toggleTabsOnTop) {
if (this.position == 'top')
toggleTabsOnTop.removeAttribute('disabled');
else
toggleTabsOnTop.setAttribute('disabled', true);
}
}
if (TabsOnTop && !this.windowService.isPopupWindow) {
let updateTabsOnTop = (function() {
this.windowService.updateTabsOnTop();
}).bind(this);
// TabsOnTop.enabled is always "false" before the browser window is
// completely initialized. So, we have to check it with delay only
// on the Startup.
if (this.initialized)
updateTabsOnTop();
else
setTimeout(updateTabsOnTop, 0);
}
if (this.timers['updateTabbarState'])
clearTimeout(this.timers['updateTabbarState']);
this.timers['updateTabbarState'] = setTimeout((function() {
try {
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE);
this._fireTabbarStateChangedEvent();
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers['updateTabbarState'];
}).bind(this), 0);
var allowToCollapse = utils.getTreePref('allowSubtreeCollapseExpand.'+orient);
if (this.allowSubtreeCollapseExpand != allowToCollapse)
this.collapseExpandAllSubtree(false, false);
this.allowSubtreeCollapseExpand = allowToCollapse;
this.maxTreeLevel = utils.getTreePref('maxTreeLevel.'+orient);
this.setTabbrowserAttribute(this.kALLOW_STACK, this.canStackTabs ? 'true' : null);
this.updateTabsZIndex(this.canStackTabs);
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'
};
var newState = {
fixed : utils.getTreePref('tabbar.fixed.'+orient),
maxTreeLevel : utils.getTreePref('maxTreeLevel.'+orient),
indented : utils.getTreePref('maxTreeLevel.'+orient) != 0,
canCollapse : utils.getTreePref('allowSubtreeCollapseExpand.'+orient)
};
if (oldState.fixed == newState.fixed &&
oldState.maxTreeLevel == newState.maxTreeLevel &&
oldState.indented == newState.indented &&
oldState.canCollapse == newState.canCollapse)
return false;
var data = {
oldState : oldState,
newState : newState
};
/* PUBLIC API */
this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGING, this.mTabBrowser, true, false, data);
// for backward compatibility
this.fireCustomEvent(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'
};
var data = {
state : state
};
/* PUBLIC API */
this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED, this.mTabBrowser, true, false, data);
// for backward compatibility
this.fireCustomEvent(this.kEVENT_TYPE_TABBAR_STATE_CHANGED.replace(/^nsDOM/, ''), this.mTabBrowser, true, false, data);
return true;
},
updateFloatingTabbar : function TSTBrowser_updateFloatingTabbar(aReason)
{
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;
if (DEBUG) {
let humanReadableReason =
(aReason & this.kTABBAR_UPDATE_BY_RESET ? 'reset ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_PREF_CHANGE ? 'prefchange ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_APPEARANCE_CHANGE ? 'appearance-change ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR ? 'showhide ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_TABBAR_RESIZE ? 'tabbar-resize ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_WINDOW_RESIZE ? 'window-resize ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_FULLSCREEN ? 'fullscreen ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE ? 'autohide ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_INITIALIZE ? 'initialize ' : '' ) +
(aReason & this.kTABBAR_UPDATE_BY_TOGGLE_SIDEBAR ? 'toggle-sidebar ' : '' );
dump('TSTBrowser_updateFloatingTabbarInternal: ' + humanReadableReason + '\n');
}
var d = this.document;
// When the tab bar is invisible even if the tab bar is resizing, then
// now I'm trying to expand the tab bar from collapsed state.
// Then the tab bar must be shown.
if (aReason & this.kTABBAR_UPDATE_BY_TABBAR_RESIZE &&
!this.browser.tabContainer.visible)
this.browser.tabContainer.visible = true;
var splitter = this.splitter;
if (splitter.collapsed || splitter.getAttribute('state') != 'collapsed') {
// Synchronize visibility of the tab bar to the placeholder,
// because the tab bar can be shown/hidden by someone
// (Tab Mix Plus, Pale Moon, or some addons).
this._tabStripPlaceHolder.collapsed =
splitter.collapsed =
!this.browser.tabContainer.visible;
}
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(this.autoHide.kSHOWN_BY_ANY_REASON);
let box = this._tabStripPlaceHolder.boxObject;
let root = d.documentElement.boxObject;
let realSize = this.getTabbarPlaceholderSize();
let width = (this.autoHide.expanded && this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ?
// this.maxTabbarWidth(this.tabbarWidth) :
this.maxTabbarWidth(this.autoHide.expandedWidth) :
0
) || realSize.width;
let height = (this.autoHide.expanded && !this.isVertical && (aReason & this.kTABBAR_UPDATE_SYNC_TO_TABBAR) ?
this.maxTabbarHeight(this.tabbarHeight) :
0
) || realSize.height;
let yOffset = pos == 'bottom' ? height - realSize.height : 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 = (strip.width = tabContainerBox.width = width)+'px';
stripStyle.height = (strip.height = tabContainerBox.height = height)+'px';
this._updateFloatingTabbarResizer({
width : width,
realWidth : realSize.width,
height : height,
realHeight : realSize.height
});
this._lastTabbarPlaceholderSize = realSize;
strip.collapsed = tabContainerBox.collapsed = collapsed;
if (statusPanel && utils.getTreePref('repositionStatusPanel')) {
let offsetParentBox = this.base.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 ?
(contentBox.width-5)+'px' : // emulate the margin defined on https://bugzilla.mozilla.org/show_bug.cgi?id=632634
'' ;
statusPanel.__treestyletab__repositioned = true;
}
this.mTabBrowser.tabContainer.setAttribute('context', this.mTabBrowser.tabContextMenu.id);
this._updateChatbar();
}
else {
strip.collapsed = tabContainerBox.collapsed = collapsed;
stripStyle.top = stripStyle.left = stripStyle.right = stripStyle.width = stripStyle.height = '';
if (
statusPanel &&
(
utils.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');
this._resetChatbar();
}
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);
if (!collapsed && aReason & this.kTABBAR_UPDATE_BY_AUTOHIDE)
setTimeout((function() {
if (this.browser) // ignore calling after destroyed...
this.scrollToTab(this.browser.selectedTab);
}).bind(this), 0);
},
getTabbarPlaceholderSize: function TSTBrowser_getTabbarPlaceholderSize()
{
var box = this._tabStripPlaceHolder.boxObject;
return {
width: parseInt(this._tabStripPlaceHolder.getAttribute('width') || box.width),
height: parseInt(this._tabStripPlaceHolder.getAttribute('height') || box.height)
};
},
getExistingTabsCount : function TSTBrowser_getTabsCount()
{
return this.getAllTabs(this.mTabBrowser).length - this.mTabBrowser._removingTabs.length;
},
_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');
box.setAttribute(this.kTAB_STRIP_ELEMENT, true);
splitter = d.createElement('splitter');
splitter.setAttribute(this.kTAB_STRIP_ELEMENT, true);
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' ;
},
get _chatbarBox()
{
var chatbar = this.document.getElementById('pinnedchats');
return chatbar && chatbar.innerbox;
},
_updateChatbar : function TSTBrowser_updateChatbar()
{
var box = this._chatbarBox;
if (!box)
return;
this._resetChatbar();
var tabbarSize = this.getTabbarPlaceholderSize();
var splitterBox = this.splitter.boxObject;
switch (this.position)
{
case 'left':
box.style.marginLeft = (tabbarSize.width + splitterBox.width) + 'px';
break;
case 'right':
box.style.marginRight = (tabbarSize.width + splitterBox.width) + 'px';
break;
case 'bottom':
box.style.marginBottom = (tabbarSize.height + splitterBox.height) + 'px';
break;
}
},
_resetChatbar : function TSTBrowser_resetChatbar()
{
var box = this._chatbarBox;
if (!box)
return;
var style = box.style;
style.marginLeft =
style.marginRight =
style.marginBottom = '';
},
updateTabbarOverflow : function TSTBrowser_updateTabbarOverflow()
{
var d = this.document;
var b = this.mTabBrowser;
b.mTabContainer.removeAttribute('overflow');
var container = d.getAnonymousElementByAttribute(b.mTabContainer, 'class', 'tabs-container') || b.mTabContainer;
if (container != b.mTabContainer)
container.removeAttribute('overflow');
var scrollBox = this.scrollBox;
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);
if (container != b.mTabContainer)
container.setAttribute('overflow', true);
}
else {
b.mTabContainer.removeAttribute('overflow');
if (container != b.mTabContainer)
container.removeAttribute('overflow');
}
},
reinitAllTabs : function TSTBrowser_reinitAllTabs(aSouldUpdateCount)
{
var tabs = this.getAllTabs(this.mTabBrowser);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
this.initTabAttributes(tab);
this.initTabContents(tab);
if (aSouldUpdateCount)
this.updateTabsCount(tab);
}
},
destroy : function TSTBrowser_destroy()
{
this.stopSmoothScroll();
Object.keys(this.timers).forEach(function(key) {
if (!this.timers[key])
return;
let timer = this.timers[key];
if (typeof timer == 'object')
timer = timer.timer;
clearTimeout(timer);
delete this.timers[key];
}, this);
this.autoHide.destroy();
delete this._autoHide;
this._initDNDObservers(); // ensure initialized
this.tabbarDNDObserver.destroy();
delete this._tabbarDNDObserver;
this.panelDNDObserver.destroy();
delete this._panelDNDObserver;
if (this.tooltipManager) {
this.tooltipManager.destroy();
delete this.tooltipManager;
}
if (this.tabStripPlaceHolderBoxObserver) {
this.tabStripPlaceHolderBoxObserver.destroy();
delete this.tabStripPlaceHolderBoxObserver;
}
var w = this.window;
var d = this.document;
var b = this.mTabBrowser;
delete b.tabContainer.treeStyleTab;
var tabs = this.getAllTabs(b);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
this.stopTabIndentAnimation(tab);
this.stopTabCollapseAnimation(tab);
this.destroyTab(tab);
}
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);
w.removeEventListener('tabviewframeinitialized', this, false);
w.removeEventListener(this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END, this, false);
w.removeEventListener('SSWindowStateBusy', 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;
}
Services.obs.removeObserver(this, this.kTOPIC_INDENT_MODIFIED);
Services.obs.removeObserver(this, this.kTOPIC_COLLAPSE_EXPAND_ALL);
Services.obs.removeObserver(this, this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY);
Services.obs.removeObserver(this, 'lightweight-theme-styling-update');
prefs.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 &&
aTab == this.tabsHash[id])
delete this.tabsHash[id];
if (aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave) {
this.document.removeEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true);
this.document.removeEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true);
delete aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave;
}
this.autoHide.notifyStatusToTab(aTab);
aTab.__treestyletab__contentBridge.destroy();
delete aTab.__treestyletab__contentBridge;
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);
tabContainer.removeEventListener('mouseover', this, true);
tabContainer.removeEventListener('mouseout', 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);
},
saveCurrentState : function TSTBrowser_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'
};
for (var i in prefs)
{
if (prefs[i] !== void(0) && utils.getTreePref(i) != prefs[i])
utils.setTreePref(i, prefs[i]);
}
},
/* toolbar customization */
destroyTabbar : function TSTBrowser_destroyTabbar()
{
this._endListenTabbarEvents();
this.tabbarDNDObserver.endListenEvents();
this._lastTreeViewEnabledBeforeDestroyed = this.treeViewEnabled;
this.treeViewEnabled = false;
this.maxTreeLevel = 0;
this._lastTabbarPositionBeforeDestroyed = this.position;
if (this.position != 'top') {
new Promise((function(aResolve, aReject) {
var onRestored = (function() {
this.mTabBrowser.removeEventListener(this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, onRestored, false);
aResolve();
}).bind(this);
this.mTabBrowser.addEventListener(this.kEVENT_TYPE_TABBAR_POSITION_CHANGED, onRestored, false);
}).bind(this))
.then(this.destroyTabbarPostProcess.bind(this));
this.temporaryPosition = 'top';
}
else {
this.destroyTabbarPostProcess();
}
},
destroyTabbarPostProcess : function TSTBrowser_destroyTabbarPostProcess()
{
this.fixed = true;
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.window.setTimeout(function(aSelf) {
aSelf.updateCustomizedTabsToolbar();
}, 100, this);
},
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');
},
reinitTabbar : function TSTBrowser_reinitTabbar()
{
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(this.safeRemovePopup, this);
var position = this._lastTabbarPositionBeforeDestroyed || this.position;
delete this._lastTabbarPositionBeforeDestroyed;
this.initTabbar(position, 'top').then((function() {
this.reinitAllTabs(true);
this.tabbarDNDObserver.startListenEvents();
this.treeViewEnabled = this._lastTreeViewEnabledBeforeDestroyed;
delete this._lastTreeViewEnabledBeforeDestroyed;
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_RESET);
}).bind(this));
},
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 = utils.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 onPopuphidden(aEvent) {
aPopup.removeEventListener(aEvent.type, onPopuphidden, 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 '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)
{
// ignore after destruction
if (!this.window || !this.window.TreeStyleTabService)
return;
var b = this.mTabBrowser;
var value = prefs.getPref(aPrefName);
var tabContainer = b.mTabContainer;
var tabs = this.getAllTabs(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();
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.initTabContents(tabs[i]);
}
return;
case 'extensions.treestyletab.tabbar.invertTabContents':
this.setTabbrowserAttribute(this.kTAB_CONTENTS_INVERTED, value);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.initTabContents(tabs[i]);
}
return;
case 'extensions.treestyletab.tabbar.invertClosebox':
this.setTabbrowserAttribute(this.kCLOSEBOX_INVERTED, value);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.initTabContents(tabs[i]);
}
return;
case 'extensions.treestyletab.tabbar.style':
case 'extensions.treestyletab.tabbar.style.aero':
this.setTabbarStyle(utils.getTreePref('tabbar.style'));
value = utils.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':
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':
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');
this.setTabStripAttribute('width', this.autoHide.placeHolderWidthFromMode);
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_PREF_CHANGE);
}
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':
this.setTabbrowserAttribute(this.kANIMATION_ENABLED,
prefs.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.getTabs(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.faviconized':
return this.positionPinnedTabsWithDelay();
case 'extensions.treestyletab.counter.role.horizontal':
if (!this.isVertical) {
if (this.timers[aPrefName])
clearTimeout(this.timers[aPrefName]);
this.timers[aPrefName] = setTimeout((function() {
try {
this.updateAllTabsCount();
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[aPrefName];
}).bind(this), 0);
}
return;
case 'extensions.treestyletab.counter.role.vertical':
if (this.isVertical) {
if (this.timers[aPrefName])
clearTimeout(this.timers[aPrefName]);
this.timers[aPrefName] = setTimeout((function() {
try {
this.updateAllTabsCount();
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[aPrefName];
}).bind(this), 0);
}
return;
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)
utils.setTreePref('tabbar.style', aStyle = aStyle.replace('default', 'plain'));
}
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 (utils.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 (utils.getTreePref('tabbar.style') == 'sidebar') {
aStyle = 'osx';
}
else if (
prefs.getPref('extensions.informationaltab.thumbnail.enabled') &&
prefs.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);
},
/* DOM Event Handling */
handleEvent : function TSTBrowser_handleEvent(aEvent)
{
switch (aEvent.type)
{
case 'TabOpen':
return this.onTabOpen(aEvent);
case 'TabClose':
return this.onTabClose(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.onTabPinned(aEvent.originalTarget);
case 'TabUnpinned':
return this.onTabUnpinned(aEvent.originalTarget);
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 'popuphiding':
return this.onPopupHiding(aEvent);
case 'mouseover':
if (!this.tooltipManager)
this._initTooltipManager();
if (!this._DNDObserversInitialized)
this._initDNDObservers();
{
let tab = aEvent.target;
if (tab.__treestyletab__twistyHoverTimer)
this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer);
if (this.isEventFiredOnTwisty(aEvent)) {
tab.setAttribute(this.kTWISTY_HOVER, true);
tab.__treestyletab__twistyHoverTimer = this.window.setTimeout(function(aSelf) {
tab.setAttribute(aSelf.kTWISTY_HOVER, true);
delete tab.__treestyletab__twistyHoverTimer;
}, 0, this);
}
}
return;
case 'mouseout':
{
let tab = aEvent.target;
if (tab.__treestyletab__twistyHoverTimer) {
this.window.clearTimeout(tab.__treestyletab__twistyHoverTimer);
delete tab.__treestyletab__twistyHoverTimer;
}
tab.removeAttribute(this.kTWISTY_HOVER);
}
return;
case 'dragover':
if (!this.tooltipManager)
this._initTooltipManager();
if (!this._DNDObserversInitialized)
this._initDNDObservers();
return;
case 'overflow':
case 'underflow':
return this.onTabbarOverflow(aEvent);
case 'resize':
return this.onResize(aEvent);
// toolbar customizing
case 'beforecustomization':
this.toolbarCustomizing = true;
return this.destroyTabbar();
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.reinitTabbar();
case 'customizationchange':
return this.updateCustomizedTabsToolbar();
case 'tabviewframeinitialized':
return this.lastTabViewGroup = this.getTabViewGroupId();
case this.kEVENT_TYPE_PRINT_PREVIEW_ENTERED:
return this.onTreeStyleTabPrintPreviewEntered(aEvent);
case this.kEVENT_TYPE_PRINT_PREVIEW_EXITED:
return this.onTreeStyleTabPrintPreviewExited(aEvent);
case this.kEVENT_TYPE_TAB_FOCUS_SWITCHING_END:
return this.cancelDelayedExpandOnTabSelect();
case 'SSWindowStateBusy':
return this.needRestoreTree = true;
case 'nsDOMMultipleTabHandlerTabsClosing':
if (!this.onTabsClosing(aEvent))
aEvent.preventDefault();
return;
}
},
lastScrollX : -1,
lastScrollY : -1,
restoreLastScrollPosition : function TSTBrowser_restoreLastScrollPosition()
{
if (this.lastScrollX < 0 || this.lastScrollY < 0 || !this.isVisible)
return;
var lastX = this.lastScrollX;
var lastY = this.lastScrollY;
this.clearLastScrollPosition();
// don't restore scroll position if another scroll is already running.
if (this.isSmoothScrolling())
return;
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 = this.lastScrollY = -1;
},
updateLastScrollPosition : function TSTBrowser_updateLastScrollPosition()
{
if (!this.isVertical || !this.isVisible)
return;
var x = {}, y = {};
var scrollBoxObject = this.scrollBoxObject;
if (!scrollBoxObject)
return;
scrollBoxObject.getPosition(x, y);
this.lastScrollX = x.value;
this.lastScrollY = y.value;
},
cancelPerformingAutoScroll : function TSTBrowser_cancelPerformingAutoScroll(aOnlyCancel)
{
this.stopSmoothScroll();
this.clearLastScrollPosition();
if (this.timers['cancelPerformingAutoScroll']) {
clearTimeout(this.timers['cancelPerformingAutoScroll']);
delete this.timers['cancelPerformingAutoScroll'];
}
if (aOnlyCancel)
return;
this.timers['cancelPerformingAutoScroll'] = setTimeout((function() {
delete this.timers['cancelPerformingAutoScroll'];
}).bind(this), 300);
},
shouldCancelEnsureElementIsVisible : function TSTBRowser_shouldCancelEnsureElementIsVisible()
{
return (
this.timers['cancelPerformingAutoScroll'] &&
(new Error()).stack.indexOf('onxblDOMMouseScroll') < 0
);
},
onTabOpen : function TSTBrowser_onTabOpen(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 > -1 && 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 &&
utils.getTreePref('insertNewChildAt') == this.kINSERT_FISRT &&
(this.multipleCount <= 0 || this._addedCountInThisLoop <= 0)
) {
/* 複数の子タブを一気に開く場合、最初に開いたタブだけを
子タブの最初の位置に挿入し、続くタブは「最初の開いたタブ」と
「元々最初の子だったタブ」との間に挿入していく */
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);
let self = this;
this.updateTabCollapsed(tab, false, this.windowService.restoringTree, function() {
/**
* When the system is too slow, the animation can start after
* smooth scrolling is finished. The smooth scrolling should be
* started together with the start of the animation effect.
*/
self.scrollToNewTab(tab);
});
}
else {
this.scrollToNewTab(tab);
}
this.updateInsertionPositionInfo(tab);
if (prefs.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.getTabs(b);
if (tabs.length == 2)
this.updateInvertedTabContentsOrder(tabs);
/**
* gBrowser.addTab() 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,
scrollToNewTab : function TSTBrowser_scrollToNewTab(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
if (this.scrollToNewTabMode > 0)
this.scrollToTab(aTab, this.scrollToNewTabMode < 2);
},
updateInsertionPositionInfo : function TSTBrowser_updateInsertionPositionInfo(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var prev = this.getPreviousSiblingTab(aTab);
if (prev) {
this.setTabValue(aTab, this.kINSERT_AFTER, prev.getAttribute(this.kID));
this.setTabValue(prev, this.kINSERT_BEFORE, aTab.getAttribute(this.kID));
}
var next = this.getNextSiblingTab(aTab);
if (next) {
this.setTabValue(aTab, this.kINSERT_BEFORE, next.getAttribute(this.kID));
this.setTabValue(next, this.kINSERT_AFTER, aTab.getAttribute(this.kID));
}
},
onTabClose : function TSTBrowser_onTabClose(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 backupAttributes = this._collectBackupAttributes(tab);
if (DEBUG)
dump('onTabClose: backupAttributes = '+JSON.stringify(backupAttributes)+'\n');
if (closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN ||
this.isSubtreeCollapsed(tab))
this._closeChildTabs(tab);
this._saveAndUpdateReferenceTabsInfo(tab);
var firstChild = this.getFirstChildTab(tab);
this.detachAllChildren(tab, {
behavior : closeParentBehavior
});
var nextFocusedTab = null;
if (firstChild &&
(closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN ||
closeParentBehavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD))
nextFocusedTab = firstChild;
var toBeClosedTabs = this._collectNeedlessGroupTabs(tab);
var parentTab = this.getParentTab(tab);
if (parentTab) {
if (!nextFocusedTab && tab == this.getLastChildTab(parentTab)) {
if (tab == this.getFirstChildTab(parentTab)) // this is the really last child
nextFocusedTab = parentTab;
else
nextFocusedTab = this.getPreviousSiblingTab(tab);
}
if (nextFocusedTab && toBeClosedTabs.indexOf(nextFocusedTab) > -1)
nextFocusedTab = this.getNextFocusedTab(parentTab);
}
else if (!nextFocusedTab) {
nextFocusedTab = this.getNextFocusedTab(tab);
}
if (nextFocusedTab && toBeClosedTabs.indexOf(nextFocusedTab) > -1)
nextFocusedTab = this.getNextFocusedTab(nextFocusedTab);
if (nextFocusedTab && nextFocusedTab.hasAttribute(this.kREMOVED))
nextFocusedTab = null;
this._reserveCloseRelatedTabs(toBeClosedTabs);
this.detachTab(tab, { dontUpdateIndent : true });
this._restoreTabAttributes(tab, backupAttributes);
if (b.selectedTab == tab)
this._tryMoveFocusFromClosingCurrentTab(nextFocusedTab);
this.updateLastScrollPosition();
this.destroyTab(tab);
if (tab.getAttribute('pinned') == 'true')
this.positionPinnedTabsWithDelay();
if (prefs.getPref('browser.tabs.autoHide'))
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_SHOWHIDE_TABBAR);
if (this.canStackTabs)
this.updateTabsZIndex(true);
},
_collectBackupAttributes : function TSTBrowser_collectBackupAttributes(aTab)
{
var attributes = {};
if (this.hasChildTabs(aTab)) {
attributes[this.kCHILDREN] = this.getTabValue(aTab, this.kCHILDREN);
attributes[this.kSUBTREE_COLLAPSED] = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED);
}
var ancestors = this.getAncestorTabs(aTab);
if (ancestors.length) {
let next = this.getNextSiblingTab(aTab);
ancestors = ancestors.map(function(aAncestor) {
if (!next && (next = this.getNextSiblingTab(aAncestor)))
attributes[this.kINSERT_BEFORE] = next.getAttribute(this.kID);
return aAncestor.getAttribute(this.kID);
}, this);
attributes[this.kANCESTORS] = ancestors.join('|');
}
return attributes;
},
_closeChildTabs : function TSTBrowser_closeChildTabs(aTab)
{
var tabs = this.getDescendantTabs(aTab);
if (!this.fireTabSubtreeClosingEvent(aTab, tabs))
return;
this.markAsClosedSet([aTab].concat(tabs));
tabs.reverse();
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.mTabBrowser.removeTab(tabs[i], { animate : true });
}
this.fireTabSubtreeClosedEvent(this.mTabBrowser, aTab, tabs);
},
_collectNeedlessGroupTabs : function TSTBrowser_collectNeedlessGroupTabs(aTab)
{
var tabs = [];
if (!aTab || !aTab.parentNode)
return tabs;
var parent = this.getParentTab(aTab);
var siblings = this.getSiblingTabs(aTab);
var groupTabs = siblings.filter(function(aTab) {
return this.isTemporaryGroupTab(aTab);
}, this);
var groupTab = (
groupTabs.length == 1 &&
siblings.length == 1 &&
this.hasChildTabs(groupTabs[0])
) ? groupTabs[0] : null ;
if (groupTab)
tabs.push(groupTab);
var shouldCloseParentTab = (
parent &&
this.isTemporaryGroupTab(parent) &&
this.getDescendantTabs(parent).length == 1
);
if (shouldCloseParentTab)
tabs.push(parent);
return tabs;
},
_reserveCloseRelatedTabs : function TSTBrowser_reserveCloseRelatedTabs(aTabs)
{
if (!aTabs.length)
return;
var key = 'onTabClose_'+parseInt(Math.random() * 65000);
this.timers[key] = setTimeout((function() {
try {
aTabs.forEach(function(aTab) {
if (aTab.parentNode)
this.mTabBrowser.removeTab(aTab, { animate : true });
}, this);
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
aTabs = null;
key = null;
}).bind(this), 0);
},
_saveAndUpdateReferenceTabsInfo : function TSTBrowser_saveAndUpdateReferenceTabsInfo(aTab)
{
var prev = this.getPreviousSiblingTab(aTab);
var next = this.getNextSiblingTab(aTab);
if (prev) {
this.setTabValue(aTab, 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(aTab, 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);
}
},
_restoreTabAttributes : function TSTBrowser_restoreTabAttributes(aTab, aAttributes)
{
for (var i in aAttributes)
{
this.setTabValue(aTab, i, aAttributes[i]);
}
},
_tryMoveFocusFromClosingCurrentTab : function TSTBrowser_tryMoveFocusFromClosingCurrentTab(aNextFocusedTab)
{
if (!aNextFocusedTab || aNextFocusedTab.hidden)
return;
var currentTab = this.mTabBrowser.selectedTab;
var d = this.document;
var event = d.createEvent('Events');
event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB, true, true);
var canFocus = currentTab.dispatchEvent(event);
// for backward compatibility
event = d.createEvent('Events');
event.initEvent(this.kEVENT_TYPE_FOCUS_NEXT_TAB.replace(/^nsDOM/, ''), true, true);
canFocus = canFocus && currentTab.dispatchEvent(event);
if (canFocus) {
this._focusChangedByCurrentTabRemove = true;
this.mTabBrowser.selectedTab = aNextFocusedTab;
}
},
onTabsClosing : function TSTBrowser_onTabsClosing(aEvent)
{
var tabs = aEvent.detail && aEvent.detail.tabs ||
aEvent.getData('tabs') // for backward compatibility;
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(this.markAsClosedSet, this);
let key = 'onTabClosing_'+parseInt(Math.random() * 65000);
this.timers[key] = setTimeout((function() {
try {
for (let i = 0, maxi = trees.length; i < maxi; i++)
{
let tabs = trees[i];
this.fireTabSubtreeClosedEvent(b, tabs[0], tabs);
}
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
}).bind(this), 0);
return true;
},
onTabMove : function TSTBrowser_onTabMove(aEvent)
{
var tab = aEvent.originalTarget;
var b = this.mTabBrowser;
tab.__treestyletab__previousPosition = aEvent.detail;
// When the tab was moved before TabOpen event is fired, we have to update manually.
var newlyOpened = !this.isTabInitialized(tab) && this.onTabOpen(null, tab);
var restored = false;
// twisty vanished after the tab is moved!!
this.initTabContents(tab);
// On Firefox 29, 30 and laters, reopened (restored) tab can be
// placed in wrong place, because "TabMove" event fires before
// "SSTabRestoring" event and "kINSERT_BEFORE" information is
// unexpectedly cleared. So now I simulate the "SSTabRestoring"
// event here.
// See: https://github.com/piroor/treestyletab/issues/676#issuecomment-47700158
if (tab.__SS_extdata) {
let storedId = tab.__SS_extdata[this.kID]; // getTabValue() doesn't get the value!
if (storedId && tab.getAttribute(this.kID) != storedId)
restored = this.onTabRestoring(aEvent);
}
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.getAllTabs(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;
if (!restored)
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.getAllTabs(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.detachTab(aTab);
}
}
},
updateChildrenArray : function TSTBrowser_updateChildrenArray(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
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.parentNode || // do nothing for closed tab!
!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)
{
/**
* Note: On this timing, we cannot know that which is the reason of this
* event, by exitting from Panorama or the "Move to Group" command in the
* context menu on tabs. So, we have to do operations with a delay to compare
* last and current group which is updated in the next event loop.
*/
var tab = aEvent.originalTarget;
this.updateInvertedTabContentsOrder(tab);
this.tabVisibilityChangedTabs.push({
tab : tab,
type : aEvent.type
});
if (this.tabVisibilityChangedTimer) {
this.window.clearTimeout(this.tabVisibilityChangedTimer);
this.tabVisibilityChangedTimer = null;
}
this.tabVisibilityChangedTimer = this.window.setTimeout(function(aSelf) {
aSelf.tabVisibilityChangedTimer = null;
var tabs = aSelf.tabVisibilityChangedTabs;
if (!tabs.length)
return;
// restore tree from bottom safely
var restoreTabs = tabs.filter(function(aChanged) {
return aChanged.type == 'TabShow' &&
aChanged.tab.__treestyletab__restoreState == aSelf.RESTORE_STATE_READY_TO_RESTORE;
})
.map(function(aChanged) {
return aChanged.tab;
})
.sort(function(aA, aB) {
return aB._tPos - aA._tPos;
})
.filter(aSelf.restoreOneTab, aSelf);
for (let i = 0, maxi = restoreTabs.length; i < maxi; i++)
{
let tab = restoreTabs[i];
aSelf.updateInsertionPositionInfo(tab);
delete tab.__treestyletab__restoreState;
}
var currentGroupId = aSelf.getTabViewGroupId();
if (aSelf.lastTabViewGroup && currentGroupId != aSelf.lastTabViewGroup) {
// We should clear it first, because updateTreeByTabVisibility() never change visibility of tabs.
aSelf.tabVisibilityChangedTabs = [];
aSelf.updateTreeByTabVisibility(tabs.map(function(aChanged) { return aChanged.tab; }));
}
else {
// For tabs moved by "Move to Group" command in the context menu on tabs
var processedTabs = {};
/**
* subtreeFollowParentAcrossTabGroups() can change visibility of child tabs, so,
* we must not clear tabVisibilityChangedTabs here, and we have to use
* simple "for" loop instead of Array.prototype.forEach.
*/
for (let i = 0; i < aSelf.tabVisibilityChangedTabs.length; i++)
{
let changed = aSelf.tabVisibilityChangedTabs[i];
let tab = changed.tab;
if (aSelf.getAncestorTabs(tab).some(function(aTab) {
return processedTabs[aTab.getAttribute(aSelf.kID)];
}))
continue;
aSelf.subtreeFollowParentAcrossTabGroups(tab);
processedTabs[tab.getAttribute(aSelf.kID)] = true;
}
// now we can clear it!
aSelf.tabVisibilityChangedTabs = [];
}
aSelf.lastTabViewGroup = currentGroupId;
aSelf.checkTabsIndentOverflow();
}, 0, this);
},
tabVisibilityChangedTimer : null,
lastTabViewGroup : null,
updateTreeByTabVisibility : function TSTBrowser_updateTreeByTabVisibility(aChangedTabs)
{
this.internallyTabMovingCount++;
var allTabs = this.getAllTabs(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 (let i = 0, maxi = normalTabs.length; i < maxi; i++)
{
let tab = normalTabs[i];
let parent = this.getParentTab(tab);
let attached = false;
if (parent && (tab.hidden != parent.hidden)) {
let lastNextTab = null;
this.getAncestorTabs(tab).some(function(aAncestor) {
if (aAncestor.hidden == tab.hidden) {
this.attachTabTo(tab, aAncestor, {
dontMove : true,
insertBefore : lastNextTab
});
attached = true;
return true;
}
lastNextTab = this.getNextSiblingTab(aAncestor);
return false;
}, this);
if (!attached) {
this.collapseExpandTab(tab, false, true);
this.detachTab(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--;
},
subtreeFollowParentAcrossTabGroups : function TSTBrowser_subtreeFollowParentAcrossTabGroups(aParent)
{
if (this.tabViewTreeIsMoving)
return;
var id = this.getTabViewGroupId(aParent);
if (!id)
return;
this.tabViewTreeIsMoving = true;
this.internallyTabMovingCount++;
var w = this.window;
var b = this.mTabBrowser;
var lastCount = this.getAllTabs(b).length - 1;
this.detachTab(aParent);
b.moveTabTo(aParent, lastCount);
var descendantTabs = this.getDescendantTabs(aParent);
for (let i = 0, maxi = descendantTabs.length; i < maxi; i++)
{
let tab = descendantTabs[i];
w.TabView.moveTabTo(tab, id);
b.moveTabTo(tab, lastCount);
}
this.internallyTabMovingCount--;
this.tabViewTreeIsMoving = false;
},
tabViewTreeIsMoving : false,
getTabViewGroupId : function TSTBrowser_getTabViewGroupId(aTab)
{
var tab = aTab || this.mTabBrowser.selectedTab;
var item = tab._tabViewTabItem;
if (!item)
return null;
var group = item.parent;
if (!group)
return null;
return group.id;
},
onRestoreTabContentStarted : function TSTBrowser_onRestoreTabContentStarted(aTab)
{
// don't override "false" value (means "already restored")!
if (typeof aTab.linkedBrowser.__treestyletab__toBeRestored == 'undefined')
aTab.linkedBrowser.__treestyletab__toBeRestored = true;
},
onTabRestoring : function TSTBrowser_onTabRestoring(aEvent)
{
this.restoreTree();
var tab = aEvent.originalTarget;
tab.linkedBrowser.__treestyletab__toBeRestored = false;
var restored = this.handleRestoredTab(tab);
/**
* 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.
*/
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.
*/
let key = 'onTabRestoring_'+parseInt(Math.random() * 65000);
this.timers[key] = setTimeout((function() {
try {
/**
* On this timing, the next "SSTabRestoring" was fired.
* Now we can decrement the counter.
*/
this.windowService.restoringCount--;
}
catch(e) {
this.defaultErrorHandler(e);
}
delete this.timers[key];
}).bind(this), 0);
}).bind(this), 0);
if (!tab.selected &&
this.mTabBrowser.currentURI.spec == 'about:sessionrestore' &&
this.mTabBrowser.selectedBrowser.getAttribute('remote') != 'true') {
// because this is a chrome document, E10S is not applied.
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, tab, item.label, this.mTabBrowser.selectedTab);
}
}
return restored;
},
handleRestoredTab : function TSTBrowser_handleRestoredTab(aTab)
{
if (aTab.__treestyletab__restoreState === undefined) {
if (DEBUG)
dump('handleRestoredTab: ' + aTab._tPos + ' is already restored!\n');
return false;
}
if (aTab.__treestyletab__restoreState == this.RESTORE_STATE_READY_TO_RESTORE) {
// this is a hidden tab in the background group, and
// have to be restored by restoreOneTab() on "TabShown" event.
this.deleteTabValue(aTab, this.kCLOSED_SET_ID);
return false;
}
var [id, mayBeDuplicated] = this._restoreTabId(aTab);
var structureRestored = aTab.__treestyletab__restoreState == this.RESTORE_STATE_STRUCTURE_RESTORED;
var children = this.getTabValue(aTab, this.kCHILDREN);
if (
!structureRestored &&
(
!mayBeDuplicated ||
aTab.getAttribute(this.kCHILDREN) != children
)
) {
// failsafe
this.detachAllChildren(aTab, {
dontUpdateIndent : true,
dontAnimate : this.windowService.restoringTree
});
}
var closeSetId = !structureRestored && this._getCloseSetId(aTab, mayBeDuplicated);
// remove temporary cache
var currentId = aTab.getAttribute(this.kID);
if (id != currentId &&
currentId &&
currentId in this.tabsHash &&
this.tabsHash[currentId] == aTab)
delete this.tabsHash[currentId];
this.setTabValue(aTab, this.kID, id);
this.tabsHash[id] = aTab;
var undoing = (
// on e10s window, SSTabRestoring event can fire while undoCloseTab() is executing.
this.browser.__treestyletab__doingUndoCloseTab ||
// on non-e10s window, SSTabRestoring event is fired after the undoCloseTab() is executing.
aTab.__treestyletab__restoredByUndoCloseTab
);
if (structureRestored && !undoing) {
this._fixMissingAttributesFromSessionData(aTab);
}
else {
let isSubtreeCollapsed = this._restoreSubtreeCollapsedState(aTab);
let restoringMultipleTabs = this.windowService.restoringTree;
let options = {
dontExpand : restoringMultipleTabs,
dontUpdateIndent : true,
dontAnimate : restoringMultipleTabs
};
let childTabs = structureRestored ?
[] :
this._restoreChildTabsRelation(aTab, children, mayBeDuplicated, options);
this._restoreTabPositionAndIndent(aTab, childTabs, mayBeDuplicated);
if (closeSetId && undoing)
this.restoreClosedSet(closeSetId, aTab);
if (isSubtreeCollapsed)
this.collapseExpandSubtree(aTab, isSubtreeCollapsed);
}
if (mayBeDuplicated) {
this.clearRedirectionTableWithDelay();
this.clearRedirectbTabRelationsWithDelay(aTab);
}
delete aTab.__treestyletab__restoreState;
return true;
},
_restoreTabId : function TSTBrowser_restoreTabId(aTab)
{
// kID can be overridden by nsSessionStore. kID_NEW is for failsafe.
var currentId = aTab.getAttribute(this.kID_NEW) || aTab.getAttribute(this.kID);
aTab.removeAttribute(this.kID_NEW);
var restoredId = this.getTabValue(aTab, this.kID);
var mayBeDuplicated = false;
aTab.setAttribute(this.kID_RESTORING, restoredId);
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.
*/
restoredId = currentId || this.redirectId(restoredId);
}
aTab.removeAttribute(this.kID_RESTORING);
return [restoredId || currentId, mayBeDuplicated];
},
_getCloseSetId : function TSTBrowser_getCloseSetId(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 detach 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;
},
_fixMissingAttributesFromSessionData : function TSTBrowser_fixMissingAttributesFromSessionData(aTab)
{
/**
* By some reasons (ex. persistTabAttribute()), actual state of
* the tab (attributes) can be lost on SSTabRestoring.
* For failsafe, we must override actual attributes by stored
* values.
*/
var keys = [
this.kINSERT_BEFORE,
this.kINSERT_AFTER
];
for (let i = 0, maxi = keys.length; i < maxi; i++)
{
let key = keys[i];
let tab = this.getTabValue(aTab, key);
if (this.getTabById(tab))
this.setTabValue(aTab, key, tab);
}
let parentId = this.getTabValue(aTab, this.kPARENT);
let parentTab = this.getTabById(parentId);
if (parentTab && parentTab._tPos < aTab._tPos)
this.setTabValue(aTab, this.kPARENT, parentId);
else
this.deleteTabValue(aTab, this.kPARENT);
let ancestors = [aTab].concat(this.getAncestorTabs(aTab));
let children = this.getTabValue(aTab, this.kCHILDREN);
children = children.split('|').filter(function(aChild) {
let tab = this.getTabById(aChild);
return tab && ancestors.indexOf(tab) < 0;
}, this);
this.setTabValue(aTab, this.kCHILDREN, children.join('|'));
let subtreeCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED);
if (subtreeCollapsed != aTab.getAttribute(this.kSUBTREE_COLLAPSED))
this.collapseExpandSubtree(aTab, subtreeCollapsed == 'true', true);
},
_restoreSubtreeCollapsedState : function TSTBrowser_restoreSubtreeCollapsedState(aTab, aCollapsed)
{
var shouldCollapse = utils.getTreePref('collapseExpandSubtree.sessionRestore');
if (aCollapsed === void(0))
aCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true';
var isSubtreeCollapsed = (
this.windowService.restoringTree &&
(
shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_LAST_STATE ?
aCollapsed :
shouldCollapse == this.RESTORED_TREE_COLLAPSED_STATE_COLLAPSED
)
);
this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, isSubtreeCollapsed);
return isSubtreeCollapsed;
},
_restoreChildTabsRelation : function TSTBrowser_restoreChildTabsRelation(aTab, aChildrenList, aMayBeDuplicated, aOptions)
{
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);
for (let i = 0, maxi = aChildrenList.length; i < maxi; i++)
{
let childTab = aChildrenList[i];
if (childTab && (childTab = this.getTabById(childTab))) {
let options = aOptions;
if (options && typeof options == 'function')
options = options(childTab);
this.attachTabTo(childTab, aTab, options);
childTabs.push(childTab);
}
}
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 (DEBUG)
dump('handleRestoredTab: found parent = ' + parent+'\n');
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.kANCESTORS) || this.getTabValue(aTab, this.kPARENT)).split('|');
if (DEBUG)
dump('handleRestoredTab: ancestors = ' + ancestors+'\n');
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.kANCESTORS);
/**
* 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 (DEBUG)
dump('handleRestoredTab: parent = ' + parent+'\n');
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);
if (descendants.length)
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)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING);
if (!restoringChildren)
return;
var children = aTab.getAttribute(this.kCHILDREN);
if (restoringChildren != children) {
var restoringChildrenIDs = restoringChildren.split('|').reverse();
for (let i = 0, maxi = restoringChildrenIDs.length; i < maxi; i++)
{
let child = this.getTabById(restoringChildrenIDs[i]);
if (!child)
continue;
let nextTab = i > 0 ?
this.getTabById(restoringChildrenIDs[i-1]) :
this.getNextSiblingTab(aTab) ;
if (nextTab == this.getNextSiblingTab(child))
continue;
let newPos = -1;
if (nextTab) {
newPos = nextTab._tPos;
if (newPos > child._tPos)
newPos--;
}
if (newPos > -1)
this.moveTabSubtreeTo(child, newPos);
}
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 : {},
clearRedirectionTableWithDelay : function TSTBrowser_clearRedirectionTableWithDelay()
{
if (this._clearRedirectionTableTimer) {
this.window.clearTimeout(this._clearRedirectionTableTimer);
this._clearRedirectionTableTimer = null;
}
this._clearRedirectionTableTimer = this.window.setTimeout(function(aSelf) {
aSelf._redirectionTable = {};
}, 1000, this);
},
_clearRedirectionTableTimer : null,
clearRedirectbTabRelationsWithDelay : function TSTBrowser_clearRedirectbTabRelationsWithDelay(aTab)
{
if (aTab._clearRedirectbTabRelationsTimer) {
this.window.clearTimeout(aTab._clearRedirectbTabRelationsTimer);
aTab._clearRedirectbTabRelationsTimer = null;
}
aTab._clearRedirectbTabRelationsTimer = this.window.setTimeout(function(aSelf) {
aSelf.clearRedirectbTabRelations(aTab);
delete aTab._clearRedirectbTabRelationsTimer;
}, 1500, this);
},
clearRedirectbTabRelations : function TSTBrowser_clearRedirectbTabRelations(aTab)
{
if (!aTab || !aTab.parentNode)
return;
var redirectingIds = Object.keys(this._redirectionTable).map(function(aId) {
return this._redirectionTable[aId];
}, this);
var existingIds = this.getAllTabs(this.mTabBrowser).map(function(aTab) {
return this.getTabValue(aTab, this.kID);
}, this);
var validIds = redirectingIds.concat(existingIds);
validIds = validIds.filter(function(aId) {
return !!aId;
});
var ancestors = this.getTabValue(aTab, this.kANCESTORS);
if (ancestors) {
ancestors = ancestors.split('|');
let actualAncestors = this.getAncestorTabs(aTab).map(function(aTab) {
return aTab.getAttribute(this.kID);
}, this);
ancestors = ancestors.filter(function(aAncestor) {
if (actualAncestors.indexOf(aAncestor) < 0)
return false;
else
return validIds.indexOf(aAncestor) > -1;
}, this);
if (ancestors.length)
this.setTabValue(aTab, this.kANCESTORS, ancestors.join('|'));
else
this.deleteTabValue(aTab, this.kANCESTORS);
}
var children = this.getTabValue(aTab, this.kCHILDREN);
if (children) {
children = children.split('|');
children = children.filter(function(aChild) {
if (this.getParentTab(this.getTabById(aChild)) != aTab)
return false;
else
return validIds.indexOf(aChild) > -1;
}, this);
if (children.length)
this.setTabValue(aTab, this.kCHILDREN, children.join('|'));
else
this.deleteTabValue(aTab, this.kCHILDREN);
}
var restoringChildren = aTab.getAttribute(this.kCHILDREN_RESTORING);
if (restoringChildren) {
restoringChildren = restoringChildren.split('|');
restoringChildren = restoringChildren.filter(function(aChild) {
return validIds.indexOf(aChild) > -1;
}, this);
if (restoringChildren.length)
aTab.setAttribute(this.kCHILDREN_RESTORING, restoringChildren.join('|'));
else
aTab.removeAttribute(this.kCHILDREN_RESTORING);
}
},
restoreClosedSet : function TSTBrowser_restoreClosedSet(aId, aRestoredTab)
{
var behavior = this.undoCloseTabSetBehavior;
if (
this.useTMPSessionAPI ||
this._restoringClosedSet ||
!(behavior & this.kUNDO_CLOSE_SET || behavior & this.kUNDO_ASK)
)
return;
var indexes = [];
var items = utils.evalInSandbox('('+this.SessionStore.getClosedTabData(this.window)+')');
for (let i = 0, maxi = items.length; i < maxi; i++)
{
let item = items[i];
if (item.state.extData &&
item.state.extData[this.kCLOSED_SET_ID] &&
item.state.extData[this.kCLOSED_SET_ID] == aId)
indexes.push(i);
}
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('SSTabRestoring', function onSSTabRestoring(aEvent) {
aRestoredTab.removeEventListener(aEvent.type, onSSTabRestoring, false);
self.askUndoCloseTabSetBehavior(aRestoredTab, indexes.length)
.then(function(aBehavior) {
if (aBehavior & self.kUNDO_CLOSE_SET)
self.doRestoreClosedSet(aRestoredTab, indexes);
})
.catch(function(aError) {
Components.utils.reportError(aError);
});
}, 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.windowService.restoringTree = true;
var offset = 0;
for (let i = 0, maxi = aIndexes.length; i < maxi; i++)
{
this.window.undoCloseTab(aIndexes[i] - (offset++));
}
var nextFocusedTab = aRestoredTab;
this.window.setTimeout((function() {
this.windowService.restoringTree = false;
this.mTabBrowser.selectedTab = nextFocusedTab;
this._restoringClosedSet = false;
}).bind(this), 0);
},
_restoringClosedSet : false,
onTabRestored : function TSTBrowser_onTabRestored(aEvent)
{
delete aEvent.originalTarget.__treestyletab__restoredByUndoCloseTab;
},
onTabPinned : function TSTBrowser_onTabPinned(aTab)
{
var parentTab = this.getParentTab(aTab);
this.collapseExpandSubtree(aTab, false);
/**
* 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 (!parentTab) {
/**
* Universal but dangerous logic. "__treestyletab__previousPosition"
* can be broken by multiple movings.
*/
let b = this.browser;
this.internallyTabMovingCount++;
let children = this.getDescendantTabs(aTab).reverse();
for (let i = 0, maxi = children.length; i < maxi; i++)
{
let childTab = children[i];
if (childTab.__treestyletab__previousPosition > childTab._tPos)
b.moveTabTo(childTab, childTab.__treestyletab__previousPosition);
}
this.internallyTabMovingCount--;
}
else {
/**
* Safer logic. This cannot be available for "root" tabs because
* their children (already moved) have no way to know the anchor
* position (the next sibling of the pinned tab itself).
*/
let b = this.browser;
this.internallyTabMovingCount++;
let children = this.getChildTabs(aTab).reverse();
for (let i = 0, maxi = children.length; i < maxi; i++)
{
let childTab = children[i];
if (childTab._tPos < parentTab._tPos)
b.moveTabTo(childTab, parentTab._tPos);
}
this.internallyTabMovingCount--;
}
this.detachAllChildren(aTab, {
behavior : this.getCloseParentBehaviorForTab(
aTab,
this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD
)
});
this.detachTab(aTab);
this.collapseExpandTab(aTab, false);
if (this.isVertical)
this.positionPinnedTabsWithDelay();
},
onTabUnpinned : function TSTBrowser_onTabUnpinned(aTab)
{
var style = aTab.style;
style.marginLeft = style.marginRight = style.marginTop = '';
this.updateInvertedTabContentsOrder(aTab);
if (this.isVertical)
this.positionPinnedTabsWithDelay();
},
onTabSelect : function TSTBrowser_onTabSelect(aEvent)
{
var b = this.mTabBrowser;
var tab = b.selectedTab
this.cancelDelayedExpandOnTabSelect();
if (
/**
* <tabbrowser>.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;
var shouldCollapseExpandNow = utils.getTreePref('autoCollapseExpandSubtreeOnSelect');
var newActiveTabOptions = {
canCollapseTree : shouldCollapseExpandNow,
canExpandTree : shouldCollapseExpandNow
};
if (this.isCollapsed(tab)) {
if (utils.getTreePref('autoExpandSubtreeOnCollapsedChildFocused')) {
this.getAncestorTabs(tab).forEach(function(aAncestor) {
this.collapseExpandSubtree(aAncestor, false);
}, this);
this.handleNewActiveTab(tab, newActiveTabOptions);
}
else {
b.selectedTab = this.getRootTab(tab);
}
}
else if (
(
/**
* Focus movings by arrow keys should not be handled on TabSelect,
* because they are already handled by handleAdvanceSelectedTab().
*/
this.windowService.arrowKeyEventOnTab &&
this.windowService.arrowKeyEventOnTab.advanceFocus
) ||
(
/**
* Focus movings by closing of the old current tab should be handled
* only when it is activated by user preference expressly.
*/
this._focusChangedByCurrentTabRemove &&
!utils.getTreePref('autoCollapseExpandSubtreeOnSelect.onCurrentTabRemove')
)
) {
// do nothing!
}
else if (this.hasChildTabs(tab) && this.isSubtreeCollapsed(tab)) {
if (
this._focusChangedByShortcut &&
this.windowService.accelKeyPressed
) {
if (utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut')) {
newActiveTabOptions.canExpandTree = true;
newActiveTabOptions.canCollapseTree = (
newActiveTabOptions.canCollapseTree &&
utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.collapseOthers')
);
let delay = utils.getTreePref('autoExpandSubtreeOnSelect.whileFocusMovingByShortcut.delay');
if (delay > 0) {
this._autoExpandOnTabSelectTimer = this.window.setTimeout(function(aSelf) {
if (tab && tab.parentNode)
aSelf.handleNewActiveTab(tab, newActiveTabOptions);
}, delay, this);
}
else {
this.handleNewActiveTab(tab, newActiveTabOptions);
}
}
else if (newActiveTabOptions.canExpandTree) {
this.windowService.expandTreeAfterKeyReleased(tab);
}
}
else {
this.handleNewActiveTab(tab, newActiveTabOptions);
}
}
this._focusChangedByCurrentTabRemove = false;
this._focusChangedByShortcut = false;
this.updateInvertedTabContentsOrder();
if (!this.isTabInViewport(tab)) {
this.scrollToTab(tab);
aEvent.stopPropagation();
}
},
cancelDelayedExpandOnTabSelect : function TSTBrowser_cancelDelayedExpandOnTabSelect() {
if (this._autoExpandOnTabSelectTimer) {
this.window.clearTimeout(this._autoExpandOnTabSelectTimer);
this._autoExpandOnTabSelectTimer = null;
}
},
handleNewActiveTab : function TSTBrowser_handleNewActiveTab(aTab, aOptions)
{
if (this.doingCollapseExpand || !aTab || !aTab.parentNode)
return;
aOptions = aOptions || {};
if (this._handleNewActiveTabTimer)
this.window.clearTimeout(this._handleNewActiveTabTimer);
/**
* First, we wait until all event listeners for the TabSelect
* event were processed.
*/
this._handleNewActiveTabTimer = this.window.setTimeout(function(aSelf) {
aSelf.window.clearTimeout(aSelf._handleNewActiveTabTimer);
aSelf._handleNewActiveTabTimer = null;
if (aOptions.canExpandTree) {
if (aOptions.canCollapseTree &&
utils.getTreePref('autoExpand.intelligently'))
aSelf.collapseExpandTreesIntelligentlyFor(aTab);
else
aSelf.collapseExpandSubtree(aTab, false);
}
}, 0, this);
},
_handleNewActiveTabTimer : null,
handleAdvanceSelectedTab : function TSTBrowser_handleAdvanceSelectedTab(aDir, aWrap)
{
this._focusChangedByShortcut = this.windowService.accelKeyPressed;
if (!this.canCollapseSubtree(this.mTabBrowser.selectedTab) ||
utils.getTreePref('focusMode') != this.kFOCUS_VISIBLE)
return false;
if (this.processArrowKeyOnFocusAdvanced())
return true;
return this.advanceSelectedTab(aDir, aWrap);
},
processArrowKeyOnFocusAdvanced : function TSTBrowser_processArrowKeyOnFocusAdvanced()
{
var event = this.windowService.arrowKeyEventOnTab;
if (!event)
return false;
if (
event.altKey ||
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
(this.isVertical ? (event.up || event.down) : (event.left || event.right))
) {
event.advanceFocus = true;
return false;
}
var collapse, expand;
switch (this.position)
{
case 'top':
collapse = event.up;
expand = event.down;
break;
case 'bottom':
collapse = event.down;
expand = event.up;
break;
case 'left':
collapse = event.left;
expand = event.right;
break;
case 'right':
if (utils.getTreePref('tabbar.invertTab')) {
collapse = event.right;
expand = event.left;
}
else {
collapse = event.left;
expand = event.right;
}
break;
}
var tab = this.mTabBrowser.selectedTab;
var collapsed = this.isSubtreeCollapsed(tab);
if (this.hasChildTabs(tab) && (collapsed ? expand : collapse )) {
event.collapse = collapse;
event.expand = expand;
this.collapseExpandSubtree(tab, !collapsed);
return true;
}
var nextSelected;
if (expand)
nextSelected = this.getFirstChildTab(tab);
else if (collapse)
nextSelected = this.getParentTab(tab);
if (nextSelected) {
event.advanceFocus = true;
this.mTabBrowser.selectedTab = nextSelected;
return true;
}
return true;
},
advanceSelectedTab : function TSTBrowser_advanceSelectedTab(aDir, aWrap)
{
var tab = this.mTabBrowser.selectedTab;
var tabbar = this.mTabBrowser.mTabContainer;
var nextTab = (aDir < 0) ? this.getPreviousVisibleTab(tab) : this.getNextVisibleTab(tab) ;
if (!nextTab && aWrap) {
let tabs = tabbar.querySelectorAll('tab:not(['+this.kCOLLAPSED+'="true"])');
nextTab = tabs[aDir < 0 ? tabs.length-1 : 0 ];
}
if (nextTab && nextTab != tab)
tabbar._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.manualCollapseExpandSubtree(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;
}
},
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) &&
utils.getTreePref('collapseExpandSubtree.dblclick')) {
this.manualCollapseExpandSubtree(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 (this.isEventFiredOnScrollbar(aEvent))
this.cancelPerformingAutoScroll();
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) {
/**
* By horizontal overflow/underflow, Firefox can wrongly
* removes "overflow" attribute for vertical tab bar.
* We have to override the result.
*/
this.updateTabbarOverflow();
}
else {
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 this.window.Window)
)
return;
var resizedTopFrame = aEvent.originalTarget.top;
// for E10S tabs, isContentResize is always false.
var isInProcessTab = this.mTabBrowser.selectedBrowser.getAttribute('remote') != 'true';
var isContentResize = isInProcessTab && resizedTopFrame == this.mTabBrowser.contentWindow;
var isChromeResize = resizedTopFrame == this.window;
if (isChromeResize && aEvent.originalTarget != resizedTopFrame) {
// ignore resizing of sub frames in "position:fixed" box
let target = aEvent.target;
try {
let node = target.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
let root = node.ownerDocument.documentElement;
while (node && node != root) {
if (node.boxObject && !node.boxObject.parentBox) {
isChromeResize = false;
break;
}
node = node.parentNode;
}
}
catch(e) {
}
}
// Ignore events when a background tab raises to the foreground.
if (isContentResize && this._lastTabbarPlaceholderSize) {
let newSize = this.getTabbarPlaceholderSize();
isContentResize =
newSize.width != this._lastTabbarPlaceholderSize.width ||
newSize.height != this._lastTabbarPlaceholderSize.height;
}
if (isContentResize || isChromeResize) {
this.updateFloatingTabbar(this.kTABBAR_UPDATE_BY_WINDOW_RESIZE);
this.updateInvertedTabContentsOrder(true);
this.mTabBrowser.mTabContainer.adjustTabstrip();
}
},
onPopupShowing : function TSTBrowser_onPopupShowing(aEvent)
{
if (aEvent.target == aEvent.currentTarget)
this.initTabContextMenu(aEvent);
},
initTabContextMenu : function TSTBrowser_initTabContextMenu(aEvent)
{
var b = this.mTabBrowser;
var sep, items = {};
var ids = [
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
];
for (let i = 0, maxi = ids.length; i < maxi; i++)
{
let id = ids[i];
let item = aEvent.currentTarget.querySelector('*[id^="'+id+'"]');
if (!item)
continue;
items[id] = item;
if (utils.getTreePref('show.'+id))
item.removeAttribute('hidden');
else
item.setAttribute('hidden', true);
switch (id)
{
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]);
continue;
default:
continue;
}
}
// collapse/expand all
sep = aEvent.currentTarget.querySelector('menuseparator[id^="'+this.kMENUITEM_COLLAPSEEXPAND_SEPARATOR+'"]');
let collapseItem = items[this.kMENUITEM_COLLAPSE];
let expandItem = items[this.kMENUITEM_EXPAND];
if (this.canCollapseSubtree(b) &&
b.mTabContainer.querySelector('tab['+this.kCHILDREN+']')) {
if (collapseItem) {
if (b.mTabContainer.querySelector('tab['+this.kCHILDREN+']:not(['+this.kSUBTREE_COLLAPSED+'="true"])'))
collapseItem.removeAttribute('disabled');
else
collapseItem.setAttribute('disabled', true);
}
if (expandItem) {
if (b.mTabContainer.querySelector('tab['+this.kCHILDREN+']['+this.kSUBTREE_COLLAPSED+'="true"]'))
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 = aEvent.currentTarget.querySelector('menuseparator[id^="'+this.kMENUITEM_AUTOHIDE_SEPARATOR+'"]');
if (sep) {
if (
(autohide && autohide.getAttribute('hidden') != 'true') ||
(fixed && fixed.getAttribute('hidden') != 'true')
) {
sep.removeAttribute('hidden');
}
else {
sep.setAttribute('hidden', true);
}
}
let closeTabsToEnd = aEvent.currentTarget.querySelector('*[id^="'+this.kMENUITEM_CLOSE_TABS_TO_END+'"]');
if (closeTabsToEnd) { // https://bugzilla.mozilla.org/show_bug.cgi?id=866880
let label, accesskey;
if (this.isVertical) {
label = utils.treeBundle.getString('closeTabsToTheEnd_vertical_label');
accesskey = utils.treeBundle.getString('closeTabsToTheEnd_vertical_accesskey');
}
else {
label = this._closeTabsToEnd_horizontalLabel;
accesskey = this._closeTabsToEnd_horizontalAccesskey;
}
closeTabsToEnd.setAttribute('label', label);
closeTabsToEnd.setAttribute('accesskey', accesskey);
}
},
onTabsOnTopSyncCommand : function TSTBrowser_onTabsOnTopSyncCommand(aEnabled)
{
if (
this.windowService.tabsOnTopChangingByUI ||
!aEnabled ||
this.position != 'top' ||
this.fixed ||
this.windowService.isPopupWindow
)
return;
this.windowService.tabsOnTopChangingByUI = true;
if (this.timers['onTabsOnTopSyncCommand'])
clearTimeout(this.timers['onTabsOnTopSyncCommand']);
this.timers['onTabsOnTopSyncCommand'] = setTimeout((function() {
Deferred.resolve()
.then((function() {
this.windowService.toggleFixed(this.mTabBrowser);
return wait(0);
}).bind(this))
.then((function() {
if (this.window.TabsOnTop.enabled != aEnabled)
this.window.TabsOnTop.enabled = aEnabled;
}).bind(this))
.catch(this.defaultErrorHandler.bind(this))
.then((function() {
this.windowService.tabsOnTopChangingByUI = false;
delete this.timers['onTabsOnTopSyncCommand'];
}).bind(this));
}).bind(this), 0);
},
onBeforeFullScreenToggle : function TSTBrowser_onBeforeFullScreenToggle()
{
if (this.position != 'top') {
var isEnteringFullScreenMode = !this.window.fullScreen;
// entering to the DOM-fullscreen (ex. YouTube Player)
if (this.document.mozFullScreen && isEnteringFullScreenMode) {
this.setTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED, true);
}
else {
if (this.document.documentElement.getAttribute(this.kDOM_FULLSCREEN_ACTIVATED) != 'true') {
if (isEnteringFullScreenMode)
this.autoHide.startForFullScreen();
else
this.autoHide.endForFullScreen();
}
this.removeTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED);
}
}
},
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, aDetachAllChildren)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
if (aDetachAllChildren)
this.detachAllChildren(aTab, {
dontUpdateIndent : true,
dontAnimate : true
});
this.detachTab(aTab, {
dontUpdateIndent : true,
dontAnimate : true
});
this.resetTabState(aTab);
this.updateTabsIndent([aTab], undefined, true);
},
resetTabState : function TSTBrowser_resetTabState(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
aTab.removeAttribute(this.kID);
aTab.removeAttribute(this.kID_RESTORING);
aTab.removeAttribute(this.kPARENT);
aTab.removeAttribute(this.kCHILDREN);
aTab.removeAttribute(this.kCHILDREN_RESTORING);
aTab.removeAttribute(this.kSUBTREE_COLLAPSED);
aTab.removeAttribute(this.kSUBTREE_EXPANDED_MANUALLY);
aTab.removeAttribute(this.kCOLLAPSED);
aTab.removeAttribute(this.kNEST);
this.updateTabCollapsed(aTab, false, true);
},
resetAllTabs : function TSTBrowser_resetAllTabs(aDetachAllChildren)
{
var tabs = this.getAllTabs(this.mTabBrowser);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.resetTab(tabs[i], aDetachAllChildren);
}
},
resetTabbarSize : function TSTBrowser_resetTabbarSize()
{
if (this.isVertical) {
//this.tabbarWidth = utils.getTreePref('tabbar.width.default');
this.autoHide.resetWidth();
}
else {
this.tabbarHeight = utils.getTreePref('tabbar.height.default');
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)
{
var newValue = !!aValue;
if (newValue == this._treeViewEnabled)
return aValue;
this._treeViewEnabled = newValue;
if (this._treeViewEnabled) {
if (this._lastAllowSubtreeCollapseExpand)
this.allowSubtreeCollapseExpand = true;
delete this._lastAllowSubtreeCollapseExpand;
let tabs = this.getAllTabs(this.browser);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
if (tab._TSTLastSubtreeCollapsed)
this.collapseExpandSubtree(tab, true, true);
if (tab._TSTLastSubtreeExpandedManually)
this.setTabValue(tab, this.kSUBTREE_EXPANDED_MANUALLY, true);
delete tab._TSTLastSubtreeCollapsed;
delete tab._TSTLastSubtreeExpandedManually;
this.updateTabIndent(tab, 0, true);
}
this.updateTabsIndent(this.rootTabs, undefined, true);
}
else {
let tabs = this.getAllTabs(this.browser);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
this.updateTabIndent(tab, 0, true);
tab._TSTLastSubtreeCollapsed = this.isSubtreeCollapsed(tab);
tab._TSTLastSubtreeExpandedManually = this.getTabValue(tab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true';
this.collapseExpandSubtree(tab, false, true);
}
this._lastAllowSubtreeCollapseExpand = this.allowSubtreeCollapseExpand;
this.allowSubtreeCollapseExpand = false;
}
return aValue;
},
// _treeViewEnabled : true,
/* attach/detach */
attachTabTo : function TSTBrowser_attachTabTo(aChild, aParent, aInfo) /* PUBLIC API */
{
if (!aChild.parentNode || (aParent && !aParent.parentNode)) // do nothing for closed tab!
return;
aInfo = aInfo || {};
var newAncestors = [];
if (aParent) {
newAncestors = [aParent].concat(this.getAncestorTabs(aParent));
if (this.maxTreeLevelPhisical && this.maxTreeLevel > -1) {
let level = parseInt(aParent.getAttribute(this.kNEST) || 0) + 1;
newAncestors.some(function(aAncestor) {
if (level <= this.maxTreeLevel)
return true;
level--;
return false;
}, this);
}
}
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;
}
// avoid recursive tree
var ancestors = [aParent].concat(this.getAncestorTabs(aChild));
if (ancestors.indexOf(aChild) > -1)
return;
currentParent = ancestors[ancestors.length-1];
var 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.detachTab(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.forceExpand) {
this.collapseExpandSubtree(aParent, false, aInfo.dontAnimate);
}
else if (!aInfo.dontExpand) {
if (utils.getTreePref('autoCollapseExpandSubtreeOnAttach') &&
this.shouldTabAutoExpanded(aParent))
this.collapseExpandTreesIntelligentlyFor(aParent);
if (utils.getTreePref('autoCollapseExpandSubtreeOnSelect')) {
newAncestors.forEach(function(aAncestor) {
if (this.shouldTabAutoExpanded(aAncestor))
this.collapseExpandSubtree(aAncestor, false, aInfo.dontAnimate);
}, this);
}
else if (this.shouldTabAutoExpanded(aParent)) {
if (utils.getTreePref('autoExpandSubtreeOnAppendChild')) {
newAncestors.forEach(function(aAncestor) {
if (this.shouldTabAutoExpanded(aAncestor))
this.collapseExpandSubtree(aAncestor, false, aInfo.dontAnimate);
}, this);
}
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.fireAttachedEvent(aChild, aParent);
},
fireAttachedEvent : function TSTBrowser_fireAttachedEvent(aChild, aParent)
{
var data = {
parentTab : aParent
};
/* PUBLIC API */
this.fireCustomEvent(this.kEVENT_TYPE_ATTACHED, aChild, true, false, data);
// for backward compatibility
this.fireCustomEvent(this.kEVENT_TYPE_ATTACHED.replace(/^nsDOM/, ''), aChild, true, false, data);
},
shouldTabAutoExpanded : function TSTBrowser_shouldTabAutoExpanded(aTab)
{
return this.hasChildTabs(aTab) &&
this.isSubtreeCollapsed(aTab);
},
detachTab : function TSTBrowser_detachTab(aChild, aInfo) /* PUBLIC API */
{
if (!aChild || !aChild.parentNode)
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();
}
var data = {
parentTab : parentTab
};
/* PUBLIC API */
this.fireCustomEvent(this.kEVENT_TYPE_DETACHED, aChild, true, false, data);
// for backward compatibility
this.fireCustomEvent(this.kEVENT_TYPE_DETACHED.replace(/^nsDOM/, ''), aChild, true, false, data);
if (this.isTemporaryGroupTab(parentTab) && !this.hasChildTabs(parentTab)) {
this.window.setTimeout(function(aTabBrowser) {
if (parentTab.parentNode)
aTabBrowser.removeTab(parentTab, { animate : true });
parentTab = null;
}, 0, this.getTabBrowserFromChild(parentTab));
}
},
partTab : function TSTBrowser_partTab(aChild, aInfo) /* PUBLIC API, for backward compatibility */
{
return this.detachTab(aChild, aInfo);
},
detachAllChildren : function TSTBrowser_detachAllChildren(aTab, aInfo)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var children = this.getChildTabs(aTab);
if (!children.length)
return;
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);
if (
this.isGroupTab(aTab) &&
this.getTabs(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 &&
!utils.getTreePref('closeParentBehavior.moveDetachedTabsToBottom')) {
insertBefore = this.getNextSiblingTab(this.getRootTab(aTab));
}
for (let i = 0, maxi = children.length; i < maxi; i++)
{
let tab = children[i];
if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_DETACH_ALL_CHILDREN) {
this.detachTab(tab, aInfo);
this.moveTabSubtreeTo(tab, insertBefore ? insertBefore._tPos - 1 : this.getLastTab(b)._tPos );
}
else if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD) {
this.detachTab(tab, aInfo);
if (i == 0) {
if (parentTab) {
this.attachTabTo(tab, parentTab, inherit(aInfo, {
dontExpand : true,
dontMove : true
}));
}
this.collapseExpandSubtree(tab, false);
this.deleteTabValue(tab, this.kSUBTREE_COLLAPSED);
}
else {
this.attachTabTo(tab, children[0], inherit(aInfo, {
dontExpand : true,
dontMove : true
}));
}
}
else if (aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_ALL_CHILDREN && parentTab) {
this.attachTabTo(tab, parentTab, inherit(aInfo, {
dontExpand : true,
dontMove : true
}));
}
else { // aInfo.behavior == this.kCLOSE_PARENT_BEHAVIOR_SIMPLY_DETACH_ALL_CHILDREN
this.detachTab(tab, aInfo);
}
}
},
partAllChildren : function TSTBrowser_partAllChildren(aTab, aInfo) /* for backward compatibility */
{
return this.detachAllChildren(aTab, aInfo);
},
detachTabs : function TSTBrowser_detachTabs(aTabs)
{
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
let tab = aTabs[i];
if (aTabs.indexOf(this.getParentTab(tab)) > -1)
continue;
this.detachAllChildren(tab, {
behavior : this.getCloseParentBehaviorForTab(
tab,
this.kCLOSE_PARENT_BEHAVIOR_PROMOTE_FIRST_CHILD
)
});
}
},
partTabs : function TSTBrowser_partTabs(aTabs) /* for backward compatibility */
{
return this.detachTabs(aTabs);
},
getCloseParentBehaviorForTab : function TSTBrowser_getCloseParentBehaviorForTab(aTab, aDefaultBehavior)
{
var closeParentBehavior = utils.getTreePref('closeParentBehavior');
var closeRootBehavior = utils.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);
}
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
let tab = aTabs[i];
if (!tab.parentNode)
continue; // ignore removed tabs
this.updateTabIndent(tab, indent, aJustNow);
tab.setAttribute(this.kNEST, aLevel);
this.updateCanCollapseSubtree(tab, aLevel);
this.updateTabsIndent(this.getChildTabs(tab), aLevel+1, aJustNow);
}
},
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 = [];
for (let i = 0, maxi = aSelf.updateTabsIndentWithDelayTabs.length; i < maxi; i++)
{
let tab = aSelf.updateTabsIndentWithDelayTabs[i];
if (tabs.indexOf(tab) < 0 && tab.parentNode)
tabs.push(tab);
}
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)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
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;';
let boxes = this.document.getAnonymousNodes(aTab);
for (let i = 0, box = boxes.length; i < maxi; i++)
{
let box = boxes[i];
if (box.nodeType != Node.ELEMENT_NODE)
continue;
box.setAttribute(
'style',
box.getAttribute('style')
.replace(/(-moz-)?border-(top|bottom)(-[^:]*)?.*:[^;]+;?/g, '') +
'; border-'+this.indentTarget+': solid transparent '+aIndent+'px !important;'+colors
);
}
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) {
delete aTab.__treestyletab__updateTabIndentTask;
if (!self.isDestroying)
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) {
if (self.isDestroying)
return true;
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)
{
if (!aTab.parentNode)
return; // do nothing for closed tab!
this.animationManager.removeTask(
aTab.__treestyletab__updateTabIndentTask
);
delete 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(aDelay)
{
this.cancelCheckTabsIndentOverflow();
this.checkTabsIndentOverflowTimer = this.window.setTimeout(function(aSelf) {
aSelf.checkTabsIndentOverflowTimer = null;
aSelf.checkTabsIndentOverflowCallback();
}, aDelay || 100, this);
},
cancelCheckTabsIndentOverflow : function TSTBrowser_cancelCheckTabsIndentOverflow()
{
if (this.checkTabsIndentOverflowTimer) {
this.window.clearTimeout(this.checkTabsIndentOverflowTimer);
this.checkTabsIndentOverflowTimer = null;
}
},
checkTabsIndentOverflowTimer : null,
checkTabsIndentOverflowCallback : function TSTBrowser_checkTabsIndentOverflowCallback()
{
if (!utils.getTreePref('indent.autoShrink')) {
this.indent = -1;
return;
}
var b = this.mTabBrowser;
var tabbarSize = b.mTabContainer.boxObject[this.invertedSizeProp];
if (!tabbarSize) // don't update indent for collapsed tab bar
return;
var tabs = Array.slice(b.mTabContainer.querySelectorAll(
'tab['+this.kNEST+']:not(['+this.kNEST+'="0"]):not(['+this.kNEST+'=""])'+
':not(['+this.kCOLLAPSED+'="true"])'+
':not([hidden="true"])'+
':not([collapsed="true"])'
));
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],
tabbarSize
);
var isVertical = this.isVertical;
if (!isVertical) {
if (this._horizontalTabMaxIndentBase)
maxIndentBase = this._horizontalTabMaxIndentBase;
else
this._horizontalTabMaxIndentBase = maxIndentBase;
}
var maxIndent = maxIndentBase * (isVertical ? 0.33 : 0.5 );
var indentMin = utils.getTreePref(isVertical ? 'indent.min.vertical' : 'indent.min.horizontal');
var indentUnit = Math.max(Math.floor(maxIndent / nest), indentMin);
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 (!aTab.parentNode) // do nothing for closed tab!
return;
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)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var count = this.document.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER);
if (count) {
let value = this.getDescendantTabs(aTab).length;
if (this.counterRole == this.kCOUNTER_ROLE_ALL_TABS)
value += 1;
count.setAttribute('value', value);
}
if (!aDontUpdateAncestor) {
let parent = this.getParentTab(aTab);
if (parent)
this.updateTabsCount(parent);
}
},
updateAllTabsCount : function TSTBrowser_updateAllTabsCount()
{
var tabs = this.rootTabs;
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
this.updateTabsCount(tab, this);
}
},
promoteTooDeepLevelTabs : function TSTBrowser_promoteTooDeepLevelTabs(aParent)
{
if (this.maxTreeLevel < 0 || !this.maxTreeLevelPhisical)
return;
var tabs = aParent ? this.getDescendantTabs(aParent) : this.getAllTabs(this.mTabBrowser) ;
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let level = parseInt(tab.getAttribute(this.kNEST) || 0);
if (level <= this.maxTreeLevel)
continue;
let parent = this.getParentTab(tab);
let newParent = this.getParentTab(parent);
if (this.maxTreeLevel == 0 || !newParent) {
this.detachTab(aTab);
}
else {
let nextSibling = this.getNextTab(tab);
this.attachTabTo(tab, newParent, {
dontMove : true,
insertBefore : nextSibling
});
}
}
},
/* move */
moveTabSubtreeTo : function TSTBrowser_moveTabSubtreeTo(aTab, aIndex)
{
if (!aTab || !aTab.parentNode)
return;
var b = this.mTabBrowser;
this.subTreeMovingCount++;
this.internallyTabMovingCount++;
b.moveTabTo(aTab, aIndex);
this.internallyTabMovingCount--;
this.subTreeChildrenMovingCount++;
this.internallyTabMovingCount++;
var descendantTabs = this.getDescendantTabs(aTab);
for (let i = 0, maxi = descendantTabs.length; i < maxi; i++)
{
let descendantTab = descendantTabs[i];
b.moveTabTo(descendantTab, aTab._tPos + i + (aTab._tPos < descendantTab._tPos ? 1 : 0 ));
}
this.internallyTabMovingCount--;
this.subTreeChildrenMovingCount--;
this.subTreeMovingCount--;
},
moveTabSubTreeTo : function(...aArgs) {
return this.moveTabSubtreeTo.apply(this, aArgs);
}, // 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.detachTab(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.getAllTabs(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.getTabs(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)
{
if (!aTab.parentNode) // do nothing for closed tab!
return null;
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)
{
if (!aTab.parentNode) // do nothing for closed tab!
return null;
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 || !aTab.parentNode)
return;
if (this.isSubtreeCollapsed(aTab) == aCollapse)
return;
var b = this.mTabBrowser;
this.doingCollapseExpand = true;
this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, aCollapse);
var expandedTabs = this.getChildTabs(aTab);
var lastExpandedTabIndex = expandedTabs.length - 1;
for (let i = 0, maxi = expandedTabs.length; i < maxi; i++)
{
let childTab = expandedTabs[i];
if (!aCollapse && !aJustNow && i == lastExpandedTabIndex) {
let self = this;
this.collapseExpandTab(childTab, aCollapse, aJustNow, function() {
self.scrollToTabSubtree(aTab);
});
}
else
this.collapseExpandTab(childTab, aCollapse, aJustNow);
}
if (aCollapse)
this.deleteTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY);
if (utils.getTreePref('indent.autoShrink') &&
utils.getTreePref('indent.autoShrink.onlyForVisible'))
this.checkTabsIndentOverflow();
this.doingCollapseExpand = false;
},
manualCollapseExpandSubtree : function(aTab, aCollapse, aJustNow)
{
this.collapseExpandSubtree(aTab, aCollapse, aJustNow);
if (!aCollapse)
this.setTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY, true);
if (utils.getTreePref('indent.autoShrink') &&
utils.getTreePref('indent.autoShrink.onlyForVisible')) {
this.cancelCheckTabsIndentOverflow();
if (!aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave) {
var self = this;
var stillOver = false;
var id = this.getTabValue(aTab, this.kID);
aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave = function checkTabsIndentOverflowOnMouseLeave(aEvent, aDelayed) {
if (aEvent.type == 'mouseover') {
if (self.evaluateXPath(
'ancestor-or-self::*[@' + self.kID + '="' + id + '"]',
aEvent.originalTarget || aEvent.target,
Ci.nsIDOMXPathResult.BOOLEAN_TYPE
).booleanValue)
stillOver = true;
return;
}
else if (!aDelayed) {
if (stillOver) {
stillOver = false;
}
setTimeout(function() {
checkTabsIndentOverflowOnMouseLeave.call(null, aEvent, true);
}, 0);
return;
} else if (stillOver) {
return;
}
var x = aEvent.clientX;
var y = aEvent.clientY;
var rect = aTab.getBoundingClientRect();
if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom)
return;
self.document.removeEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true);
self.document.removeEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true);
delete aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave;
self.checkTabsIndentOverflow();
};
this.document.addEventListener('mouseover', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true);
this.document.addEventListener('mouseout', aTab.__treestyletab__checkTabsIndentOverflowOnMouseLeave, true);
}
}
},
collapseExpandTab : function TSTBrowser_collapseExpandTab(aTab, aCollapse, aJustNow, aCallbackToRunOnStartAnimation)
{
if (!aTab || !aTab.parentNode || !this.getParentTab(aTab))
return;
this.setTabValue(aTab, this.kCOLLAPSED, aCollapse);
this.updateTabCollapsed(aTab, aCollapse, aJustNow, aCallbackToRunOnStartAnimation);
var data = {
collapsed : aCollapse
};
/* PUBLIC API */
this.fireCustomEvent(this.kEVENT_TYPE_TAB_COLLAPSED_STATE_CHANGED, aTab, true, false, data);
// for backward compatibility
this.fireCustomEvent(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;
this.getAncestorTabs(aTab).some(function(aAncestor) {
if (!this.isCollapsed(aAncestor)) {
newSelection = aAncestor;
return true;
}
return false;
}, this);
b.selectedTab = newSelection;
}
if (!this.isSubtreeCollapsed(aTab)) {
let tabs = this.getChildTabs(aTab);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
this.collapseExpandTab(tabs[i], aCollapse, aJustNow);
}
}
},
updateTabCollapsed : function TSTBrowser_updateTabCollapsed(aTab, aCollapsed, aJustNow, aCallbackToRunOnStartAnimation)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
this.stopTabCollapseAnimation(aTab);
aTab.removeAttribute(this.kX_OFFSET);
aTab.removeAttribute(this.kY_OFFSET);
if (!this.canCollapseSubtree(this.getRootTab(aTab)))
aCollapsed = false;
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);
// Pinned tabs are positioned by "margin-top", so
// we must not reset the property for pinned tabs.
// (However, we still must update "opacity".)
let pinned = aTab.getAttribute('pinned') == 'true';
let canExpand = !pinned || this.collapseCSSProp != 'margin-top';
if (CSSTransitionEnabled) {
if (canExpand)
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 {
if (canExpand)
aTab.style.removeProperty(this.collapseCSSProp);
aTab.style.removeProperty('opacity');
}
if (aCallbackToRunOnStartAnimation)
aCallbackToRunOnStartAnimation();
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 (self.isDestroying)
return true;
if (firstFrame) {
// The callback must be started before offsetAttr is changed!
if (aCallbackToRunOnStartAnimation)
aCallbackToRunOnStartAnimation();
if (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)
{
if (!aTab.parentNode)
return; // do nothing for closed tab!
this.animationManager.removeTask(
aTab.__treestyletab__updateTabCollapsedTask
);
},
collapseExpandTreesIntelligentlyFor : function TSTBrowser_collapseExpandTreesIntelligentlyFor(aTab, aJustNow)
{
if (!aTab ||
!aTab.parentNode ||
this.doingCollapseExpand ||
!this.canCollapseSubtree(aTab))
return;
var b = this.mTabBrowser;
var sameParentTab = this.getParentTab(aTab);
var expandedAncestors = [aTab].concat(this.getAncestorTabs(aTab))
.map(function(aAncestor) {
return aAncestor.getAttribute(this.kID);
}, this)
.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("'+expandedAncestors+'", @'+this.kID+')) and not(@hidden="true")]',
b.mTabContainer
);
for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++)
{
let dontCollapse = false;
let collapseTab = xpathResult.snapshotItem(i);
let parentTab = this.getParentTab(collapseTab);
if (parentTab) {
dontCollapse = true;
if (!this.isSubtreeCollapsed(parentTab)) {
this.getAncestorTabs(collapseTab).some(function(aAncestor) {
if (expandedAncestors.indexOf(aAncestor.getAttribute(this.kID)) < 0)
return false;
dontCollapse = false;
return true;
}, this);
}
}
let manuallyExpanded = this.getTabValue(collapseTab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true';
if (!dontCollapse && !manuallyExpanded)
this.collapseExpandSubtree(collapseTab, true, aJustNow);
}
this.collapseExpandSubtree(aTab, false, aJustNow);
},
collapseExpandAllSubtree : function TSTBrowser_collapseExpandAllSubtree(aCollapse, aJustNow)
{
var tabs = this.mTabBrowser.mTabContainer.querySelectorAll(
'tab['+this.kID+']['+this.kCHILDREN+']'+
(
aCollapse ?
':not(['+this.kSUBTREE_COLLAPSED+'="true"])' :
'['+this.kSUBTREE_COLLAPSED+'="true"]'
)
);
for (var i = 0, maxi = tabs.length; i < maxi; i++)
{
this.collapseExpandSubtree(tabs[i], aCollapse, aJustNow);
}
},
/* scroll */
scrollTo : function TSTBrowser_scrollTo(aEndX, aEndY, aTargetElement)
{
if (this.timers['cancelPerformingAutoScroll'])
return;
if (this.animationEnabled || this.smoothScrollEnabled) {
this.smoothScrollTo(aEndX, aEndY, null, aTargetElement);
}
else {
try {
this.cancelPerformingAutoScroll();
this.scrollBoxObject.scrollTo(aEndX, aEndY);
}
catch(e) {
}
}
},
smoothScrollTo : function TSTBrowser_smoothScrollTo(aEndX, aEndY, aDuration, aTargetElement)
{
this.cancelPerformingAutoScroll(true);
var b = this.mTabBrowser;
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;
// Use Firefox's native smooth scroll if possible
// because it can be accelerated.
if (typeof this.scrollBox._smoothScrollByPixels == 'function') {
let amountToScroll = this.isVertical ? deltaY : deltaX ;
return this.scrollBox._smoothScrollByPixels(amountToScroll, aTargetElement);
}
// Otherwise fallback to TST's custom one.
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) {
if (self.isDestroying)
return true;
var scrollBoxObject = self.scrollBoxObject;
if (aTime >= aDuration || self.timers['cancelPerformingAutoScroll']) {
if (!self.timers['cancelPerformingAutoScroll']) {
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);
let key = 'smoothScrollTo_'+parseInt(Math.random() * 65000);
self.timers[key] = setTimeout(function() {
try {
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.mTabBrowser.mTabContainer.querySelector('tab['+self.kCOLLAPSING_PHASE+'="'+self.kCOLLAPSING_PHASE_TO_BE_EXPANDED+'"]')
)
self.smoothScrollTo(aEndX, aEndY, parseInt(aDuration * 0.5));
}
catch(e) {
self.defaultErrorHandler(e);
}
delete self.timers[key];
scrollBoxObject = null;
self = null;
}, 0);
}
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, aDuration || this.smoothScrollDuration, 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,
isSmoothScrolling : function TSTBrowser_isSmoothScrolling()
{
return Boolean(
// Firefox's native scroll
this.scrollBox._smoothScrollTimer ||
// TST's custom one
this.smoothScrollTask
);
},
stopSmoothScroll : function TSTBrowser_stopSmoothScroll()
{
// Firefox's native scroll
if (typeof this.scrollBox._stopSmoothScroll == 'function')
this.scrollBox._stopSmoothScroll();
// TST's custom one
if (this.smoothScrollTask) {
this.animationManager.removeTask(this.smoothScrollTask);
this.smoothScrollTask = null;
}
},
scrollToTab : function TSTBrowser_scrollToTab(aTab, aOnlyWhenCurrentTabIsInViewport)
{
if (!aTab || !aTab.parentNode)
return;
this.cancelPerformingAutoScroll(true);
if (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 = this.getFutureBoxObject(aTab);
var baseTabBox = this.getFirstNormalTab(b).boxObject;
var targetX = (targetTabBox.screenX < scrollBoxObject.screenX) ?
(targetTabBox.screenX - baseTabBox.screenX) - (targetTabBox.width * 0.5) :
(targetTabBox.screenX - baseTabBox.screenX) - scrollBoxObject.width + (targetTabBox.width * 1.5) ;
var targetY = (targetTabBox.screenY < scrollBoxObject.screenY) ?
(targetTabBox.screenY - baseTabBox.screenY) - (targetTabBox.height * 0.5) :
(targetTabBox.screenY - 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 > scrollBoxObject.width ||
targetTabBox.screenY - box.screenY + baseTabBox.height > scrollBoxObject.height)
return;
}
this.scrollTo(targetX, targetY, aTab);
},
scrollToTabSubtree : function TSTBrowser_scrollToTabSubtree(aTab)
{
if (!aTab.parentNode) // do nothing for closed tab!
return;
var descendants = this.getDescendantTabs(aTab);
return this.scrollToTabs([aTab].concat(descendants));
},
scrollToTabs : function TSTBrowser_scrollToTabs(aTabs)
{
var firstTab = aTabs[0];
if (!firstTab.parentNode) // do nothing for closed tab!
return;
var b = this.mTabBrowser;
var parentTabBox = this.getFutureBoxObject(firstTab);
var containerPosition = this.tabStrip.boxObject[this.screenPositionProp];
var containerSize = this.tabStrip.boxObject[this.sizeProp];
var parentPosition = parentTabBox[this.screenPositionProp];
var lastVisible = firstTab;
for (let i = aTabs.length-1; i > -1; i--)
{
let tab = aTabs[i];
if (this.isCollapsed(tab))
continue;
let box = this.getFutureBoxObject(tab);
if (box[this.screenPositionProp] + box[this.sizeProp] - parentPosition > containerSize)
continue;
lastVisible = tab;
break;
}
this.cancelPerformingAutoScroll(true);
if (this.isTabInViewport(firstTab) && this.isTabInViewport(lastVisible))
return;
var lastVisibleBox = this.getFutureBoxObject(lastVisible);
var lastPosition = lastVisibleBox[this.screenPositionProp];
var tabSize = lastVisibleBox[this.sizeProp];
var treeHeight = lastPosition - parentPosition + tabSize;
var treeIsLargerThanViewport = treeHeight > containerSize - tabSize;
if (treeIsLargerThanViewport) {
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(firstTab) && this.isTabInViewport(lastVisible)) {
this.scrollToTab(firstTab);
}
else if (this.isTabInViewport(firstTab) && !this.isTabInViewport(lastVisible)) {
this.scrollToTab(lastVisible);
}
else if (parentPosition < containerPosition) {
this.scrollToTab(firstTab);
}
else {
this.scrollToTab(lastVisible);
}
},
notifyBackgroundTab : function TSTBrowser_notifyBackgroundTab()
{
var animateElement = this.mTabBrowser.mTabContainer._animateElement;
var attrName = this.kBG_NOTIFY_PHASE;
if (!animateElement)
return;
if (this.timers['notifyBackgroundTab'])
clearTimeout(this.timers['notifyBackgroundTab']);
if (!animateElement.hasAttribute(attrName))
animateElement.setAttribute(attrName, 'ready');
this.timers['notifyBackgroundTab'] = setTimeout((function() {
Promise.resolve()
.then(function() {
animateElement.setAttribute(attrName, 'notifying');
return wait(150);
})
.then(function() {
animateElement.setAttribute(attrName, 'finish');
return wait(1000);
})
.then(function() {
animateElement.removeAttribute(attrName);
})
.catch(this.defaultErrorHandler.bind(this))
.then((function() {
delete this.timers['notifyBackgroundTab'];
}).bind(this));
}).bind(this), 0);
},
restoreTree : function TSTBrowser_restoreTree()
{
if (!this.needRestoreTree || this.useTMPSessionAPI)
return;
this.needRestoreTree = false;
if (this.useTMPSessionAPI && prefs.getPref('extensions.tabmix.sessions.manager'))
return;
var level = utils.getTreePref('restoreTree.level');
var tabs = this.getAllTabs(this.mTabBrowser);
var tabsToRestore = 0; // it is the number of pending tabs.
if (utils.SessionStoreInternal &&
utils.SessionStoreInternal._browserEpochs) {
let browserEpochs = utils.SessionStoreInternal._browserEpochs;
tabsToRestore = tabs.filter(function(aTab) {
return browserEpochs.has(aTab.linkedBrowser.permanentKey);
}).length;
}
else {
Components.utils.reportError(new Error('There is no property named "_browserEpochs"!!'));
}
dump('TSTBrowser::restoreTree\n');
dump(' level = '+level+'\n');
dump(' tabsToRestore = '+tabsToRestore+'\n');
if (
level <= this.kRESTORE_TREE_LEVEL_NONE ||
tabsToRestore <= 1
)
return;
var onlyVisible = level <= this.kRESTORE_TREE_ONLY_VISIBLE;
tabs = tabs.filter(function(aTab) {
// The "to be restored" state can be still undetermined.
// If the previous state is "undetermined" ("undefined")
// and now it has became certain, then we should save the state
// for now and the next time..
if (typeof aTab.linkedBrowser.__treestyletab__toBeRestored == 'undefined' &&
utils.isTabNotRestoredYet(aTab))
aTab.linkedBrowser.__treestyletab__toBeRestored = true;
return (
aTab.linkedBrowser.__treestyletab__toBeRestored &&
(!onlyVisible || !aTab.hidden)
);
});
dump(' restoring member tabs = '+tabs.length+' ('+tabs.map(function(aTab) { return aTab._tPos; })+')\n');
if (tabs.length <= 1)
return;
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
let currentId = tab.getAttribute(this.kID);
if (this.tabsHash[currentId] == tab)
delete this.tabsHash[currentId];
this.resetTabState(tab);
tab.setAttribute(this.kID, currentId); // to fallback to it
let [id, duplicated] = this._restoreTabId(tab);
this.setTabValue(tab, this.kID, id);
this.tabsHash[id] = tab;
tab.__treestyletab__restoreState = this.RESTORE_STATE_READY_TO_RESTORE;
tab.__treestyletab__duplicated = duplicated;
}
this.updateAllTabsIndent(true);
// restore tree from bottom safely
tabs.reverse()
.filter(this.restoreOneTab, this)
.forEach(this.updateInsertionPositionInfo, this);
},
restoreOneTab : function TSTBrowser_restoreOneTab(aTab)
{
if (aTab.__treestyletab__restoreState != this.RESTORE_STATE_READY_TO_RESTORE)
return false;
let duplicated = aTab.__treestyletab__duplicated;
let children = this.getTabValue(aTab, this.kCHILDREN);
if (children) {
this.deleteTabValue(aTab, this.kCHILDREN);
let manuallyExpanded = this.getTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY) == 'true';
let subTreeCollapsed = this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true';
subTreeCollapsed = this._restoreSubtreeCollapsedState(aTab, subTreeCollapsed);
let self = this;
this._restoreChildTabsRelation(aTab, children, duplicated, function(aChild) {
/**
* When the child has the reference to the parent tab, attachTabTo()
* does nothing. To ensure they are correctly related, we have to
* clear the relation here.
*/
self.deleteTabValue(aChild, self.kPARENT);
let refId = self.getTabValue(aChild, self.kINSERT_BEFORE);
if (refId && duplicated)
refId = self.redirectId(refId);
return {
forceExpand : true, // to prevent to collapse the selected tab
dontAnimate : true,
insertBefore : self.getTabById(refId)
};
});
this.collapseExpandSubtree(aTab, subTreeCollapsed, true);
if (manuallyExpanded && !subTreeCollapsed)
this.setTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY, true);
else
this.deleteTabValue(aTab, this.kSUBTREE_EXPANDED_MANUALLY);
}
delete aTab.__treestyletab__duplicated;
aTab.__treestyletab__restoreState = this.RESTORE_STATE_STRUCTURE_RESTORED;
return true
},
/* sub modules */
get tabbarDNDObserver()
{
if (!this._tabbarDNDObserver) {
this._tabbarDNDObserver = new TabbarDNDObserver(this.mTabBrowser);
}
return this._tabbarDNDObserver;
},
get panelDNDObserver()
{
if (!this._panelDNDObserver) {
this._panelDNDObserver = new 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(...aArgs) {
return this._callWindowServiceMethod('isPopupShown', aArgs);
},
updateTabsOnTop : function TSTBrowser_updateTabsOnTop(...aArgs) {
return this._callWindowServiceMethod('updateTabsOnTop', aArgs);
},
registerTabFocusAllowance : function TSTBrowser_registerTabFocusAllowance(...aArgs) {
return this._callWindowServiceMethod('registerTabFocusAllowance', aArgs);
},
isPopupShown : function TSTBrowser_isPopupShown(...aArgs) {
return this._callWindowServiceMethod('isPopupShown', aArgs);
},
toggleAutoHide : function TSTBrowser_toggleAutoHide(...aArgs) {
return this._callWindowServiceMethod('toggleAutoHide', aArgs);
},
/* show/hide tab bar */
get autoHide()
{
if (!this._autoHide) {
this._autoHide = new 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 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(); }
});