var TreeStyleTabService = { PREFROOT : 'extensions.treestyletab@piro.sakura.ne.jp', kID : 'treestyletab-id', kCHILDREN : 'treestyletab-children', kPARENT : 'treestyletab-parent', kINSERT_BEFORE : 'treestyletab-insert-before', kSUBTREE_COLLAPSED : 'treestyletab-subtree-collapsed', kCOLLAPSED : 'treestyletab-collapsed', kNEST : 'treestyletab-nest', kDROP_POSITION : 'treestyletab-drop-position', kTABBAR_POSITION : 'treestyletab-tabbar-position', kVERTICAL : 'treestyletab-vertical', kINVERTED : 'treestyletab-appearance-inverted', kSTYLE : 'treestyletab-style', kFIRSTTAB_BORDER : 'treestyletab-firsttab-border', kTWISTY : 'treestyletab-twisty', kTWISTY_CONTAINER : 'treestyletab-twisty-container', kDROP_MARKER : 'treestyletab-drop-marker', kDROP_MARKER_CONTAINER : 'treestyletab-drop-marker-container', kCOUNTER : 'treestyletab-counter', kCOUNTER_CONTAINER : 'treestyletab-counter-container', kSPLITTER : 'treestyletab-splitter', kMENUITEM_REMOVESUBTREE_SELECTION : 'multipletab-selection-item-removeTabSubTree', kMENUITEM_REMOVESUBTREE_CONTEXT : 'context-item-removeTabSubTree', kFOCUS_ALL : 0, kFOCUS_VISIBLE : 1, kDROP_BEFORE : -1, kDROP_ON : 0, kDROP_AFTER : 1, kACTION_MOVE : 1, kACTION_ATTACH : 2, kACTION_PART : 4, kTABBAR_TOP : 1, kTABBAR_BOTTOM : 2, kTABBAR_LEFT : 4, kTABBAR_RIGHT : 8, kTABBAR_HORIZONTAL : 3, kTABBAR_VERTICAL : 12, levelMargin : 12, levelMarginProp : 'margin-left', positionProp : 'screenY', sizeProp : 'height', invertedSizeProp : 'width', 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 ''; } } }, get SessionStore() { if (!this._SessionStore) { this._SessionStore = Components.classes['@mozilla.org/browser/sessionstore;1'].getService(Components.interfaces.nsISessionStore); } return this._SessionStore; }, _SessionStore : null, get ObserverService() { if (!this._ObserverService) { this._ObserverService = Components.classes['@mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService); } return this._ObserverService; }, _ObserverService : null, get IOService() { if (!this._IOService) { this._IOService = Components.classes['@mozilla.org/network/io-service;1'].getService(Components.interfaces.nsIIOService); } return this._IOService; }, _IOService : null, /* API */ readyToOpenChildTab : function(aFrameOrTabBrowser, aMultiple) { var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); if (!frame) return; var ownerBrowser = this.getTabBrowserFromFrame(frame); ownerBrowser.__treestyletab__readyToAttachNewTab = true; ownerBrowser.__treestyletab__readyToAttachMultiple = aMultiple || false ; ownerBrowser.__treestyletab__parentTab = this.getTabFromFrame(frame, ownerBrowser).getAttribute(this.kID); }, readyToOpenNewTabGroup : function(aFrameOrTabBrowser) { var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); if (!frame) return; this.stopToOpenChildTab(frame); var ownerBrowser = this.getTabBrowserFromFrame(frame); ownerBrowser.__treestyletab__readyToAttachNewTabGroup = true; ownerBrowser.__treestyletab__readyToAttachMultiple = true; }, stopToOpenChildTab : function(aFrameOrTabBrowser) { var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); if (!frame) return; var ownerBrowser = this.getTabBrowserFromFrame(frame); ownerBrowser.__treestyletab__readyToAttachNewTab = false; ownerBrowser.__treestyletab__readyToAttachNewTabGroup = false; ownerBrowser.__treestyletab__readyToAttachMultiple = false; ownerBrowser.__treestyletab__parentTab = null; }, checkToOpenChildTab : function(aFrameOrTabBrowser) { var frame = this.getFrameFromTabBrowserElements(aFrameOrTabBrowser); if (!frame) return false; var ownerBrowser = this.getTabBrowserFromFrame(frame); return ownerBrowser.__treestyletab__readyToAttachNewTab || ownerBrowser.__treestyletab__readyToAttachNewTabGroup ? true : false ; }, /* Utilities */ isEventFiredOnTabIcon : function(aEvent) { var tab = this.getTabFromEvent(aEvent); if (!tab) return false; var icon = document.getAnonymousElementByAttribute(tab, 'class', 'tab-icon'); var box = icon.boxObject; if (aEvent.screenX > box.screenX && aEvent.screenY > box.screenY && aEvent.screenX < box.screenX + box.width && aEvent.screenY < box.screenY + box.height) return true; return false; }, isEventFiredOnTwisty : function(aEvent) { var tab = this.getTabFromEvent(aEvent); var twisty = document.getAnonymousElementByAttribute(tab, 'class', this.kTWISTY); if (!twisty || !tab.hasAttribute(this.kCHILDREN)) return false; var box = twisty.parentNode.parentNode.boxObject; return !(aEvent.screenX < box.screenX || aEvent.screenX > box.screenX + box.width || aEvent.screenY < box.screenY || aEvent.screenY > box.screenY + box.height); }, get browser() { return 'SplitBrowser' && this.getPref('extensions.treestyletab.inSubbrowsers.enabled') ? SplitBrowser.activeBrowser : gBrowser ; }, evaluateXPath : function(aExpression, aContext, aType) { if (!aType) aType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE; try { var xpathResult = document.evaluate( aExpression, aContext, this.NSResolver, aType, null ); } catch(e) { return { singleNodeValue : null, snapshotLength : 0, snapshotItem : function() { return null } }; } return xpathResult; }, getArrayFromXPathResult : function(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; }, getTabFromEvent : function(aEvent) { var target = aEvent.originalTarget || aEvent.target; while (target.localName != 'tab' && target.localName != 'tabs' && target.parentNode) target = target.parentNode; return (target.localName == 'tab') ? target : null ; }, getTabFromFrame : function(aFrame, aTabBrowser) { var b = aTabBrowser || this.browser; var docShell = aFrame.top .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShell); var tabs = b.mTabContainer.childNodes; for (var i = 0, maxi = tabs.length; i < maxi; i++) { if (tabs[i].linkedBrowser.docShell == docShell) return tabs[i]; } return null; }, getTabBrowserFromChildren : function(aTab) { if (!aTab) return null; if (aTab.__treestyletab__linkedTabBrowser) return aTab.__treestyletab__linkedTabBrowser; var target = aTab; while (target.localName != 'tabbrowser' && target.parentNode) target = target.parentNode; return (target.localName == 'tabbrowser') ? target : null ; }, getTabBrowserFromFrame : function(aFrame) { return ('SplitBrowser' in window) ? this.getTabBrowserFromChildren(SplitBrowser.getSubBrowserAndBrowserFromFrame(aFrame.top).browser) : gBrowser ; }, getFrameFromTabBrowserElements : function(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.getTabBrowserFromChildren(frame); if (!frame) return null; frame = frame.contentWindow; } } if (!frame) frame = this.browser.contentWindow; return frame; }, isTabVertical : function(aTabOrChild) { var b = this.getTabBrowserFromChildren(aTabOrChild); if (!b) return false; var box = b.mTabContainer.mTabstrip || b.mTabContainer ; return (box.getAttribute('orient') || window.getComputedStyle(box, '').getPropertyValue('-moz-box-orient')) == 'vertical'; }, isTabVisible : function(aTab) { if (!aTab) return false; var tabBox = aTab.boxObject; var barBox = this.getTabBrowserFromChildren(aTab).mTabContainer.mTabstrip.boxObject; return (tabBox.screenX >= barBox.screenX && tabBox.screenX + tabBox.width <= barBox.screenX + barBox.width && tabBox.screenY >= barBox.screenY && tabBox.screenY + tabBox.height <= barBox.screenY + barBox.height); }, cleanUpTabsArray : function(aTabs) { var b = this.getTabBrowserFromChildren(aTabs[0]); var self = this; aTabs = aTabs.map(function(aTab) { return aTab.getAttribute(self.kID); }); aTabs.sort(); aTabs = aTabs.join('|').replace(/([^\|]+)(\|\1)+/g, '$1').split('|'); for (var i = 0, maxi = aTabs.length; i < maxi; i++) { aTabs[i] = this.getTabById(aTabs[i], b); } return aTabs; }, makeURIFromSpec : function(aURI) { var newURI; aURI = aURI || ''; if (aURI && String(aURI).indexOf('file:') == 0) { var fileHandler = this.IOService.getProtocolHandler('file').QueryInterface(Components.interfaces.nsIFileProtocolHandler); var tempLocalFile = fileHandler.getFileFromURLSpec(aURI); newURI = this.IOService.newFileURI(tempLocalFile); } else { newURI = this.IOService.newURI(aURI, null, null); } return newURI; }, /* Initializing */ init : function() { if (!('gBrowser' in window)) return; window.removeEventListener('load', this, false); document.getElementById('contentAreaContextMenu').addEventListener('popupshowing', this, false); if (this.getPref('extensions.treestyletab.inSubbrowsers.enabled')) { var appcontent = document.getElementById('appcontent'); appcontent.addEventListener('SubBrowserAdded', this, false); appcontent.addEventListener('SubBrowserRemoveRequest', this, false); } this.addPrefListener(this); this.observe(null, 'nsPref:changed', 'extensions.treestyletab.levelMargin'); this.overrideGlobalFunctions(); this.overrideExtensions(); // hacks.js this.initTabBrowser(gBrowser); }, initTabBrowser : function(aTabBrowser) { this.initTabbar(aTabBrowser); aTabBrowser.mTabContainer.addEventListener('TreeStyleTab:TabOpen', this, true); aTabBrowser.mTabContainer.addEventListener('TabClose', this, true); aTabBrowser.mTabContainer.addEventListener('TabMove', this, true); aTabBrowser.mTabContainer.addEventListener('SSTabRestoring', this, true); aTabBrowser.mTabContainer.addEventListener('click', this, true); aTabBrowser.mTabContainer.addEventListener('dblclick', this, true); aTabBrowser.mTabContainer.addEventListener('mousedown', this, true); aTabBrowser.mTabContainer.addEventListener('select', this, true); // aTabBrowser.mPanelContainer.addEventListener('click', this, true); aTabBrowser.__treestyletab__levelMargin = -1; var selectNewTab = '_selectNewTab' in aTabBrowser.mTabContainer ? '_selectNewTab' : 'selectNewTab' ; // Fx3 / Fx2 eval('aTabBrowser.mTabContainer.'+selectNewTab+' = '+ aTabBrowser.mTabContainer[selectNewTab].toSource().replace( /\{/, <> ) ); eval('aTabBrowser.mTabContainer.advanceSelectedTab = '+ aTabBrowser.mTabContainer.advanceSelectedTab.toSource().replace( /\{/, <> ) ); eval('aTabBrowser.mTabContainer._handleTabSelect = '+ aTabBrowser.mTabContainer._handleTabSelect.toSource().replace( /\{/, <> ) ); eval('aTabBrowser.mTabContainer._notifyBackgroundTab = '+ aTabBrowser.mTabContainer._notifyBackgroundTab.toSource().replace( /\.screenX/g, '[TreeStyleTabService.positionProp]' ).replace( /\.width/g, '[TreeStyleTabService.sizeProp]' ) ); eval('aTabBrowser.canDrop = '+ aTabBrowser.canDrop.toSource().replace( /\.screenX/g, '[TreeStyleTabService.positionProp]' ).replace( /\.width/g, '[TreeStyleTabService.sizeProp]' ).replace( /return true;/, <> ) ); eval('aTabBrowser.onDragOver = '+ aTabBrowser.onDragOver.toSource().replace( '{', <> ) ); eval('aTabBrowser.onDragExit = '+ aTabBrowser.onDragExit.toSource().replace( /(this.mTabDropIndicatorBar\.[^;]+;)/, '$1; TreeStyleTabService.clearDropPosition(this);' ) ); eval('aTabBrowser.onDrop = '+ aTabBrowser.onDrop.toSource().replace( '{', <> ).replace( /(if \([^\)]+\) \{)/, '$1'+<> ).replace( /(this.loadOneTab\([^;]+\));/, <> ).replace( 'document.getBindingParent(aEvent.originalTarget).localName != "tab"', '!TreeStyleTabService.getTabFromEvent(aEvent)' ).replace( 'var tab = aEvent.target;', <> ) ); eval('aTabBrowser.getNewIndex = '+ aTabBrowser.getNewIndex.toSource().replace( /\.screenX/g, '[TreeStyleTabService.positionProp]' ).replace( /\.width/g, '[TreeStyleTabService.sizeProp]' ) ); eval('aTabBrowser.moveTabForward = '+ aTabBrowser.moveTabForward.toSource().replace( '{', '{ var nextTab;' ).replace( 'tabPos < this.browsers.length - 1', 'nextTab = TreeStyleTabService.getNextSiblingTab(this.mCurrentTab)' ).replace( 'tabPos + 1', 'nextTab._tPos' ).replace( 'this.moveTabTo(', <> ).replace( 'this.moveTabToStart();', <> ) ); eval('aTabBrowser.moveTabBackward = '+ aTabBrowser.moveTabBackward.toSource().replace( '{', '{ var prevTab;' ).replace( 'tabPos > 0', 'prevTab = TreeStyleTabService.getPreviousSiblingTab(this.mCurrentTab)' ).replace( 'tabPos - 1', 'prevTab._tPos' ).replace( 'this.moveTabToEnd();', <> ) ); eval('aTabBrowser._keyEventHandler.handleEvent = '+ aTabBrowser._keyEventHandler.handleEvent.toSource().replace( 'this.tabbrowser.moveTabOver(aEvent);', <> ).replace( 'this.tabbrowser.moveTabForward();', <> ).replace( 'this.tabbrowser.moveTabBackward();', <> ) ); eval('aTabBrowser.loadTabs = '+ aTabBrowser.loadTabs.toSource().replace( 'var tabNum = ', <> ).replace( 'if (!aLoadInBackground)', <> ) ); var addTabMethod = 'addTab'; var removeTabMethod = 'removeTab'; if (aTabBrowser.__tabextensions__addTab) { addTabMethod = '__tabextensions__addTab'; removeTabMethod = '__tabextensions__removeTab'; } aTabBrowser.__treestyletab__originalAddTab = aTabBrowser[addTabMethod]; aTabBrowser[addTabMethod] = function() { var tab = this.__treestyletab__originalAddTab.apply(this, arguments); try { TreeStyleTabService.initTab(tab, this); } catch(e) { } return tab; }; aTabBrowser.__treestyletab__originalRemoveTab = aTabBrowser[removeTabMethod]; aTabBrowser[removeTabMethod] = function(aTab) { TreeStyleTabService.destroyTab(aTab, this); var retVal = this.__treestyletab__originalRemoveTab.apply(this, arguments); try { if (aTab.parentNode) TreeStyleTabService.initTab(aTab, this); } catch(e) { } return retVal; }; var tabs = aTabBrowser.mTabContainer.childNodes; for (var i = 0, maxi = tabs.length; i < maxi; i++) { this.initTab(tabs[i], aTabBrowser); } aTabBrowser.__treestyletab__observer = new TreeStyleTabBrowserObserver(aTabBrowser); aTabBrowser.__treestyletab__observer.observe(null, 'nsPref:changed', 'extensions.treestyletab.tabbar.style'); aTabBrowser.__treestyletab__observer.observe(null, 'nsPref:changed', 'extensions.treestyletab.showBorderForFirstTab'); delete addTabMethod; delete removeTabMethod; delete i; delete maxi; delete tabs; }, initTab : function(aTab, aTabBrowser) { var id = 'tab-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>'; this.setTabValue(aTab, this.kID, id); aTab.__treestyletab__linkedTabBrowser = aTabBrowser; var pos = this.getPref('extensions.treestyletab.tabbar.position'); if (pos == 'left' || pos == 'right') { aTab.setAttribute('align', 'stretch'); aTab.maxWidth = 65000; aTab.minWidth = 0; } this.initTabContents(aTab); var event = document.createEvent('Events'); event.initEvent('TreeStyleTab:TabOpen', true, false); aTab.dispatchEvent(event); }, initTabContents : function(aTab) { if (!document.getAnonymousElementByAttribute(aTab, 'class', this.kTWISTY)) { var twisty = document.createElement('image'); twisty.setAttribute('class', this.kTWISTY); var container = document.createElement('hbox'); container.setAttribute('class', this.kTWISTY_CONTAINER); container.appendChild(twisty); var icon = document.getAnonymousElementByAttribute(aTab, 'class', 'tab-icon'); icon.appendChild(container); var marker = document.createElement('image'); marker.setAttribute('class', this.kDROP_MARKER); container = document.createElement('hbox'); container.setAttribute('class', this.kDROP_MARKER_CONTAINER); container.appendChild(marker); icon.appendChild(container); } if (!document.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER_CONTAINER)) { var counter = document.createElement('hbox'); counter.setAttribute('class', this.kCOUNTER_CONTAINER); counter.appendChild(document.createElement('label')); counter.lastChild.setAttribute('class', this.kCOUNTER); counter.lastChild.setAttribute('value', '(0)'); var text = document.getAnonymousElementByAttribute(aTab, 'class', 'tab-text'); if (text) { if (text.nextSibling) text.parentNode.insertBefore(counter, text.nextSibling); else text.parentNode.appendChild(counter); } } }, overrideGlobalFunctions : function() { var funcs; eval('nsContextMenu.prototype.openLinkInTab = '+ nsContextMenu.prototype.openLinkInTab.toSource().replace( '{', <> ) ); eval('nsContextMenu.prototype.openFrameInTab = '+ nsContextMenu.prototype.openFrameInTab.toSource().replace( '{', <> ) ); eval('nsContextMenu.prototype.viewImage = '+ nsContextMenu.prototype.viewImage.toSource().replace( 'openUILink(', <> ) ); eval('nsContextMenu.prototype.viewBGImage = '+ nsContextMenu.prototype.viewBGImage.toSource().replace( 'openUILink(', <> ) ); eval('nsContextMenu.prototype.addDictionaries = '+ nsContextMenu.prototype.addDictionaries.toSource().replace( 'openUILinkIn(', <> ) ); eval('nsContextMenu.prototype.viewPartialSource = '+ nsContextMenu.prototype.viewPartialSource.toSource().replace( 'window.openDialog(', <> ) ); eval('nsContextMenu.prototype.viewFrameSource = '+ nsContextMenu.prototype.viewFrameSource.toSource().replace( '{', <> ) ); eval('window.BrowserViewSourceOfDocument = '+ window.BrowserViewSourceOfDocument.toSource().replace( 'ViewSourceOfURL(', <> ) ); eval('window.ViewSourceOfURL = '+ window.ViewSourceOfURL.toSource().replace( 'gViewSourceUtils.openInExternalEditor(', <> ) ); eval('gViewSourceUtils.openInInternalViewer = '+ gViewSourceUtils.openInInternalViewer.toSource().replace( /(openDialog\([^\)]+\))/, <> ) ); funcs = 'handleLinkClick __splitbrowser__handleLinkClick __ctxextensions__handleLinkClick'.split(' '); for (var i in funcs) { if (funcs[i] in window) eval('window.'+funcs[i]+' = '+ window[funcs[i]].toSource().replace( /openNewTabWith\(/g, <> ) ); } funcs = 'gotoHistoryIndex BrowserForward BrowserBack __rewindforward__BrowserForward __rewindforward__BrowserBack'.split(' '); for (var i in funcs) { if (funcs[i] in window) eval('window.'+funcs[i]+' = '+ window[funcs[i]].toSource().replace( /openUILinkIn\(/g, <> ) ); } eval('window.BrowserHomeClick = '+ window.BrowserHomeClick.toSource().replace( 'gBrowser.loadTabs(', <> ) ); eval('nsBrowserAccess.prototype.openURI = '+ nsBrowserAccess.prototype.openURI.toSource().replace( /switch\s*\(aWhere\)/, <> ) ); window.QueryInterface(Components.interfaces.nsIDOMChromeWindow).browserDOMWindow = null; window.QueryInterface(Components.interfaces.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); eval('FeedHandler.loadFeed = '+ FeedHandler.loadFeed.toSource().replace( 'openUILink(', <> ) ); if ('BookmarksCommand' in window) { // Firefox 2 eval('BookmarksCommand.openGroupBookmark = '+ BookmarksCommand.openGroupBookmark.toSource().replace( 'browser.addTab(uri);', <> ).replace( 'if (index == index0)', <> ) ); } if ('PlacesUtils' in window) { // Firefox 3 eval('PlacesUtils.openContainerNodeInTabs = '+ PlacesUtils.openContainerNodeInTabs.toSource().replace( 'this._openTabset(', <> ) ); } }, destroy : function() { this.destroyTabBrowser(gBrowser); window.removeEventListener('unload', this, false); document.getElementById('contentAreaContextMenu').removeEventListener('popupshowing', this, false); if (this.getPref('extensions.treestyletab.inSubbrowsers.enabled')) { var appcontent = document.getElementById('appcontent'); appcontent.removeEventListener('SubBrowserAdded', this, false); appcontent.removeEventListener('SubBrowserRemoveRequest', this, false); } this.removePrefListener(this); var tabs = gBrowser.mTabContainer.childNodes; for (var i = 0, maxi = tabs.length; i < maxi; i++) { this.destroyTab(tabs[i]); } }, destroyTabBrowser : function(aTabBrowser) { aTabBrowser.__treestyletab__observer.destroy(); delete aTabBrowser.__treestyletab__observer; var tabs = aTabBrowser.mTabContainer.childNodes; var parent; for (var i = 0, maxi = tabs.length; i < maxi; i++) { parent = this.getParentTab(tabs[i]); if (parent) this.setTabValue(tabs[i], this.kPARENT, parent.getAttribute(this.kID)); } aTabBrowser.mTabContainer.removeEventListener('TreeStyleTab:TabOpen', this, true); aTabBrowser.mTabContainer.removeEventListener('TabClose', this, true); aTabBrowser.mTabContainer.removeEventListener('TabMove', this, true); aTabBrowser.mTabContainer.removeEventListener('SSTabRestoring', this, true); aTabBrowser.mTabContainer.removeEventListener('click', this, true); aTabBrowser.mTabContainer.removeEventListener('dblclick', this, true); aTabBrowser.mTabContainer.removeEventListener('mousedown', this, true); aTabBrowser.mTabContainer.removeEventListener('select', this, true); // aTabBrowser.mPanelContainer.removeEventListener('click', this, true); }, destroyTab : function(aTab, aTabBrowser) { delete aTab.__treestyletab__linkedTabBrowser; }, /* Event Handling */ handleEvent : function(aEvent) { switch (aEvent.type) { case 'TreeStyleTab:TabOpen': this.onTabAdded(aEvent); return; case 'TabClose': this.onTabRemoved(aEvent); return; case 'TabMove': this.onTabMove(aEvent); return; case 'SSTabRestoring': this.onTabRestored(aEvent); return; case 'click': if (aEvent.target.ownerDocument == document) { this.onTabClick(aEvent); return; } /* var isMiddleClick = ( aEvent.button == 1 || aEvent.button == 0 && (aEvent.ctrlKey || aEvent.metaKey) ); var node = aEvent.originalTarget; while (node.parentNode && !node.href) { node = node.parentNode; } if (node.href && isMiddleClick) { var b = this.getTabBrowserFromChildren(aEvent.currentTarget); this.readyToOpenChildTab(b.selectedTab); } */ return; case 'dblclick': var tab = this.getTabFromEvent(aEvent); if (tab && tab.getAttribute(this.kCHILDREN) && this.getPref('extensions.treestyletab.collapseExpandSubTree.dblclick')) { this.collapseExpandTabSubTree(tab, tab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); aEvent.preventDefault(); aEvent.stopPropagation(); } return; case 'mousedown': this.onTabMouseDown(aEvent); return; case 'select': this.onTabSelect(aEvent); return; case 'load': this.init(); return; case 'unload': this.destroy(); return; case 'popupshowing': if (aEvent.target != aEvent.currentTarget) return; this.initContextMenu(); return; case 'SubBrowserAdded': this.initTabBrowser(aEvent.originalTarget.browser); return; case 'SubBrowserRemoveRequest': this.destroyTabBrowser(aEvent.originalTarget.browser); return; } }, onTabAdded : function(aEvent) { var tab = aEvent.originalTarget; var b = this.getTabBrowserFromChildren(tab); if (b.__treestyletab__readyToAttachNewTab) { var parent = this.getTabById(b.__treestyletab__parentTab, b); if (parent) this.attachTabTo(tab, parent); } if (!b.__treestyletab__readyToAttachMultiple) { this.stopToOpenChildTab(b); } }, onTabRemoved : function(aEvent) { var tab = aEvent.originalTarget; var b = this.getTabBrowserFromChildren(tab); if (tab.getAttribute(this.kSUBTREE_COLLAPSED) == 'true') { var descendant = this.getDescendantTabs(tab); for (var i = descendant.length-1; i > -1; i--) { b.removeTab(descendant[i]); } } var firstChild = this.getFirstChildTab(tab); var parentTab = this.getParentTab(tab); var nextFocusedTab = null; if (parentTab) { this.setTabValue(tab, this.kPARENT, parentTab.getAttribute(this.kID)); var next = this.getNextSiblingTab(tab); if (next) this.setTabValue(tab, this.kINSERT_BEFORE, next.getAttribute(this.kID)); } if (firstChild) { var backupChildren = this.getTabValue(tab, this.kCHILDREN); var children = this.getChildTabs(tab); var self = this; var attach = this.getPref('extensions.treestyletab.attachChildrenToGrandParentOnRemoveTab'); var processTab = !attach ? function(aTab) { self.partTab(aTab, true); self.moveTabSubTreeTo(aTab, b.mTabContainer.lastChild._tPos); } : parentTab ? function(aTab) { self.attachTabTo(aTab, parentTab, { insertBefore : tab, dontUpdateIndent : true }); } : function(aTab) { self.partTab(aTab, true); }; for (var i = 0, maxi = children.length; i < maxi; i++) { processTab(children[i]); } this.updateTabsIndent(children); this.checkTabsIndentOverflow(b); if (attach) { nextFocusedTab = firstChild; } this.setTabValue(tab, this.kCHILDREN, backupChildren); } if (!nextFocusedTab) { if (parentTab) { var firstSibling = this.getFirstChildTab(parentTab); var lastSibling = this.getLastChildTab(parentTab); if (tab == lastSibling) { if (tab == firstSibling) { // there is only one child nextFocusedTab = parentTab; } else { // previous sibling tab nextFocusedTab = this.getPreviousSiblingTab(tab); } } this.partTab(tab, true); } else { nextFocusedTab = this.getNextSiblingTab(tab); } } if (nextFocusedTab && b.selectedTab == tab) b.selectedTab = nextFocusedTab; this.checkTabsIndentOverflow(b); }, onTabMove : function(aEvent) { var tab = aEvent.originalTarget; this.initTabContents(tab); // twisty vanished after the tab is moved!! var b = this.getTabBrowserFromChildren(tab); if (tab.getAttribute(this.kCHILDREN) && !b.__treestyletab__isSubTreeMoving) { this.moveTabSubTreeTo(tab, tab._tPos); } var parentTab = this.getParentTab(tab); if (parentTab && !b.__treestyletab__isSubTreeChildrenMoving) { var children = this.getChildTabs(parentTab); children.sort(function(aA, aB) { return aA._tPos - aB._tPos; }); var self = this; this.setTabValue(parentTab, this.kCHILDREN, children.map(function(aItem) { return aItem.getAttribute(self.kID); }).join('|')); } }, onTabRestored : function(aEvent) { var tab = aEvent.originalTarget; var b = this.getTabBrowserFromChildren(tab); var id = this.getTabValue(tab, this.kID); this.setTabValue(tab, this.kID, id); var isSubTreeCollapsed = (this.getTabValue(tab, this.kSUBTREE_COLLAPSED) == 'true'); var children = this.getTabValue(tab, this.kCHILDREN); if (children) { children = children.split('|'); var tabs = []; for (var i = 0, maxi = children.length; i < maxi; i++) { if (children[i] && (children[i] = this.getTabById(children[i], b))) { this.attachTabTo(children[i], tab, { dontExpand : true, dontUpdateIndent : true }); tabs.push(children[i]); } } } var parent = this.getTabValue(tab, this.kPARENT); var before = this.getTabValue(tab, this.kINSERT_BEFORE); if (parent && (parent = this.getTabById(parent, b))) { this.attachTabTo(tab, parent, { dontExpand : true, insertBefore : (before ? this.getTabById(before, b) : null ), dontUpdateIndent : true }); this.deleteTabValue(tab, this.kPARENT); this.updateTabsIndent([tab]); this.checkTabsIndentOverflow(b); } else if (children) { this.updateTabsIndent(tabs); this.checkTabsIndentOverflow(b); } if (isSubTreeCollapsed) { this.collapseExpandTabSubTree(tab, isSubTreeCollapsed); } }, onTabMouseDown : function(aEvent) { if (aEvent.button != 0 || !this.isEventFiredOnTwisty(aEvent)) return; this.getTabFromEvent(aEvent).__treestyletab__preventSelect = true; }, onTabClick : function(aEvent) { if (aEvent.button != 0 || !this.isEventFiredOnTwisty(aEvent)) return; var tab = this.getTabFromEvent(aEvent); this.collapseExpandTabSubTree(tab, tab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true'); aEvent.preventDefault(); aEvent.stopPropagation(); }, onTabSelect : function(aEvent) { var b = this.getTabBrowserFromChildren(aEvent.currentTarget); var tab = b.selectedTab /* var p; if ((tab.getAttribute(this.kCOLLAPSED) == 'true') && (p = this.getParentTab(tab))) { b.selectedTab = p; } */ if (tab.getAttribute(this.kCOLLAPSED) == 'true') { var parentTab = tab; while (parentTab = this.getParentTab(parentTab)) { this.collapseExpandTabSubTree(parentTab, false); } } else if (tab.getAttribute(this.kCHILDREN) && (tab.getAttribute(this.kSUBTREE_COLLAPSED) == 'true') && this.getPref('extensions.treestyletab.autoCollapseExpandSubTreeOnSelect')) { this.collapseExpandTreesIntelligentlyFor(tab); } }, onTabbarResized : function(aEvent) { var b = this.getTabBrowserFromChildren(aEvent.originalTarget); this.setPref('extensions.treestyletab.tabbar.width', b.mStrip.boxObject.width); }, processDropAction : function(aInfo, aTabBrowser, aTarget) { var b = this.getTabBrowserFromChildren(aTabBrowser); var tabs = b.mTabContainer.childNodes; if (aTarget && aInfo.action & this.kACTION_PART) { this.partTab(aTarget); } else if (aInfo.action & this.kACTION_ATTACH) { if (aInfo.parent) this.attachTabTo(aTarget, aInfo.parent); else this.partTab(aTarget); } else { return false; } if ( aInfo.action & this.kACTION_MOVE && ( !aInfo.insertBefore || this.getNextVisibleTab(aTarget) != aInfo.insertBefore ) ) { var newIndex = aInfo.insertBefore ? aInfo.insertBefore._tPos : tabs.length - 1 ; if (aInfo.insertBefore && newIndex > aTarget._tPos) newIndex--; b.moveTabTo(aTarget, newIndex); } return true; }, initContextMenu : function() { var item = document.getElementById('context-treestyletab-openSelectionLinks'); var sep = document.getElementById('context-treestyletab-openSelectionLinks-separator'); if (this.getPref('extensions.treestyletab.show.openSelectionLinks') && this.getSelectionLinks().length) { item.removeAttribute('hidden'); sep.removeAttribute('hidden'); } else { item.setAttribute('hidden', true); sep.setAttribute('hidden', true); } }, /* Tab Utilities */ getTabValue : function(aTab, aKey) { var value = null; try { value = this.SessionStore.getTabValue(aTab, aKey); } catch(e) { } return value; }, setTabValue : function(aTab, aKey, aValue) { if (!aValue) { return this.deleteTabValue(aTab, aKey); } aTab.setAttribute(aKey, aValue); try { this.SessionStore.setTabValue(aTab, aKey, aValue); } catch(e) { } return aValue; }, deleteTabValue : function(aTab, aKey) { aTab.removeAttribute(aKey); try { this.SessionStore.deleteTabValue(aTab, aKey); } catch(e) { } }, getTabById : function(aId, aTabBrowser) { return this.evaluateXPath( 'descendant::xul:tab[@'+this.kID+' = "'+aId+'"]', aTabBrowser.mTabContainer, XPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; }, getNextVisibleTab : function(aTab) { var xpathResult = this.evaluateXPath( 'following-sibling::xul:tab[not(@'+TreeStyleTabService.kCOLLAPSED+'="true")]', aTab ); return xpathResult.snapshotItem(0); }, getPreviousVisibleTab : function(aTab) { var xpathResult = this.evaluateXPath( 'preceding-sibling::xul:tab[not(@'+TreeStyleTabService.kCOLLAPSED+'="true")]', aTab ); return xpathResult.snapshotItem(xpathResult.snapshotLength-1); }, /* tree */ getRootTabs : function(aTabBrowser) { return this.evaluateXPath( 'child::xul:tab[not(@'+this.kNEST+') or @'+this.kNEST+'="0" or @'+this.kNEST+'=""]', aTabBrowser.mTabContainer ); }, getParentTab : function(aTab) { var id = aTab.getAttribute(this.kID); return this.evaluateXPath( 'parent::*/child::xul:tab[contains(@'+this.kCHILDREN+', "'+id+'")]', aTab, XPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; }, getNextSiblingTab : function(aTab) { var id = aTab.getAttribute(this.kID); var parentTab = this.getParentTab(aTab); if (!parentTab) { var next = aTab; do { next = next.nextSibling; } while (next && this.getParentTab(next)); return next; } var b = this.getTabBrowserFromChildren(aTab); var children = parentTab.getAttribute(this.kCHILDREN); if (children) { var list = ('|'+children).split('|'+id)[1].split('|'); for (var i = 0, maxi = list.length; i < maxi; i++) { var firstChild = this.getTabById(list[i], b); if (firstChild) return firstChild; } } return null; }, getPreviousSiblingTab : function(aTab) { var id = aTab.getAttribute(this.kID); var parentTab = this.getParentTab(aTab); if (!parentTab) { var prev = aTab; do { prev = prev.previousSibling; } while (prev && this.getParentTab(prev)); return prev; } var b = this.getTabBrowserFromChildren(aTab); var children = parentTab.getAttribute(this.kCHILDREN); if (children) { var list = ('|'+children).split('|'+id)[0].split('|'); for (var i = list.length-1; i > -1; i--) { var lastChild = this.getTabById(list[i], b) if (lastChild) return lastChild; } } return null; }, getChildTabs : function(aTab, aAllTabsArray) { var tabs = []; var children = aTab.getAttribute(this.kCHILDREN); if (!children) return tabs; if (aAllTabsArray) tabs = aAllTabsArray; var list = children.split('|'); var b = this.getTabBrowserFromChildren(aTab); var tab; for (var i = 0, maxi = list.length; i < maxi; i++) { tab = this.getTabById(list[i], b) if (!tab) continue; tabs.push(tab); if (aAllTabsArray) this.getChildTabs(tab, tabs); } return tabs; }, getDescendantTabs : function(aTab) { var tabs = []; this.getChildTabs(aTab, tabs); return tabs; }, getFirstChildTab : function(aTab) { var b = this.getTabBrowserFromChildren(aTab); var children = aTab.getAttribute(this.kCHILDREN); var firstChild = null; if (children) { var list = children.split('|'); for (var i = 0, maxi = list.length; i < maxi; i++) { firstChild = this.getTabById(list[i], b) if (firstChild) break; } } return firstChild; }, getLastChildTab : function(aTab) { var b = this.getTabBrowserFromChildren(aTab); var children = aTab.getAttribute(this.kCHILDREN); var lastChild = null; if (children) { var list = children.split('|'); for (var i = list.length-1; i > -1; i--) { lastChild = this.getTabById(list[i], b) if (lastChild) break; } } return lastChild; }, getDropAction : function(aEvent, aDragSession) { var info = this.getDropActionInternal(aEvent); info.canDrop = true; if (info.action & this.kACTION_ATTACH && aDragSession && aDragSession.sourceNode && aDragSession.sourceNode.localName == 'tab') { var orig = aDragSession.sourceNode; if (orig == info.parent) { info.canDrop = false; } else { var tab = info.target; while (tab = this.getParentTab(tab)) { if (tab != orig) continue; info.canDrop = false; break; } } } return info; }, getDropActionInternal : function(aEvent) { var tab = aEvent.target; var b = this.getTabBrowserFromChildren(tab); var tabs = b.mTabContainer.childNodes; var isInverted = this.isTabVertical(b) ? false : window.getComputedStyle(b.parentNode, null).direction == 'ltr'; var info = { target : null, position : null, action : null, parent : null, insertBefore : null }; if (tab.localName != 'tab') { if (aEvent[this.positionProp] < tabs[0].boxObject[this.positionProp]) { info.target = info.parent = info.insertBefore = tabs[0]; info.position = isInverted ? this.kDROP_AFTER : this.kDROP_BEFORE ; info.action = this.kACTION_MOVE | this.kACTION_PART; return info; } else if (aEvent[this.positionProp] > tabs[tabs.length-1].boxObject[this.positionProp] + tabs[tabs.length-1].boxObject[this.sizeProp]) { info.target = info.parent = tabs[tabs.length-1]; info.position = isInverted ? this.kDROP_BEFORE : this.kDROP_AFTER ; info.action = this.kACTION_MOVE | this.kACTION_PART; return info; } else { info.target = tabs[Math.min(b.getNewIndex(aEvent), tabs.length - 1)]; } } else { info.target = tab; } var boxPos = tab.boxObject[this.positionProp]; var boxUnit = Math.round(tab.boxObject[this.sizeProp] / 3); if (aEvent[this.positionProp] < boxPos + boxUnit) { info.position = isInverted ? this.kDROP_AFTER : this.kDROP_BEFORE ; } else if (aEvent[this.positionProp] > boxPos + boxUnit + boxUnit) { info.position = isInverted ? this.kDROP_BEFORE : this.kDROP_AFTER ; } else { info.position = this.kDROP_ON; } switch (info.position) { case this.kDROP_ON: info.action = this.kACTION_ATTACH; info.parent = tab; info.insertBefore = this.getNextVisibleTab(tab); break; case this.kDROP_BEFORE: /* [TARGET ] ↑part from parent, and move [ ] [TARGET ] ↑attach to the parent of the target, and move [ ] [TARGET ] ↑attach to the parent of the target, and move [ ] [TARGET] ↑attach to the parent of the target (previous tab), and move */ var prevTab = this.getPreviousVisibleTab(tab); if (!prevTab) { info.action = this.kACTION_MOVE | this.kACTION_PART; info.insertBefore = tabs[0]; } else { var prevNest = Number(prevTab.getAttribute(this.kNEST)); var targetNest = Number(tab.getAttribute(this.kNEST)); info.parent = (prevNest < targetNest) ? prevTab : this.getParentTab(tab) ; info.action = this.kACTION_MOVE | (info.parent ? this.kACTION_ATTACH : this.kACTION_PART ); info.insertBefore = tab; } break; case this.kDROP_AFTER: /* [TARGET ] ↓if the target has a parent, attach to it and and move [TARGET] ↓attach to the parent of the target, and move [ ] [TARGET ] ↓attach to the parent of the target, and move [ ] [TARGET ] ↓attach to the target, and move [ ] */ var nextTab = this.getNextVisibleTab(tab); if (!nextTab) { info.action = this.kACTION_MOVE | this.kACTION_ATTACH; info.parent = this.getParentTab(tab); } else { var targetNest = Number(tab.getAttribute(this.kNEST)); var nextNest = Number(nextTab.getAttribute(this.kNEST)); info.parent = (targetNest < nextNest) ? tab : this.getParentTab(tab) ; info.action = this.kACTION_MOVE | (info.parent ? this.kACTION_ATTACH : this.kACTION_PART ); info.insertBefore = nextTab; } break; } return info; }, clearDropPosition : function(aTabBrowser) { var b = this.getTabBrowserFromChildren(aTabBrowser); var xpathResult = this.evaluateXPath( 'child::xul:tab[@'+this.kDROP_POSITION+']', b.mTabContainer ); for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++) { xpathResult.snapshotItem(i).removeAttribute(this.kDROP_POSITION); } }, /* Commands */ initTabbar : function(aTabBrowser, aPosition) { if (!aPosition) aPosition = this.getPref('extensions.treestyletab.tabbar.position'); aPosition = String(aPosition).toLowerCase(); var pos = (aPosition == 'left') ? this.kTABBAR_LEFT : (aPosition == 'right') ? this.kTABBAR_RIGHT : (aPosition == 'bottom') ? this.kTABBAR_BOTTOM : this.kTABBAR_TOP; var splitter = document.getAnonymousElementByAttribute(aTabBrowser, 'class', this.kSPLITTER); if (!splitter) { splitter = document.createElement('splitter'); splitter.setAttribute('class', this.kSPLITTER); splitter.setAttribute('onmouseup', 'TreeStyleTabService.onTabbarResized(event);'); splitter.appendChild(document.createElement('grippy')); var ref = aTabBrowser.mPanelContainer; ref.parentNode.insertBefore(splitter, ref); } var scrollInnerBox = document.getAnonymousNodes(aTabBrowser.mTabContainer.mTabstrip._scrollbox)[0]; if (pos & this.kTABBAR_VERTICAL) { this.positionProp = 'screenY'; this.sizeProp = 'height'; this.invertedSizeProp = 'width'; aTabBrowser.mTabBox.orient = 'horizontal'; aTabBrowser.mTabContainer.orient = aTabBrowser.mTabContainer.mTabstrip.orient = 'vertical'; scrollInnerBox.removeAttribute('flex'); aTabBrowser.mPanelContainer.removeAttribute('width'); aTabBrowser.mStrip.setAttribute('width', this.getPref('extensions.treestyletab.tabbar.width')); aTabBrowser.setAttribute(this.kVERTICAL, true); if (pos == this.kTABBAR_RIGHT) { aTabBrowser.setAttribute(this.kTABBAR_POSITION, 'right'); if (this.getPref('extensions.treestyletab.tabbar.invertUI')) { aTabBrowser.setAttribute(this.kINVERTED, 'true'); this.levelMarginProp = 'margin-right'; } else { aTabBrowser.removeAttribute(this.kINVERTED); this.levelMarginProp = 'margin-left'; } window.setTimeout(function() { aTabBrowser.mStrip.setAttribute('ordinal', 30); splitter.setAttribute('ordinal', 20); aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); }, 0); } else { aTabBrowser.setAttribute(this.kTABBAR_POSITION, 'left'); aTabBrowser.removeAttribute(this.kINVERTED); this.levelMarginProp = 'margin-left'; window.setTimeout(function() { aTabBrowser.mStrip.setAttribute('ordinal', 10); splitter.setAttribute('ordinal', 20); aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); }, 0); } } else { this.positionProp = 'screenX'; this.sizeProp = 'width'; this.invertedSizeProp = 'height'; aTabBrowser.mTabBox.orient = 'vertical'; aTabBrowser.mTabContainer.orient = aTabBrowser.mTabContainer.mTabstrip.orient = 'horizontal'; scrollInnerBox.setAttribute('flex', 1); aTabBrowser.mStrip.removeAttribute('width'); aTabBrowser.mPanelContainer.removeAttribute('width'); aTabBrowser.removeAttribute(this.kVERTICAL); if (pos == this.kTABBAR_BOTTOM) { aTabBrowser.setAttribute(this.kTABBAR_POSITION, 'bottom'); if (this.getPref('extensions.treestyletab.tabbar.invertUI')) { aTabBrowser.setAttribute(this.kINVERTED, 'true'); this.levelMarginProp = 'margin-bottom'; } else { aTabBrowser.removeAttribute(this.kINVERTED); this.levelMarginProp = 'margin-top'; } window.setTimeout(function() { aTabBrowser.mStrip.setAttribute('ordinal', 30); splitter.setAttribute('ordinal', 20); aTabBrowser.mPanelContainer.setAttribute('ordinal', 10); }, 0); } else { aTabBrowser.setAttribute(this.kTABBAR_POSITION, 'top'); aTabBrowser.removeAttribute(this.kINVERTED); this.levelMarginProp = 'margin-top'; window.setTimeout(function() { aTabBrowser.mStrip.setAttribute('ordinal', 10); splitter.setAttribute('ordinal', 20); aTabBrowser.mPanelContainer.setAttribute('ordinal', 30); }, 0); } } }, /* attach/part */ attachTabTo : function(aChild, aParent, aInfo) { if ( !aChild || !aParent || aChild == aParent || this.getParentTab(aChild) == aParent ) return; if (!aInfo) aInfo = {}; var id = aChild.getAttribute(this.kID); var b = this.getTabBrowserFromChildren(aParent); this.partTab(aChild, true); var children = aParent.getAttribute(this.kCHILDREN); var newIndex; if (children.indexOf(id) > -1) { children = ('|'+children).replace('|'+id, '').replace(/^\|/); } var insertBefore = aInfo.insertBefore; var beforeTab = insertBefore ? insertBefore.getAttribute(this.kID) : null ; if (beforeTab && children.indexOf(beforeTab) > -1) { children = children.replace(beforeTab, id+'|'+beforeTab); newIndex = insertBefore._tPos; } else { children = ((children || '')+'|'+id).replace(/^\|/, ''); var refTab = aParent; var descendant = this.getDescendantTabs(aParent); if (descendant.length) refTab = descendant[descendant.length-1]; newIndex = refTab._tPos+1; } this.setTabValue(aParent, this.kCHILDREN, children); this.updateTabsCount(aParent); if (newIndex > aChild._tPos) newIndex--; this.moveTabSubTreeTo(aChild, newIndex); if (!aInfo.dontExpand) { if ( /* ( aParent.getAttribute(this.kSUBTREE_COLLAPSED) == 'true' || children.indexOf('|') > -1 // not a first child ) && */ this.getPref('extensions.treestyletab.autoCollapseExpandSubTreeOnSelect') ) { this.collapseExpandTreesIntelligentlyFor(aParent); var p = aParent; do { this.collapseExpandTabSubTree(p, false); } while (p = this.getParentTab(p)); } else if (aParent.getAttribute(this.kSUBTREE_COLLAPSED) == 'true') { if (this.getPref('extensions.treestyletab.autoExpandSubTreeOnAppendChild')) { var p = aParent; do { this.collapseExpandTabSubTree(p, false); } while (p = this.getParentTab(p)); } else this.collapseExpandTab(aChild, true); } if (aParent.getAttribute(this.kCOLLAPSED) == 'true') this.collapseExpandTab(aChild, true); } else if (aParent.getAttribute(this.kSUBTREE_COLLAPSED) == 'true' || aParent.getAttribute(this.kCOLLAPSED) == 'true') { this.collapseExpandTab(aChild, true); } if (!aInfo.dontUpdateIndent) { this.updateTabsIndent([aChild]); this.checkTabsIndentOverflow(b); } }, partTab : function(aChild, aDontUpdateIndent) { if (!aChild) return; var parentTab = this.getParentTab(aChild); if (!parentTab) return; var id = aChild.getAttribute(this.kID); var children = ('|'+parentTab.getAttribute(this.kCHILDREN)) .replace(new RegExp('\\|'+id), '') .replace(/^\|/, ''); this.setTabValue(parentTab, this.kCHILDREN, children); this.updateTabsCount(parentTab); if (!aDontUpdateIndent) { this.updateTabsIndent([aChild]); var b = this.getTabBrowserFromChildren(aChild); this.checkTabsIndentOverflow(b); } }, updateTabsIndent : function(aTabs, aLevel, aProp) { if (!aTabs || !aTabs.length) return; if (aLevel === void(0)) { var parentTab = this.getParentTab(aTabs[0]); var aLevel = 0; while (parentTab) { aLevel++; parentTab = this.getParentTab(parentTab); } } if (!aProp) aProp = this.levelMarginProp; var b = this.getTabBrowserFromChildren(aTabs[0]); var margin = b.__treestyletab__levelMargin < 0 ? this.levelMargin : b.__treestyletab__levelMargin ; var indent = margin * aLevel; for (var i = 0, maxi = aTabs.length; i < maxi; i++) { aTabs[i].setAttribute('style', aTabs[i].getAttribute('style')+'; margin: 0 !important; '+aProp+':'+indent+'px !important;'); aTabs[i].setAttribute(this.kNEST, aLevel); this.updateTabsIndent(this.getChildTabs(aTabs[i]), aLevel+1, aProp); } }, updateAllTabsIndent : function(aTabBrowser) { var b = this.getTabBrowserFromChildren(aTabBrowser); this.updateTabsIndent( this.getArrayFromXPathResult( this.getRootTabs(b) ), 0 ); // this.checkTabsIndentOverflow(b); }, checkTabsIndentOverflow : function(aTabBrowser) { if (this.checkTabsIndentOverflowTimer) { window.clearTimeout(this.checkTabsIndentOverflowTimer); this.checkTabsIndentOverflowTimer = null; } this.checkTabsIndentOverflowTimer = window.setTimeout(function(aSelf, aTabBrowser) { aSelf.checkTabsIndentOverflowCallback(aTabBrowser); }, 100, this, aTabBrowser); }, checkTabsIndentOverflowTimer : null, checkTabsIndentOverflowCallback : function(aTabBrowser) { var b = aTabBrowser; var tabs = this.getArrayFromXPathResult(this.evaluateXPath( 'child::xul:tab[@'+this.kNEST+' and not(@'+this.kNEST+'="0" or @'+this.kNEST+'="")]', b.mTabContainer )); if (!tabs.length) return; var self = this; tabs.sort(function(aA, aB) { return Number(aA.getAttribute(self.kNEST)) - Number(aB.getAttribute(self.kNEST)); }); var nest = tabs[tabs.length-1].getAttribute(self.kNEST); if (!nest) return; var oldMargin = b.__treestyletab__levelMargin; var indent = (oldMargin < 0 ? this.levelMargin : oldMargin ) * nest; var maxIndent = b.mTabContainer.childNodes[0].boxObject[this.invertedSizeProp] * 0.33; var marginUnit = Math.max(Math.floor(maxIndent / nest), 1); if (indent > maxIndent) { b.__treestyletab__levelMargin = marginUnit; } else { b.__treestyletab__levelMargin = -1; if ((this.levelMargin * nest) > maxIndent) b.__treestyletab__levelMargin = marginUnit; } if (oldMargin != b.__treestyletab__levelMargin) { this.updateAllTabsIndent(b); } }, updateTabsCount : function(aTab) { var count = document.getAnonymousElementByAttribute(aTab, 'class', this.kCOUNTER); if (count) { count.setAttribute('value', '('+this.getDescendantTabs(aTab).length+')'); } var parent = this.getParentTab(aTab); if (parent) this.updateTabsCount(parent); }, /* move */ moveTabSubTreeTo : function(aTab, aIndex) { if (!aTab) return; var b = this.getTabBrowserFromChildren(aTab); b.__treestyletab__isSubTreeMoving = true; b.moveTabTo(aTab, aIndex); b.__treestyletab__isSubTreeChildrenMoving = true; var tabs = this.getDescendantTabs(aTab); for (var i = 0, maxi = tabs.length; i < maxi; i++) { b.moveTabTo(tabs[i], aTab._tPos+i+(aTab._tPos < tabs[i]._tPos ? 1 : 0 )); } b.__treestyletab__isSubTreeChildrenMoving = false; b.__treestyletab__isSubTreeMoving = false; }, moveTabLevel : function(aEvent) { var b = this.getTabBrowserFromChildren(aEvent.target); var parentTab = this.getParentTab(b.mCurrentTab); if (aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) { var 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 == KeyEvent.DOM_VK_LEFT && parentTab) { var grandParent = this.getParentTab(parentTab); if (grandParent) { this.attachTabTo(b.mCurrentTab, grandParent, { insertBefore : this.getNextSiblingTab(parentTab) }); b.mCurrentTab.focus(); return true; } else { var nextTab = this.getNextSiblingTab(parentTab); this.partTab(b.mCurrentTab); if (nextTab) { b.moveTabTo(b.mCurrentTab, nextTab._tPos - 1); } else { b.moveTabTo(b.mCurrentTab, b.mTabContainer.lastChild._tPos); } b.mCurrentTab.focus(); return true; } } return false; }, /* collapse/expand */ collapseExpandTabSubTree : function(aTab, aCollapse) { if (!aTab) return; if ((aTab.getAttribute(this.kSUBTREE_COLLAPSED) == 'true') == aCollapse) return; var b = this.getTabBrowserFromChildren(aTab); b.__treestyletab__doingCollapseExpand = true; this.setTabValue(aTab, this.kSUBTREE_COLLAPSED, aCollapse); var tabs = this.getChildTabs(aTab); for (var i = 0, maxi = tabs.length; i < maxi; i++) { this.collapseExpandTab(tabs[i], aCollapse); } if (!aCollapse) this.scrollToTabSubTree(aTab); b.__treestyletab__doingCollapseExpand = false; }, collapseExpandTab : function(aTab, aCollapse) { if (!aTab) return; this.setTabValue(aTab, this.kCOLLAPSED, aCollapse); var b = this.getTabBrowserFromChildren(aTab); var p; if (aCollapse && aTab == b.selectedTab && (p = this.getParentTab(aTab))) { b.selectedTab = p; } var isSubTreeCollapsed = (aTab.getAttribute(this.kSUBTREE_COLLAPSED) == 'true'); var tabs = this.getChildTabs(aTab); for (var i = 0, maxi = tabs.length; i < maxi; i++) { if (!isSubTreeCollapsed) this.collapseExpandTab(tabs[i], aCollapse); } }, collapseExpandTreesIntelligentlyFor : function(aTab) { var b = this.getTabBrowserFromChildren(aTab); if (b.__treestyletab__doingCollapseExpand) return; var sameParentTab = this.getParentTab(aTab); var expandedParentTabs = [ aTab.getAttribute(this.kID) ]; var parentTab = aTab; while (parentTab = this.getParentTab(parentTab)) { expandedParentTabs.push(parentTab.getAttribute(this.kID)); } expandedParentTabs = expandedParentTabs.join('|'); var xpathResult = this.evaluateXPath( 'child::xul:tab[@'+this.kCHILDREN+' and not(@'+this.kCOLLAPSED+'="true") and not(@'+this.kSUBTREE_COLLAPSED+'="true") and not(contains("'+expandedParentTabs+'", @'+this.kID+'))]', b.mTabContainer ); var collapseTab; var dontCollapse; for (var i = 0, maxi = xpathResult.snapshotLength; i < maxi; i++) { dontCollapse = false; collapseTab = xpathResult.snapshotItem(i); parentTab = this.getParentTab(collapseTab); if (parentTab) { dontCollapse = true; if (parentTab.getAttribute(this.kSUBTREE_COLLAPSED) != 'true') { do { if (expandedParentTabs.indexOf(parentTab.getAttribute(this.kID)) < 0) continue; dontCollapse = false; break; } while (parentTab = this.getParentTab(parentTab)); } } if (!dontCollapse) this.collapseExpandTabSubTree(collapseTab, true); } this.collapseExpandTabSubTree(aTab, false); }, /* scroll */ scrollTo : function(aTabBrowser, aEndX, aEndY) { if (this.getPref('extensions.treestyletab.tabbar.scroll.smooth')) this.smoothScrollTo(aTabBrowser, aEndX, aEndY); else this.getTabBrowserFromChildren(aTabBrowser).mTabstrip.scrollBoxObject.scrollTo(aEndX, aEndY); }, smoothScrollTo : function(aTabBrowser, aEndX, aEndY) { var b = this.getTabBrowserFromChildren(aTabBrowser); if (b.__treestyletab__smoothScrollTimer) { window.clearInterval(b.__treestyletab__smoothScrollTimer); b.__treestyletab__smoothScrollTimer = null; } var scrollBoxObject = b.mTabContainer.mTabstrip.scrollBoxObject; var x = {}, y = {}; scrollBoxObject.getPosition(x, y); b.__treestyletab__smoothScrollTimer = window.setInterval( this.smoothScrollToCallback, 10, this, b, x.value, y.value, aEndX, aEndY, Date.now(), this.getPref('extensions.treestyletab.tabbar.scroll.timeout') ); }, smoothScrollToCallback : function(aSelf, aTabBrowser, aStartX, aStartY, aEndX, aEndY, aStartTime, aTimeout) { var newX = aStartX + parseInt( (aEndX - aStartX) * ((Date.now() - aStartTime) / aTimeout) ); var newY = aStartY + parseInt( (aEndY - aStartY) * ((Date.now() - aStartTime) / aTimeout) ); var scrollBoxObject = aTabBrowser.mTabContainer.mTabstrip.scrollBoxObject; var x = {}, y = {}; scrollBoxObject.getPosition(x, y); var w = {}, h = {}; scrollBoxObject.getScrolledSize(w, h); var maxX = Math.max(0, w.value - scrollBoxObject.width); var maxY = Math.max(0, h.value - scrollBoxObject.height); if ( ( aEndX - aStartX > 0 ? x.value >= Math.min(aEndX, maxX) : x.value <= Math.min(aEndX, maxX) ) && ( aEndY - aStartY > 0 ? y.value >= Math.min(aEndY, maxY) : y.value <= Math.min(aEndY, maxY) ) ) { if (aTabBrowser.__treestyletab__smoothScrollTimer) { window.clearInterval(aTabBrowser.__treestyletab__smoothScrollTimer); aTabBrowser.__treestyletab__smoothScrollTimer = null; } return; } scrollBoxObject.scrollTo(newX, newY); }, scrollToTab : function(aTab) { if (!aTab || this.isTabVisible(aTab)) return; var b = this.getTabBrowserFromChildren(aTab); var scrollBoxObject = b.mTabContainer.mTabstrip.scrollBoxObject; var w = {}, h = {}; scrollBoxObject.getScrolledSize(w, h); var targetTabBox = aTab.boxObject; var baseTabBox = aTab.parentNode.firstChild.boxObject; var targetX = (aTab.boxObject.screenX < scrollBoxObject.screenX) ? (targetTabBox.screenX - baseTabBox.screenX) - (targetTabBox.width * 0.5) : (targetTabBox.screenX - baseTabBox.screenX) - scrollBoxObject.width + (targetTabBox.width * 1.5) ; var targetY = (aTab.boxObject.screenY < scrollBoxObject.screenY) ? (targetTabBox.screenY - baseTabBox.screenY) - (targetTabBox.height * 0.5) : (targetTabBox.screenY - baseTabBox.screenY) - scrollBoxObject.height + (targetTabBox.height * 1.5) ; this.scrollTo(b, targetX, targetY); }, scrollToTabSubTree : function(aTab) { var b = this.getTabBrowserFromChildren(aTab); var descendant = this.getDescendantTabs(aTab); var lastVisible = aTab; for (var i = descendant.length-1; i > -1; i--) { if (descendant[i].getAttribute(this.kCOLLAPSED) == 'true') continue; lastVisible = descendant[i]; break; } var containerPosition = b.mStrip.boxObject[this.positionProp]; var containerSize = b.mStrip.boxObject[this.sizeProp]; var parentPosition = aTab.boxObject[this.positionProp]; var lastPosition = lastVisible.boxObject[this.positionProp]; var tabSize = lastVisible.boxObject[this.sizeProp]; if (this.isTabVisible(aTab) && this.isTabVisible(lastVisible)) { return; } if (lastPosition - parentPosition + tabSize > containerSize - tabSize) { // out of screen var endPos = parentPosition - b.mTabContainer.firstChild.boxObject[this.positionProp] - tabSize * 0.5; var endX = this.isTabVertical(aTab) ? 0 : endPos ; var endY = this.isTabVertical(aTab) ? endPos : 0 ; this.scrollTo(b, endX, endY); } else if (!this.isTabVisible(aTab) && this.isTabVisible(lastVisible)) { this.scrollToTab(aTab); } else if (this.isTabVisible(aTab) && !this.isTabVisible(lastVisible)) { this.scrollToTab(lastVisible); } else if (parentPosition < containerPosition) { this.scrollToTab(aTab); } else { this.scrollToTab(lastVisible); } }, removeTabSubTree : function(aTabOrTabs) { var tabs = aTabOrTabs; if (!(tabs instanceof Array)) { tabs = [aTabOrTabs]; } var descendant = []; for (var i = 0, maxi = tabs.length; i < maxi; i++) { descendant = descendant.concat(this.getDescendantTabs(tabs[i])); } tabs = this.cleanUpTabsArray(tabs.concat(descendant)); var max = tabs.length; if (!max) return; var b = this.getTabBrowserFromChildren(tabs[0]); if ( max > 1 && !b.warnAboutClosingTabs(true, max) ) return; for (var i = tabs.length-1; i > -1; i--) { b.removeTab(tabs[i]); } }, openSelectionLinks : function() { var links = this.getSelectionLinks(); if (!links.length) return; var b = this.browser; var targetWindow = document.commandDispatcher.focusedWindow; if (!targetWindow || targetWindow.top == window) targetWindow = b.contentWindow; var referrer = this.makeURIFromSpec(targetWindow.location.href); this.readyToOpenChildTab(targetWindow, true); var self = this; links.forEach(function(aItem, aIndex) { var tab = b.addTab(aItem.uri, referrer); if (aIndex == 0 && !self.getPref('browser.tabs.loadInBackground')) b.selectedTab = tab; }); this.stopToOpenChildTab(targetWindow); }, getSelectionLinks : function() { var links = []; var targetWindow = document.commandDispatcher.focusedWindow; if (!targetWindow || targetWindow.top == window) targetWindow = this.browser.contentWindow; var selection = targetWindow.getSelection(); if (!selection || !selection.rangeCount) return links; const count = selection.rangeCount; var range, node, link, uri, nodeRange = targetWindow.document.createRange(); for (var i = 0; i < count; i++) { range = selection.getRangeAt(0); node = range.startContainer; traceTree: while (true) { nodeRange.selectNode(node); // 「ノードの終端が、選択範囲の先端より後にあるかどうか」をチェック。 // 後にあるならば、そのノードは選択範囲内にあると考えられる。 if (nodeRange.compareBoundaryPoints(Range.START_TO_END, range) > -1) { // 「ノードの先端が、選択範囲の終端より後にあるかどうか」をチェック。 // 後にあるならば、そのノードは選択範囲外にあると考えられる。 if (nodeRange.compareBoundaryPoints(Range.END_TO_START, range) > 0) { // 「リンクテキストが実際には選択されていないリンク」については除外する if ( links.length && range.startContainer.nodeType != Node.ELEMENT_NODE && range.startOffset == range.startContainer.nodeValue.length && links[0].node == this.getParentLink(range.startContainer) ) links.splice(0, 1); if ( links.length && range.endContainer.nodeType != Node.ELEMENT_NODE && range.endOffset == 0 && links[links.length-1].node == this.getParentLink(range.endContainer) ) links.splice(links.length-1, 1); break; } else if (link = this.getParentLink(node)) { try { uri = link.href; if (uri && uri.indexOf('mailto:') < 0) links.push({ node : link, uri : uri }); } catch(e) { } } } if (node.hasChildNodes() && !link) { node = node.firstChild; } else { while (!node.nextSibling) { node = node.parentNode; if (!node) break traceTree; } node = node.nextSibling; } } } nodeRange.detach(); return links; }, getParentLink : function(aNode) { var node = aNode; while (!node.href && node.parentNode) node = node.parentNode; return node.href ? node : null ; }, /* view source */ kVIEWSOURCE_URI : 'treestyletab-viewsource-uri', kVIEWSOURCE_CHARSET : 'treestyletab-viewsource-charset', kVIEWSOURCE_REF : 'treestyletab-viewsource-reference', kVIEWSOURCE_CONTEXT : 'treestyletab-viewsource-context', kVIEWSOURCE_SOURCE : 'treestyletab-viewsource-source', viewSource : { clear : function() { this.frame = null; this.uri = null; this.charset = null; this.reference = null; this.context = null; }, frame : null, uri : null, charset : null, reference : null, context : null }, /* Pref Listener */ domain : 'extensions.treestyletab', observe : function(aSubject, aTopic, aPrefName) { if (aTopic != 'nsPref:changed') return; var value = this.getPref(aPrefName); switch (aPrefName) { case 'extensions.treestyletab.levelMargin': this.levelMargin = value; this.ObserverService.notifyObservers(null, 'TreeStyleTab:levelMarginModified', value); break; default: break; } }, /* Save/Load Prefs */ get Prefs() { if (!this._Prefs) { this._Prefs = Components.classes['@mozilla.org/preferences;1'].getService(Components.interfaces.nsIPrefBranch); } return this._Prefs; }, _Prefs : null, getPref : function(aPrefstring) { try { switch (this.Prefs.getPrefType(aPrefstring)) { case this.Prefs.PREF_STRING: return decodeURIComponent(escape(this.Prefs.getCharPref(aPrefstring))); break; case this.Prefs.PREF_INT: return this.Prefs.getIntPref(aPrefstring); break; default: return this.Prefs.getBoolPref(aPrefstring); break; } } catch(e) { } return null; }, setPref : function(aPrefstring, aNewValue) { var pref = this.Prefs ; var type; try { type = typeof aNewValue; } catch(e) { type = null; } switch (type) { case 'string': pref.setCharPref(aPrefstring, unescape(encodeURIComponent(aNewValue))); break; case 'number': pref.setIntPref(aPrefstring, parseInt(aNewValue)); break; default: pref.setBoolPref(aPrefstring, aNewValue); break; } return true; }, clearPref : function(aPrefstring) { try { this.Prefs.clearUserPref(aPrefstring); } catch(e) { } return; }, addPrefListener : function(aObserver) { var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ; try { var pbi = this.Prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal); for (var i = 0; i < domains.length; i++) pbi.addObserver(domains[i], aObserver, false); } catch(e) { } }, removePrefListener : function(aObserver) { var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ; try { var pbi = this.Prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal); for (var i = 0; i < domains.length; i++) pbi.removeObserver(domains[i], aObserver, false); } catch(e) { } } }; window.addEventListener('load', TreeStyleTabService, false); window.addEventListener('unload', TreeStyleTabService, false); function TreeStyleTabBrowserObserver(aTabBrowser) { this.mTabBrowser = aTabBrowser; TreeStyleTabService.ObserverService.addObserver(this, 'TreeStyleTab:levelMarginModified', false); TreeStyleTabService.addPrefListener(this); } TreeStyleTabBrowserObserver.prototype = { domain : 'extensions.treestyletab', mTabBrowser : null, observe : function(aSubject, aTopic, aData) { switch (aTopic) { case 'TreeStyleTab:levelMarginModified': if (this.mTabBrowser.__treestyletab__levelMargin > -1) { TreeStyleTabService.updateAllTabsIndent(this.mTabBrowser); } break; case 'nsPref:changed': var value = TreeStyleTabService.getPref(aData); switch (aData) { case 'extensions.treestyletab.tabbar.position': case 'extensions.treestyletab.tabbar.invertUI': TreeStyleTabService.initTabbar(this.mTabBrowser); TreeStyleTabService.updateAllTabsIndent(this.mTabBrowser); break; case 'extensions.treestyletab.tabbar.style': this.mTabBrowser.setAttribute(TreeStyleTabService.kSTYLE, value); break; case 'extensions.treestyletab.showBorderForFirstTab': if (value) this.mTabBrowser.setAttribute(TreeStyleTabService.kFIRSTTAB_BORDER, true); else this.mTabBrowser.removeAttribute(TreeStyleTabService.kFIRSTTAB_BORDER); break; default: break; } break; default: break; } }, destroy : function() { TreeStyleTabService.ObserverService.removeObserver(this, 'TreeStyleTab:levelMarginModified'); TreeStyleTabService.removePrefListener(this); delete this.mTabBrowser; } };