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