/* ***** 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 log(...aArgs) { utils.log.apply(utils, ['bookmark'].concat(aArgs)); } function logWithStackTrace(...aArgs) { utils.logWithStackTrace.apply(utils, ['bookmark'].concat(aArgs)); } 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]) || 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)) 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, 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) { log('handleTabsOpenProcess: open as a group'); let treeStructure = result.behavior & this.kGROUP_BOOKMARK_DONT_RESTORE_TREE_STRUCTURE ? null : this.getTreeStructureFromItems(aIDs) ; log(' treeStructure => '+JSON.stringify(treeStructure)); 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) { log(' trying to use dummy group tab'); let parentCount = 0; let childCount = 0; for (let i in treeStructure) { if (treeStructure[i] == -1) parentCount++; else childCount++; } log(' parentCount: '+parentCount); log(' childCount: '+childCount); 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 }) log(' updated treeStructure => '+JSON.stringify(treeStructure)); } } 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; 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) { log('TSTBookmarks_openTabset'); 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); log(' '+aIndex+': '+id+' / '+aItem.uri); return true; } return false; }); log(' items => '+aItemsToOpen.length); 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'; log(' where: '+where); if (where === 'window') return this.__treestyletab__openTabset.apply(this, allArgs); var result = BS.handleTabsOpenProcess(where, aEvent, w, ids, uris, aItemsToOpen, this.__treestyletab__folderName); log(' result: '+JSON.stringify(result)); var tabs = TST.doAndGetNewTabs((function() { this.__treestyletab__openTabset.apply(this, allArgs); }).bind(this), w.gBrowser); log(' tabs: '+tabs.length); if (!result.treeStructure) tabs = []; 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; } }; PlacesUIUtils.__treestyletab__openNodeWithEvent = PlacesUIUtils.openNodeWithEvent; PlacesUIUtils.openNodeWithEvent = function(aNode, aEvent, aView, ...aArgs) { var window = aView.ownerWindow; if (!window.gBrowser) window = PlacesUIUtils._getTopBrowserWin(); if (window && window.gBrowser) window.gBrowser.treeStyleTab.readyToOpenOrphanTabNow(); return PlacesUIUtils.__treestyletab__openNodeWithEvent.apply(this, [aNode, aEvent, aView].concat(aArgs)); };