treestyletab/modules/base.js
Piro / YUKI Hiroshi f4fb6174d2 Read value of custom attributes of tabs via TSTBase.getTabAttribute() safely.
Tab's ID and other attributes saved via setTabValue is not available before the tab is completely restored.
As the result, TST sometimes fail to restore tab positions. (See: #676)
This issue doesn't happen on Firefox 24ESR, because recent Firefox (29 and later) uses message manager to restore tabs and it causes such "delay" of initializations about custom attributes.
2014-07-02 04:29:49 +09:00

2586 lines
78 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) 2010-2014
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): YUKI "Piro" Hiroshi <piro.outsider.reflex@gmail.com>
* 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 = ['TreeStyleTabBase'];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Timer.jsm');
Cu.import('resource://treestyletab-modules/lib/inherit.jsm');
Cu.import('resource://treestyletab-modules/constants.js');
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;
});
XPCOMUtils.defineLazyGetter(this, 'extensions', function() {
Cu.import('resource://treestyletab-modules/lib/extensions.js', {});
return window['piro.sakura.ne.jp'].extensions;
});
XPCOMUtils.defineLazyGetter(this, 'animationManager', function() {
Cu.import('resource://treestyletab-modules/lib/animationManager.js', {});
return window['piro.sakura.ne.jp'].animationManager;
});
XPCOMUtils.defineLazyGetter(this, 'autoScroll', function() {
Cu.import('resource://treestyletab-modules/lib/autoScroll.js', {});
return window['piro.sakura.ne.jp'].autoScroll;
});
XPCOMUtils.defineLazyModuleGetter(this, 'UninstallationListener',
'resource://treestyletab-modules/lib/UninstallationListener.js');
XPCOMUtils.defineLazyModuleGetter(this, 'Deferred',
'resource://treestyletab-modules/lib/jsdeferred.js');
XPCOMUtils.defineLazyModuleGetter(this, 'confirmWithPopup', 'resource://treestyletab-modules/lib/confirmWithPopup.js');
XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils');
XPCOMUtils.defineLazyServiceGetter(this, 'SessionStore',
'@mozilla.org/browser/sessionstore;1', 'nsISessionStore');
if (Services.appinfo.OS === 'WINNT') {
XPCOMUtils.defineLazyModuleGetter(this, 'AeroPeek',
'resource:///modules/WindowsPreviewPerTab.jsm', 'AeroPeek');
}
else {
this.AeroPeek = null;
}
var TreeStyleTabBase = inherit(TreeStyleTabConstants, {
tabsHash : null,
inWindowDestoructionProcess : false,
/* base variables */
baseIndentVertical : 12,
baseIndentHorizontal : 4,
shouldDetectClickOnIndentSpaces : true,
smoothScrollEnabled : true,
smoothScrollDuration : 150,
animationEnabled : true,
indentDuration : 200,
collapseDuration : 150,
shouldExpandTwistyArea : true,
scrollToNewTabMode : false,
counterRoleHorizontal : -1,
counterRoleVertical : -1,
get SessionStore() {
return SessionStore;
},
get extensions() { return extensions; },
get animationManager() { return animationManager; },
get autoScroll() { return autoScroll; },
get Deferred() { return Deferred; },
get AeroPeek() { return AeroPeek; }, // for Windows
init : function TSTBase_init()
{
if (this._initialized)
return;
this.isMac = Services.appinfo.OS == 'Darwin';
this.applyPlatformDefaultPrefs();
utils.migratePrefs();
prefs.addPrefListener(this);
this.initUninstallationListener();
this.onPrefChange('extensions.treestyletab.indent.vertical');
this.onPrefChange('extensions.treestyletab.indent.horizontal');
this.onPrefChange('extensions.treestyletab.clickOnIndentSpaces.enabled');
this.onPrefChange('browser.tabs.insertRelatedAfterCurrent.override');
this.onPrefChange('extensions.stm.tabBarMultiRows.override'); // Super Tab Mode
this.onPrefChange('extensions.treestyletab.tabbar.scroll.smooth');
this.onPrefChange('extensions.treestyletab.tabbar.scroll.duration');
this.onPrefChange('extensions.treestyletab.tabbar.scrollToNewTab.mode');
this.onPrefChange('extensions.treestyletab.tabbar.narrowScrollbar.size');
this.onPrefChange('browser.tabs.animate');
this.onPrefChange('extensions.treestyletab.animation.indent.duration');
this.onPrefChange('extensions.treestyletab.animation.collapse.duration');
this.onPrefChange('extensions.treestyletab.twisty.expandSensitiveArea');
this.onPrefChange('extensions.treestyletab.counter.role.horizontal');
this.onPrefChange('extensions.treestyletab.counter.role.vertical');
try {
this.overrideExtensions();
}
catch(e) {
dump(e+'\n');
}
},
_initialized : false,
applyPlatformDefaultPrefs : function TSTBase_applyPlatformDefaultPrefs()
{
var OS = Services.appinfo.OS;
var processed = {};
var originalKeys = prefs.getDescendant('extensions.treestyletab.platform.'+OS);
for (let i = 0, maxi = originalKeys.length; i < maxi; i++)
{
let originalKey = originalKeys[i];
let key = originalKey.replace('platform.'+OS+'.', '');
prefs.setDefaultPref(key, prefs.getPref(originalKey));
processed[key] = true;
}
originalKeys = prefs.getDescendant('extensions.treestyletab.platform.default');
for (let i = 0, maxi = originalKeys.length; i < maxi; i++)
{
let originalKey = originalKeys[i];
let key = originalKey.replace('platform.default.', '');
if (!(key in processed))
prefs.setDefaultPref(key, prefs.getPref(originalKey));
}
},
initUninstallationListener : function TSTWindow_initUninstallationListener()
{
var restorePrefs = function() {
// Remove pref listener before restore backuped prefs.
prefs.removePrefListener(this);
let restorePrefs = [
'browser.tabs.insertRelatedAfterCurrent',
'extensions.stm.tabBarMultiRows' // Super Tab Mode
];
for (let i = 0, maxi = restorePrefs.length; i < maxi; i++)
{
let pref = restorePrefs[i];
let backup = prefs.getPref(pref+'.backup');
if (backup === null)
continue;
// restore user preference.
prefs.setPref(pref, backup);
// clear backup pref.
prefs.clearPref(pref+'.backup');
}
}.bind(this);
new UninstallationListener({
id : 'treestyletab@piro.sakura.ne.jp',
onuninstalled : restorePrefs,
ondisabled : restorePrefs
});
},
overrideExtensions : function TSTBase_overrideExtensions()
{
// Scriptish
// https://addons.mozilla.org/firefox/addon/scriptish/
if (utils.getTreePref('compatibility.Scriptish')) {
try {
let tabModule = Cu.import('resource://scriptish/utils/Scriptish_openInTab.js', {});
let Scriptish_openInTab = tabModule.Scriptish_openInTab;
tabModule.Scriptish_openInTab = function(aURL, aLoadInBackground, aReuse, aChromeWin, ...aExtraArgs) {
try {
aChromeWin.TreeStyleTabService.readyToOpenChildTabNow(aChromeWin.gBrowser);
}
catch(e) {
Cu.reportError(e);
}
var allArgs = [aURL, aLoadInBackground, aReuse, aChromeWin].concat(aExtraArgs);
return Scriptish_openInTab.apply(this, allArgs);
};
}
catch(e) {
}
}
},
updateNarrowScrollbarStyle : function TSTBase_updateNarrowScrollbarStyle()
{
const SSS = Cc['@mozilla.org/content/style-sheet-service;1']
.getService(Ci.nsIStyleSheetService);
if (this.lastAgentSheet &&
SSS.sheetRegistered(this.lastAgentSheet, SSS.AGENT_SHEET))
SSS.unregisterSheet(this.lastAgentSheet, SSS.AGENT_SHEET);
const style = 'data:text/css,'+encodeURIComponent(
('@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");' +
'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' +
' .tabbrowser-arrowscrollbox' +
' > scrollbox' +
' > scrollbar[orient="vertical"],' +
'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' +
' .tabbrowser-arrowscrollbox' +
' > scrollbox' +
' > scrollbar[orient="vertical"] * {' +
' max-width: %SIZE%;' +
' min-width: %SIZE%;' +
'}' +
'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' +
' .tabbrowser-arrowscrollbox' +
' > scrollbox' +
' > scrollbar[orient="vertical"] {' +
' font-size: %SIZE%;' +
'}' +
'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' +
' .tabbrowser-arrowscrollbox' +
' > scrollbox' +
' > scrollbar[orient="vertical"] * {' +
' padding-left: 0;' +
' padding-right: 0;' +
' margin-left: 0;' +
' margin-right: 0;' +
'}' +
'%FORCE_NARROW_SCROLLBAR%')
.replace(/%FORCE_NARROW_SCROLLBAR%/g,
utils.getTreePref('tabbar.narrowScrollbar.overrideSystemAppearance') ?
this.kOVERRIDE_SYSTEM_SCROLLBAR_APPEARANCE : '' )
.replace(/%MODE%/g, this.kMODE)
.replace(/%NARROW%/g, this.kNARROW_SCROLLBAR)
.replace(/%SIZE%/g, utils.getTreePref('tabbar.narrowScrollbar.size'))
);
this.lastAgentSheet = this.makeURIFromSpec(style);
SSS.loadAndRegisterSheet(this.lastAgentSheet, SSS.AGENT_SHEET);
},
kOVERRIDE_SYSTEM_SCROLLBAR_APPEARANCE :
'tabs.tabbrowser-tabs[%MODE%="vertical"][%NARROW%="true"]' +
' .tabbrowser-arrowscrollbox' +
' > scrollbox' +
' > scrollbar[orient="vertical"] {' +
' appearance: none;' +
' -moz-appearance: none;' +
' background: ThreeDFace;' +
' border: 1px solid ThreeDShadow;' +
'}',
lastAgentSheet : null,
/* references to the owner */
get browserWindow()
{
return this.topBrowserWindow;
},
get topBrowserWindow()
{
return Services.wm.getMostRecentWindow('navigator:browser');
},
get browserWindows()
{
var windows = [];
var targets = Services.wm.getZOrderDOMWindowEnumerator('navigator:browser', true);
// By the bug 156333, we cannot find windows by their Z order on Linux.
// https://bugzilla.mozilla.org/show_bug.cgi?id=156333
if (!targets.hasMoreElements())
targets = Services.wm.getEnumerator('navigator:browser');
while (targets.hasMoreElements())
{
let target = targets.getNext()
.QueryInterface(Ci.nsIDOMWindow);
windows.push(target);
}
return windows;
},
get browser()
{
var w = this.browserWindow;
return !w ? null :
'SplitBrowser' in w ? w.SplitBrowser.activeBrowser :
w.gBrowser ;
},
get window()
{
return this.browser.ownerDocument.defaultView;
},
get currentDragSession()
{
return Cc['@mozilla.org/widget/dragservice;1']
.getService(Ci.nsIDragService)
.getCurrentSession();
},
/* calculated behaviors */
dropLinksOnTabBehavior : function TSTBase_dropLinksOnTabBehavior()
{
var behavior = utils.getTreePref('dropLinksOnTab.behavior');
if (behavior & this.kDROPLINK_FIXED)
return behavior;
var checked = { value : false };
var newChildTab = Services.prompt.confirmEx(this.browserWindow,
utils.treeBundle.getString('dropLinkOnTab.title'),
utils.treeBundle.getString('dropLinkOnTab.text'),
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1),
utils.treeBundle.getString('dropLinkOnTab.openNewChildTab'),
utils.treeBundle.getString('dropLinkOnTab.loadInTheTab'),
null,
utils.treeBundle.getString('dropLinkOnTab.never'),
checked
) == 0;
behavior = newChildTab ? this.kDROPLINK_NEWTAB : this.kDROPLINK_LOAD ;
if (checked.value)
utils.setTreePref('dropLinksOnTab.behavior', behavior);
return behavior
},
kDROPLINK_ASK : 0,
kDROPLINK_FIXED : 1 + 2,
kDROPLINK_LOAD : 1,
kDROPLINK_NEWTAB : 2,
openGroupBookmarkBehavior : function TSTBase_openGroupBookmarkBehavior()
{
var behavior = utils.getTreePref('openGroupBookmark.behavior');
if (behavior & this.kGROUP_BOOKMARK_FIXED)
return behavior;
var dummyTabFlag = behavior & this.kGROUP_BOOKMARK_USE_DUMMY;
var checked = { value : false };
var button = Services.prompt.confirmEx(this.browserWindow,
utils.treeBundle.getString('openGroupBookmarkBehavior.title'),
utils.treeBundle.getString('openGroupBookmarkBehavior.text'),
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1),
utils.treeBundle.getString('openGroupBookmarkBehavior.subTree'),
utils.treeBundle.getString('openGroupBookmarkBehavior.separate'),
null,
utils.treeBundle.getString('openGroupBookmarkBehavior.never'),
checked
);
if (button < 0)
button = 1;
var behaviors = [
this.kGROUP_BOOKMARK_SUBTREE | dummyTabFlag,
this.kGROUP_BOOKMARK_SEPARATE
];
behavior = behaviors[button];
if (checked.value) {
utils.setTreePref('openGroupBookmark.behavior', behavior);
}
return behavior;
},
kGROUP_BOOKMARK_ASK : 0,
kGROUP_BOOKMARK_FIXED : 1 + 2 + 4,
kGROUP_BOOKMARK_SUBTREE : 1,
kGROUP_BOOKMARK_SEPARATE : 2,
kGROUP_BOOKMARK_USE_DUMMY : 256,
kGROUP_BOOKMARK_USE_DUMMY_FORCE : 1024,
kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE : 512,
kGROUP_BOOKMARK_EXPAND_ALL_TREE : 2048,
bookmarkDroppedTabsBehavior : function TSTBase_bookmarkDroppedTabsBehavior()
{
var behavior = utils.getTreePref('bookmarkDroppedTabs.behavior');
if (behavior & this.kBOOKMARK_DROPPED_TABS_FIXED)
return behavior;
var checked = { value : false };
var button = Services.prompt.confirmEx(this.browserWindow,
utils.treeBundle.getString('bookmarkDroppedTabs.title'),
utils.treeBundle.getString('bookmarkDroppedTabs.text'),
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1),
utils.treeBundle.getString('bookmarkDroppedTabs.bookmarkAll'),
utils.treeBundle.getString('bookmarkDroppedTabs.bookmarkOnlyParent'),
null,
utils.treeBundle.getString('bookmarkDroppedTabs.never'),
checked
);
if (button < 0)
button = 1;
var behaviors = [
this.kBOOKMARK_DROPPED_TABS_ALL,
this.kBOOKMARK_DROPPED_TABS_ONLY_PARENT
];
behavior = behaviors[button];
if (checked.value)
utils.setTreePref('bookmarkDroppedTabs.behavior', behavior);
return behavior;
},
kBOOKMARK_DROPPED_TABS_ASK : 0,
kBOOKMARK_DROPPED_TABS_FIXED : 1 | 2,
kBOOKMARK_DROPPED_TABS_ALL : 1,
kBOOKMARK_DROPPED_TABS_ONLY_PARENT : 2,
askUndoCloseTabSetBehavior : function TSTBase_askUndoCloseTabSetBehavior(aRestoredTab, aCount)
{
var behavior = this.undoCloseTabSetBehavior;
if (behavior & this.kUNDO_CLOSE_SET)
behavior ^= this.kUNDO_CLOSE_SET;
var self = this;
return confirmWithPopup({
browser : aRestoredTab.linkedBrowser,
label : utils.treeBundle.getFormattedString('undoCloseTabSetBehavior.label', [aCount]),
value : 'treestyletab-undo-close-tree',
image : 'chrome://treestyletab/content/res/icon.png',
buttons : [
utils.treeBundle.getString('undoCloseTabSetBehavior.restoreOnce'),
utils.treeBundle.getString('undoCloseTabSetBehavior.restoreForever'),
utils.treeBundle.getString('undoCloseTabSetBehavior.ignoreForever')
],
persistence : -1 // don't hide even if the tab is restored after the panel is shown.
})
.next(function(aButtonIndex) {
if (aButtonIndex < 2) {
behavior |= self.kUNDO_CLOSE_SET;
}
if (aButtonIndex > 0) {
behavior ^= self.kUNDO_ASK;
utils.setTreePref('undoCloseTabSet.behavior', behavior);
}
return behavior;
});
},
get undoCloseTabSetBehavior()
{
return utils.getTreePref('undoCloseTabSet.behavior');
},
kUNDO_ASK : 1,
kUNDO_CLOSE_SET : 2,
kUNDO_CLOSE_FULL_SET : 256,
/* utilities */
doAndWaitDOMEvent : function TSTBase_doAndWaitDOMEvent(...aArgs)
{
var type, target, delay, task;
for (let i = 0, maxi = aArgs.length; i < maxi; i++)
{
let arg = aArgs[i];
switch(typeof arg)
{
case 'string':
type = arg;
continue;
case 'number':
delay = arg;
continue;
case 'function':
task = arg;
continue;
default:
target = arg;
continue;
}
}
if (!target || !type) {
if (task)
task();
return;
}
var done = false;
var listener = function(aEvent) {
setTimeout(function() {
done = true;
}, delay || 0);
target.removeEventListener(type, listener, false);
};
if (task)
Deferred.next(function() {
try {
task();
}
catch(e) {
dump(e+'\n');
target.removeEventListener(type, listener, false);
done = true;
}
}).error(this.defaultDeferredErrorHandler);
target.addEventListener(type, listener, false);
var thread = Components
.classes['@mozilla.org/thread-manager;1']
.getService()
.mainThread;
while (!done)
{
//dump('WAIT '+type+' '+Date.now()+'\n');
thread.processNextEvent(true);
}
},
findOffsetParent : function TSTBase_findOffsetParent(aNode)
{
var parent = aNode.parentNode;
var doc = aNode.ownerDocument || aNode;
var view = doc.defaultView;
while (parent && parent instanceof Ci.nsIDOMElement)
{
let position = view.getComputedStyle(parent, null).getPropertyValue('position');
if (position != 'static')
return parent;
parent = parent.parentNode;
}
return doc.documentElement;
},
assertBeforeDestruction : function TSTBase_assertBeforeDestruction(aNotDestructed)
{
if (aNotDestructed)
return;
var message = 'ERROR: accessed after destruction!';
var error = new Error(message);
dump(message+'\n'+error.stack+'\n');
throw error;
},
defaultDeferredErrorHandler : function TSTBase_defaultDeferredErrorHandler(aError)
{
if (aError.stack)
Cu.reportError(aError.message+'\n'+aError.stack);
else
Cu.reportError(aError);
},
// event
isNewTabAction : function TSTBase_isNewTabAction(aEvent)
{
return aEvent.button == 1 || (aEvent.button == 0 && this.isAccelKeyPressed(aEvent));
},
isAccelKeyPressed : function TSTBase_isAccelKeyPressed(aEvent)
{
if ( // this is releasing of the accel key!
(aEvent.type == 'keyup') &&
(aEvent.keyCode == (this.isMac ? Ci.nsIDOMKeyEvent.DOM_VK_META : Ci.nsIDOMKeyEvent.DOM_VK_CONTROL ))
) {
return false;
}
return this.isMac ?
(aEvent.metaKey || (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_META)) :
(aEvent.ctrlKey || (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL)) ;
},
isCopyAction : function TSTBase_isCopyAction(aEvent)
{
return this.isAccelKeyPressed(aEvent) ||
(aEvent.dataTransfer && aEvent.dataTransfer.dropEffect == 'copy');
},
isEventFiredOnClosebox : function TSTBase_isEventFiredOnClosebox(aEvent)
{
return this.evaluateXPath(
'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tab-close-button ")]',
aEvent.originalTarget || aEvent.target,
Ci.nsIDOMXPathResult.BOOLEAN_TYPE
).booleanValue;
},
isEventFiredOnClickable : function TSTBase_isEventFiredOnClickable(aEvent)
{
return this.evaluateXPath(
'ancestor-or-self::*[contains(" button toolbarbutton scrollbar nativescrollbar popup menupopup panel tooltip splitter textbox ", concat(" ", local-name(), " "))]',
aEvent.originalTarget,
Ci.nsIDOMXPathResult.BOOLEAN_TYPE
).booleanValue;
},
isEventFiredOnScrollbar : function TSTBase_isEventFiredOnScrollbar(aEvent)
{
return this.evaluateXPath(
'ancestor-or-self::*[local-name()="scrollbar" or local-name()="nativescrollbar"]',
aEvent.originalTarget,
Ci.nsIDOMXPathResult.BOOLEAN_TYPE
).booleanValue;
},
isEventFiredOnTwisty : function TSTBase_isEventFiredOnTwisty(aEvent)
{
var tab = this.getTabFromEvent(aEvent);
if (!tab ||
!this.hasChildTabs(tab) ||
!this.canCollapseSubtree(tab))
return false;
var twisty = tab.ownerDocument.getAnonymousElementByAttribute(tab, 'class', this.kTWISTY);
if (!twisty)
return false;
var box = twisty.boxObject;
var left = box.screenX;
var top = box.screenY;
var right = left + box.width;
var bottom = top + box.height;
var favicon = this.getFaviconRect(tab);
if (!box.width || !box.height) {
left = favicon.left;
top = favicon.top;
right = favicon.right;
bottom = favicon.bottom;
}
else if (
this.shouldExpandTwistyArea &&
!this._expandTwistyAreaBlockers.length
) {
left = Math.min(left, favicon.left);
top = Math.min(top, favicon.top);
right = Math.max(right, favicon.right);
bottom = Math.max(bottom, favicon.bottom);
}
var x = aEvent.screenX;
var y = aEvent.screenY;
return (x >= left && x <= right && y >= top && y <= bottom);
},
getFaviconRect : function TSTBase_getFaviconRect(aTab)
{
var icon = aTab.ownerDocument.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon-image');
var iconBox = icon.boxObject;
var iconRect = {
left : iconBox.screenX,
top : iconBox.screenY,
right : iconBox.screenX + iconBox.width,
bottom : iconBox.screenY + iconBox.height
};
var throbber = aTab.ownerDocument.getAnonymousElementByAttribute(aTab, 'class', 'tab-throbber');
var throbberBox = throbber.boxObject;
var throbberRect = {
left : throbberBox.screenX,
top : throbberBox.screenY,
right : throbberBox.screenX + throbberBox.width,
bottom : throbberBox.screenY + throbberBox.height
};
if (!iconBox.width && !iconBox.height)
return throbberRect;
if (!throbberBox.width && !throbberBox.height)
return iconRect;
return {
left : Math.min(throbberRect.left, iconRect.left),
right : Math.max(throbberRect.right, iconRect.right),
top : Math.min(throbberRect.top, iconRect.top),
bottom : Math.max(throbberRect.bottom, iconRect.bottom)
};
},
// called with target(nsIDOMEventTarget), document(nsIDOMDocument), type(string) and data(object)
fireCustomEvent : function TSTBase_fireCustomEvent(...aArgs)
{
var target, document, type, data, canBubble, cancelable;
for (let i = 0, maxi = aArgs.length; i < maxi; i++)
{
let arg = aArgs[i];
if (typeof arg == 'boolean') {
if (canBubble === void(0))
canBubble = arg;
else
cancelable = arg;
}
else if (typeof arg == 'string')
type = arg;
else if (arg instanceof Ci.nsIDOMDocument)
document = arg;
else if (arg instanceof Ci.nsIDOMEventTarget)
target = arg;
else
data = arg;
}
if (!target)
target = document;
if (!document)
document = target.ownerDocument || target;
var event = new this.window.CustomEvent(type, {
bubbles : canBubble,
cancelable : cancelable,
detail : data
});
return target.dispatchEvent(event);
},
registerExpandTwistyAreaBlocker : function TSTBase_registerExpandTwistyAreaBlocker(aBlocker) /* PUBLIC API */
{
if (this._expandTwistyAreaBlockers.indexOf(aBlocker) < 0)
this._expandTwistyAreaBlockers.push(aBlocker);
},
_expandTwistyAreaBlockers : [],
registerExpandTwistyAreaAllowance : function TSTBase_registerExpandTwistyAreaAllowance(aAllowance) /* PUBLIC API, obsolete, for backward compatibility */
{
this.registerExpandTwistyAreaBlocker(aAllowance.toSource());
},
// string
makeNewId : function TSTBase_makeNewId()
{
return 'tab-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>';
},
makeNewClosedSetId : function TSTBase_makeNewId()
{
return 'tabs-closed-set-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>';
},
makeURIFromSpec : function TSTBase_makeURIFromSpec(aURI)
{
var newURI;
aURI = aURI || '';
if (aURI && String(aURI).indexOf('file:') == 0) {
var fileHandler = Services.io.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler);
var tempLocalFile = fileHandler.getFileFromURLSpec(aURI);
newURI = Services.io.newFileURI(tempLocalFile);
}
else {
if (!/^\w+\:/.test(aURI))
aURI = 'http://'+aURI;
newURI = Services.io.newURI(aURI, null, null);
}
return newURI;
},
getGroupTabURI : function TSTBase_getGroupTabURI(aOptions)
{
aOptions = aOptions || {};
var parameters = [];
parameters.push('title=' + encodeURIComponent(aOptions.title || ''));
parameters.push('temporary=' + !!aOptions.temporary);
return 'about:treestyletab-group?' + parameters.join('&');
},
// xpath
NSResolver : {
lookupNamespaceURI : function(aPrefix)
{
switch (aPrefix)
{
case 'xul':
return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
case 'html':
case 'xhtml':
return 'http://www.w3.org/1999/xhtml';
case 'xlink':
return 'http://www.w3.org/1999/xlink';
default:
return '';
}
}
},
evaluateXPath : function TSTBase_evaluateXPath(aExpression, aContext, aType)
{
if (!aType)
aType = Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
try {
var XPathResult = (aContext.ownerDocument || aContext).evaluate(
aExpression,
(aContext || document),
this.NSResolver,
aType,
null
);
}
catch(e) {
return {
singleNodeValue : null,
snapshotLength : 0,
snapshotItem : function() {
return null
}
};
}
return XPathResult;
},
getArrayFromXPathResult : function TSTBase_getArrayFromXPathResult(aXPathResult)
{
var max = aXPathResult.snapshotLength;
var array = new Array(max);
if (!max)
return array;
for (var i = 0; i < max; i++)
{
array[i] = aXPathResult.snapshotItem(i);
}
return array;
},
/* Session Store API */
getTabAttribute : function TSTBase_getTabAttribute(aTab, aAttribute)
{
if (aTab.__SS_extdata &&
aTab.__SS_extdata[aAttribute])
return aTab.__SS_extdata[aAttribute];
return aTab.getAttribute(aAttribute);
},
getTabValue : function TSTBase_getTabValue(aTab, aKey)
{
if (aTab.__SS_extdata &&
aTab.__SS_extdata[aKey])
return aTab.__SS_extdata[aKey];
var value = '';
try {
value = SessionStore.getTabValue(aTab, aKey);
}
catch(e) {
}
if (this.useTMPSessionAPI) {
let TMPValue = aTab.getAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey);
if (TMPValue)
value = TMPValue;
}
return value;
},
setTabValue : function TSTBase_setTabValue(aTab, aKey, aValue)
{
if (!aValue)
return this.deleteTabValue(aTab, aKey);
aTab.setAttribute(aKey, aValue);
try {
this.checkCachedSessionDataExpiration(aTab);
SessionStore.setTabValue(aTab, aKey, aValue);
}
catch(e) {
}
if (this.useTMPSessionAPI)
aTab.setAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey, aValue);
return aValue;
},
deleteTabValue : function TSTBase_deleteTabValue(aTab, aKey)
{
aTab.removeAttribute(aKey);
try {
this.checkCachedSessionDataExpiration(aTab);
SessionStore.setTabValue(aTab, aKey, '');
SessionStore.deleteTabValue(aTab, aKey);
}
catch(e) {
}
if (this.useTMPSessionAPI)
aTab.removeAttribute(this.kTMP_SESSION_DATA_PREFIX+aKey);
},
// workaround for http://piro.sakura.ne.jp/latest/blosxom/mozilla/extension/treestyletab/2009-09-29_debug.htm
// This is obsolete for lately Firefox and no need to be updated. See: https://github.com/piroor/treestyletab/issues/508#issuecomment-17526429
checkCachedSessionDataExpiration : function TSTBase_checkCachedSessionDataExpiration(aTab)
{
var data = aTab.linkedBrowser.__SS_data;
if (data &&
data._tabStillLoading &&
aTab.getAttribute('busy') != 'true' &&
!utils.isTabNeedToBeRestored(aTab))
data._tabStillLoading = false;
},
markAsClosedSet : function TSTBase_markAsClosedSet(aTabs) /* PUBLIC API */
{
if (!aTabs || aTabs.length <= 1)
return;
var id = this.makeNewClosedSetId() + '::' + aTabs.length;
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
this.setTabValue(aTabs[i], this.kCLOSED_SET_ID, id);
}
},
unmarkAsClosedSet : function TSTBase_unmarkAsClosedSet(aTabs) /* PUBLIC API */
{
if (!aTabs || !aTabs.length)
return;
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
this.deleteTabValue(aTabs[i], this.kCLOSED_SET_ID);
}
},
useTMPSessionAPI : false,
kTMP_SESSION_DATA_PREFIX : 'tmp-session-data-',
// tab
getTabStrip : function TSTBase_getTabStrip(aTabBrowser)
{
if (!(aTabBrowser instanceof Ci.nsIDOMElement))
return null;
var strip = aTabBrowser.mStrip;
return (strip && strip instanceof Ci.nsIDOMElement) ?
strip :
this.evaluateXPath(
'ancestor::xul:toolbar[1]',
aTabBrowser.tabContainer,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue || aTabBrowser.tabContainer.parentNode;
},
get tabStrip()
{
return this.getTabStrip(this.browser);
},
getTabContainerBox : function TSTBase_getTabContainerBox(aTabBrowser)
{
if (!(aTabBrowser instanceof Ci.nsIDOMElement))
return null;
var strip = this.getTabStrip(aTabBrowser);
return strip.treeStyleTabToolbarInnerBox || aTabBrowser.tabContainer;
},
get tabContainerBox()
{
return this.getTabContainerBox(this.browser);
},
setTabbrowserAttribute : function TSTBase_setTabbrowserAttribute(aName, aValue, aTabBrowser)
{
aTabBrowser = aTabBrowser || this.mTabBrowser || this.browser;
if (aValue) {
aTabBrowser.setAttribute(aName, aValue);
aTabBrowser.mTabContainer.setAttribute(aName, aValue);
aTabBrowser.treeStyleTab.setTabStripAttribute(aName, aValue);
}
else {
aTabBrowser.removeAttribute(aName);
aTabBrowser.mTabContainer.removeAttribute(aName);
aTabBrowser.treeStyleTab.removeTabStripAttribute(aName);
}
},
removeTabbrowserAttribute : function TSTBase_removeTabbrowserAttribute(aName, aTabBrowser)
{
this.setTabbrowserAttribute(aName, null, aTabBrowser);
},
setTabStripAttribute : function TSTBase_setTabStripAttribute(aAttr, aValue)
{
var strip = this.tabStrip;
if (!strip)
return;
var isFeatureAttribute = aAttr.indexOf('treestyletab-') == 0;
if (aValue) {
if (this._tabStripPlaceHolder)
this._tabStripPlaceHolder.setAttribute(aAttr, aValue);
if (!this._tabStripPlaceHolder || aAttr != 'ordinal')
strip.setAttribute(aAttr, aValue);
if (strip.treeStyleTabToolbarInnerBox)
strip.treeStyleTabToolbarInnerBox.setAttribute(aAttr, aValue);
if (isFeatureAttribute) {
// Only attributes for TST's feature are applied to the root element.
// (width, height, and other general attributes have to be ignored!)
strip.ownerDocument.defaultView.setTimeout(function(aSelf) {
strip.ownerDocument.documentElement.setAttribute(aAttr, aValue);
}, 10, this);
}
}
else {
if (this._tabStripPlaceHolder)
this._tabStripPlaceHolder.removeAttribute(aAttr);
if (!this._tabStripPlaceHolder || aAttr != 'ordinal')
strip.removeAttribute(aAttr);
if (strip.treeStyleTabToolbarInnerBox)
strip.treeStyleTabToolbarInnerBox.removeAttribute(aAttr);
if (isFeatureAttribute) {
strip.ownerDocument.defaultView.setTimeout(function(aSelf) {
strip.ownerDocument.documentElement.removeAttribute(aAttr);
}, 10, this);
}
}
},
removeTabStripAttribute : function TSTBase_removeTabStripAttribute(aAttr)
{
this.setTabStripAttribute(aAttr, null);
},
getTabFromChild : function TSTBase_getTabFromChild(aTab)
{
return this.evaluateXPath(
'ancestor-or-self::xul:tab',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getTabFromEvent : function TSTBase_getTabFromEvent(aEvent)
{
return this.getTabFromChild(aEvent.originalTarget || aEvent.target);
},
getNewTabButtonFromEvent : function TSTBase_getNewTabButtonFromEvent(aEvent)
{
return this.evaluateXPath(
'ancestor-or-self::*['
+'@id="new-tab-button" or '
+'contains(concat(" ", normalize-space(@class), " "), " tabs-newtab-button ")'
+'][1]',
aEvent.originalTarget,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getSplitterFromEvent : function TSTBase_getSplitterFromEvent(aEvent)
{
return this.evaluateXPath(
'ancestor-or-self::xul:splitter[contains(concat(" ", normalize-space(@class), " "), " '+this.kSPLITTER+' ")]',
aEvent.originalTarget,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
isEventFiredOnGrippy : function TSTBase_isEventFiredOnGrippy(aEvent)
{
return this.evaluateXPath(
'ancestor-or-self::xul:grippy',
aEvent.originalTarget,
Ci.nsIDOMXPathResult.BOOLEAN_TYPE
).booleanValue;
},
getTabFromBrowser : function TSTBase_getTabFromBrowser(aBrowser, aTabBrowser)
{
var b = aTabBrowser || this.browser;
var tabs = this.getAllTabs(b);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
if (tab.linkedBrowser == aBrowser)
return tab;
}
return null;
},
getTabFromFrame : function TSTBase_getTabFromFrame(aFrame, aTabBrowser)
{
var b = aTabBrowser || this.browser;
var top = aFrame.top;
var tabs = this.getAllTabs(b);
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
let tab = tabs[i];
if (tab.linkedBrowser.contentWindow == top)
return tab;
}
return null;
},
getTabbarFromChild : function TSTBase_getTabbarFromChild(aNode)
{
return this.evaluateXPath(
'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tabbrowser-strip ")] | ' +
'ancestor-or-self::xul:tabs[@tabbrowser] | ' +
'ancestor-or-self::xul:toolbar/child::xul:tabs[@tabbrowser]',
aNode,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getAncestorTabbarFromChild : function TSTBase_getAncestorTabbarFromChild(aNode)
{
return this.evaluateXPath(
'ancestor-or-self::*[contains(concat(" ", normalize-space(@class), " "), " tabbrowser-strip ")] | ' +
'ancestor-or-self::xul:tabs[@tabbrowser]',
aNode,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getTabbarFromEvent : function TSTBase_getTabbarFromEvent(aEvent)
{
return this.getTabbarFromChild(aEvent.originalTarget || aEvent.target);
},
getAncestorTabbarFromEvent : function TSTBase_getAncestorTabbarFromEvent(aEvent)
{
return this.getAncestorTabbarFromChild(aEvent.originalTarget || aEvent.target);
},
cleanUpTabsArray : function TSTBase_cleanUpTabsArray(aTabs)
{
var newTabs = [];
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
let tab = aTabs[i];
if (!tab || !tab.parentNode)
continue; // ignore removed tabs
if (newTabs.indexOf(tab) < 0)
newTabs.push(tab);
}
newTabs.sort(this.sortTabsByOrder);
return newTabs;
},
sortTabsByOrder : function TSTBase_sortTabsByOrder(aA, aB)
{
return aA._tPos - aB._tPos;
},
gatherSubtreeMemberTabs : function TSTBase_gatherSubtreeMemberTabs(aTabOrTabs, aOnlyChildren)
{
var tabs = aTabOrTabs;
if (!(tabs instanceof Array)) {
tabs = [aTabOrTabs];
}
var b = this.getTabBrowserFromChild(tabs[0]);
var descendant = [];
for (var i = 0, maxi = tabs.length; i < maxi; i++)
{
descendant = descendant.concat(b.treeStyleTab.getDescendantTabs(tabs[i]));
}
return this.cleanUpTabsArray(aOnlyChildren ? descendant : tabs.concat(descendant));
},
splitTabsToSubtrees : function TSTBase_splitTabsToSubtrees(aTabs) /* PUBLIC API */
{
var groups = [];
var group = [];
aTabs = this.cleanUpTabsArray(aTabs);
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
let tab = aTabs[i];
let parent = this.getParentTab(tab);
if (!parent || group.indexOf(parent) < 0) {
if (group.length)
groups.push(group);
group = [tab];
}
else {
group.push(tab);
}
}
if (group.length)
groups.push(group);
return groups;
},
// tabbrowser
getTabBrowserFromChild : function TSTBase_getTabBrowserFromChild(aTabBrowserChild)
{
if (!aTabBrowserChild)
return null;
if (aTabBrowserChild.__treestyletab__linkedTabBrowser) // tab
return aTabBrowserChild.__treestyletab__linkedTabBrowser;
if (aTabBrowserChild.localName == 'tabbrowser') // itself
return aTabBrowserChild;
if (aTabBrowserChild.tabbrowser) // tabs
return aTabBrowserChild.tabbrowser;
if (aTabBrowserChild.localName == 'toolbar') // tabs toolbar
return aTabBrowserChild.getElementsByTagName('tabs')[0].tabbrowser;
// tab context menu
var popup = this.evaluateXPath(
'ancestor-or-self::xul:menupopup[@id="tabContextMenu"]',
aTabBrowserChild,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
if (popup && 'TabContextMenu' in aTabBrowserChild.ownerDocument.defaultView)
return this.getTabBrowserFromChild(aTabBrowserChild.ownerDocument.defaultView.TabContextMenu.contextTab);
var b = this.evaluateXPath(
'ancestor::xul:tabbrowser | '+
'ancestor::xul:tabs[@tabbrowser] |'+
'ancestor::xul:toolbar/descendant::xul:tabs',
aTabBrowserChild,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
return (b && b.tabbrowser) || b;
},
getTabBrowserFromFrame : function TSTBase_getTabBrowserFromFrame(aFrame)
{
var w = this.browserWindow;
return !w ? null :
('SplitBrowser' in w) ? this.getTabBrowserFromChild(w.SplitBrowser.getSubBrowserAndBrowserFromFrame(aFrame.top).browser) :
this.browser ;
},
getFrameFromTabBrowserElements : function TSTBase_getFrameFromTabBrowserElements(aFrameOrTabBrowser)
{
var frame = aFrameOrTabBrowser;
if (frame == '[object XULElement]') {
if (frame.localName == 'tab') {
frame = frame.linkedBrowser.contentWindow;
}
else if (frame.localName == 'browser') {
frame = frame.contentWindow;
}
else {
frame = this.getTabBrowserFromChild(frame);
if (!frame)
return null;
frame = frame.contentWindow;
}
}
if (!frame)
frame = this.browser.contentWindow;
return frame;
},
/* get tab(s) */
getTabById : function TSTBase_getTabById(aId, aTabBrowserChildren)
{
if (!aId)
return null;
if (aTabBrowserChildren && !(aTabBrowserChildren instanceof Ci.nsIDOMNode))
aTabBrowserChildren = null;
var b = this.getTabBrowserFromChild(aTabBrowserChildren) || this.browser;
if (this.tabsHash) // XPath-less implementation
return this.tabsHash[aId] || null;
return b.mTabContainer.querySelector('tab['+this.kID+'="'+aId+'"]');
},
isTabDuplicated : function TSTBase_isTabDuplicated(aTab)
{
if (!aTab)
return false;
var id = this.getTabValue(aTab, this.kID);
var b = this.getTabBrowserFromChild(aTab) || this.browser;
var tabs = b.mTabContainer.querySelectorAll('tab['+this.kID+'="'+id+'"], tab['+this.kID_RESTORING+'="'+id+'"]');
return tabs.length > 1;
},
/**
* Returns all tabs in the current group as an array.
* It includes tabs hidden by Tab Panorama.
*/
getAllTabs : function TSTBase_getTabs(aTabBrowserChild)
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
this.assertBeforeDestruction(b && b.mTabContainer);
return Array.slice(b.mTabContainer.querySelectorAll('tab'));
},
/**
* Returns all tabs in the current group as an array.
* It excludes tabs hidden by Tab Panorama.
*/
getTabs : function TSTBase_getTabs(aTabBrowserChild)
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
this.assertBeforeDestruction(b && b.mTabContainer);
return Array.slice(b.mTabContainer.querySelectorAll('tab:not([hidden="true"])'));
},
getAllTabsArray : function TSTBase_getAllTabsArray(aTabBrowserChild) /* for backward compatibility */
{
return this.getAllTabs(aTabBrowserChild);
},
getTabsArray : function TSTBase_getTabsArray(aTabBrowserChild) /* for backward compatibility */
{
return this.getTabs(aTabBrowserChild);
},
/**
* Returns the first tab in the current group.
*/
getFirstTab : function TSTBase_getFirstTab(aTabBrowserChild)
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
this.assertBeforeDestruction(b && b.mTabContainer);
var tabs = b.visibleTabs;
return tabs ? tabs[0] : b.mTabContainer.firstChild;
},
/**
* Returns the first visible, not collapsed, and not pinned tab.
*/
getFirstNormalTab : function TSTBase_getFirstNormalTab(aTabBrowserChild)
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
this.assertBeforeDestruction(b && b.mTabContainer);
return b.mTabContainer.querySelector('tab:not([pinned="true"]):not([hidden="true"])');
},
/**
* Returns the last tab in the current group.
*/
getLastTab : function TSTBase_getLastTab(aTabBrowserChild)
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
this.assertBeforeDestruction(b && b.mTabContainer);
var tabs = b.visibleTabs;
return tabs ? tabs[tabs.length-1] : b.mTabContainer.lastChild ;
},
/**
* Returns the next tab in the current group.
*/
getNextTab : function TSTBase_getNextTab(aTab)
{
if (!aTab)
return null;
var b = this.getTabBrowserFromChild(aTab);
this.assertBeforeDestruction(b && b.mTabContainer);
var tabs = b.visibleTabs;
if (tabs) {
let index = tabs.indexOf(aTab);
if (index > -1)
return tabs.length > index ? tabs[index+1] : null
}
var tab = aTab.nextSibling;
return (tab && tab.localName == 'tab') ? tab : null ;
},
/**
* Returns the previous tab in the current group.
*/
getPreviousTab : function TSTBase_getPreviousTab(aTab)
{
if (!aTab)
return null;
var b = this.getTabBrowserFromChild(aTab);
this.assertBeforeDestruction(b && b.mTabContainer);
var tabs = b.visibleTabs;
if (tabs) {
let index = tabs.indexOf(aTab);
if (index > -1)
return 0 < index ? tabs[index-1] : null
}
var tab = aTab.previousSibling;
return (tab && tab.localName == 'tab') ? tab : null ;
},
/**
* Returns the index of the specified tab, in the current group.
*/
getTabIndex : function TSTBase_getTabIndex(aTab)
{
if (!aTab)
return -1;
var b = this.getTabBrowserFromChild(aTab);
return this.getTabs(b).indexOf(aTab);
},
/**
* Returns the next not collapsed tab in the current group.
*/
getNextVisibleTab : function TSTBase_getNextVisibleTab(aTab)
{
if (!aTab)
return null;
var b = this.getTabBrowserFromChild(aTab);
if (!this.canCollapseSubtree(b))
return this.getNextTab(aTab);
var tabs = this.getVisibleTabs(b);
if (tabs.indexOf(aTab) < 0)
tabs.push(aTab);
tabs.sort(this.sortTabsByOrder);
var index = tabs.indexOf(aTab);
return (index < tabs.length-1) ? tabs[index+1] : null ;
},
/**
* Returns the previous not collapsed tab in the current group.
*/
getPreviousVisibleTab : function TSTBase_getPreviousVisibleTab(aTab)
{
if (!aTab)
return null;
var b = this.getTabBrowserFromChild(aTab);
if (!this.canCollapseSubtree(b))
return this.getPreviousTab(aTab);
var tabs = this.getVisibleTabs(b);
if (tabs.indexOf(aTab) < 0)
tabs.push(aTab);
tabs.sort(this.sortTabsByOrder);
var index = tabs.indexOf(aTab);
return (index > 0) ? tabs[index-1] : null ;
},
/**
* Returns the last not collapsed tab in the current group.
*/
getLastVisibleTab : function TSTBase_getLastVisibleTab(aTabBrowserChild)
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
if (!b)
return null;
var tabs = this.getVisibleTabs(b);
return tabs.length ? tabs[tabs.length-1] : null ;
},
/**
* Returns a XPathResult of not collapsed tabs in the current group.
*/
getVisibleTabs : function TSTBase_getVisibleTabs(aTabBrowserChild) /* OBSOLETE */
{
var b = this.getTabBrowserFromChild(aTabBrowserChild || this.browser);
if (!this.canCollapseSubtree(b))
return this.getTabs(b);
return Array.slice(b.mTabContainer.querySelectorAll('tab:not(['+this.kCOLLAPSED+'="true"]):not([hidden="true"])'));
},
getVisibleTabsArray : function TSTBase_getVisibleTabsArray(aTabBrowserChild) /* for backward compatibility */
{
return this.getVisibleTabs(aTabBrowserChild);
},
/**
* Returns the index of the specified tab, in the array of not collapsed
* tabs in the current group.
*/
getVisibleIndex : function TSTBase_getVisibleIndex(aTab)
{
if (!aTab)
return -1;
var b = this.getTabBrowserFromChild(aTab);
return this.getVisibleTabs(b).indexOf(aTab);
},
/**
* Returns tabs which are newly opened in the given operation.
*/
getNewTabsWithOperation : function TSTBase_getNewTabsWithOperation(aOperation, aTabBrowser)
{
var previousTabs = this.getTabsInfo(aTabBrowser);
aOperation.call(this);
return this.getNewTabsFromPreviousTabsInfo(aTabBrowser, previousTabs);
},
/**
* Returns tabs which are newly opened. This requires the "previous state".
*/
getNewTabsFromPreviousTabsInfo : function TSTBase_getNewTabsFromPreviousTabsInfo(aTabBrowser, aTabsInfo)
{
var tabs = this.getTabs(aTabBrowser);
var currentTabsInfo = this.getTabsInfo(aTabBrowser);
return tabs.filter(function(aTab, aIndex) {
return aTabsInfo.indexOf(currentTabsInfo[aIndex]) < 0;
});
},
getTabsInfo : function TSTBase_getTabsInfo(aTabBrowser)
{
var tabs = this.getTabs(aTabBrowser);
return tabs.map(function(aTab) {
return this.getTabAttribute(aTab, this.kID)+'\n'+
aTab.getAttribute('busy')+'\n'+
aTab.linkedBrowser.currentURI.spec;
}, this);
},
/* notify "ready to open child tab(s)" */
readyToOpenChildTab : function TSTBase_readyToOpenChildTab(aFrameOrTabBrowser, aMultiple, aInsertBefore) /* PUBLIC API */
{
if (!utils.getTreePref('autoAttach'))
return false;
var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser);
if (!frame)
return false;
var ownerBrowser = this.getTabBrowserFromFrame(frame);
var parentTab = this.getTabFromFrame(frame, ownerBrowser);
if (!parentTab || parentTab.getAttribute('pinned') == 'true')
return false;
ownerBrowser.treeStyleTab.ensureTabInitialized(parentTab);
var parentId = this.getTabAttribute(parentTab, this.kID);
var refId = null;
if (aInsertBefore) {
ownerBrowser.treeStyleTab.ensureTabInitialized(parentTab);
refId = this.getTabAttribute(aInsertBefore, this.kID);
}
ownerBrowser.treeStyleTab.readiedToAttachNewTab = true;
ownerBrowser.treeStyleTab.readiedToAttachMultiple = aMultiple || false ;
ownerBrowser.treeStyleTab.multipleCount = aMultiple ? 0 : -1 ;
ownerBrowser.treeStyleTab.parentTab = parentId;
ownerBrowser.treeStyleTab.insertBefore = refId;
return true;
},
/**
* Extended version. If you don't know whether a new tab will be actually
* opened or not (by the command called after TST's API), then use this.
* This version automatically cancels the "ready" state with delay.
*/
readyToOpenChildTabNow : function TSTBase_readyToOpenChildTabNow(...aArgs) /* PUBLIC API */
{
if (this.readyToOpenChildTab.apply(this, aArgs)) {
let self = this;
this.Deferred.next(function() {
self.stopToOpenChildTab(aArgs[0]);
}).error(this.defaultDeferredErrorHandler);
return true;
}
return false;
},
readyToOpenNextSiblingTab : function TSTBase_readyToOpenNextSiblingTab(aFrameOrTabBrowser) /* PUBLIC API */
{
var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser);
if (!frame)
return false;
var ownerBrowser = this.getTabBrowserFromFrame(frame);
var tab = this.getTabFromFrame(frame, ownerBrowser);
if (!tab || tab.getAttribute('pinned') == 'true')
return false;
var parentTab = this.getParentTab(tab);
var nextTab = this.getNextSiblingTab(tab);
if (parentTab) {
/**
* If the base tab has a parent, open the new tab as a child of
* the parent tab.
*/
return this.readyToOpenChildTab(parentTab, false, nextTab);
}
else {
/**
* Otherwise, open the tab as a new root tab. If there is no
* tab next to the base tab (in other words, if the tab is the
* last tab), then do nothing.
*/
if (!nextTab)
return;
ownerBrowser.treeStyleTab.readiedToAttachNewTab = true;
ownerBrowser.treeStyleTab.parentTab = null;
ownerBrowser.treeStyleTab.insertBefore = this.getTabAttribute(nextTab, this.kID);
return true;
}
},
/**
* Extended version. If you don't know whether a new tab will be actually
* opened or not (by the command called after TST's API), then use this.
* This version automatically cancels the "ready" state with delay.
*/
readyToOpenNextSiblingTabNow : function TSTBase_readyToOpenNextSiblingTabNow(...aArgs) /* PUBLIC API */
{
if (this.readyToOpenNextSiblingTab.apply(this, aArgs)) {
let self = this;
this.Deferred.next(function() {
self.stopToOpenChildTab(aArgs[0]);
}).error(this.defaultDeferredErrorHandler);
return true;
}
return false;
},
readyToOpenNewTabGroup : function TSTBase_readyToOpenNewTabGroup(aFrameOrTabBrowser, aTreeStructure, aExpandAllTree) /* PUBLIC API */
{
if (!utils.getTreePref('autoAttach'))
return false;
var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser);
if (!frame)
return false;
this.stopToOpenChildTab(frame);
var ownerBrowser = this.getTabBrowserFromFrame(frame);
ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup = true;
ownerBrowser.treeStyleTab.readiedToAttachMultiple = true;
ownerBrowser.treeStyleTab.multipleCount = 0;
ownerBrowser.treeStyleTab.treeStructure = aTreeStructure;
ownerBrowser.treeStyleTab.shouldExpandAllTree = !!aExpandAllTree;
return true;
},
/**
* Extended version. If you don't know whether new tabs will be actually
* opened or not (by the command called after TST's API), then use this.
* This version automatically cancels the "ready" state with delay.
*/
readyToOpenNewTabGroupNow : function TSTBase_readyToOpenNewTabGroupNow(...aArgs) /* PUBLIC API */
{
if (this.readyToOpenNewTabGroup.apply(this, aArgs)) {
let self = this;
this.Deferred.next(function() {
self.stopToOpenChildTab(aArgs[0]);
}).error(this.defaultDeferredErrorHandler);
return true;
}
return false;
},
stopToOpenChildTab : function TSTBase_stopToOpenChildTab(aFrameOrTabBrowser) /* PUBLIC API */
{
var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser);
if (!frame)
return false;
var ownerBrowser = this.getTabBrowserFromFrame(frame);
ownerBrowser.treeStyleTab.readiedToAttachNewTab = false;
ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup = false;
ownerBrowser.treeStyleTab.readiedToAttachMultiple = false;
ownerBrowser.treeStyleTab.multipleCount = -1;
ownerBrowser.treeStyleTab.parentTab = null;
ownerBrowser.treeStyleTab.insertBefore = null;
ownerBrowser.treeStyleTab.treeStructure = null;
ownerBrowser.treeStyleTab.shouldExpandAllTree = false;
return true;
},
checkToOpenChildTab : function TSTBase_checkToOpenChildTab(aFrameOrTabBrowser) /* PUBLIC API */
{
var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser);
if (!frame)
return false;
var ownerBrowser = this.getTabBrowserFromFrame(frame);
return !!(ownerBrowser.treeStyleTab.readiedToAttachNewTab || ownerBrowser.treeStyleTab.readiedToAttachNewTabGroup);
},
kNEWTAB_DO_NOTHING : -1,
kNEWTAB_OPEN_AS_ORPHAN : 0,
kNEWTAB_OPEN_AS_CHILD : 1,
kNEWTAB_OPEN_AS_SIBLING : 2,
kNEWTAB_OPEN_AS_NEXT_SIBLING : 3,
readyToOpenRelatedTabAs : function TSTBase_readyToOpenRelatedTabAs(aBaseTab, aBehavior)
{
var frame = this.getFrameFromTabBrowserElements(aBaseTab);
if (!frame)
return;
aBaseTab = this.getTabFromFrame(frame, this.getTabBrowserFromFrame(frame));
switch (aBehavior)
{
case this.kNEWTAB_OPEN_AS_ORPHAN:
case this.kNEWTAB_DO_NOTHING:
default:
break;
case this.kNEWTAB_OPEN_AS_CHILD:
this.readyToOpenChildTabNow(aBaseTab);
break;
case this.kNEWTAB_OPEN_AS_SIBLING:
let (parentTab = this.getParentTab(aBaseTab)) {
if (parentTab)
this.readyToOpenChildTabNow(parentTab);
}
break;
case this.kNEWTAB_OPEN_AS_NEXT_SIBLING:
this.readyToOpenNextSiblingTabNow(aBaseTab);
break;
}
},
handleNewTabFromCurrent : function TSTBase_handleNewTabFromCurrent(aBaseTab)
{
this.readyToOpenRelatedTabAs(aBaseTab, utils.getTreePref('autoAttach.fromCurrent'));
},
/* tree manipulations */
get treeViewEnabled() /* PUBLIC API */
{
return this._treeViewEnabled;
},
set treeViewEnabled(aValue)
{
this._treeViewEnabled = !!aValue;
Services.obs.notifyObservers(
window,
this.kTOPIC_CHANGE_TREEVIEW_AVAILABILITY,
this._treeViewEnabled
);
return aValue;
},
_treeViewEnabled : true,
get rootTabs() /* PUBLIC API */
{
return Array.slice(this.browser.mTabContainer.querySelectorAll('tab:not(['+this.kNEST+']), tab['+this.kNEST+'=""], tab['+this.kNEST+'="0"]'));
},
get allRootTabs() /* PUBLIC API */
{
return this.rootTabs;
},
get visibleRootTabs() /* PUBLIC API */
{
return this.rootTabs.filter(function(aTab) {
return !aTab.hidden;
});
},
canCollapseSubtree : function TSTBase_canCollapseSubtree(aTabOrTabBrowser) /* PUBLIC API */
{
if (aTabOrTabBrowser &&
aTabOrTabBrowser.localName == 'tab' &&
aTabOrTabBrowser.getAttribute(this.kALLOW_COLLAPSE) != 'true')
return false;
var b = this.getTabBrowserFromChild(aTabOrTabBrowser) || this.browser;
return b && b.getAttribute(this.kALLOW_COLLAPSE) == 'true';
},
isCollapsed : function TSTBase_isCollapsed(aTab) /* PUBLIC API */
{
if (!aTab ||
!this.canCollapseSubtree(this.getRootTab(aTab)))
return false;
return this.getTabAttribute(aTab, this.kCOLLAPSED) == 'true';
},
isSubtreeCollapsed : function TSTBase_isSubtreeCollapsed(aTab) /* PUBLIC API */
{
if (!aTab || !this.canCollapseSubtree(aTab) || !this.hasChildTabs(aTab))
return false;
return this.getTabAttribute(aTab, this.kSUBTREE_COLLAPSED) == 'true';
},
shouldCloseTabSubtreeOf : function TSTBase_shouldCloseTabSubtreeOf(aTab)
{
return (
this.hasChildTabs(aTab) &&
(
utils.getTreePref('closeParentBehavior') == this.kCLOSE_PARENT_BEHAVIOR_CLOSE_ALL_CHILDREN ||
this.isSubtreeCollapsed(aTab)
)
);
},
shouldCloseTabSubTreeOf : function TSTBase_shouldCloseTabSubTreeOf(...aArgs) {
return this.shouldCloseTabSubtreeOf.apply(this, aArgs);
}, // obsolete, for backward compatibility
shouldCloseLastTabSubtreeOf : function TSTBase_shouldCloseLastTabSubtreeOf(aTab)
{
var b = this.getTabBrowserFromChild(aTab);
return (
b &&
this.shouldCloseTabSubtreeOf(aTab) &&
this.getDescendantTabs(aTab).length + 1 == this.getAllTabs(b).length
);
},
shouldCloseLastTabSubTreeOf : function TSTBase_shouldCloseLastTabSubTreeOf(...aArgs) {
return this.shouldCloseLastTabSubtreeOf.apply(this, aArgs);
}, // obsolete, for backward compatibility
getParentTab : function TSTBase_getParentTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
var parent;
var id = this.getTabAttribute(aTab, this.kPARENT);
if (this.tabsHash) { // XPath-less implementation
parent = this.getTabById(id);
if (parent && !parent.parentNode && this.tabsHash) {
delete this.tabsHash[id];
parent = null;
}
}
else {
parent = this.evaluateXPath(
'preceding-sibling::xul:tab[@'+this.kID+'="'+id+'"][1]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
}
return (parent && parent != aTab) ? parent : null ;
},
getAncestorTabs : function TSTBase_getAncestorTabs(aTab) /* PUBLIC API */
{
var tabs = [aTab];
var parentTab = aTab;
while (parentTab = this.getParentTab(parentTab))
{
if (tabs.indexOf(parentTab) > -1) {
let message = 'recursive tree detected!\n'+
tabs.concat([parentTab])
.reverse().map(function(aTab) {
return ' '+aTab._tPos+' : '+
aTab.label+'\n '+
this.getTabAttribute(aTab, this.kID);
}, this).join('\n');
dump(message+'\n');
break;
}
if (aTab._tPos < parentTab._tPos) {
let message = 'broken tree detected!\n'+
tabs.concat([parentTab])
.reverse().map(function(aTab) {
return ' '+aTab._tPos+' : '+
aTab.label+'\n '+
this.getTabAttribute(aTab, this.kID);
}, this).join('\n');
dump(message+'\n');
}
tabs.push(parentTab);
aTab = parentTab;
}
return tabs.slice(1);
},
getRootTab : function TSTBase_getRootTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
if (this.tabsHash) { // XPath-less implementation
let ancestors = this.getAncestorTabs(aTab);
return ancestors.length ? ancestors[ancestors.length-1] : aTab ;
}
return this.evaluateXPath(
'(self::*[not(@'+this.kPARENT+')] | preceding-sibling::xul:tab[not(@'+this.kPARENT+')])[last()]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getNextSiblingTab : function TSTBase_getNextSiblingTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
if (this.tabsHash) { // XPath-less implementation
let parentTab = this.getParentTab(aTab);
if (!parentTab) {
let next = aTab;
do {
next = next.nextSibling;
}
while (next &&
next.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
this.getParentTab(next));
return next;
}
let children = this.getTabAttribute(parentTab, this.kCHILDREN);
if (children) {
let list = ('|'+children).split('|'+this.getTabAttribute(aTab, this.kID));
list = list.length > 1 ? list[1].split('|') : [] ;
for (let i = 0, maxi = list.length; i < maxi; i++)
{
let firstChild = this.getTabById(list[i], aTab);
if (firstChild)
return firstChild;
}
}
return null;
}
var parent = this.getTabAttribute(aTab, this.kPARENT);
return this.evaluateXPath(
'following-sibling::xul:tab['+
(parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+
'][1]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getPreviousSiblingTab : function TSTBase_getPreviousSiblingTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
if (this.tabsHash) { // XPath-less implementation
let parentTab = this.getParentTab(aTab);
if (!parentTab) {
let prev = aTab;
do {
prev = prev.previousSibling;
}
while (prev &&
prev.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
this.getParentTab(prev));
return prev;
}
let children = this.getTabAttribute(parentTab, this.kCHILDREN);
if (children) {
let list = ('|'+children).split('|'+this.getTabAttribute(aTab, this.kID))[0].split('|');
for (let i = list.length-1; i > -1; i--)
{
let lastChild = this.getTabById(list[i], aTab);
if (lastChild)
return lastChild;
}
}
return null;
}
var parent = this.getTabAttribute(aTab, this.kPARENT);
return this.evaluateXPath(
'preceding-sibling::xul:tab['+
(parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+
'][1]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getSiblingTabs : function TSTBase_getSiblingTabs(aTab) /* PUBLIC API */
{
var parent = this.getParentTab(aTab);
var siblings = parent && parent.parentNode ? this.getChildTabs(parent) : this.visibleRootTabs ;
return siblings.filter(function(aSiblingTab) {
return aSiblingTab != aTab;
});
},
getChildTabs : function TSTBase_getChildTabs(aTab, aAllTabsArray) /* PUBLIC API */
{
var tabs = [];
if (!aTab)
return tabs;
var children = this.getTabAttribute(aTab, this.kCHILDREN);
if (!children)
return tabs;
if (aAllTabsArray)
tabs = aAllTabsArray;
var list = children.split('|');
for (let i = 0, maxi = list.length; i < maxi; i++)
{
let tab = this.getTabById(list[i], aTab);
if (!tab || tab == aTab)
continue;
if (tabs.indexOf(tab) > -1) {
let message = 'broken (possible recursive) tree detected!\n'+
tabs.map(function(aTab) {
return ' '+aTab._tPos+' : '+
aTab.label+'\n '+
this.getTabAttribute(aTab, this.kID);
}, this).join('\n');
dump(message+'\n');
continue;
}
tabs.push(tab);
if (aAllTabsArray)
this.getChildTabs(tab, tabs);
}
return tabs;
},
hasChildTabs : function TSTBase_hasChildTabs(aTab) /* PUBLIC API */
{
if (!aTab)
return false;
return aTab.hasAttribute(this.kCHILDREN);
},
getDescendantTabs : function TSTBase_getDescendantTabs(aTab) /* PUBLIC API */
{
var tabs = [];
this.getChildTabs(aTab, tabs);
return tabs;
},
getFirstChildTab : function TSTBase_getFirstChildTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
if (this.tabsHash) { // XPath-less implementation
let children = this.getTabAttribute(aTab, this.kCHILDREN);
let firstChild = null;
if (children) {
let list = children.split('|');
for (let i = 0, maxi = list.length; i < maxi; i++)
{
firstChild = this.getTabById(list[i], aTab);
if (firstChild && firstChild != aTab)
break;
}
}
return firstChild;
}
return this.evaluateXPath(
'following-sibling::xul:tab[@'+this.kPARENT+'="'+this.getTabAttribute(aTab, this.kID)+'"][1]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getLastChildTab : function TSTBase_getLastChildTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
if (this.tabsHash) { // XPath-less implementation
let children = this.getTabAttribute(aTab, this.kCHILDREN);
let lastChild = null;
if (children) {
let list = children.split('|');
for (let i = list.length-1; i > -1; i--)
{
lastChild = this.getTabById(list[i], aTab);
if (lastChild && lastChild != aTab)
break;
}
}
return lastChild;
}
return this.evaluateXPath(
'following-sibling::xul:tab[@'+this.kPARENT+'="'+this.getTabAttribute(aTab, this.kID)+'"][last()]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
getLastDescendantTab : function TSTBase_getLastDescendantTab(aTab) /* PUBLIC API */
{
if (!aTab)
return null;
if (this.tabsHash) { // XPath-less implementation
let tabs = this.getDescendantTabs(aTab);
return tabs.length ? tabs[tabs.length-1] : null ;
}
var parent = this.getTabAttribute(aTab, this.kPARENT);
return this.evaluateXPath(
'following-sibling::xul:tab['+
(parent ? '@'+this.kPARENT+'="'+parent+'"' : 'not(@'+this.kPARENT+')' )+
'][1]/preceding-sibling::xul:tab[1][not(@'+this.kID+'="'+this.getTabAttribute(aTab, this.kID)+'")]',
aTab,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE
).singleNodeValue;
},
collectRootTabs : function TSTBase_collectRootTabs(aTabs) /* PUBLIC API */
{
aTabs = Array.slice(aTabs);
return aTabs.filter(function(aTab) {
var parent = this.getParentTab(aTab);
return !parent || aTabs.indexOf(parent) < 0;
}, this);
},
getChildIndex : function TSTBase_getChildIndex(aTab, aParent) /* PUBLIC API */
{
var parent = this.getParentTab(aTab);
if (!aParent || !parent || aParent != parent) {
let tabs = [aTab].concat(this.getAncestorTabs(aTab));
parent = aTab;
for (let i = 0, maxi = tabs.length; i < maxi && parent != aParent; i++)
{
aTab = parent;
parent = i < maxi ? tabs[i+1] : null ;
}
if (parent != aParent)
return -1;
aParent = parent;
}
if (aParent) {
let children = this.getTabAttribute(aParent, this.kCHILDREN);
let list = children.split('|');
let id = this.getTabAttribute(aTab, this.kID);
for (let i = 0, maxi = list.length; i < maxi; i++)
{
if (list[i] == id)
return i;
}
return -1;
}
else {
let tabs = this.rootTabs;
for (let i = 0, maxi = tabs.length; i < maxi; i++)
{
if (tabs[i] == aTab)
return i;
}
}
return -1;
},
getXOffsetOfTab : function TSTBase_getXOffsetOfTab(aTab)
{
var extraCondition = this.canCollapseSubtree(aTab) ?
'[not(@'+this.kCOLLAPSED+'="true")]' :
'' ;
return this.evaluateXPath(
'sum((self::* | preceding-sibling::xul:tab[not(@hidden="true")]'+extraCondition+')'+
'/attribute::'+this.kX_OFFSET+')',
aTab,
Ci.nsIDOMXPathResult.NUMBER_TYPE
).numberValue;
},
getYOffsetOfTab : function TSTBase_getYOffsetOfTab(aTab)
{
var extraCondition = this.canCollapseSubtree(aTab) ?
'[not(@'+this.kCOLLAPSED+'="true")]' :
'';
return this.evaluateXPath(
'sum((self::* | preceding-sibling::xul:tab[not(@hidden="true")]'+extraCondition+')'+
'/attribute::'+this.kY_OFFSET+')',
aTab,
Ci.nsIDOMXPathResult.NUMBER_TYPE
).numberValue;
},
getFutureBoxObject : function TSTBase_getFutureBoxObject(aTab)
{
var tabBox = aTab.boxObject;
var xOffset = this.getXOffsetOfTab(aTab);
var yOffset = this.getYOffsetOfTab(aTab);
return {
width : tabBox.width,
height : tabBox.height,
x : tabBox.x + xOffset,
y : tabBox.y + yOffset,
screenX : tabBox.screenX + xOffset,
screenY : tabBox.screenY + yOffset
};
},
getTabActualScreenPosition : function TSTBase_getTabActualScreenPosition(aTab, aOrient)
{
aOrient = aOrient || aTab.parentNode.orient;
return aOrient == 'vertical' ?
this.getTabActualScreenY(aTab) :
this.getTabActualScreenX(aTab) ;
},
MATRIX_PATTERN : /matrix\((-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\)/,
getTabActualScreenX : function TSTBase_getTabActualScreenX(aTab)
{
var x = aTab.boxObject.screenX;
var w = aTab.ownerDocument.defaultView;
var transform = w.getComputedStyle(aTab, null).transform;
var offset = transform && transform.match(this.MATRIX_PATTERN);
offset = offset ? parseFloat(offset[5]) : 0 ;
return x + offset;
},
getTabActualScreenY : function TSTBase_getTabActualScreenY(aTab)
{
var y = aTab.boxObject.screenY;
var w = aTab.ownerDocument.defaultView;
var transform = w.getComputedStyle(aTab, null).transform;
var offset = transform && transform.match(this.MATRIX_PATTERN);
offset = offset ? parseFloat(offset[6]) : 0 ;
return y + offset;
},
isGroupTab : function TSTBase_isGroupTab(aTab, aLazyCheck)
{
return (
(aLazyCheck || aTab.linkedBrowser.sessionHistory.count == 1) &&
aTab.linkedBrowser.currentURI.spec.indexOf('about:treestyletab-group') == 0
);
},
isTemporaryGroupTab : function TSTBase_isTemporaryGroupTab(aTab)
{
return (
this.isGroupTab(aTab, true) &&
/.*[\?&;]temporary=(?:1|yes|true)/i.test(aTab.linkedBrowser.currentURI.spec)
);
},
get pinnedTabsCount()
{
return this.browser.mTabContainer.querySelectorAll('tab[pinned="true"]').length;
},
forceExpandTabs : function TSTBase_forceExpandTabs(aTabs)
{
var collapsedStates = aTabs.map(function(aTab) {
return this.getTabValue(aTab, this.kSUBTREE_COLLAPSED) == 'true';
}, this);
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
let tab = aTabs[i];
this.collapseExpandSubtree(tab, false, true);
this.collapseExpandTab(tab, false, true);
}
return collapsedStates;
},
getTreeStructureFromTabs : function TSTBase_getTreeStructureFromTabs(aTabs)
{
/* this returns...
[A] => -1 (parent is not in this tree)
[B] => 0 (parent is 1st item in this tree)
[C] => 0 (parent is 1st item in this tree)
[D] => 2 (parent is 2nd in this tree)
[E] => -1 (parent is not in this tree, and this creates another tree)
[F] => 0 (parent is 1st item in this another tree)
*/
return this.cleanUpTreeStructureArray(
aTabs.map(function(aTab, aIndex) {
let tab = this.getParentTab(aTab);
let index = tab ? aTabs.indexOf(tab) : -1 ;
return index >= aIndex ? -1 : index ;
}, this),
-1
);
},
cleanUpTreeStructureArray : function TSTBase_cleanUpTreeStructureArray(aTreeStructure, aDefaultParent)
{
var offset = 0;
aTreeStructure = aTreeStructure
.map(function(aPosition, aIndex) {
return (aPosition == aIndex) ? -1 : aPosition ;
})
.map(function(aPosition, aIndex) {
if (aPosition == -1) {
offset = aIndex;
return aPosition;
}
return aPosition - offset;
});
/* The final step, this validates all of values.
Smaller than -1 is invalid, so it becomes to -1. */
aTreeStructure = aTreeStructure.map(function(aIndex) {
return aIndex < -1 ? aDefaultParent : aIndex ;
}, this);
return aTreeStructure;
},
applyTreeStructureToTabs : function TSTBase_applyTreeStructureToTabs(aTabs, aTreeStructure, aExpandStates)
{
var b = this.getTabBrowserFromChild(aTabs[0]);
if (!b)
return;
var sv = b.treeStyleTab;
aTabs = aTabs.slice(0, aTreeStructure.length);
aTreeStructure = aTreeStructure.slice(0, aTabs.length);
aExpandStates = (aExpandStates && typeof aExpandStates == 'object') ?
aExpandStates :
aTabs.map(function(aTab) {
return !!aExpandStates;
});
aExpandStates = aExpandStates.slice(0, aTabs.length);
while (aExpandStates.length < aTabs.length) aExpandStates.push(-1);
var parentTab = null;
for (let i = 0, maxi = aTabs.length; i < maxi; i++)
{
let tab = aTabs[i];
if (sv.isCollapsed(tab))
sv.collapseExpandTab(tab, false, true);
sv.detachTab(tab);
let parentIndexInTree = aTreeStructure[i];
if (parentIndexInTree < 0) // there is no parent, so this is a new parent!
parentTab = sv.getTabAttribute(tab, sv.kID);
let parent = sv.getTabById(parentTab);
if (parent) {
let tabs = [parent].concat(sv.getDescendantTabs(parent));
parent = parentIndexInTree < tabs.length ? tabs[parentIndexInTree] : parent ;
}
if (parent) {
sv.attachTabTo(tab, parent, {
forceExpand : true,
dontMove : true
});
}
}
for (let i = aTabs.length-1; i > -1; i--)
{
sv.collapseExpandSubtree(aTabs[i], !sv.hasChildTabs(aTabs[i]) || !aExpandStates[i], true);
}
},
getTreeStructureFromTabBrowser : function TSTBase_getTreeStructureFromTabBrowser(aTabBrowser)
{
return this.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser));
},
applyTreeStructureToTabBrowser : function TSTBase_applyTreeStructureToTabBrowser(aTabBrowser, aTreeStructure, aExpandAllTree)
{
var tabs = this.getAllTabs(aTabBrowser);
return this.applyTreeStructureToTabs(tabs, aTreeStructure, aExpandAllTree);
},
/* tabbar position */
get position() /* PUBLIC API */
{
return utils.getTreePref('tabbar.position') || 'top';
},
set position(aValue)
{
var position = String(aValue).toLowerCase();
if (!position || !/^(top|bottom|left|right)$/.test(position))
position = 'top';
if (position != utils.getTreePref('tabbar.position'))
utils.setTreePref('tabbar.position', position);
return aValue;
},
get currentTabbarPosition() /* for backward compatibility */
{
return this.position;
},
set currentTabbarPosition(aValue)
{
return this.position = aValue;
},
getPositionFlag : function TSTBase_getPositionFlag(aPosition)
{
aPosition = String(aPosition).toLowerCase();
return (aPosition == 'left') ? this.kTABBAR_LEFT :
(aPosition == 'right') ? this.kTABBAR_RIGHT :
(aPosition == 'bottom') ? this.kTABBAR_BOTTOM :
this.kTABBAR_TOP;
},
/* Pref Listener */
domains : [
'extensions.treestyletab.',
'browser.tabs.animate',
'browser.tabs.insertRelatedAfterCurrent',
'extensions.stm.tabBarMultiRows' // Super Tab Mode
],
observe : function TSTBase_observe(aSubject, aTopic, aData)
{
switch (aTopic)
{
case 'nsPref:changed':
this.onPrefChange(aData);
return;
}
},
onPrefChange : function TSTBase_onPrefChange(aPrefName)
{
var value = prefs.getPref(aPrefName);
switch (aPrefName)
{
case 'extensions.treestyletab.indent.vertical':
this.baseIndentVertical = value;
Services.obs.notifyObservers(null, this.kTOPIC_INDENT_MODIFIED, value);
return;
case 'extensions.treestyletab.indent.horizontal':
this.baseIndentHorizontal = value;
Services.obs.notifyObservers(null, this.kTOPIC_INDENT_MODIFIED, value);
return;
case 'extensions.treestyletab.tabbar.width':
case 'extensions.treestyletab.tabbar.shrunkenWidth':
return this.updateTabWidthPrefs(aPrefName);
case 'browser.tabs.insertRelatedAfterCurrent':
case 'extensions.stm.tabBarMultiRows': // Super Tab Mode
if (this.prefOverriding)
return;
aPrefName += '.override';
prefs.setPref(aPrefName, value);
case 'browser.tabs.insertRelatedAfterCurrent.override':
case 'extensions.stm.tabBarMultiRows.override': // Super Tab Mode
if (prefs.getPref(aPrefName+'.force')) {
let defaultValue = prefs.getDefaultPref(aPrefName);
if (value != defaultValue) {
prefs.setPref(aPrefName, defaultValue);
return;
}
}
this.prefOverriding = true;
let (target = aPrefName.replace('.override', '')) {
let originalValue = prefs.getPref(target);
if (originalValue !== null && originalValue != value)
prefs.setPref(target+'.backup', originalValue);
prefs.setPref(target, prefs.getPref(aPrefName));
}
this.prefOverriding = false;
return;
case 'extensions.treestyletab.clickOnIndentSpaces.enabled':
return this.shouldDetectClickOnIndentSpaces = prefs.getPref(aPrefName);
case 'extensions.treestyletab.tabbar.scroll.smooth':
return this.smoothScrollEnabled = value;
case 'extensions.treestyletab.tabbar.scroll.duration':
return this.smoothScrollDuration = value;
case 'extensions.treestyletab.tabbar.scrollToNewTab.mode':
return this.scrollToNewTabMode = value;
case 'extensions.treestyletab.tabbar.narrowScrollbar.size':
return this.updateNarrowScrollbarStyle();
case 'browser.tabs.animate':
return this.animationEnabled = value;
case 'extensions.treestyletab.animation.indent.duration':
return this.indentDuration = value;
case 'extensions.treestyletab.animation.collapse.duration':
return this.collapseDuration = value;
case 'extensions.treestyletab.twisty.expandSensitiveArea':
return this.shouldExpandTwistyArea = value;
case 'extensions.treestyletab.counter.role.horizontal':
return this.counterRoleHorizontal = value;
case 'extensions.treestyletab.counter.role.vertical':
return this.counterRoleVertical = value;
default:
return;
}
},
updateTabWidthPrefs : function TSTBase_updateTabWidthPrefs(aPrefName)
{
var expanded = utils.getTreePref('tabbar.width');
var shrunken = utils.getTreePref('tabbar.shrunkenWidth');
var originalExpanded = expanded;
var originalShrunken = shrunken;
if (aPrefName == 'extensions.treestyletab.tabbar.shrunkenWidth') {
if (expanded <= shrunken)
expanded = parseInt(shrunken / this.DEFAULT_SHRUNKEN_WIDTH_RATIO)
let w = this.browserWindow;
if (w && expanded > w.gBrowser.boxObject.width) {
expanded = w.gBrowser.boxObject.width * this.MAX_TABBAR_SIZE_RATIO;
if (expanded <= shrunken)
shrunken = parseInt(expanded * this.DEFAULT_SHRUNKEN_WIDTH_RATIO)
}
}
else {
if (expanded <= shrunken)
shrunken = parseInt(expanded * this.DEFAULT_SHRUNKEN_WIDTH_RATIO);
}
if (expanded != originalExpanded ||
shrunken != originalShrunken) {
utils.setTreePref('tabbar.width', Math.max(0, expanded));
utils.setTreePref('tabbar.shrunkenWidth', Math.max(0, shrunken));
}
},
get shouldApplyNewPref()
{
return (
!this.applyOnlyForActiveWindow ||
this.window == this.topBrowserWindow
) &&
!this.inWindowDestoructionProcess;
},
applyOnlyForActiveWindow : false,
setPrefForActiveWindow : function TSTBase_setPrefForActiveWindow(aTask) {
TreeStyleTabBase.applyOnlyForActiveWindow = true;
try {
aTask.call(this);
}
finally {
TreeStyleTabBase.applyOnlyForActiveWindow = false;
}
}
});
TreeStyleTabBase.init();