From fdc472441b9e58a36828c485b1668b9a42f824a0 Mon Sep 17 00:00:00 2001 From: YUKI Hiroshi Date: Thu, 21 Jan 2016 18:23:11 +0900 Subject: [PATCH] Move codes to override behaviors around bookmarks to a JS code module. Moreover, it reduces use of eval. --- content/treestyletab/bookmarksOverlay.js | 493 +----------------- .../treestyletab/bookmarksOverlayEditable.js | 13 +- defaults/preferences/treestyletab.js | 1 + modules/base.js | 56 +- modules/bookmark.js | 489 +++++++++++++++++ modules/browser.js | 5 +- modules/constants.js | 16 + modules/tabbarDNDObserver.js | 2 +- modules/utils.js | 44 ++ 9 files changed, 594 insertions(+), 525 deletions(-) create mode 100644 modules/bookmark.js diff --git a/content/treestyletab/bookmarksOverlay.js b/content/treestyletab/bookmarksOverlay.js index 335dd130..b2396510 100644 --- a/content/treestyletab/bookmarksOverlay.js +++ b/content/treestyletab/bookmarksOverlay.js @@ -1,221 +1,15 @@ Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); XPCOMUtils.defineLazyModuleGetter(this, 'TreeStyleTabUtils', 'resource://treestyletab-modules/utils.js'); +XPCOMUtils.defineLazyModuleGetter(this, + 'TreeStyleTabBookmarksService', 'resource://treestyletab-modules/bookmark.js'); (function() { let { ReferenceCounter } = Components.utils.import('resource://treestyletab-modules/ReferenceCounter.js', {}); let { inherit } = Components.utils.import('resource://treestyletab-modules/lib/inherit.jsm', {}); -var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, { - - get BookmarksService() { - if (!this._BookmarksService) { - this._BookmarksService = Components - .classes['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Components.interfaces.nsINavBookmarksService); - } - return this._BookmarksService; - }, - _BookmarksService : null, - - - beginAddBookmarksFromTabs : function TSTBMService_beginAddBookmarksFromTabs(aTabs) /* PUBLIC API */ - { - if (this._observing) return; - this._observing = true; - - aTabs = this.cleanUpTabsArray(aTabs); - - this._addingBookmarks = []; - this._addingBookmarkTreeStructure = aTabs.map(function(aTab) { - var parent = this.getParentTab(aTab); - return aTabs.indexOf(parent); - }, this); - - this.BookmarksService.addObserver(this, false); - }, - - endAddBookmarksFromTabs : function TSTBMService_endAddBookmarksFromTabs() /* PUBLIC API */ - { - if (!this._observing) return; - this._observing = false; - - this.BookmarksService.removeObserver(this); - this.handleNewBookmarksFromTabs(this._addingBookmarks, this._addingBookmarkTreeStructure); - this._addingBookmarks = []; - this._addingBookmarkTreeStructure = []; - }, - - handleNewBookmarksFromTabs : function TSTBMService_handleNewBookmarksFromTabs(aBookarmks, aTreeStructure) - { - // this is adding bookmark folder from tabs, so ignroe the first item! - if ( - aBookarmks.length == aTreeStructure.length+1 && - this.BookmarksService.getItemType(aBookarmks[0].id) == this.BookmarksService.TYPE_FOLDER - ) { - aBookarmks.shift(); - } - else if (aBookarmks.length != aTreeStructure.length) { - return; - } - - for (let i = 0, maxi = aBookarmks.length; i < maxi; i++) - { - let item = aBookarmks[i]; - item.position = this.BookmarksService.getItemIndex(item.id); - } - aBookarmks.sort(function(aA, aB) { - return aA.position - aB.position; - }); - - for (let i = 0, maxi = aBookarmks.length; i < maxi; i++) - { - let item = aBookarmks[i]; - if (this.BookmarksService.getItemType(item.id) != this.BookmarksService.TYPE_BOOKMARK) - continue; - - let uri = this.BookmarksService.getBookmarkURI(item.id); - if (/^about:treestyletab-group\b/.test(uri.spec)) { - let title = this.BookmarksService.getItemTitle(item.id); - let folderId = this.BookmarksService.createFolder(item.parent, title, item.position); - this.BookmarksService.removeItem(item.id); - item.id = folderId; - item.isFolder = true; - } - - let index = aTreeStructure[i]; - let parent = index > -1 ? aBookarmks[index] : null ; - if (parent && (parent.folder || parent).isFolder) { - let folder = parent.isFolder ? parent : parent.folder ; - this.BookmarksService.moveItem(item.id, folder.id, -1); - item.folder = folder; - } - if (parent && !parent.isFolder) { - PlacesUtils.setAnnotationsForItem(item.id, [{ - name : this.kPARENT, - value : parent ? parent.id : -1, - expires : PlacesUtils.annotations.EXPIRE_NEVER - }]); - } - } - }, - - bookmarkTabSubtree : function TSTBMService_bookmarkTabSubtree(aTabOrTabs) - { - var tabs = aTabOrTabs; - if (!Array.isArray(tabs)) { - tabs = [aTabOrTabs]; - } - - var folderName = (this.isGroupTab(tabs[0], true) || tabs.length == 1) ? - tabs[0].label : - null ; - - var b = this.getTabBrowserFromChild(tabs[0]); - var bookmarkedTabs = []; - for (let i = 0, maxi = tabs.length; i < maxi; i++) - { - let tab = tabs[i]; - if (!this.isGroupTab(tab, i == 0)) bookmarkedTabs.push(tab); - bookmarkedTabs = bookmarkedTabs.concat(b.treeStyleTab.getDescendantTabs(tab)); - } - - this.beginAddBookmarksFromTabs(bookmarkedTabs); - try { - window['piro.sakura.ne.jp'].bookmarkMultipleTabs.addBookmarkFor(bookmarkedTabs, folderName); - } - catch(e) { - } - this.endAddBookmarksFromTabs(); - }, - bookmarkTabSubTree : function() { return this.bookmarkTabSubtree.apply(this, arguments); }, // obsolete, for backward compatibility - - getParentItem : function TSTBMService_getParentItem(aId) - { - if (aId < 0) return -1; - var annotations = PlacesUtils.getAnnotationsForItem(aId); - for (let i in annotations) - { - if (annotations[i].name != this.kPARENT) continue; - return parseInt(annotations[i].value); - } - return -1; - }, - - getTreeStructureFromItems : function TSTBMService_getTreeStructureFromItems(aIDs, aDefaultParentID) - { - /* this returns a result same to getTreeStructureFromTabs(). - [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) - */ - if (aDefaultParentID === void(0)) - aDefaultParentID = -1; - - /* Get array of parents. The index becomes to -1, - if there is NO PARENT or the parent is THE TAB ITSELF. */ - var treeStructure = aIDs.map(function(aId, aIndex) { - let id = this.getParentItem(aId); - let index = aIDs.indexOf(id); - return index >= aIndex ? aDefaultParentID : index ; - }, this); - - /* Correct patterns like: - [TabA] - [TabB] - this has no parent - [TabC] - TabA's child - to: - [TabA] - [TabB] - [TabC] - */ - treeStructure = treeStructure.reverse(); - treeStructure = treeStructure.map(function(aPosition, aIndex) { - if (aIndex > 0 && - aIndex < treeStructure.length-1 && - aPosition < 0) { - aPosition = treeStructure[aIndex-1]; - } - return aPosition; - }); - treeStructure = treeStructure.reverse(); - - return this.cleanUpTreeStructureArray(treeStructure, aDefaultParentID); - }, - - // based on PlacesUtils.getURLsForContainerNode() - getItemIdsForContainerNode : function TSTBMService_getItemIdsForContainerNode(aNode) - { - var ids = []; - if (!aNode || !PlacesUtils.nodeIsContainer(aNode)) return ids; - - var root = aNode; - if ('getContainerNodeWithOptions' in PlacesUtils) { - root = PlacesUtils.getContainerNodeWithOptions(root, false, true); - } - var oldViewer = root.parentResult.viewer; - var wasOpen = root.containerOpen; - if (!wasOpen) { - if (oldViewer) - root.parentResult.viewer = null; - root.containerOpen = true; - } - for (let i = 0, maxi = root.childCount; i < maxi; ++i) - { - let child = root.getChild(i); - if (PlacesUtils.nodeIsURI(child)) ids.push(child.itemId || -1); - } - if (!wasOpen) { - root.containerOpen = false; - if (oldViewer) - root.parentResult.viewer = oldViewer; - } - return ids; - }, +var TreeStyleTabBookmarksUIService = inherit(TreeStyleTabService, { preInit : function TSTBMService_preInit() { window.addEventListener('load', this, false); @@ -231,250 +25,35 @@ var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, { window.addEventListener('unload', this, false); ReferenceCounter.add('window,unload,TSTBMService,false'); - if (!('PlacesUIUtils' in window)) return; - - if (!PlacesUIUtils.__treestyletab__done) { - var ns = Components.utils.import('resource:///modules/PlacesUIUtils.jsm', {}); - var sv = this; - with (ns) { - - { - let method = (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && PlacesUIUtils.TU__openTabset) ? - 'TU__openTabset' : - '_openTabset'; - if (PlacesUIUtils.__openbookmarkintab__openTabset) - method = '__openbookmarkintab__openTabset'; - TreeStyleTabUtils.doPatching(PlacesUIUtils[method], 'PlacesUIUtils.'+method, function(aName, aSource) { - var patched = eval(aName+' = '+aSource.replace( - /(function[^\(]*\([^\)]+)(\))/, - '$1, aFolderTitle$2' - ).replace( - '{', - '{ var TSTTreeStructure = null, TSTPreviousTabs, TSTTreeStructureApplied = true, TSTOpenGroupBookmarkBehavior;' - ).replace( - 'var urls = [];', - '$& var ids = [];' - ).replace( - 'urls.push(item.uri);', - 'if (item.uri) { $& ids.push(item.id); }' - ).replace( - 'this.markPageAsTyped(item.uri);', - 'if (item.uri) { $& }' - ).replace( - /(browserWindow\.(?:getBrowser\(\)|gBrowser)\.loadTabs\([^;]+\);)/, - 'var TSTResult = browserWindow.TreeStyleTabBookmarksService.handleTabsOpenProcess(where, aEvent, browserWindow, ids, urls, aFolderTitle);\n' + - 'TSTTreeStructure = TSTResult.treeStructure;\n' + - 'TSTPreviousTabs = TSTResult.previousTabs;\n' + - 'TSTTreeStructureApplied = TSTResult.treeStructureApplied;\n' + - 'TSTOpenGroupBookmarkBehavior = TSTResult.behavior;\n' + - '$1' - ).replace( - /(\}\)?)$/, - ' if (TSTTreeStructure && TSTPreviousTabs) {\n' + - ' let tabs = browserWindow.TreeStyleTabService.getNewTabsFromPreviousTabsInfo(browserWindow.gBrowser, TSTPreviousTabs);\n' + - ' if (!TSTTreeStructureApplied)\n' + - ' browserWindow.TreeStyleTabService.applyTreeStructureToTabs(tabs, TSTTreeStructure, TSTOpenGroupBookmarkBehavior & browserWindow.TreeStyleTabBookmarksService.kGROUP_BOOKMARK_EXPAND_ALL_TREE);\n' + - ' if (!loadInBackground) {\n' + - ' browserWindow.setTimeout(function() {\n' + - ' browserWindow.gBrowser.treeStyleTab.scrollToTabs(tabs);\n' + - ' }, browserWindow.gBrowser.treeStyleTab.collapseDuration); // start scroll after expanding animation is finished\n' + - ' }\n' + - ' }\n' + - '$1' - )); - if (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && method.indexOf('TU_') > -1) - window[method] = patched; - return patched; - }, 'TreeStyleTab'); - } - - { - let method = (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && PlacesUIUtils.TU_openContainerNodeInTabs) ? - 'TU_openContainerNodeInTabs' : - 'openContainerNodeInTabs'; - TreeStyleTabUtils.doPatching(PlacesUIUtils[method], 'PlacesUIUtils.'+method, function(aName, aSource) { - var patched = eval(aName+' = '+aSource.replace( - /(this\._openTabset\([^\)]+)(\))/, - '{\n' + - ' let w = "_getTopBrowserWin" in this ?\n' + - ' this._getTopBrowserWin() :\n' + - ' "_getCurrentActiveWin" in this ?\n' + - ' this._getCurrentActiveWin() :\n' + - ' window;\n' + - ' let nodes = w.TreeStyleTabBookmarksService.getItemIdsForContainerNode(aNode);\n' + - ' for (let i in nodes) {\n' + - ' urlsToOpen[i].id = nodes[i];\n' + - ' }\n' + - '}\n' + - '$1, aNode.title$2' - )); - if (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && method.indexOf('TU_') > -1) - window[method] = patched; - return patched; - }, 'TreeStyleTab'); - } - - { - let method = (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && PlacesUIUtils.TU_openURINodesInTabs) ? - 'TU_openURINodesInTabs' : - 'openURINodesInTabs'; - TreeStyleTabUtils.doPatching(PlacesUIUtils[method], 'PlacesUIUtils.'+method, function(aName, aSource) { - var patched = eval(aName+' = '+aSource.replace( - '{', - '{\n' + - ' var TSTBS, TSTUtils;\n' + - ' {\n'+ - ' let w = "_getTopBrowserWin" in this ?\n' + - ' this._getTopBrowserWin() :\n' + - ' "_getCurrentActiveWin" in this ?\n' + - ' this._getCurrentActiveWin() :\n' + - ' window;\n' + - ' TSTBS = w.TreeStyleTabBookmarksService;\n' + - ' TSTUtils = w.TreeStyleTabUtils;\n' + - ' PlacesUtils = w.PlacesUtils;\n' + - ' }' - ).replace( - 'uri: aNodes[i].uri,', - 'id: aNodes[i].itemId, $&' - ).replace( - /(this\._openTabset\([^\)]+)(\))/, - '$1,\n' + - ' TSTUtils.treeBundle\n' + - ' .getFormattedString(\n' + - ' PlacesUtils.nodeIsBookmark(aNodes[0]) ?\n' + - ' "openSelectedPlaces.bookmarks" :\n' + - ' "openSelectedPlaces.history",\n' + - ' [aNodes[0].title, aNodes.length]\n' + - ' )\n' + - '$2' - )); - if (TreeStyleTabUtils.getTreePref('compatibility.TabUtilities') && method.indexOf('TU_') > -1) - window[method] = patched; - return patched; - }, 'TreeStyleTab'); - } - - PlacesUIUtils.__treestyletab__done = true; - - } // end of with - } - if ('PlacesCommandHook' in window && 'bookmarkCurrentPages' in PlacesCommandHook) { // Bookmark All Tabs - TreeStyleTabUtils.doPatching(PlacesCommandHook.bookmarkCurrentPages, 'PlacesCommandHook.bookmarkCurrentPages', function(aName, aSource) { - return eval(aName+' = '+aSource.replace( - '{', - '{\n' + - ' TreeStyleTabBookmarksService.beginAddBookmarksFromTabs((function() {\n' + - ' var tabs = [];\n' + - ' var seen = {};\n' + - ' var allTabs = getBrowser().mTabContainer.childNodes;\n' + - ' for (let i = 0, maxi = allTabs.length; i < maxi; i++)\n' + - ' {\n' + - ' let tab = allTabs[i];\n' + - ' let uri = tab.linkedBrowser.currentURI.spec;\n' + - ' if (uri in seen) continue;\n' + - ' seen[uri] = true;\n' + - ' tabs.push(tab);\n' + - ' }\n' + - ' return tabs;\n' + - ' })());\n' + - ' try {' - ).replace( - /(\}\)?)$/, - ' }\n' + - ' catch(e) {\n' + - ' }\n' + - ' TreeStyleTabBookmarksService.endAddBookmarksFromTabs();\n' + - '$1' - )); - }, 'TreeStyleTab'); - } - }, - handleTabsOpenProcess : function TSTBMService_handleTabsOpenProcess(aWhere, aEvent, aBrowserWindow, aIDs, aURLs, aFolderTitle) - { - var result = { - behavior : undefined, - treeStructure : undefined, - previousTabs : undefined, - treeStructureApplied : false + PlacesCommandHook.__treestyletab__bookmarkCurrentPages = PlacesCommandHook.bookmarkCurrentPages; + PlacesCommandHook.bookmarkCurrentPages = function(...aArgs) { + TreeStyleTabBookmarksService.beginAddBookmarksFromTabs((function() { + var tabs = []; + var seen = {}; + var allTabs = gBrowser.mTabContainer.childNodes; + for (let i = 0, maxi = allTabs.length; i < maxi; i++) + { + let tab = allTabs[i]; + let uri = tab.linkedBrowser.currentURI.spec; + if (uri in seen) + continue; + seen[uri] = true; + tabs.push(tab); + } + return tabs; + })()); + try { + return this.__treestyletab__bookmarkCurrentPages.apply(this, aArgs); + } + finally { + TreeStyleTabBookmarksService.endAddBookmarksFromTabs(); + } }; - if ( - aEvent.type != 'drop' && - aWhere.indexOf('tab') != 0 && - aEvent.target.id != 'placesContext_openContainer:tabs' && - aEvent.target.id != 'placesContext_openLinks:tabs' && - aEvent.target != aEvent.target.parentNode._endOptOpenAllInTabs && - aEvent.target.getAttribute('openInTabs') != 'true' - ) - return result; - - var sv = aBrowserWindow.TreeStyleTabBookmarksService; - result.behavior = sv.openGroupBookmarkBehavior(); - if (result.behavior & sv.kGROUP_BOOKMARK_SUBTREE) { - let treeStructure = result.behavior & sv.kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE ? - null : - sv.getTreeStructureFromItems(aIDs) ; - if (treeStructure) { - let parentTabs = treeStructure.filter(function(aParent) { - return aParent < 0; - }); - let haveMultipleTrees = parentTabs.length != treeStructure.length; - if (result.behavior & sv.kGROUP_BOOKMARK_USE_DUMMY) { - let parentCount = 0; - let childCount = 0; - for (let i in treeStructure) { - if (treeStructure[i] == -1) - parentCount++; - else - childCount++; - } - if ( - parentCount > 1 && - ( - result.behavior & sv.kGROUP_BOOKMARK_USE_DUMMY_FORCE || - // when there is any orphan, then all of parents and orphans should be grouped under a dummy tab. - childCount < parentCount - ) - ) { - aIDs.unshift(-1); - treeStructure = sv.getTreeStructureFromItems(aIDs, 0); - aURLs.unshift(sv.getGroupTabURI({ - title: aFolderTitle, - temporary: TreeStyleTabUtils.getTreePref('openGroupBookmark.temporaryGroup') - })); - } - } - else if (!haveMultipleTrees) { - // make the first item parent. - treeStructure = treeStructure.map(function(aParent, aIndex) { - if (aIndex == 0) - return aParent; - if (aParent < 0) - return 0; - return aParent; - }); - } - } - - result.treeStructure = treeStructure; - result.previousTabs = aBrowserWindow.TreeStyleTabService.getTabsInfo(aBrowserWindow.gBrowser); - - if (TreeStyleTabUtils.getTreePref('compatibility.TMP') && - 'TMP_Places' in aBrowserWindow && - 'openGroup' in aBrowserWindow.TMP_Places) { - result.treeStructureApplied = false; - } - else { - sv.readyToOpenNewTabGroup(null, treeStructure, result.behavior & sv.kGROUP_BOOKMARK_EXPAND_ALL_TREE); - result.treeStructureApplied = true; - } } - return result; }, - - destroy : function TSTBMService_destroy() { window.removeEventListener('unload', this, false); @@ -483,22 +62,6 @@ var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, { ReferenceCounter.remove('window,EVENT_TYPE_TABS_DROP,TSTBMService,false'); }, - // observer for nsINavBookmarksService - onItemAdded : function TSTBMService_onItemAdded(aID, aFolderID, aPosition) - { - this._addingBookmarks.push({ - id : aID, - parent : aFolderID - }); - }, - onItemRemoved : function TSTBMService_onItemRemoved(aID, aFolderID, aPosition) {}, - onItemMoved : function TSTBMService_onItemMoved(aID, aFolderID, aPosition) {}, - onItemChanged : function TSTBMService_onItemChanged(aID, aChange, aIsAnnotation, aNewValue) {}, - onItemVisited : function TSTBMService_onItemVisited(aID, aHistoryID, aDate) {}, - onBeginUpdateBatch : function TSTBMService_onBeginUpdateBatch() {}, - onEndUpdateBatch : function TSTBMService_onEndUpdateBatch() {}, - - _onTabsDrop : function TSTBMService_onTabsDrop(aEvent) { var tabs = aEvent.detail.tabs; @@ -533,7 +96,7 @@ var TreeStyleTabBookmarksService = inherit(TreeStyleTabService, { }); -TreeStyleTabBookmarksService.preInit(); +TreeStyleTabBookmarksUIService.preInit(); -window.TreeStyleTabBookmarksService = TreeStyleTabBookmarksService; +window.TreeStyleTabBookmarksUIService = TreeStyleTabBookmarksUIService; })(); diff --git a/content/treestyletab/bookmarksOverlayEditable.js b/content/treestyletab/bookmarksOverlayEditable.js index aa5b4299..fdc1e713 100644 --- a/content/treestyletab/bookmarksOverlayEditable.js +++ b/content/treestyletab/bookmarksOverlayEditable.js @@ -327,13 +327,22 @@ var TreeStyleTabBookmarksServiceEditable = inherit(TreeStyleTabBookmarksService, }, _getSiblingItemsIterator : function TSTBMEditable_getSiblingItemsIterator(aId) { - return this._getItemsInFolderIterator(PlacesUtils.bookmarks.getFolderIdForItem(aId)); + try { + var folderId = PlacesUtils.bookmarks.getFolderIdForItem(aId); + return this._getItemsInFolderIterator(folderId); + } + catch(e) { + dump('TSTBMEditable_getSiblingItemsIterator('+aId+') failed.\n'); + dump(e+'\n'); + dump(new Error().stack+'\n'); + } }, saveParentFor : function TSTBMEditable_saveParentFor(aId, aJustNow) { var newParentId = parseInt(this.menulist.value || -1); - if (this.canceled || newParentId == this.getParentItem(aId)) return; + if (this.canceled || newParentId == this.getParentItem(aId)) + return; var itemsIterator = this._getSiblingItemsIterator(aId); var items = []; diff --git a/defaults/preferences/treestyletab.js b/defaults/preferences/treestyletab.js index e547075d..90352227 100644 --- a/defaults/preferences/treestyletab.js +++ b/defaults/preferences/treestyletab.js @@ -726,6 +726,7 @@ pref("extensions.treestyletab.prefsVersion", 0); pref("extensions.treestyletab.debug.all", false); pref("extensions.treestyletab.debug.autoHide", false); pref("extensions.treestyletab.debug.base", false); +pref("extensions.treestyletab.debug.bookmark", false); pref("extensions.treestyletab.debug.browser", false); pref("extensions.treestyletab.debug.browserUIShowHideObserver", false); pref("extensions.treestyletab.debug.contentBridge", false); diff --git a/modules/base.js b/modules/base.js index ae2a9658..7ad29263 100644 --- a/modules/base.js +++ b/modules/base.js @@ -370,10 +370,6 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, { return behavior }, - kDROPLINK_ASK : 0, - kDROPLINK_FIXED : 1 + 2, - kDROPLINK_LOAD : 1, - kDROPLINK_NEWTAB : 2, openGroupBookmarkBehavior : function TSTBase_openGroupBookmarkBehavior() { @@ -409,14 +405,6 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, { } 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() { @@ -2282,48 +2270,6 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, { 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]); @@ -2375,7 +2321,7 @@ var TreeStyleTabBase = inherit(TreeStyleTabConstants, { getTreeStructureFromTabBrowser : function TSTBase_getTreeStructureFromTabBrowser(aTabBrowser) { - return this.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser)); + return utils.getTreeStructureFromTabs(this.getAllTabs(aTabBrowser)); }, applyTreeStructureToTabBrowser : function TSTBase_applyTreeStructureToTabBrowser(aTabBrowser, aTreeStructure, aExpandAllTree) diff --git a/modules/bookmark.js b/modules/bookmark.js new file mode 100644 index 00000000..d48ee2aa --- /dev/null +++ b/modules/bookmark.js @@ -0,0 +1,489 @@ +/* ***** 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) 2016 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): YUKI "Piro" Hiroshi + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ******/ + +var EXPORTED_SYMBOLS = ['TreeStyleTabBookmarksService']; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +Cu.import('resource:///modules/PlacesUIUtils.jsm'); +Cu.import('resource://gre/modules/PlacesUtils.jsm'); + +Cu.import('resource://treestyletab-modules/lib/inherit.jsm'); +Cu.import('resource://treestyletab-modules/constants.js'); + +XPCOMUtils.defineLazyModuleGetter(this, 'utils', 'resource://treestyletab-modules/utils.js', 'TreeStyleTabUtils'); + +function mydump(aString) { + if (utils.isDebugging('bookmark')) + dump(aString); +} + +var TreeStyleTabBookmarksService = inherit(TreeStyleTabConstants, { + get BookmarksService() { + if (!this._BookmarksService) { + this._BookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] + .getService(Ci.nsINavBookmarksService); + } + return this._BookmarksService; + }, + _BookmarksService : null, + + + beginAddBookmarksFromTabs : function TSTBMService_beginAddBookmarksFromTabs(aTabs) /* PUBLIC API */ + { + if (this._observing || + !aTabs || + aTabs.length <= 0) + return; + + this._observing = true; + + var TST = aTabs[0].ownerDocument.defaultView.TreeStyleTabService; + aTabs = TST.cleanUpTabsArray(aTabs); + + this._addingBookmarks = []; + this._addingBookmarkTreeStructure = aTabs.map(function(aTab) { + var parent = TST.getParentTab(aTab); + return aTabs.indexOf(parent); + }, this); + + this.BookmarksService.addObserver(this, false); + }, + + endAddBookmarksFromTabs : function TSTBMService_endAddBookmarksFromTabs() /* PUBLIC API */ + { + if (!this._observing) + return; + + this._observing = false; + + this.BookmarksService.removeObserver(this); + this.handleNewBookmarksFromTabs(this._addingBookmarks, this._addingBookmarkTreeStructure); + this._addingBookmarks = []; + this._addingBookmarkTreeStructure = []; + }, + + handleNewBookmarksFromTabs : function TSTBMService_handleNewBookmarksFromTabs(aBookarmks, aTreeStructure) + { + // this is adding bookmark folder from tabs, so ignroe the first item! + if ( + aBookarmks.length == aTreeStructure.length+1 && + this.BookmarksService.getItemType(aBookarmks[0].id) == this.BookmarksService.TYPE_FOLDER + ) { + aBookarmks.shift(); + } + else if (aBookarmks.length != aTreeStructure.length) { + return; + } + + for (let i = 0, maxi = aBookarmks.length; i < maxi; i++) + { + let item = aBookarmks[i]; + item.position = this.BookmarksService.getItemIndex(item.id); + } + aBookarmks.sort(function(aA, aB) { + return aA.position - aB.position; + }); + + for (let i = 0, maxi = aBookarmks.length; i < maxi; i++) + { + let item = aBookarmks[i]; + if (this.BookmarksService.getItemType(item.id) != this.BookmarksService.TYPE_BOOKMARK) + continue; + + let uri = this.BookmarksService.getBookmarkURI(item.id); + if (/^about:treestyletab-group\b/.test(uri.spec)) { + let title = this.BookmarksService.getItemTitle(item.id); + let folderId = this.BookmarksService.createFolder(item.parent, title, item.position); + this.BookmarksService.removeItem(item.id); + item.id = folderId; + item.isFolder = true; + } + + let index = aTreeStructure[i]; + let parent = index > -1 ? aBookarmks[index] : null ; + if (parent && (parent.folder || parent).isFolder) { + let folder = parent.isFolder ? parent : parent.folder ; + this.BookmarksService.moveItem(item.id, folder.id, -1); + item.folder = folder; + } + if (parent && !parent.isFolder) { + PlacesUtils.setAnnotationsForItem(item.id, [{ + name : this.kPARENT, + value : parent ? parent.id : -1, + expires : PlacesUtils.annotations.EXPIRE_NEVER + }]); + } + } + }, + + bookmarkTabSubtree : function TSTBMService_bookmarkTabSubtree(aTabOrTabs) + { + var tabs = aTabOrTabs; + if (!Array.isArray(tabs)) { + tabs = [aTabOrTabs]; + } + if (tabs.length <= 0) + return; + + var window = tabs[0].ownerDocument.defaultView; + var TST = window.TreeStyleTabService; + + var folderName = (TST.isGroupTab(tabs[0], true) || tabs.length == 1) ? + tabs[0].label : + null ; + + var b = TST.getTabBrowserFromChild(tabs[0]); + var bookmarkedTabs = []; + for (let i = 0, maxi = tabs.length; i < maxi; i++) + { + let tab = tabs[i]; + if (!TST.isGroupTab(tab, i == 0)) + bookmarkedTabs.push(tab); + bookmarkedTabs = bookmarkedTabs.concat(b.treeStyleTab.getDescendantTabs(tab)); + } + + this.beginAddBookmarksFromTabs(bookmarkedTabs); + try { + window['piro.sakura.ne.jp'].bookmarkMultipleTabs.addBookmarkFor(bookmarkedTabs, folderName); + } + catch(e) { + } + this.endAddBookmarksFromTabs(); + }, + bookmarkTabSubTree : function() { return this.bookmarkTabSubtree.apply(this, arguments); }, // obsolete, for backward compatibility + + getParentItem : function TSTBMService_getParentItem(aId) + { + if (aId < 0) return -1; + var annotations = PlacesUtils.getAnnotationsForItem(aId); + for (let i in annotations) + { + if (annotations[i].name != this.kPARENT) continue; + return parseInt(annotations[i].value); + } + return -1; + }, + + getTreeStructureFromItems : function TSTBMService_getTreeStructureFromItems(aIDs, aDefaultParentID) + { + /* this returns a result same to utils.getTreeStructureFromTabs(). + [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) + */ + if (aDefaultParentID === void(0)) + aDefaultParentID = -1; + + /* Get array of parents. The index becomes to -1, + if there is NO PARENT or the parent is THE TAB ITSELF. */ + var treeStructure = aIDs.map(function(aId, aIndex) { + let id = this.getParentItem(aId); + let index = aIDs.indexOf(id); + return index >= aIndex ? aDefaultParentID : index ; + }, this); + + /* Correct patterns like: + [TabA] + [TabB] - this has no parent + [TabC] - TabA's child + to: + [TabA] + [TabB] + [TabC] + */ + treeStructure = treeStructure.reverse(); + treeStructure = treeStructure.map(function(aPosition, aIndex) { + if (aIndex > 0 && + aIndex < treeStructure.length-1 && + aPosition < 0) { + aPosition = treeStructure[aIndex-1]; + } + return aPosition; + }); + treeStructure = treeStructure.reverse(); + + return utils.cleanUpTreeStructureArray(treeStructure, aDefaultParentID); + }, + + // based on PlacesUtils.getURLsForContainerNode() + getItemIdsForContainerNode : function TSTBMService_getItemIdsForContainerNode(aNode) + { + var ids = []; + if (!aNode || !PlacesUtils.nodeIsContainer(aNode)) return ids; + + var root = aNode; + if ('getContainerNodeWithOptions' in PlacesUtils) { + root = PlacesUtils.getContainerNodeWithOptions(root, false, true); + } + var oldViewer = root.parentResult.viewer; + var wasOpen = root.containerOpen; + if (!wasOpen) { + if (oldViewer) + root.parentResult.viewer = null; + root.containerOpen = true; + } + for (let i = 0, maxi = root.childCount; i < maxi; ++i) + { + let child = root.getChild(i); + if (PlacesUtils.nodeIsURI(child)) ids.push(child.itemId || -1); + } + if (!wasOpen) { + root.containerOpen = false; + if (oldViewer) + root.parentResult.viewer = oldViewer; + } + return ids; + }, + + handleTabsOpenProcess : function TSTBMService_handleTabsOpenProcess(aWhere, aEvent, aBrowserWindow, aIDs, aURLs, aItemsToOpen, aFolderTitle) + { + var result = { + behavior : undefined, + treeStructure : undefined, + previousTabs : undefined, + treeStructureApplied : false + }; + if ( + aEvent.type != 'drop' && + aWhere.indexOf('tab') != 0 && + aEvent.target.id != 'placesContext_openContainer:tabs' && + aEvent.target.id != 'placesContext_openLinks:tabs' && + aEvent.target != aEvent.target.parentNode._endOptOpenAllInTabs && + aEvent.target.getAttribute('openInTabs') != 'true' + ) + return result; + + var TST = aBrowserWindow.TreeStyleTabService; + + result.behavior = TST.openGroupBookmarkBehavior(); + if (result.behavior & this.kGROUP_BOOKMARK_SUBTREE) { + mydump('TSTBMService_handleTabsOpenProcess: open as a group\n'); + let treeStructure = result.behavior & this.kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE ? + null : + this.getTreeStructureFromItems(aIDs) ; + mydump(' treeStructure => '+JSON.stringify(treeStructure)+'\n'); + if (treeStructure) { + let parentTabs = treeStructure.filter(function(aParent) { + return aParent < 0; + }); + let haveMultipleTrees = parentTabs.length != treeStructure.length; + if (result.behavior & this.kGROUP_BOOKMARK_USE_DUMMY) { + mydump(' trying to use dummy group tab\n'); + let parentCount = 0; + let childCount = 0; + for (let i in treeStructure) { + if (treeStructure[i] == -1) + parentCount++; + else + childCount++; + } + mydump(' parentCount: '+parentCount+'\n'); + mydump(' childCount: '+childCount+'\n'); + if ( + parentCount > 1 && + ( + result.behavior & this.kGROUP_BOOKMARK_USE_DUMMY_FORCE || + // when there is any orphan, then all of parents and orphans should be grouped under a dummy tab. + childCount < parentCount + ) + ) { + aIDs.unshift(-1); + treeStructure = this.getTreeStructureFromItems(aIDs, 0); + let uri = TST.getGroupTabURI({ + title: aFolderTitle, + temporary: utils.getTreePref('openGroupBookmark.temporaryGroup') + }); + aURLs.unshift(uri); + aItemsToOpen.unshift({ + itemId: -1, + title: aFolderTitle, + uri: uri + }) + mydump(' updated treeStructure => '+JSON.stringify(treeStructure)+'\n'); + } + } + else if (!haveMultipleTrees) { + // make the first item parent. + treeStructure = treeStructure.map(function(aParent, aIndex) { + if (aIndex == 0) + return aParent; + if (aParent < 0) + return 0; + return aParent; + }); + } + } + + result.treeStructure = treeStructure; + result.previousTabs = TST.getTabsInfo(aBrowserWindow.gBrowser); + + if (utils.getTreePref('compatibility.TMP') && + 'TMP_Places' in aBrowserWindow && + 'openGroup' in aBrowserWindow.TMP_Places) { + result.treeStructureApplied = false; + } + else { + TST.readyToOpenNewTabGroup(null, treeStructure, result.behavior & this.kGROUP_BOOKMARK_EXPAND_ALL_TREE); + result.treeStructureApplied = true; + } + } + return result; + }, + + + // observer for nsINavBookmarksService + onItemAdded : function TSTBMService_onItemAdded(aID, aFolderID, aPosition) + { + this._addingBookmarks.push({ + id : aID, + parent : aFolderID + }); + }, + onItemRemoved : function TSTBMService_onItemRemoved(aID, aFolderID, aPosition) {}, + onItemMoved : function TSTBMService_onItemMoved(aID, aFolderID, aPosition) {}, + onItemChanged : function TSTBMService_onItemChanged(aID, aChange, aIsAnnotation, aNewValue) {}, + onItemVisited : function TSTBMService_onItemVisited(aID, aHistoryID, aDate) {}, + onBeginUpdateBatch : function TSTBMService_onBeginUpdateBatch() {}, + onEndUpdateBatch : function TSTBMService_onEndUpdateBatch() {} +}); + + +PlacesUIUtils.__treestyletab__openTabset = PlacesUIUtils._openTabset; +PlacesUIUtils._openTabset = function(aItemsToOpen, aEvent, aWindow, ...aArgs) { + mydump('TSTBookmarks_openTabset\n'); + + var uris = []; + var ids = []; + var nodes = this.__treestyletab__openTabset_rawNodes || []; + aItemsToOpen = aItemsToOpen.filter(function(aItem, aIndex) { + if (aItem.uri) { + uris.push(aItem.uri); + let id = aItem.id; + if (!id && aIndex in nodes) + id = nodes[aIndex].itemId; + ids.push(id); + mydump(' '+aIndex+': '+id+' / '+aItem.uri+'\n'); + return true; + } + return false; + }); + mydump(' items => '+aItemsToOpen.length+'\n'); + + var allArgs = [aItemsToOpen, aEvent, aWindow].concat(aArgs); + if (aItemsToOpen.length <= 0) + return this.__treestyletab__openTabset.apply(this, allArgs); + + var w = aWindow && aWindow.document.documentElement.getAttribute('windowtype') == 'navigator:browser' ? + aWindow : + this._getTopBrowserWin() ; + var TST = w.TreeStyleTabService; + var BS = TreeStyleTabBookmarksService; + + var where = w && w.whereToOpenLink(aEvent, false, true) || 'window'; + mydump(' where: '+where+'\n'); + if (where === 'window') + return this.__treestyletab__openTabset.apply(this, allArgs); + + var result = BS.handleTabsOpenProcess(where, aEvent, w, ids, uris, aItemsToOpen, this.__treestyletab__folderName); + + mydump(' result: '+JSON.stringify(result)+'\n'); + this.__treestyletab__openTabset.apply(this, allArgs); + + var tabs = []; + if (result.treeStructure && result.previousTabs) + tabs = TST.getNewTabsFromPreviousTabsInfo(w.gBrowser, result.previousTabs); + + if (!result.treeStructureApplied) + TST.applyTreeStructureToTabs( + tabs, + result.treeStructure, + result.behavior & BS.kGROUP_BOOKMARK_EXPAND_ALL_TREE + ); + + var loadInBackground = where == 'tabshifted'; + if (!loadInBackground) { + // start scroll after expanding animation is finished + w.setTimeout(function() { + w.gBrowser.treeStyleTab.scrollToTabs(tabs); + }, w.gBrowser.treeStyleTab.collapseDuration); + } +}; + +PlacesUtils.__treestyletab__getURLsForContainerNode = PlacesUtils.getURLsForContainerNode; +PlacesUtils.getURLsForContainerNode = function(aNode, ...aArgs) { + var uris = this.__treestyletab__getURLsForContainerNode.apply(this, [aNode].concat(aArgs)); + var nodes = TreeStyleTabBookmarksService.getItemIdsForContainerNode(aNode); + for (let i in nodes) { + uris[i].id = nodes[i]; + } + return uris; +}; + +PlacesUIUtils.__treestyletab__openContainerNodeInTabs = PlacesUIUtils.openContainerNodeInTabs; +PlacesUIUtils.openContainerNodeInTabs = function(aNode, ...aArgs) { + this.__treestyletab__folderName = aNode.title; + try { + return this.__treestyletab__openContainerNodeInTabs.apply(this, [aNode].concat(aArgs)); + } + finally { + delete this.__treestyletab__folderName; + } +}; + +PlacesUIUtils.__treestyletab__openURINodesInTabs = PlacesUIUtils.openURINodesInTabs; +PlacesUIUtils.openURINodesInTabs = function(aNode, ...aArgs) { + try { + this.__treestyletab__openTabset_rawNodes = aNodes; + this.__treestyletab__folderName = utils.treeBundle.getFormattedString( + PlacesUtils.nodeIsBookmark(aNodes[0]) ? + 'openSelectedPlaces.bookmarks' : + 'openSelectedPlaces.history', + [aNodes[0].title, aNodes.length] + ); + return this.__treestyletab__openURINodesInTabs.apply(this, [aNode].concat(aArgs)); + } + finally { + delete this.__treestyletab__openTabset_rawNodes; + delete this.__treestyletab__folderName; + } +}; diff --git a/modules/browser.js b/modules/browser.js index c239ab66..7706af21 100644 --- a/modules/browser.js +++ b/modules/browser.js @@ -6274,7 +6274,7 @@ TreeStyleTabBrowser.prototype = inherit(TreeStyleTabWindow.prototype, { sourceService.getAllTabs(sourceBrowser).length == aTabs.length ); var newTabs = []; - var treeStructure = sourceService.getTreeStructureFromTabs(aTabs); + var treeStructure = utils.getTreeStructureFromTabs(aTabs); // Firefox fails to "move" collapsed tabs. So, expand them first // and collapse them after they are moved. @@ -6979,7 +6979,8 @@ TreeStyleTabBrowser.prototype = inherit(TreeStyleTabWindow.prototype, { scrollToTabs : function TSTBrowser_scrollToTabs(aTabs) { var firstTab = aTabs[0]; - if (!firstTab.parentNode) // do nothing for closed tab! + if (!firstTab || + !firstTab.parentNode) // do nothing for closed tab! return; var b = this.mTabBrowser; diff --git a/modules/constants.js b/modules/constants.js index 9cc4c256..07e6588f 100644 --- a/modules/constants.js +++ b/modules/constants.js @@ -220,6 +220,22 @@ var TreeStyleTabConstants = Object.freeze({ RESTORE_STATE_STRUCTURE_RESTORED : 2, + kDROPLINK_ASK : 0, + kDROPLINK_FIXED : 1 + 2, + kDROPLINK_LOAD : 1, + kDROPLINK_NEWTAB : 2, + + + 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, + + CONTENT_SCRIPT : 'chrome://treestyletab/content/content-utils.js', CONTENT_SCRIPT_AUTOHIDE : 'chrome://treestyletab/content/content-utils-autohide.js', MESSAGE_TYPE : 'treestyletab', diff --git a/modules/tabbarDNDObserver.js b/modules/tabbarDNDObserver.js index e45033ee..b3657fe1 100644 --- a/modules/tabbarDNDObserver.js +++ b/modules/tabbarDNDObserver.js @@ -572,7 +572,7 @@ catch(e) { } } - var treeStructure = sourceService.getTreeStructureFromTabs(draggedTabs); + var treeStructure = utils.getTreeStructureFromTabs(draggedTabs); var newTabs = sv.moveTabsInternal(draggedTabs, { duplicate : aInfo.action & sv.kACTION_DUPLICATE, diff --git a/modules/utils.js b/modules/utils.js index 8c8c026c..93609984 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -335,6 +335,50 @@ var TreeStyleTabUtils = { this.isPrefChanging(aKey); }, + + + getTreeStructureFromTabs : function TSTUtils_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 TSTUtils_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; + }, + /* Pref Listener */ domains : [ 'extensions.treestyletab.'