treestyletab/modules/browser.js
2015-08-28 17:39:04 +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') {
this.temporaryPosition = 'top';
return 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));
}
else {
this.destroyTabbarPostProcess();
return Promise.resolve();
}
},
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(aEnterFS)
{
if (this.position != 'top') {
// entering to the DOM-fullscreen (ex. YouTube Player)
if (this.document.mozFullScreen) {
this.setTabbrowserAttribute(this.kDOM_FULLSCREEN_ACTIVATED, true);
}
else {
if (this.document.documentElement.getAttribute(this.kDOM_FULLSCREEN_ACTIVATED) != 'true') {
if (aEnterFS)
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(); }
});