1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 cr.define('bmm', function() { 6 /** 7 * Whether a node contains another node. 8 * TODO(yosin): Once JavaScript style guide is updated and linter follows 9 * that, we'll remove useless documentations for |parent| and |descendant|. 10 * TODO(yosin): bmm.contains() should be method of BookmarkTreeNode. 11 * @param {!BookmarkTreeNode} parent . 12 * @param {!BookmarkTreeNode} descendant . 13 * @return {boolean} Whether the parent contains the descendant. 14 */ 15 function contains(parent, descendant) { 16 if (descendant.parentId == parent.id) 17 return true; 18 // the bmm.treeLookup contains all folders 19 var parentTreeItem = bmm.treeLookup[descendant.parentId]; 20 if (!parentTreeItem || !parentTreeItem.bookmarkNode) 21 return false; 22 return this.contains(parent, parentTreeItem.bookmarkNode); 23 } 24 25 /** 26 * @param {!BookmarkTreeNode} node The node to test. 27 * @return {boolean} Whether a bookmark node is a folder. 28 */ 29 function isFolder(node) { 30 return !('url' in node); 31 } 32 33 var loadingPromises = {}; 34 35 /** 36 * Promise version of chrome.bookmarkManagerPrivate.getSubtree. 37 * @param {string} id . 38 * @param {boolean} foldersOnly . 39 * @return {!Promise.<!Array.<!BookmarkTreeNode>>} . 40 */ 41 function getSubtreePromise(id, foldersOnly) { 42 return new Promise(function(resolve) { 43 chrome.bookmarkManagerPrivate.getSubtree(id, foldersOnly, resolve); 44 }); 45 } 46 47 /** 48 * Loads a subtree of the bookmark tree and returns a {@code Promise} that 49 * will be fulfilled when done. This reuses multiple loads so that we do not 50 * load the same subtree more than once at the same time. 51 * @return {!Promise.<!BookmarkTreeNode>} The future promise for the load. 52 */ 53 function loadSubtree(id) { 54 if (!loadingPromises[id]) { 55 loadingPromises[id] = getSubtreePromise(id, false).then(function(nodes) { 56 delete loadingPromises[id]; 57 return nodes && nodes[0]; 58 }); 59 } 60 return loadingPromises[id]; 61 } 62 63 /** 64 * Loads the entire bookmark tree and returns a {@code Promise} that will 65 * be fulfilled when done. This reuses multiple loads so that we do not load 66 * the same tree more than once at the same time. 67 * @return {!Promise.<Node>} The future promise for the load. 68 */ 69 function loadTree() { 70 return loadSubtree(''); 71 } 72 73 var bookmarkCache = { 74 /** 75 * Removes the cached item from both the list and tree lookups. 76 */ 77 remove: function(id) { 78 var treeItem = bmm.treeLookup[id]; 79 if (treeItem) { 80 var items = treeItem.items; // is an HTMLCollection 81 for (var i = 0; i < items.length; ++i) { 82 var item = items[i]; 83 var bookmarkNode = item.bookmarkNode; 84 delete bmm.treeLookup[bookmarkNode.id]; 85 } 86 delete bmm.treeLookup[id]; 87 } 88 }, 89 90 /** 91 * Updates the underlying bookmark node for the tree items and list items by 92 * querying the bookmark backend. 93 * @param {string} id The id of the node to update the children for. 94 * @param {Function=} opt_f A funciton to call when done. 95 */ 96 updateChildren: function(id, opt_f) { 97 function updateItem(bookmarkNode) { 98 var treeItem = bmm.treeLookup[bookmarkNode.id]; 99 if (treeItem) { 100 treeItem.bookmarkNode = bookmarkNode; 101 } 102 } 103 104 chrome.bookmarks.getChildren(id, function(children) { 105 if (children) 106 children.forEach(updateItem); 107 108 if (opt_f) 109 opt_f(children); 110 }); 111 } 112 }; 113 114 /** 115 * Called when the title of a bookmark changes. 116 * @param {string} id The id of changed bookmark node. 117 * @param {!Object} changeInfo The information about how the node changed. 118 */ 119 function handleBookmarkChanged(id, changeInfo) { 120 if (bmm.tree) 121 bmm.tree.handleBookmarkChanged(id, changeInfo); 122 if (bmm.list) 123 bmm.list.handleBookmarkChanged(id, changeInfo); 124 } 125 126 /** 127 * Callback for when the user reorders by title. 128 * @param {string} id The id of the bookmark folder that was reordered. 129 * @param {!Object} reorderInfo The information about how the items where 130 * reordered. 131 */ 132 function handleChildrenReordered(id, reorderInfo) { 133 if (bmm.tree) 134 bmm.tree.handleChildrenReordered(id, reorderInfo); 135 if (bmm.list) 136 bmm.list.handleChildrenReordered(id, reorderInfo); 137 bookmarkCache.updateChildren(id); 138 } 139 140 /** 141 * Callback for when a bookmark node is created. 142 * @param {string} id The id of the newly created bookmark node. 143 * @param {!Object} bookmarkNode The new bookmark node. 144 */ 145 function handleCreated(id, bookmarkNode) { 146 if (bmm.list) 147 bmm.list.handleCreated(id, bookmarkNode); 148 if (bmm.tree) 149 bmm.tree.handleCreated(id, bookmarkNode); 150 bookmarkCache.updateChildren(bookmarkNode.parentId); 151 } 152 153 /** 154 * Callback for when a bookmark node is moved. 155 * @param {string} id The id of the moved bookmark node. 156 * @param {!Object} moveInfo The information about move. 157 */ 158 function handleMoved(id, moveInfo) { 159 if (bmm.list) 160 bmm.list.handleMoved(id, moveInfo); 161 if (bmm.tree) 162 bmm.tree.handleMoved(id, moveInfo); 163 164 bookmarkCache.updateChildren(moveInfo.parentId); 165 if (moveInfo.parentId != moveInfo.oldParentId) 166 bookmarkCache.updateChildren(moveInfo.oldParentId); 167 } 168 169 /** 170 * Callback for when a bookmark node is removed. 171 * @param {string} id The id of the removed bookmark node. 172 * @param {!Object} bookmarkNode The information about removed. 173 */ 174 function handleRemoved(id, removeInfo) { 175 if (bmm.list) 176 bmm.list.handleRemoved(id, removeInfo); 177 if (bmm.tree) 178 bmm.tree.handleRemoved(id, removeInfo); 179 180 bookmarkCache.updateChildren(removeInfo.parentId); 181 bookmarkCache.remove(id); 182 } 183 184 /** 185 * Callback for when all bookmark nodes have been deleted. 186 */ 187 function handleRemoveAll() { 188 // Reload the list and the tree. 189 if (bmm.list) 190 bmm.list.reload(); 191 if (bmm.tree) 192 bmm.tree.reload(); 193 } 194 195 /** 196 * Callback for when importing bookmark is started. 197 */ 198 function handleImportBegan() { 199 chrome.bookmarks.onCreated.removeListener(handleCreated); 200 chrome.bookmarks.onChanged.removeListener(handleBookmarkChanged); 201 } 202 203 /** 204 * Callback for when importing bookmark node is finished. 205 */ 206 function handleImportEnded() { 207 // When importing is done we reload the tree and the list. 208 209 function f() { 210 bmm.tree.removeEventListener('load', f); 211 212 chrome.bookmarks.onCreated.addListener(handleCreated); 213 chrome.bookmarks.onChanged.addListener(handleBookmarkChanged); 214 215 if (!bmm.list) 216 return; 217 218 // TODO(estade): this should navigate to the newly imported folder, which 219 // may be the bookmark bar if there were no previous bookmarks. 220 bmm.list.reload(); 221 } 222 223 if (bmm.tree) { 224 bmm.tree.addEventListener('load', f); 225 bmm.tree.reload(); 226 } 227 } 228 229 /** 230 * Adds the listeners for the bookmark model change events. 231 */ 232 function addBookmarkModelListeners() { 233 chrome.bookmarks.onChanged.addListener(handleBookmarkChanged); 234 chrome.bookmarks.onChildrenReordered.addListener(handleChildrenReordered); 235 chrome.bookmarks.onCreated.addListener(handleCreated); 236 chrome.bookmarks.onMoved.addListener(handleMoved); 237 chrome.bookmarks.onRemoved.addListener(handleRemoved); 238 chrome.bookmarks.onImportBegan.addListener(handleImportBegan); 239 chrome.bookmarks.onImportEnded.addListener(handleImportEnded); 240 }; 241 242 return { 243 contains: contains, 244 isFolder: isFolder, 245 loadSubtree: loadSubtree, 246 loadTree: loadTree, 247 addBookmarkModelListeners: addBookmarkModelListeners 248 }; 249 }); 250