1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/PlacesUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,3000 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +this.EXPORTED_SYMBOLS = [ 1.10 + "PlacesUtils" 1.11 +, "PlacesAggregatedTransaction" 1.12 +, "PlacesCreateFolderTransaction" 1.13 +, "PlacesCreateBookmarkTransaction" 1.14 +, "PlacesCreateSeparatorTransaction" 1.15 +, "PlacesCreateLivemarkTransaction" 1.16 +, "PlacesMoveItemTransaction" 1.17 +, "PlacesRemoveItemTransaction" 1.18 +, "PlacesEditItemTitleTransaction" 1.19 +, "PlacesEditBookmarkURITransaction" 1.20 +, "PlacesSetItemAnnotationTransaction" 1.21 +, "PlacesSetPageAnnotationTransaction" 1.22 +, "PlacesEditBookmarkKeywordTransaction" 1.23 +, "PlacesEditBookmarkPostDataTransaction" 1.24 +, "PlacesEditItemDateAddedTransaction" 1.25 +, "PlacesEditItemLastModifiedTransaction" 1.26 +, "PlacesSortFolderByNameTransaction" 1.27 +, "PlacesTagURITransaction" 1.28 +, "PlacesUntagURITransaction" 1.29 +]; 1.30 + 1.31 +const Ci = Components.interfaces; 1.32 +const Cc = Components.classes; 1.33 +const Cr = Components.results; 1.34 +const Cu = Components.utils; 1.35 + 1.36 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.37 + 1.38 +XPCOMUtils.defineLazyModuleGetter(this, "Services", 1.39 + "resource://gre/modules/Services.jsm"); 1.40 + 1.41 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", 1.42 + "resource://gre/modules/NetUtil.jsm"); 1.43 + 1.44 +XPCOMUtils.defineLazyModuleGetter(this, "Task", 1.45 + "resource://gre/modules/Task.jsm"); 1.46 + 1.47 +XPCOMUtils.defineLazyModuleGetter(this, "Promise", 1.48 + "resource://gre/modules/Promise.jsm"); 1.49 + 1.50 +XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", 1.51 + "resource://gre/modules/Deprecated.jsm"); 1.52 + 1.53 +XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils", 1.54 + "resource://gre/modules/BookmarkJSONUtils.jsm"); 1.55 + 1.56 +XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups", 1.57 + "resource://gre/modules/PlacesBackups.jsm"); 1.58 + 1.59 +// The minimum amount of transactions before starting a batch. Usually we do 1.60 +// do incremental updates, a batch will cause views to completely 1.61 +// refresh instead. 1.62 +const MIN_TRANSACTIONS_FOR_BATCH = 5; 1.63 + 1.64 +#ifdef XP_MACOSX 1.65 +// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where we 1.66 +// really just want "\n". 1.67 +const NEWLINE= "\n"; 1.68 +#else 1.69 +// On other platforms, the transferable system converts "\r\n" to "\n". 1.70 +const NEWLINE = "\r\n"; 1.71 +#endif 1.72 + 1.73 +function QI_node(aNode, aIID) { 1.74 + var result = null; 1.75 + try { 1.76 + result = aNode.QueryInterface(aIID); 1.77 + } 1.78 + catch (e) { 1.79 + } 1.80 + return result; 1.81 +} 1.82 +function asContainer(aNode) QI_node(aNode, Ci.nsINavHistoryContainerResultNode); 1.83 +function asQuery(aNode) QI_node(aNode, Ci.nsINavHistoryQueryResultNode); 1.84 + 1.85 +this.PlacesUtils = { 1.86 + // Place entries that are containers, e.g. bookmark folders or queries. 1.87 + TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container", 1.88 + // Place entries that are bookmark separators. 1.89 + TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator", 1.90 + // Place entries that are not containers or separators 1.91 + TYPE_X_MOZ_PLACE: "text/x-moz-place", 1.92 + // Place entries in shortcut url format (url\ntitle) 1.93 + TYPE_X_MOZ_URL: "text/x-moz-url", 1.94 + // Place entries formatted as HTML anchors 1.95 + TYPE_HTML: "text/html", 1.96 + // Place entries as raw URL text 1.97 + TYPE_UNICODE: "text/unicode", 1.98 + // Used to track the action that populated the clipboard. 1.99 + TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action", 1.100 + 1.101 + EXCLUDE_FROM_BACKUP_ANNO: "places/excludeFromBackup", 1.102 + LMANNO_FEEDURI: "livemark/feedURI", 1.103 + LMANNO_SITEURI: "livemark/siteURI", 1.104 + POST_DATA_ANNO: "bookmarkProperties/POSTData", 1.105 + READ_ONLY_ANNO: "placesInternal/READ_ONLY", 1.106 + CHARSET_ANNO: "URIProperties/characterSet", 1.107 + 1.108 + TOPIC_SHUTDOWN: "places-shutdown", 1.109 + TOPIC_INIT_COMPLETE: "places-init-complete", 1.110 + TOPIC_DATABASE_LOCKED: "places-database-locked", 1.111 + TOPIC_EXPIRATION_FINISHED: "places-expiration-finished", 1.112 + TOPIC_FEEDBACK_UPDATED: "places-autocomplete-feedback-updated", 1.113 + TOPIC_FAVICONS_EXPIRED: "places-favicons-expired", 1.114 + TOPIC_VACUUM_STARTING: "places-vacuum-starting", 1.115 + TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin", 1.116 + TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success", 1.117 + TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed", 1.118 + 1.119 + asContainer: function(aNode) asContainer(aNode), 1.120 + asQuery: function(aNode) asQuery(aNode), 1.121 + 1.122 + endl: NEWLINE, 1.123 + 1.124 + /** 1.125 + * Makes a URI from a spec. 1.126 + * @param aSpec 1.127 + * The string spec of the URI 1.128 + * @returns A URI object for the spec. 1.129 + */ 1.130 + _uri: function PU__uri(aSpec) { 1.131 + return NetUtil.newURI(aSpec); 1.132 + }, 1.133 + 1.134 + /** 1.135 + * Wraps a string in a nsISupportsString wrapper. 1.136 + * @param aString 1.137 + * The string to wrap. 1.138 + * @returns A nsISupportsString object containing a string. 1.139 + */ 1.140 + toISupportsString: function PU_toISupportsString(aString) { 1.141 + let s = Cc["@mozilla.org/supports-string;1"]. 1.142 + createInstance(Ci.nsISupportsString); 1.143 + s.data = aString; 1.144 + return s; 1.145 + }, 1.146 + 1.147 + getFormattedString: function PU_getFormattedString(key, params) { 1.148 + return bundle.formatStringFromName(key, params, params.length); 1.149 + }, 1.150 + 1.151 + getString: function PU_getString(key) { 1.152 + return bundle.GetStringFromName(key); 1.153 + }, 1.154 + 1.155 + /** 1.156 + * Determines whether or not a ResultNode is a Bookmark folder. 1.157 + * @param aNode 1.158 + * A result node 1.159 + * @returns true if the node is a Bookmark folder, false otherwise 1.160 + */ 1.161 + nodeIsFolder: function PU_nodeIsFolder(aNode) { 1.162 + return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER || 1.163 + aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT); 1.164 + }, 1.165 + 1.166 + /** 1.167 + * Determines whether or not a ResultNode represents a bookmarked URI. 1.168 + * @param aNode 1.169 + * A result node 1.170 + * @returns true if the node represents a bookmarked URI, false otherwise 1.171 + */ 1.172 + nodeIsBookmark: function PU_nodeIsBookmark(aNode) { 1.173 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI && 1.174 + aNode.itemId != -1; 1.175 + }, 1.176 + 1.177 + /** 1.178 + * Determines whether or not a ResultNode is a Bookmark separator. 1.179 + * @param aNode 1.180 + * A result node 1.181 + * @returns true if the node is a Bookmark separator, false otherwise 1.182 + */ 1.183 + nodeIsSeparator: function PU_nodeIsSeparator(aNode) { 1.184 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR; 1.185 + }, 1.186 + 1.187 + /** 1.188 + * Determines whether or not a ResultNode is a URL item. 1.189 + * @param aNode 1.190 + * A result node 1.191 + * @returns true if the node is a URL item, false otherwise 1.192 + */ 1.193 + nodeIsURI: function PU_nodeIsURI(aNode) { 1.194 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI; 1.195 + }, 1.196 + 1.197 + /** 1.198 + * Determines whether or not a ResultNode is a Query item. 1.199 + * @param aNode 1.200 + * A result node 1.201 + * @returns true if the node is a Query item, false otherwise 1.202 + */ 1.203 + nodeIsQuery: function PU_nodeIsQuery(aNode) { 1.204 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY; 1.205 + }, 1.206 + 1.207 + /** 1.208 + * Generator for a node's ancestors. 1.209 + * @param aNode 1.210 + * A result node 1.211 + */ 1.212 + nodeAncestors: function PU_nodeAncestors(aNode) { 1.213 + let node = aNode.parent; 1.214 + while (node) { 1.215 + yield node; 1.216 + node = node.parent; 1.217 + } 1.218 + }, 1.219 + 1.220 + /** 1.221 + * Cache array of read-only item IDs. 1.222 + * 1.223 + * The first time this property is called: 1.224 + * - the cache is filled with all ids with the RO annotation 1.225 + * - an annotation observer is added 1.226 + * - a shutdown observer is added 1.227 + * 1.228 + * When the annotation observer detects annotations added or 1.229 + * removed that are the RO annotation name, it adds/removes 1.230 + * the ids from the cache. 1.231 + * 1.232 + * At shutdown, the annotation and shutdown observers are removed. 1.233 + */ 1.234 + get _readOnly() { 1.235 + // Add annotations observer. 1.236 + this.annotations.addObserver(this, false); 1.237 + this.registerShutdownFunction(function () { 1.238 + this.annotations.removeObserver(this); 1.239 + }); 1.240 + 1.241 + var readOnly = this.annotations.getItemsWithAnnotation(this.READ_ONLY_ANNO); 1.242 + this.__defineGetter__("_readOnly", function() readOnly); 1.243 + return this._readOnly; 1.244 + }, 1.245 + 1.246 + QueryInterface: XPCOMUtils.generateQI([ 1.247 + Ci.nsIAnnotationObserver 1.248 + , Ci.nsIObserver 1.249 + , Ci.nsITransactionListener 1.250 + ]), 1.251 + 1.252 + _shutdownFunctions: [], 1.253 + registerShutdownFunction: function PU_registerShutdownFunction(aFunc) 1.254 + { 1.255 + // If this is the first registered function, add the shutdown observer. 1.256 + if (this._shutdownFunctions.length == 0) { 1.257 + Services.obs.addObserver(this, this.TOPIC_SHUTDOWN, false); 1.258 + } 1.259 + this._shutdownFunctions.push(aFunc); 1.260 + }, 1.261 + 1.262 + ////////////////////////////////////////////////////////////////////////////// 1.263 + //// nsIObserver 1.264 + observe: function PU_observe(aSubject, aTopic, aData) 1.265 + { 1.266 + switch (aTopic) { 1.267 + case this.TOPIC_SHUTDOWN: 1.268 + Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN); 1.269 + while (this._shutdownFunctions.length > 0) { 1.270 + this._shutdownFunctions.shift().apply(this); 1.271 + } 1.272 + if (this._bookmarksServiceObserversQueue.length > 0) { 1.273 + // Since we are shutting down, there's no reason to add the observers. 1.274 + this._bookmarksServiceObserversQueue.length = 0; 1.275 + } 1.276 + break; 1.277 + case "bookmarks-service-ready": 1.278 + this._bookmarksServiceReady = true; 1.279 + while (this._bookmarksServiceObserversQueue.length > 0) { 1.280 + let observerInfo = this._bookmarksServiceObserversQueue.shift(); 1.281 + this.bookmarks.addObserver(observerInfo.observer, observerInfo.weak); 1.282 + } 1.283 + break; 1.284 + } 1.285 + }, 1.286 + 1.287 + ////////////////////////////////////////////////////////////////////////////// 1.288 + //// nsIAnnotationObserver 1.289 + 1.290 + onItemAnnotationSet: function PU_onItemAnnotationSet(aItemId, aAnnotationName) 1.291 + { 1.292 + if (aAnnotationName == this.READ_ONLY_ANNO && 1.293 + this._readOnly.indexOf(aItemId) == -1) 1.294 + this._readOnly.push(aItemId); 1.295 + }, 1.296 + 1.297 + onItemAnnotationRemoved: 1.298 + function PU_onItemAnnotationRemoved(aItemId, aAnnotationName) 1.299 + { 1.300 + var index = this._readOnly.indexOf(aItemId); 1.301 + if (aAnnotationName == this.READ_ONLY_ANNO && index > -1) 1.302 + delete this._readOnly[index]; 1.303 + }, 1.304 + 1.305 + onPageAnnotationSet: function() {}, 1.306 + onPageAnnotationRemoved: function() {}, 1.307 + 1.308 + 1.309 + ////////////////////////////////////////////////////////////////////////////// 1.310 + //// nsITransactionListener 1.311 + 1.312 + didDo: function PU_didDo(aManager, aTransaction, aDoResult) 1.313 + { 1.314 + updateCommandsOnActiveWindow(); 1.315 + }, 1.316 + 1.317 + didUndo: function PU_didUndo(aManager, aTransaction, aUndoResult) 1.318 + { 1.319 + updateCommandsOnActiveWindow(); 1.320 + }, 1.321 + 1.322 + didRedo: function PU_didRedo(aManager, aTransaction, aRedoResult) 1.323 + { 1.324 + updateCommandsOnActiveWindow(); 1.325 + }, 1.326 + 1.327 + didBeginBatch: function PU_didBeginBatch(aManager, aResult) 1.328 + { 1.329 + // A no-op transaction is pushed to the stack, in order to make safe and 1.330 + // easy to implement "Undo" an unknown number of transactions (including 0), 1.331 + // "above" beginBatch and endBatch. Otherwise,implementing Undo that way 1.332 + // head to dataloss: for example, if no changes were done in the 1.333 + // edit-item panel, the last transaction on the undo stack would be the 1.334 + // initial createItem transaction, or even worse, the batched editing of 1.335 + // some other item. 1.336 + // DO NOT MOVE this to the window scope, that would leak (bug 490068)! 1.337 + this.transactionManager.doTransaction({ doTransaction: function() {}, 1.338 + undoTransaction: function() {}, 1.339 + redoTransaction: function() {}, 1.340 + isTransient: false, 1.341 + merge: function() { return false; } 1.342 + }); 1.343 + }, 1.344 + 1.345 + willDo: function PU_willDo() {}, 1.346 + willUndo: function PU_willUndo() {}, 1.347 + willRedo: function PU_willRedo() {}, 1.348 + willBeginBatch: function PU_willBeginBatch() {}, 1.349 + willEndBatch: function PU_willEndBatch() {}, 1.350 + didEndBatch: function PU_didEndBatch() {}, 1.351 + willMerge: function PU_willMerge() {}, 1.352 + didMerge: function PU_didMerge() {}, 1.353 + 1.354 + 1.355 + /** 1.356 + * Determines if a node is read only (children cannot be inserted, sometimes 1.357 + * they cannot be removed depending on the circumstance) 1.358 + * @param aNode 1.359 + * A result node 1.360 + * @returns true if the node is readonly, false otherwise 1.361 + */ 1.362 + nodeIsReadOnly: function PU_nodeIsReadOnly(aNode) { 1.363 + let itemId = aNode.itemId; 1.364 + if (itemId != -1) { 1.365 + return this._readOnly.indexOf(itemId) != -1; 1.366 + } 1.367 + 1.368 + if (this.nodeIsQuery(aNode) && 1.369 + asQuery(aNode).queryOptions.resultType != 1.370 + Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) 1.371 + return aNode.childrenReadOnly; 1.372 + return false; 1.373 + }, 1.374 + 1.375 + /** 1.376 + * Determines whether or not a ResultNode is a host container. 1.377 + * @param aNode 1.378 + * A result node 1.379 + * @returns true if the node is a host container, false otherwise 1.380 + */ 1.381 + nodeIsHost: function PU_nodeIsHost(aNode) { 1.382 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && 1.383 + aNode.parent && 1.384 + asQuery(aNode.parent).queryOptions.resultType == 1.385 + Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY; 1.386 + }, 1.387 + 1.388 + /** 1.389 + * Determines whether or not a ResultNode is a day container. 1.390 + * @param node 1.391 + * A NavHistoryResultNode 1.392 + * @returns true if the node is a day container, false otherwise 1.393 + */ 1.394 + nodeIsDay: function PU_nodeIsDay(aNode) { 1.395 + var resultType; 1.396 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && 1.397 + aNode.parent && 1.398 + ((resultType = asQuery(aNode.parent).queryOptions.resultType) == 1.399 + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || 1.400 + resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY); 1.401 + }, 1.402 + 1.403 + /** 1.404 + * Determines whether or not a result-node is a tag container. 1.405 + * @param aNode 1.406 + * A result-node 1.407 + * @returns true if the node is a tag container, false otherwise 1.408 + */ 1.409 + nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) { 1.410 + return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY && 1.411 + asQuery(aNode).queryOptions.resultType == 1.412 + Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS; 1.413 + }, 1.414 + 1.415 + /** 1.416 + * Determines whether or not a ResultNode is a container. 1.417 + * @param aNode 1.418 + * A result node 1.419 + * @returns true if the node is a container item, false otherwise 1.420 + */ 1.421 + containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, 1.422 + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT, 1.423 + Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY], 1.424 + nodeIsContainer: function PU_nodeIsContainer(aNode) { 1.425 + return this.containerTypes.indexOf(aNode.type) != -1; 1.426 + }, 1.427 + 1.428 + /** 1.429 + * Determines whether or not a ResultNode is an history related container. 1.430 + * @param node 1.431 + * A result node 1.432 + * @returns true if the node is an history related container, false otherwise 1.433 + */ 1.434 + nodeIsHistoryContainer: function PU_nodeIsHistoryContainer(aNode) { 1.435 + var resultType; 1.436 + return this.nodeIsQuery(aNode) && 1.437 + ((resultType = asQuery(aNode).queryOptions.resultType) == 1.438 + Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY || 1.439 + resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY || 1.440 + resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY || 1.441 + this.nodeIsDay(aNode) || 1.442 + this.nodeIsHost(aNode)); 1.443 + }, 1.444 + 1.445 + /** 1.446 + * Determines whether or not a node is a readonly folder. 1.447 + * @param aNode 1.448 + * The node to test. 1.449 + * @returns true if the node is a readonly folder. 1.450 + */ 1.451 + isReadonlyFolder: function(aNode) { 1.452 + return this.nodeIsFolder(aNode) && 1.453 + this._readOnly.indexOf(asQuery(aNode).folderItemId) != -1; 1.454 + }, 1.455 + 1.456 + /** 1.457 + * Gets the concrete item-id for the given node. Generally, this is just 1.458 + * node.itemId, but for folder-shortcuts that's node.folderItemId. 1.459 + */ 1.460 + getConcreteItemId: function PU_getConcreteItemId(aNode) { 1.461 + if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) 1.462 + return asQuery(aNode).folderItemId; 1.463 + else if (PlacesUtils.nodeIsTagQuery(aNode)) { 1.464 + // RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts 1.465 + // so we can still get the concrete itemId for them. 1.466 + var queries = aNode.getQueries(); 1.467 + var folders = queries[0].getFolders(); 1.468 + return folders[0]; 1.469 + } 1.470 + return aNode.itemId; 1.471 + }, 1.472 + 1.473 + /** 1.474 + * String-wraps a result node according to the rules of the specified 1.475 + * content type. 1.476 + * @param aNode 1.477 + * The Result node to wrap (serialize) 1.478 + * @param aType 1.479 + * The content type to serialize as 1.480 + * @param [optional] aOverrideURI 1.481 + * Used instead of the node's URI if provided. 1.482 + * This is useful for wrapping a container as TYPE_X_MOZ_URL, 1.483 + * TYPE_HTML or TYPE_UNICODE. 1.484 + * @return A string serialization of the node 1.485 + */ 1.486 + wrapNode: function PU_wrapNode(aNode, aType, aOverrideURI) { 1.487 + // when wrapping a node, we want all the items, even if the original 1.488 + // query options are excluding them. 1.489 + // this can happen when copying from the left hand pane of the bookmarks 1.490 + // organizer 1.491 + // @return [node, shouldClose] 1.492 + function convertNode(cNode) { 1.493 + if (PlacesUtils.nodeIsFolder(cNode) && 1.494 + cNode.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT && 1.495 + asQuery(cNode).queryOptions.excludeItems) { 1.496 + return [PlacesUtils.getFolderContents(cNode.itemId, false, true).root, true]; 1.497 + } 1.498 + 1.499 + // If we didn't create our own query, do not alter the node's open state. 1.500 + return [cNode, false]; 1.501 + } 1.502 + 1.503 + function gatherLivemarkUrl(aNode) { 1.504 + try { 1.505 + return PlacesUtils.annotations 1.506 + .getItemAnnotation(aNode.itemId, 1.507 + PlacesUtils.LMANNO_SITEURI); 1.508 + } catch (ex) { 1.509 + return PlacesUtils.annotations 1.510 + .getItemAnnotation(aNode.itemId, 1.511 + PlacesUtils.LMANNO_FEEDURI); 1.512 + } 1.513 + } 1.514 + 1.515 + function isLivemark(aNode) { 1.516 + return PlacesUtils.nodeIsFolder(aNode) && 1.517 + PlacesUtils.annotations 1.518 + .itemHasAnnotation(aNode.itemId, 1.519 + PlacesUtils.LMANNO_FEEDURI); 1.520 + } 1.521 + 1.522 + switch (aType) { 1.523 + case this.TYPE_X_MOZ_PLACE: 1.524 + case this.TYPE_X_MOZ_PLACE_SEPARATOR: 1.525 + case this.TYPE_X_MOZ_PLACE_CONTAINER: { 1.526 + let writer = { 1.527 + value: "", 1.528 + write: function PU_wrapNode__write(aStr, aLen) { 1.529 + this.value += aStr; 1.530 + } 1.531 + }; 1.532 + 1.533 + let [node, shouldClose] = convertNode(aNode); 1.534 + this._serializeNodeAsJSONToOutputStream(node, writer); 1.535 + if (shouldClose) 1.536 + node.containerOpen = false; 1.537 + 1.538 + return writer.value; 1.539 + } 1.540 + case this.TYPE_X_MOZ_URL: { 1.541 + function gatherDataUrl(bNode) { 1.542 + if (isLivemark(bNode)) { 1.543 + return gatherLivemarkUrl(bNode) + NEWLINE + bNode.title; 1.544 + } 1.545 + 1.546 + if (PlacesUtils.nodeIsURI(bNode)) 1.547 + return (aOverrideURI || bNode.uri) + NEWLINE + bNode.title; 1.548 + // ignore containers and separators - items without valid URIs 1.549 + return ""; 1.550 + } 1.551 + 1.552 + let [node, shouldClose] = convertNode(aNode); 1.553 + let dataUrl = gatherDataUrl(node); 1.554 + if (shouldClose) 1.555 + node.containerOpen = false; 1.556 + 1.557 + return dataUrl; 1.558 + } 1.559 + case this.TYPE_HTML: { 1.560 + function gatherDataHtml(bNode) { 1.561 + function htmlEscape(s) { 1.562 + s = s.replace(/&/g, "&"); 1.563 + s = s.replace(/>/g, ">"); 1.564 + s = s.replace(/</g, "<"); 1.565 + s = s.replace(/"/g, """); 1.566 + s = s.replace(/'/g, "'"); 1.567 + return s; 1.568 + } 1.569 + // escape out potential HTML in the title 1.570 + let escapedTitle = bNode.title ? htmlEscape(bNode.title) : ""; 1.571 + 1.572 + if (isLivemark(bNode)) { 1.573 + return "<A HREF=\"" + gatherLivemarkUrl(bNode) + "\">" + escapedTitle + "</A>" + NEWLINE; 1.574 + } 1.575 + 1.576 + if (PlacesUtils.nodeIsContainer(bNode)) { 1.577 + asContainer(bNode); 1.578 + let wasOpen = bNode.containerOpen; 1.579 + if (!wasOpen) 1.580 + bNode.containerOpen = true; 1.581 + 1.582 + let childString = "<DL><DT>" + escapedTitle + "</DT>" + NEWLINE; 1.583 + let cc = bNode.childCount; 1.584 + for (let i = 0; i < cc; ++i) 1.585 + childString += "<DD>" 1.586 + + NEWLINE 1.587 + + gatherDataHtml(bNode.getChild(i)) 1.588 + + "</DD>" 1.589 + + NEWLINE; 1.590 + bNode.containerOpen = wasOpen; 1.591 + return childString + "</DL>" + NEWLINE; 1.592 + } 1.593 + if (PlacesUtils.nodeIsURI(bNode)) 1.594 + return "<A HREF=\"" + bNode.uri + "\">" + escapedTitle + "</A>" + NEWLINE; 1.595 + if (PlacesUtils.nodeIsSeparator(bNode)) 1.596 + return "<HR>" + NEWLINE; 1.597 + return ""; 1.598 + } 1.599 + 1.600 + let [node, shouldClose] = convertNode(aNode); 1.601 + let dataHtml = gatherDataHtml(node); 1.602 + if (shouldClose) 1.603 + node.containerOpen = false; 1.604 + 1.605 + return dataHtml; 1.606 + } 1.607 + } 1.608 + 1.609 + // Otherwise, we wrap as TYPE_UNICODE. 1.610 + function gatherDataText(bNode) { 1.611 + if (isLivemark(bNode)) { 1.612 + return gatherLivemarkUrl(bNode); 1.613 + } 1.614 + 1.615 + if (PlacesUtils.nodeIsContainer(bNode)) { 1.616 + asContainer(bNode); 1.617 + let wasOpen = bNode.containerOpen; 1.618 + if (!wasOpen) 1.619 + bNode.containerOpen = true; 1.620 + 1.621 + let childString = bNode.title + NEWLINE; 1.622 + let cc = bNode.childCount; 1.623 + for (let i = 0; i < cc; ++i) { 1.624 + let child = bNode.getChild(i); 1.625 + let suffix = i < (cc - 1) ? NEWLINE : ""; 1.626 + childString += gatherDataText(child) + suffix; 1.627 + } 1.628 + bNode.containerOpen = wasOpen; 1.629 + return childString; 1.630 + } 1.631 + if (PlacesUtils.nodeIsURI(bNode)) 1.632 + return (aOverrideURI || bNode.uri); 1.633 + if (PlacesUtils.nodeIsSeparator(bNode)) 1.634 + return "--------------------"; 1.635 + return ""; 1.636 + } 1.637 + 1.638 + let [node, shouldClose] = convertNode(aNode); 1.639 + let dataText = gatherDataText(node); 1.640 + // Convert node could pass an open container node. 1.641 + if (shouldClose) 1.642 + node.containerOpen = false; 1.643 + 1.644 + return dataText; 1.645 + }, 1.646 + 1.647 + /** 1.648 + * Unwraps data from the Clipboard or the current Drag Session. 1.649 + * @param blob 1.650 + * A blob (string) of data, in some format we potentially know how 1.651 + * to parse. 1.652 + * @param type 1.653 + * The content type of the blob. 1.654 + * @returns An array of objects representing each item contained by the source. 1.655 + */ 1.656 + unwrapNodes: function PU_unwrapNodes(blob, type) { 1.657 + // We split on "\n" because the transferable system converts "\r\n" to "\n" 1.658 + var nodes = []; 1.659 + switch(type) { 1.660 + case this.TYPE_X_MOZ_PLACE: 1.661 + case this.TYPE_X_MOZ_PLACE_SEPARATOR: 1.662 + case this.TYPE_X_MOZ_PLACE_CONTAINER: 1.663 + nodes = JSON.parse("[" + blob + "]"); 1.664 + break; 1.665 + case this.TYPE_X_MOZ_URL: 1.666 + var parts = blob.split("\n"); 1.667 + // data in this type has 2 parts per entry, so if there are fewer 1.668 + // than 2 parts left, the blob is malformed and we should stop 1.669 + // but drag and drop of files from the shell has parts.length = 1 1.670 + if (parts.length != 1 && parts.length % 2) 1.671 + break; 1.672 + for (var i = 0; i < parts.length; i=i+2) { 1.673 + var uriString = parts[i]; 1.674 + var titleString = ""; 1.675 + if (parts.length > i+1) 1.676 + titleString = parts[i+1]; 1.677 + else { 1.678 + // for drag and drop of files, try to use the leafName as title 1.679 + try { 1.680 + titleString = this._uri(uriString).QueryInterface(Ci.nsIURL) 1.681 + .fileName; 1.682 + } 1.683 + catch (e) {} 1.684 + } 1.685 + // note: this._uri() will throw if uriString is not a valid URI 1.686 + if (this._uri(uriString)) { 1.687 + nodes.push({ uri: uriString, 1.688 + title: titleString ? titleString : uriString , 1.689 + type: this.TYPE_X_MOZ_URL }); 1.690 + } 1.691 + } 1.692 + break; 1.693 + case this.TYPE_UNICODE: 1.694 + var parts = blob.split("\n"); 1.695 + for (var i = 0; i < parts.length; i++) { 1.696 + var uriString = parts[i]; 1.697 + // text/uri-list is converted to TYPE_UNICODE but it could contain 1.698 + // comments line prepended by #, we should skip them 1.699 + if (uriString.substr(0, 1) == '\x23') 1.700 + continue; 1.701 + // note: this._uri() will throw if uriString is not a valid URI 1.702 + if (uriString != "" && this._uri(uriString)) 1.703 + nodes.push({ uri: uriString, 1.704 + title: uriString, 1.705 + type: this.TYPE_X_MOZ_URL }); 1.706 + } 1.707 + break; 1.708 + default: 1.709 + throw Cr.NS_ERROR_INVALID_ARG; 1.710 + } 1.711 + return nodes; 1.712 + }, 1.713 + 1.714 + /** 1.715 + * Generates a nsINavHistoryResult for the contents of a folder. 1.716 + * @param folderId 1.717 + * The folder to open 1.718 + * @param [optional] excludeItems 1.719 + * True to hide all items (individual bookmarks). This is used on 1.720 + * the left places pane so you just get a folder hierarchy. 1.721 + * @param [optional] expandQueries 1.722 + * True to make query items expand as new containers. For managing, 1.723 + * you want this to be false, for menus and such, you want this to 1.724 + * be true. 1.725 + * @returns A nsINavHistoryResult containing the contents of the 1.726 + * folder. The result.root is guaranteed to be open. 1.727 + */ 1.728 + getFolderContents: 1.729 + function PU_getFolderContents(aFolderId, aExcludeItems, aExpandQueries) { 1.730 + var query = this.history.getNewQuery(); 1.731 + query.setFolders([aFolderId], 1); 1.732 + var options = this.history.getNewQueryOptions(); 1.733 + options.excludeItems = aExcludeItems; 1.734 + options.expandQueries = aExpandQueries; 1.735 + 1.736 + var result = this.history.executeQuery(query, options); 1.737 + result.root.containerOpen = true; 1.738 + return result; 1.739 + }, 1.740 + 1.741 + /** 1.742 + * Fetch all annotations for a URI, including all properties of each 1.743 + * annotation which would be required to recreate it. 1.744 + * @param aURI 1.745 + * The URI for which annotations are to be retrieved. 1.746 + * @return Array of objects, each containing the following properties: 1.747 + * name, flags, expires, value 1.748 + */ 1.749 + getAnnotationsForURI: function PU_getAnnotationsForURI(aURI) { 1.750 + var annosvc = this.annotations; 1.751 + var annos = [], val = null; 1.752 + var annoNames = annosvc.getPageAnnotationNames(aURI); 1.753 + for (var i = 0; i < annoNames.length; i++) { 1.754 + var flags = {}, exp = {}, storageType = {}; 1.755 + annosvc.getPageAnnotationInfo(aURI, annoNames[i], flags, exp, storageType); 1.756 + val = annosvc.getPageAnnotation(aURI, annoNames[i]); 1.757 + annos.push({name: annoNames[i], 1.758 + flags: flags.value, 1.759 + expires: exp.value, 1.760 + value: val}); 1.761 + } 1.762 + return annos; 1.763 + }, 1.764 + 1.765 + /** 1.766 + * Fetch all annotations for an item, including all properties of each 1.767 + * annotation which would be required to recreate it. 1.768 + * @param aItemId 1.769 + * The identifier of the itme for which annotations are to be 1.770 + * retrieved. 1.771 + * @return Array of objects, each containing the following properties: 1.772 + * name, flags, expires, mimeType, type, value 1.773 + */ 1.774 + getAnnotationsForItem: function PU_getAnnotationsForItem(aItemId) { 1.775 + var annosvc = this.annotations; 1.776 + var annos = [], val = null; 1.777 + var annoNames = annosvc.getItemAnnotationNames(aItemId); 1.778 + for (var i = 0; i < annoNames.length; i++) { 1.779 + var flags = {}, exp = {}, storageType = {}; 1.780 + annosvc.getItemAnnotationInfo(aItemId, annoNames[i], flags, exp, storageType); 1.781 + val = annosvc.getItemAnnotation(aItemId, annoNames[i]); 1.782 + annos.push({name: annoNames[i], 1.783 + flags: flags.value, 1.784 + expires: exp.value, 1.785 + value: val}); 1.786 + } 1.787 + return annos; 1.788 + }, 1.789 + 1.790 + /** 1.791 + * Annotate a URI with a batch of annotations. 1.792 + * @param aURI 1.793 + * The URI for which annotations are to be set. 1.794 + * @param aAnnotations 1.795 + * Array of objects, each containing the following properties: 1.796 + * name, flags, expires. 1.797 + * If the value for an annotation is not set it will be removed. 1.798 + */ 1.799 + setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) { 1.800 + var annosvc = this.annotations; 1.801 + aAnnos.forEach(function(anno) { 1.802 + if (anno.value === undefined || anno.value === null) { 1.803 + annosvc.removePageAnnotation(aURI, anno.name); 1.804 + } 1.805 + else { 1.806 + let flags = ("flags" in anno) ? anno.flags : 0; 1.807 + let expires = ("expires" in anno) ? 1.808 + anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER; 1.809 + annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires); 1.810 + } 1.811 + }); 1.812 + }, 1.813 + 1.814 + /** 1.815 + * Annotate an item with a batch of annotations. 1.816 + * @param aItemId 1.817 + * The identifier of the item for which annotations are to be set 1.818 + * @param aAnnotations 1.819 + * Array of objects, each containing the following properties: 1.820 + * name, flags, expires. 1.821 + * If the value for an annotation is not set it will be removed. 1.822 + */ 1.823 + setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos) { 1.824 + var annosvc = this.annotations; 1.825 + 1.826 + aAnnos.forEach(function(anno) { 1.827 + if (anno.value === undefined || anno.value === null) { 1.828 + annosvc.removeItemAnnotation(aItemId, anno.name); 1.829 + } 1.830 + else { 1.831 + let flags = ("flags" in anno) ? anno.flags : 0; 1.832 + let expires = ("expires" in anno) ? 1.833 + anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER; 1.834 + annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags, 1.835 + expires); 1.836 + } 1.837 + }); 1.838 + }, 1.839 + 1.840 + // Identifier getters for special folders. 1.841 + // You should use these everywhere PlacesUtils is available to avoid XPCOM 1.842 + // traversal just to get roots' ids. 1.843 + get placesRootId() { 1.844 + delete this.placesRootId; 1.845 + return this.placesRootId = this.bookmarks.placesRoot; 1.846 + }, 1.847 + 1.848 + get bookmarksMenuFolderId() { 1.849 + delete this.bookmarksMenuFolderId; 1.850 + return this.bookmarksMenuFolderId = this.bookmarks.bookmarksMenuFolder; 1.851 + }, 1.852 + 1.853 + get toolbarFolderId() { 1.854 + delete this.toolbarFolderId; 1.855 + return this.toolbarFolderId = this.bookmarks.toolbarFolder; 1.856 + }, 1.857 + 1.858 + get tagsFolderId() { 1.859 + delete this.tagsFolderId; 1.860 + return this.tagsFolderId = this.bookmarks.tagsFolder; 1.861 + }, 1.862 + 1.863 + get unfiledBookmarksFolderId() { 1.864 + delete this.unfiledBookmarksFolderId; 1.865 + return this.unfiledBookmarksFolderId = this.bookmarks.unfiledBookmarksFolder; 1.866 + }, 1.867 + 1.868 + /** 1.869 + * Checks if aItemId is a root. 1.870 + * 1.871 + * @param aItemId 1.872 + * item id to look for. 1.873 + * @returns true if aItemId is a root, false otherwise. 1.874 + */ 1.875 + isRootItem: function PU_isRootItem(aItemId) { 1.876 + return aItemId == PlacesUtils.bookmarksMenuFolderId || 1.877 + aItemId == PlacesUtils.toolbarFolderId || 1.878 + aItemId == PlacesUtils.unfiledBookmarksFolderId || 1.879 + aItemId == PlacesUtils.tagsFolderId || 1.880 + aItemId == PlacesUtils.placesRootId; 1.881 + }, 1.882 + 1.883 + /** 1.884 + * Set the POST data associated with a bookmark, if any. 1.885 + * Used by POST keywords. 1.886 + * @param aBookmarkId 1.887 + * @returns string of POST data 1.888 + */ 1.889 + setPostDataForBookmark: function PU_setPostDataForBookmark(aBookmarkId, aPostData) { 1.890 + const annos = this.annotations; 1.891 + if (aPostData) 1.892 + annos.setItemAnnotation(aBookmarkId, this.POST_DATA_ANNO, aPostData, 1.893 + 0, Ci.nsIAnnotationService.EXPIRE_NEVER); 1.894 + else if (annos.itemHasAnnotation(aBookmarkId, this.POST_DATA_ANNO)) 1.895 + annos.removeItemAnnotation(aBookmarkId, this.POST_DATA_ANNO); 1.896 + }, 1.897 + 1.898 + /** 1.899 + * Get the POST data associated with a bookmark, if any. 1.900 + * @param aBookmarkId 1.901 + * @returns string of POST data if set for aBookmarkId. null otherwise. 1.902 + */ 1.903 + getPostDataForBookmark: function PU_getPostDataForBookmark(aBookmarkId) { 1.904 + const annos = this.annotations; 1.905 + if (annos.itemHasAnnotation(aBookmarkId, this.POST_DATA_ANNO)) 1.906 + return annos.getItemAnnotation(aBookmarkId, this.POST_DATA_ANNO); 1.907 + 1.908 + return null; 1.909 + }, 1.910 + 1.911 + /** 1.912 + * Get the URI (and any associated POST data) for a given keyword. 1.913 + * @param aKeyword string keyword 1.914 + * @returns an array containing a string URL and a string of POST data 1.915 + */ 1.916 + getURLAndPostDataForKeyword: function PU_getURLAndPostDataForKeyword(aKeyword) { 1.917 + var url = null, postdata = null; 1.918 + try { 1.919 + var uri = this.bookmarks.getURIForKeyword(aKeyword); 1.920 + if (uri) { 1.921 + url = uri.spec; 1.922 + var bookmarks = this.bookmarks.getBookmarkIdsForURI(uri); 1.923 + for (let i = 0; i < bookmarks.length; i++) { 1.924 + var bookmark = bookmarks[i]; 1.925 + var kw = this.bookmarks.getKeywordForBookmark(bookmark); 1.926 + if (kw == aKeyword) { 1.927 + postdata = this.getPostDataForBookmark(bookmark); 1.928 + break; 1.929 + } 1.930 + } 1.931 + } 1.932 + } catch(ex) {} 1.933 + return [url, postdata]; 1.934 + }, 1.935 + 1.936 + /** 1.937 + * Get all bookmarks for a URL, excluding items under tags. 1.938 + */ 1.939 + getBookmarksForURI: 1.940 + function PU_getBookmarksForURI(aURI) { 1.941 + var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI); 1.942 + 1.943 + // filter the ids list 1.944 + return bmkIds.filter(function(aID) { 1.945 + var parentId = this.bookmarks.getFolderIdForItem(aID); 1.946 + var grandparentId = this.bookmarks.getFolderIdForItem(parentId); 1.947 + // item under a tag container 1.948 + if (grandparentId == this.tagsFolderId) 1.949 + return false; 1.950 + return true; 1.951 + }, this); 1.952 + }, 1.953 + 1.954 + /** 1.955 + * Get the most recently added/modified bookmark for a URL, excluding items 1.956 + * under tags. 1.957 + * 1.958 + * @param aURI 1.959 + * nsIURI of the page we will look for. 1.960 + * @returns itemId of the found bookmark, or -1 if nothing is found. 1.961 + */ 1.962 + getMostRecentBookmarkForURI: 1.963 + function PU_getMostRecentBookmarkForURI(aURI) { 1.964 + var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI); 1.965 + for (var i = 0; i < bmkIds.length; i++) { 1.966 + // Find the first folder which isn't a tag container 1.967 + var itemId = bmkIds[i]; 1.968 + var parentId = this.bookmarks.getFolderIdForItem(itemId); 1.969 + // Optimization: if this is a direct child of a root we don't need to 1.970 + // check if its grandparent is a tag. 1.971 + if (parentId == this.unfiledBookmarksFolderId || 1.972 + parentId == this.toolbarFolderId || 1.973 + parentId == this.bookmarksMenuFolderId) 1.974 + return itemId; 1.975 + 1.976 + var grandparentId = this.bookmarks.getFolderIdForItem(parentId); 1.977 + if (grandparentId != this.tagsFolderId) 1.978 + return itemId; 1.979 + } 1.980 + return -1; 1.981 + }, 1.982 + 1.983 + /** 1.984 + * Returns a nsNavHistoryContainerResultNode with forced excludeItems and 1.985 + * expandQueries. 1.986 + * @param aNode 1.987 + * The node to convert 1.988 + * @param [optional] excludeItems 1.989 + * True to hide all items (individual bookmarks). This is used on 1.990 + * the left places pane so you just get a folder hierarchy. 1.991 + * @param [optional] expandQueries 1.992 + * True to make query items expand as new containers. For managing, 1.993 + * you want this to be false, for menus and such, you want this to 1.994 + * be true. 1.995 + * @returns A nsINavHistoryContainerResultNode containing the unfiltered 1.996 + * contents of the container. 1.997 + * @note The returned container node could be open or closed, we don't 1.998 + * guarantee its status. 1.999 + */ 1.1000 + getContainerNodeWithOptions: 1.1001 + function PU_getContainerNodeWithOptions(aNode, aExcludeItems, aExpandQueries) { 1.1002 + if (!this.nodeIsContainer(aNode)) 1.1003 + throw Cr.NS_ERROR_INVALID_ARG; 1.1004 + 1.1005 + // excludeItems is inherited by child containers in an excludeItems view. 1.1006 + var excludeItems = asQuery(aNode).queryOptions.excludeItems || 1.1007 + asQuery(aNode.parentResult.root).queryOptions.excludeItems; 1.1008 + // expandQueries is inherited by child containers in an expandQueries view. 1.1009 + var expandQueries = asQuery(aNode).queryOptions.expandQueries && 1.1010 + asQuery(aNode.parentResult.root).queryOptions.expandQueries; 1.1011 + 1.1012 + // If our options are exactly what we expect, directly return the node. 1.1013 + if (excludeItems == aExcludeItems && expandQueries == aExpandQueries) 1.1014 + return aNode; 1.1015 + 1.1016 + // Otherwise, get contents manually. 1.1017 + var queries = {}, options = {}; 1.1018 + this.history.queryStringToQueries(aNode.uri, queries, {}, options); 1.1019 + options.value.excludeItems = aExcludeItems; 1.1020 + options.value.expandQueries = aExpandQueries; 1.1021 + return this.history.executeQueries(queries.value, 1.1022 + queries.value.length, 1.1023 + options.value).root; 1.1024 + }, 1.1025 + 1.1026 + /** 1.1027 + * Returns true if a container has uri nodes in its first level. 1.1028 + * Has better performance than (getURLsForContainerNode(node).length > 0). 1.1029 + * @param aNode 1.1030 + * The container node to search through. 1.1031 + * @returns true if the node contains uri nodes, false otherwise. 1.1032 + */ 1.1033 + hasChildURIs: function PU_hasChildURIs(aNode) { 1.1034 + if (!this.nodeIsContainer(aNode)) 1.1035 + return false; 1.1036 + 1.1037 + let root = this.getContainerNodeWithOptions(aNode, false, true); 1.1038 + let result = root.parentResult; 1.1039 + let didSuppressNotifications = false; 1.1040 + let wasOpen = root.containerOpen; 1.1041 + if (!wasOpen) { 1.1042 + didSuppressNotifications = result.suppressNotifications; 1.1043 + if (!didSuppressNotifications) 1.1044 + result.suppressNotifications = true; 1.1045 + 1.1046 + root.containerOpen = true; 1.1047 + } 1.1048 + 1.1049 + let found = false; 1.1050 + for (let i = 0; i < root.childCount && !found; i++) { 1.1051 + let child = root.getChild(i); 1.1052 + if (this.nodeIsURI(child)) 1.1053 + found = true; 1.1054 + } 1.1055 + 1.1056 + if (!wasOpen) { 1.1057 + root.containerOpen = false; 1.1058 + if (!didSuppressNotifications) 1.1059 + result.suppressNotifications = false; 1.1060 + } 1.1061 + return found; 1.1062 + }, 1.1063 + 1.1064 + /** 1.1065 + * Returns an array containing all the uris in the first level of the 1.1066 + * passed in container. 1.1067 + * If you only need to know if the node contains uris, use hasChildURIs. 1.1068 + * @param aNode 1.1069 + * The container node to search through 1.1070 + * @returns array of uris in the first level of the container. 1.1071 + */ 1.1072 + getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) { 1.1073 + let urls = []; 1.1074 + if (!this.nodeIsContainer(aNode)) 1.1075 + return urls; 1.1076 + 1.1077 + let root = this.getContainerNodeWithOptions(aNode, false, true); 1.1078 + let result = root.parentResult; 1.1079 + let wasOpen = root.containerOpen; 1.1080 + let didSuppressNotifications = false; 1.1081 + if (!wasOpen) { 1.1082 + didSuppressNotifications = result.suppressNotifications; 1.1083 + if (!didSuppressNotifications) 1.1084 + result.suppressNotifications = true; 1.1085 + 1.1086 + root.containerOpen = true; 1.1087 + } 1.1088 + 1.1089 + for (let i = 0; i < root.childCount; ++i) { 1.1090 + let child = root.getChild(i); 1.1091 + if (this.nodeIsURI(child)) 1.1092 + urls.push({uri: child.uri, isBookmark: this.nodeIsBookmark(child)}); 1.1093 + } 1.1094 + 1.1095 + if (!wasOpen) { 1.1096 + root.containerOpen = false; 1.1097 + if (!didSuppressNotifications) 1.1098 + result.suppressNotifications = false; 1.1099 + } 1.1100 + return urls; 1.1101 + }, 1.1102 + 1.1103 + /** 1.1104 + * Serializes the given node (and all its descendents) as JSON 1.1105 + * and writes the serialization to the given output stream. 1.1106 + * 1.1107 + * @param aNode 1.1108 + * An nsINavHistoryResultNode 1.1109 + * @param aStream 1.1110 + * An nsIOutputStream. NOTE: it only uses the write(str, len) 1.1111 + * method of nsIOutputStream. The caller is responsible for 1.1112 + * closing the stream. 1.1113 + */ 1.1114 + _serializeNodeAsJSONToOutputStream: function (aNode, aStream) { 1.1115 + function addGenericProperties(aPlacesNode, aJSNode) { 1.1116 + aJSNode.title = aPlacesNode.title; 1.1117 + aJSNode.id = aPlacesNode.itemId; 1.1118 + if (aJSNode.id != -1) { 1.1119 + var parent = aPlacesNode.parent; 1.1120 + if (parent) { 1.1121 + aJSNode.parent = parent.itemId; 1.1122 + aJSNode.parentReadOnly = PlacesUtils.nodeIsReadOnly(parent); 1.1123 + } 1.1124 + var dateAdded = aPlacesNode.dateAdded; 1.1125 + if (dateAdded) 1.1126 + aJSNode.dateAdded = dateAdded; 1.1127 + var lastModified = aPlacesNode.lastModified; 1.1128 + if (lastModified) 1.1129 + aJSNode.lastModified = lastModified; 1.1130 + 1.1131 + // XXX need a hasAnnos api 1.1132 + var annos = []; 1.1133 + try { 1.1134 + annos = PlacesUtils.getAnnotationsForItem(aJSNode.id).filter(function(anno) { 1.1135 + // XXX should whitelist this instead, w/ a pref for 1.1136 + // backup/restore of non-whitelisted annos 1.1137 + // XXX causes JSON encoding errors, so utf-8 encode 1.1138 + //anno.value = unescape(encodeURIComponent(anno.value)); 1.1139 + if (anno.name == PlacesUtils.LMANNO_FEEDURI) 1.1140 + aJSNode.livemark = 1; 1.1141 + return true; 1.1142 + }); 1.1143 + } catch(ex) {} 1.1144 + if (annos.length != 0) 1.1145 + aJSNode.annos = annos; 1.1146 + } 1.1147 + // XXXdietrich - store annos for non-bookmark items 1.1148 + } 1.1149 + 1.1150 + function addURIProperties(aPlacesNode, aJSNode) { 1.1151 + aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE; 1.1152 + aJSNode.uri = aPlacesNode.uri; 1.1153 + if (aJSNode.id && aJSNode.id != -1) { 1.1154 + // harvest bookmark-specific properties 1.1155 + var keyword = PlacesUtils.bookmarks.getKeywordForBookmark(aJSNode.id); 1.1156 + if (keyword) 1.1157 + aJSNode.keyword = keyword; 1.1158 + } 1.1159 + 1.1160 + if (aPlacesNode.tags) 1.1161 + aJSNode.tags = aPlacesNode.tags; 1.1162 + 1.1163 + // last character-set 1.1164 + var uri = PlacesUtils._uri(aPlacesNode.uri); 1.1165 + try { 1.1166 + var lastCharset = PlacesUtils.annotations.getPageAnnotation( 1.1167 + uri, PlacesUtils.CHARSET_ANNO); 1.1168 + aJSNode.charset = lastCharset; 1.1169 + } catch (e) {} 1.1170 + } 1.1171 + 1.1172 + function addSeparatorProperties(aPlacesNode, aJSNode) { 1.1173 + aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR; 1.1174 + } 1.1175 + 1.1176 + function addContainerProperties(aPlacesNode, aJSNode) { 1.1177 + var concreteId = PlacesUtils.getConcreteItemId(aPlacesNode); 1.1178 + if (concreteId != -1) { 1.1179 + // This is a bookmark or a tag container. 1.1180 + if (PlacesUtils.nodeIsQuery(aPlacesNode) || 1.1181 + concreteId != aPlacesNode.itemId) { 1.1182 + aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE; 1.1183 + aJSNode.uri = aPlacesNode.uri; 1.1184 + // folder shortcut 1.1185 + aJSNode.concreteId = concreteId; 1.1186 + } 1.1187 + else { // Bookmark folder or a shortcut we should convert to folder. 1.1188 + aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER; 1.1189 + 1.1190 + // Mark root folders. 1.1191 + if (aJSNode.id == PlacesUtils.placesRootId) 1.1192 + aJSNode.root = "placesRoot"; 1.1193 + else if (aJSNode.id == PlacesUtils.bookmarksMenuFolderId) 1.1194 + aJSNode.root = "bookmarksMenuFolder"; 1.1195 + else if (aJSNode.id == PlacesUtils.tagsFolderId) 1.1196 + aJSNode.root = "tagsFolder"; 1.1197 + else if (aJSNode.id == PlacesUtils.unfiledBookmarksFolderId) 1.1198 + aJSNode.root = "unfiledBookmarksFolder"; 1.1199 + else if (aJSNode.id == PlacesUtils.toolbarFolderId) 1.1200 + aJSNode.root = "toolbarFolder"; 1.1201 + } 1.1202 + } 1.1203 + else { 1.1204 + // This is a grouped container query, generated on the fly. 1.1205 + aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE; 1.1206 + aJSNode.uri = aPlacesNode.uri; 1.1207 + } 1.1208 + } 1.1209 + 1.1210 + function appendConvertedComplexNode(aNode, aSourceNode, aArray) { 1.1211 + var repr = {}; 1.1212 + 1.1213 + for (let [name, value] in Iterator(aNode)) 1.1214 + repr[name] = value; 1.1215 + 1.1216 + // write child nodes 1.1217 + var children = repr.children = []; 1.1218 + if (!aNode.livemark) { 1.1219 + asContainer(aSourceNode); 1.1220 + var wasOpen = aSourceNode.containerOpen; 1.1221 + if (!wasOpen) 1.1222 + aSourceNode.containerOpen = true; 1.1223 + var cc = aSourceNode.childCount; 1.1224 + for (var i = 0; i < cc; ++i) { 1.1225 + var childNode = aSourceNode.getChild(i); 1.1226 + appendConvertedNode(aSourceNode.getChild(i), i, children); 1.1227 + } 1.1228 + if (!wasOpen) 1.1229 + aSourceNode.containerOpen = false; 1.1230 + } 1.1231 + 1.1232 + aArray.push(repr); 1.1233 + return true; 1.1234 + } 1.1235 + 1.1236 + function appendConvertedNode(bNode, aIndex, aArray) { 1.1237 + var node = {}; 1.1238 + 1.1239 + // set index in order received 1.1240 + // XXX handy shortcut, but are there cases where we don't want 1.1241 + // to export using the sorting provided by the query? 1.1242 + if (aIndex) 1.1243 + node.index = aIndex; 1.1244 + 1.1245 + addGenericProperties(bNode, node); 1.1246 + 1.1247 + var parent = bNode.parent; 1.1248 + var grandParent = parent ? parent.parent : null; 1.1249 + if (grandParent) 1.1250 + node.grandParentId = grandParent.itemId; 1.1251 + 1.1252 + if (PlacesUtils.nodeIsURI(bNode)) { 1.1253 + // Tag root accept only folder nodes 1.1254 + if (parent && parent.itemId == PlacesUtils.tagsFolderId) 1.1255 + return false; 1.1256 + 1.1257 + // Check for url validity, since we can't halt while writing a backup. 1.1258 + // This will throw if we try to serialize an invalid url and it does 1.1259 + // not make sense saving a wrong or corrupt uri node. 1.1260 + try { 1.1261 + PlacesUtils._uri(bNode.uri); 1.1262 + } catch (ex) { 1.1263 + return false; 1.1264 + } 1.1265 + 1.1266 + addURIProperties(bNode, node); 1.1267 + } 1.1268 + else if (PlacesUtils.nodeIsContainer(bNode)) { 1.1269 + // Tag containers accept only uri nodes 1.1270 + if (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId) 1.1271 + return false; 1.1272 + 1.1273 + addContainerProperties(bNode, node); 1.1274 + } 1.1275 + else if (PlacesUtils.nodeIsSeparator(bNode)) { 1.1276 + // Tag root accept only folder nodes 1.1277 + // Tag containers accept only uri nodes 1.1278 + if ((parent && parent.itemId == PlacesUtils.tagsFolderId) || 1.1279 + (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId)) 1.1280 + return false; 1.1281 + 1.1282 + addSeparatorProperties(bNode, node); 1.1283 + } 1.1284 + 1.1285 + if (!node.feedURI && node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) 1.1286 + return appendConvertedComplexNode(node, bNode, aArray); 1.1287 + 1.1288 + aArray.push(node); 1.1289 + return true; 1.1290 + } 1.1291 + 1.1292 + // serialize to stream 1.1293 + var array = []; 1.1294 + if (appendConvertedNode(aNode, null, array)) { 1.1295 + var json = JSON.stringify(array[0]); 1.1296 + aStream.write(json, json.length); 1.1297 + } 1.1298 + else { 1.1299 + throw Cr.NS_ERROR_UNEXPECTED; 1.1300 + } 1.1301 + }, 1.1302 + 1.1303 + /** 1.1304 + * Given a uri returns list of itemIds associated to it. 1.1305 + * 1.1306 + * @param aURI 1.1307 + * nsIURI or spec of the page. 1.1308 + * @param aCallback 1.1309 + * Function to be called when done. 1.1310 + * The function will receive an array of itemIds associated to aURI and 1.1311 + * aURI itself. 1.1312 + * 1.1313 + * @return A object with a .cancel() method allowing to cancel the request. 1.1314 + * 1.1315 + * @note Children of live bookmarks folders are excluded. The callback function is 1.1316 + * not invoked if the request is cancelled or hits an error. 1.1317 + */ 1.1318 + asyncGetBookmarkIds: function PU_asyncGetBookmarkIds(aURI, aCallback) 1.1319 + { 1.1320 + if (!this._asyncGetBookmarksStmt) { 1.1321 + let db = this.history.DBConnection; 1.1322 + this._asyncGetBookmarksStmt = db.createAsyncStatement( 1.1323 + "SELECT b.id " 1.1324 + + "FROM moz_bookmarks b " 1.1325 + + "JOIN moz_places h on h.id = b.fk " 1.1326 + + "WHERE h.url = :url " 1.1327 + ); 1.1328 + this.registerShutdownFunction(function () { 1.1329 + this._asyncGetBookmarksStmt.finalize(); 1.1330 + }); 1.1331 + } 1.1332 + 1.1333 + let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI; 1.1334 + this._asyncGetBookmarksStmt.params.url = url; 1.1335 + 1.1336 + // Storage does not guarantee that invoking cancel() on a statement 1.1337 + // will cause a REASON_CANCELED. Thus we wrap the statement. 1.1338 + let stmt = new AsyncStatementCancelWrapper(this._asyncGetBookmarksStmt); 1.1339 + return stmt.executeAsync({ 1.1340 + _callback: aCallback, 1.1341 + _itemIds: [], 1.1342 + handleResult: function(aResultSet) { 1.1343 + for (let row; (row = aResultSet.getNextRow());) { 1.1344 + this._itemIds.push(row.getResultByIndex(0)); 1.1345 + } 1.1346 + }, 1.1347 + handleCompletion: function(aReason) 1.1348 + { 1.1349 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.1350 + this._callback(this._itemIds, aURI); 1.1351 + } 1.1352 + } 1.1353 + }); 1.1354 + }, 1.1355 + 1.1356 + /** 1.1357 + * Lazily adds a bookmarks observer, waiting for the bookmarks service to be 1.1358 + * alive before registering the observer. This is especially useful in the 1.1359 + * startup path, to avoid initializing the service just to add an observer. 1.1360 + * 1.1361 + * @param aObserver 1.1362 + * Object implementing nsINavBookmarkObserver 1.1363 + * @param [optional]aWeakOwner 1.1364 + * Whether to use weak ownership. 1.1365 + * 1.1366 + * @note Correct functionality of lazy observers relies on the fact Places 1.1367 + * notifies categories before real observers, and uses 1.1368 + * PlacesCategoriesStarter component to kick-off the registration. 1.1369 + */ 1.1370 + _bookmarksServiceReady: false, 1.1371 + _bookmarksServiceObserversQueue: [], 1.1372 + addLazyBookmarkObserver: 1.1373 + function PU_addLazyBookmarkObserver(aObserver, aWeakOwner) { 1.1374 + if (this._bookmarksServiceReady) { 1.1375 + this.bookmarks.addObserver(aObserver, aWeakOwner === true); 1.1376 + return; 1.1377 + } 1.1378 + this._bookmarksServiceObserversQueue.push({ observer: aObserver, 1.1379 + weak: aWeakOwner === true }); 1.1380 + }, 1.1381 + 1.1382 + /** 1.1383 + * Removes a bookmarks observer added through addLazyBookmarkObserver. 1.1384 + * 1.1385 + * @param aObserver 1.1386 + * Object implementing nsINavBookmarkObserver 1.1387 + */ 1.1388 + removeLazyBookmarkObserver: 1.1389 + function PU_removeLazyBookmarkObserver(aObserver) { 1.1390 + if (this._bookmarksServiceReady) { 1.1391 + this.bookmarks.removeObserver(aObserver); 1.1392 + return; 1.1393 + } 1.1394 + let index = -1; 1.1395 + for (let i = 0; 1.1396 + i < this._bookmarksServiceObserversQueue.length && index == -1; i++) { 1.1397 + if (this._bookmarksServiceObserversQueue[i].observer === aObserver) 1.1398 + index = i; 1.1399 + } 1.1400 + if (index != -1) { 1.1401 + this._bookmarksServiceObserversQueue.splice(index, 1); 1.1402 + } 1.1403 + }, 1.1404 + 1.1405 + /** 1.1406 + * Sets the character-set for a URI. 1.1407 + * 1.1408 + * @param aURI nsIURI 1.1409 + * @param aCharset character-set value. 1.1410 + * @return {Promise} 1.1411 + */ 1.1412 + setCharsetForURI: function PU_setCharsetForURI(aURI, aCharset) { 1.1413 + let deferred = Promise.defer(); 1.1414 + 1.1415 + // Delaying to catch issues with asynchronous behavior while waiting 1.1416 + // to implement asynchronous annotations in bug 699844. 1.1417 + Services.tm.mainThread.dispatch(function() { 1.1418 + if (aCharset && aCharset.length > 0) { 1.1419 + PlacesUtils.annotations.setPageAnnotation( 1.1420 + aURI, PlacesUtils.CHARSET_ANNO, aCharset, 0, 1.1421 + Ci.nsIAnnotationService.EXPIRE_NEVER); 1.1422 + } else { 1.1423 + PlacesUtils.annotations.removePageAnnotation( 1.1424 + aURI, PlacesUtils.CHARSET_ANNO); 1.1425 + } 1.1426 + deferred.resolve(); 1.1427 + }, Ci.nsIThread.DISPATCH_NORMAL); 1.1428 + 1.1429 + return deferred.promise; 1.1430 + }, 1.1431 + 1.1432 + /** 1.1433 + * Gets the last saved character-set for a URI. 1.1434 + * 1.1435 + * @param aURI nsIURI 1.1436 + * @return {Promise} 1.1437 + * @resolve a character-set or null. 1.1438 + */ 1.1439 + getCharsetForURI: function PU_getCharsetForURI(aURI) { 1.1440 + let deferred = Promise.defer(); 1.1441 + 1.1442 + Services.tm.mainThread.dispatch(function() { 1.1443 + let charset = null; 1.1444 + 1.1445 + try { 1.1446 + charset = PlacesUtils.annotations.getPageAnnotation(aURI, 1.1447 + PlacesUtils.CHARSET_ANNO); 1.1448 + } catch (ex) { } 1.1449 + 1.1450 + deferred.resolve(charset); 1.1451 + }, Ci.nsIThread.DISPATCH_NORMAL); 1.1452 + 1.1453 + return deferred.promise; 1.1454 + }, 1.1455 + 1.1456 + /** 1.1457 + * Promised wrapper for mozIAsyncHistory::updatePlaces for a single place. 1.1458 + * 1.1459 + * @param aPlaces 1.1460 + * a single mozIPlaceInfo object 1.1461 + * @resolves {Promise} 1.1462 + */ 1.1463 + promiseUpdatePlace: function PU_promiseUpdatePlaces(aPlace) { 1.1464 + let deferred = Promise.defer(); 1.1465 + PlacesUtils.asyncHistory.updatePlaces(aPlace, { 1.1466 + _placeInfo: null, 1.1467 + handleResult: function handleResult(aPlaceInfo) { 1.1468 + this._placeInfo = aPlaceInfo; 1.1469 + }, 1.1470 + handleError: function handleError(aResultCode, aPlaceInfo) { 1.1471 + deferred.reject(new Components.Exception("Error", aResultCode)); 1.1472 + }, 1.1473 + handleCompletion: function() { 1.1474 + deferred.resolve(this._placeInfo); 1.1475 + } 1.1476 + }); 1.1477 + 1.1478 + return deferred.promise; 1.1479 + }, 1.1480 + 1.1481 + /** 1.1482 + * Promised wrapper for mozIAsyncHistory::getPlacesInfo for a single place. 1.1483 + * 1.1484 + * @param aPlaceIdentifier 1.1485 + * either an nsIURI or a GUID (@see getPlacesInfo) 1.1486 + * @resolves to the place info object handed to handleResult. 1.1487 + */ 1.1488 + promisePlaceInfo: function PU_promisePlaceInfo(aPlaceIdentifier) { 1.1489 + let deferred = Promise.defer(); 1.1490 + PlacesUtils.asyncHistory.getPlacesInfo(aPlaceIdentifier, { 1.1491 + _placeInfo: null, 1.1492 + handleResult: function handleResult(aPlaceInfo) { 1.1493 + this._placeInfo = aPlaceInfo; 1.1494 + }, 1.1495 + handleError: function handleError(aResultCode, aPlaceInfo) { 1.1496 + deferred.reject(new Components.Exception("Error", aResultCode)); 1.1497 + }, 1.1498 + handleCompletion: function() { 1.1499 + deferred.resolve(this._placeInfo); 1.1500 + } 1.1501 + }); 1.1502 + 1.1503 + return deferred.promise; 1.1504 + }, 1.1505 + 1.1506 + /** 1.1507 + * Gets favicon data for a given page url. 1.1508 + * 1.1509 + * @param aPageUrl url of the page to look favicon for. 1.1510 + * @resolves to an object representing a favicon entry, having the following 1.1511 + * properties: { uri, dataLen, data, mimeType } 1.1512 + * @rejects JavaScript exception if the given url has no associated favicon. 1.1513 + */ 1.1514 + promiseFaviconData: function (aPageUrl) { 1.1515 + let deferred = Promise.defer(); 1.1516 + PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(aPageUrl), 1.1517 + function (aURI, aDataLen, aData, aMimeType) { 1.1518 + if (aURI) { 1.1519 + deferred.resolve({ uri: aURI, 1.1520 + dataLen: aDataLen, 1.1521 + data: aData, 1.1522 + mimeType: aMimeType }); 1.1523 + } else { 1.1524 + deferred.reject(); 1.1525 + } 1.1526 + }); 1.1527 + return deferred.promise; 1.1528 + }, 1.1529 + 1.1530 + /** 1.1531 + * Get the unique id for an item (a bookmark, a folder or a separator) given 1.1532 + * its item id. 1.1533 + * 1.1534 + * @param aItemId 1.1535 + * an item id 1.1536 + * @return {Promise} 1.1537 + * @resolves to the GUID. 1.1538 + * @rejects if aItemId is invalid. 1.1539 + */ 1.1540 + promiseItemGUID: function (aItemId) GUIDHelper.getItemGUID(aItemId), 1.1541 + 1.1542 + /** 1.1543 + * Get the item id for an item (a bookmark, a folder or a separator) given 1.1544 + * its unique id. 1.1545 + * 1.1546 + * @param aGUID 1.1547 + * an item GUID 1.1548 + * @retrun {Promise} 1.1549 + * @resolves to the GUID. 1.1550 + * @rejects if there's no item for the given GUID. 1.1551 + */ 1.1552 + promiseItemId: function (aGUID) GUIDHelper.getItemId(aGUID) 1.1553 +}; 1.1554 + 1.1555 +/** 1.1556 + * Wraps the provided statement so that invoking cancel() on the pending 1.1557 + * statement object will always cause a REASON_CANCELED. 1.1558 + */ 1.1559 +function AsyncStatementCancelWrapper(aStmt) { 1.1560 + this._stmt = aStmt; 1.1561 +} 1.1562 +AsyncStatementCancelWrapper.prototype = { 1.1563 + _canceled: false, 1.1564 + _cancel: function() { 1.1565 + this._canceled = true; 1.1566 + this._pendingStmt.cancel(); 1.1567 + }, 1.1568 + handleResult: function(aResultSet) { 1.1569 + this._callback.handleResult(aResultSet); 1.1570 + }, 1.1571 + handleError: function(aError) { 1.1572 + Cu.reportError("Async statement execution returned (" + aError.result + 1.1573 + "): " + aError.message); 1.1574 + }, 1.1575 + handleCompletion: function(aReason) 1.1576 + { 1.1577 + let reason = this._canceled ? 1.1578 + Ci.mozIStorageStatementCallback.REASON_CANCELED : 1.1579 + aReason; 1.1580 + this._callback.handleCompletion(reason); 1.1581 + }, 1.1582 + executeAsync: function(aCallback) { 1.1583 + this._pendingStmt = this._stmt.executeAsync(this); 1.1584 + this._callback = aCallback; 1.1585 + let self = this; 1.1586 + return { cancel: function () { self._cancel(); } } 1.1587 + } 1.1588 +} 1.1589 + 1.1590 +XPCOMUtils.defineLazyGetter(PlacesUtils, "history", function() { 1.1591 + return Cc["@mozilla.org/browser/nav-history-service;1"] 1.1592 + .getService(Ci.nsINavHistoryService) 1.1593 + .QueryInterface(Ci.nsIBrowserHistory) 1.1594 + .QueryInterface(Ci.nsPIPlacesDatabase); 1.1595 +}); 1.1596 + 1.1597 +XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "asyncHistory", 1.1598 + "@mozilla.org/browser/history;1", 1.1599 + "mozIAsyncHistory"); 1.1600 + 1.1601 +XPCOMUtils.defineLazyGetter(PlacesUtils, "bhistory", function() { 1.1602 + return PlacesUtils.history; 1.1603 +}); 1.1604 + 1.1605 +XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "favicons", 1.1606 + "@mozilla.org/browser/favicon-service;1", 1.1607 + "mozIAsyncFavicons"); 1.1608 + 1.1609 +XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "bookmarks", 1.1610 + "@mozilla.org/browser/nav-bookmarks-service;1", 1.1611 + "nsINavBookmarksService"); 1.1612 + 1.1613 +XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "annotations", 1.1614 + "@mozilla.org/browser/annotation-service;1", 1.1615 + "nsIAnnotationService"); 1.1616 + 1.1617 +XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "tagging", 1.1618 + "@mozilla.org/browser/tagging-service;1", 1.1619 + "nsITaggingService"); 1.1620 + 1.1621 +XPCOMUtils.defineLazyGetter(PlacesUtils, "livemarks", function() { 1.1622 + return Cc["@mozilla.org/browser/livemark-service;2"]. 1.1623 + getService(Ci.mozIAsyncLivemarks); 1.1624 +}); 1.1625 + 1.1626 +XPCOMUtils.defineLazyGetter(PlacesUtils, "transactionManager", function() { 1.1627 + let tm = Cc["@mozilla.org/transactionmanager;1"]. 1.1628 + createInstance(Ci.nsITransactionManager); 1.1629 + tm.AddListener(PlacesUtils); 1.1630 + this.registerShutdownFunction(function () { 1.1631 + // Clear all references to local transactions in the transaction manager, 1.1632 + // this prevents from leaking it. 1.1633 + this.transactionManager.RemoveListener(this); 1.1634 + this.transactionManager.clear(); 1.1635 + }); 1.1636 + 1.1637 + // Bug 750269 1.1638 + // The transaction manager keeps strong references to transactions, and by 1.1639 + // that, also to the global for each transaction. A transaction, however, 1.1640 + // could be either the transaction itself (for which the global is this 1.1641 + // module) or some js-proxy in another global, usually a window. The later 1.1642 + // would leak because the transaction lifetime (in the manager's stacks) 1.1643 + // is independent of the global from which doTransaction was called. 1.1644 + // To avoid such a leak, we hide the native doTransaction from callers, 1.1645 + // and let each doTransaction call go through this module. 1.1646 + // Doing so ensures that, as long as the transaction is any of the 1.1647 + // PlacesXXXTransaction objects declared in this module, the object 1.1648 + // referenced by the transaction manager has the module itself as global. 1.1649 + return Object.create(tm, { 1.1650 + "doTransaction": { 1.1651 + value: function(aTransaction) { 1.1652 + tm.doTransaction(aTransaction); 1.1653 + } 1.1654 + } 1.1655 + }); 1.1656 +}); 1.1657 + 1.1658 +XPCOMUtils.defineLazyGetter(this, "bundle", function() { 1.1659 + const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties"; 1.1660 + return Cc["@mozilla.org/intl/stringbundle;1"]. 1.1661 + getService(Ci.nsIStringBundleService). 1.1662 + createBundle(PLACES_STRING_BUNDLE_URI); 1.1663 +}); 1.1664 + 1.1665 +XPCOMUtils.defineLazyServiceGetter(this, "focusManager", 1.1666 + "@mozilla.org/focus-manager;1", 1.1667 + "nsIFocusManager"); 1.1668 + 1.1669 +// Sometime soon, likely as part of the transition to mozIAsyncBookmarks, 1.1670 +// itemIds will be deprecated in favour of GUIDs, which play much better 1.1671 +// with multiple undo/redo operations. Because these GUIDs are already stored, 1.1672 +// and because we don't want to revise the transactions API once more when this 1.1673 +// happens, transactions are set to work with GUIDs exclusively, in the sense 1.1674 +// that they may never expose itemIds, nor do they accept them as input. 1.1675 +// More importantly, transactions which add or remove items guarantee to 1.1676 +// restore the guids on undo/redo, so that the following transactions that may 1.1677 +// done or undo can assume the items they're interested in are stil accessible 1.1678 +// through the same GUID. 1.1679 +// The current bookmarks API, however, doesn't expose the necessary means for 1.1680 +// working with GUIDs. So, until it does, this helper object accesses the 1.1681 +// Places database directly in order to switch between GUIDs and itemIds, and 1.1682 +// "restore" GUIDs on items re-created items. 1.1683 +const REASON_FINISHED = Ci.mozIStorageStatementCallback.REASON_FINISHED; 1.1684 +let GUIDHelper = { 1.1685 + // Cache for guid<->itemId paris. 1.1686 + GUIDsForIds: new Map(), 1.1687 + idsForGUIDs: new Map(), 1.1688 + 1.1689 + getItemId: function (aGUID) { 1.1690 + if (this.idsForGUIDs.has(aGUID)) 1.1691 + return Promise.resolve(this.idsForGUIDs.get(aGUID)); 1.1692 + 1.1693 + let deferred = Promise.defer(); 1.1694 + let itemId = -1; 1.1695 + 1.1696 + this._getIDStatement.params.guid = aGUID; 1.1697 + this._getIDStatement.executeAsync({ 1.1698 + handleResult: function (aResultSet) { 1.1699 + let row = aResultSet.getNextRow(); 1.1700 + if (row) 1.1701 + itemId = row.getResultByIndex(0); 1.1702 + }, 1.1703 + handleCompletion: aReason => { 1.1704 + if (aReason == REASON_FINISHED && itemId != -1) { 1.1705 + this.ensureObservingRemovedItems(); 1.1706 + this.idsForGUIDs.set(aGUID, itemId); 1.1707 + 1.1708 + deferred.resolve(itemId); 1.1709 + } 1.1710 + else if (itemId != -1) { 1.1711 + deferred.reject("no item found for the given guid"); 1.1712 + } 1.1713 + else { 1.1714 + deferred.reject("SQLite Error: " + aReason); 1.1715 + } 1.1716 + } 1.1717 + }); 1.1718 + 1.1719 + return deferred.promise; 1.1720 + }, 1.1721 + 1.1722 + getItemGUID: function (aItemId) { 1.1723 + if (this.GUIDsForIds.has(aItemId)) 1.1724 + return Promise.resolve(this.GUIDsForIds.get(aItemId)); 1.1725 + 1.1726 + let deferred = Promise.defer(); 1.1727 + let guid = ""; 1.1728 + 1.1729 + this._getGUIDStatement.params.id = aItemId; 1.1730 + this._getGUIDStatement.executeAsync({ 1.1731 + handleResult: function (aResultSet) { 1.1732 + let row = aResultSet.getNextRow(); 1.1733 + if (row) { 1.1734 + guid = row.getResultByIndex(1); 1.1735 + } 1.1736 + }, 1.1737 + handleCompletion: aReason => { 1.1738 + if (aReason == REASON_FINISHED && guid) { 1.1739 + this.ensureObservingRemovedItems(); 1.1740 + this.GUIDsForIds.set(aItemId, guid); 1.1741 + 1.1742 + deferred.resolve(guid); 1.1743 + } 1.1744 + else if (!guid) { 1.1745 + deferred.reject("no item found for the given itemId"); 1.1746 + } 1.1747 + else { 1.1748 + deferred.reject("SQLite Error: " + aReason); 1.1749 + } 1.1750 + } 1.1751 + }); 1.1752 + 1.1753 + return deferred.promise; 1.1754 + }, 1.1755 + 1.1756 + ensureObservingRemovedItems: function () { 1.1757 + if (!("observer" in this)) { 1.1758 + /** 1.1759 + * This observers serves two purposes: 1.1760 + * (1) Invalidate cached id<->guid paris on when items are removed. 1.1761 + * (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved. 1.1762 + * So, for exmaple, when the NewBookmark needs the new GUID, we already 1.1763 + * have it cached. 1.1764 + */ 1.1765 + this.observer = { 1.1766 + onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle, 1.1767 + aDateAdded, aGUID, aParentGUID) => { 1.1768 + this.GUIDsForIds.set(aItemId, aGUID); 1.1769 + this.GUIDsForIds.set(aParentId, aParentGUID); 1.1770 + }, 1.1771 + onItemRemoved: 1.1772 + (aItemId, aParentId, aIndex, aItemTyep, aURI, aGUID, aParentGUID) => { 1.1773 + this.GUIDsForIds.delete(aItemId); 1.1774 + this.idsForGUIDs.delete(aGUID); 1.1775 + this.GUIDsForIds.set(aParentId, aParentGUID); 1.1776 + }, 1.1777 + 1.1778 + QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver), 1.1779 + __noSuchMethod__: () => {}, // Catch all all onItem* methods. 1.1780 + }; 1.1781 + PlacesUtils.bookmarks.addObserver(this.observer, false); 1.1782 + PlacesUtils.registerShutdownFunction(() => { 1.1783 + PlacesUtils.bookmarks.removeObserver(this.observer); 1.1784 + }); 1.1785 + } 1.1786 + } 1.1787 +}; 1.1788 +XPCOMUtils.defineLazyGetter(GUIDHelper, "_getIDStatement", () => { 1.1789 + let s = PlacesUtils.history.DBConnection.createAsyncStatement( 1.1790 + "SELECT b.id, b.guid from moz_bookmarks b WHERE b.guid = :guid"); 1.1791 + PlacesUtils.registerShutdownFunction( () => s.finalize() ); 1.1792 + return s; 1.1793 +}); 1.1794 +XPCOMUtils.defineLazyGetter(GUIDHelper, "_getGUIDStatement", () => { 1.1795 + let s = PlacesUtils.history.DBConnection.createAsyncStatement( 1.1796 + "SELECT b.id, b.guid from moz_bookmarks b WHERE b.id = :id"); 1.1797 + PlacesUtils.registerShutdownFunction( () => s.finalize() ); 1.1798 + return s; 1.1799 +}); 1.1800 + 1.1801 +//////////////////////////////////////////////////////////////////////////////// 1.1802 +//// Transactions handlers. 1.1803 + 1.1804 +/** 1.1805 + * Updates commands in the undo group of the active window commands. 1.1806 + * Inactive windows commands will be updated on focus. 1.1807 + */ 1.1808 +function updateCommandsOnActiveWindow() 1.1809 +{ 1.1810 + let win = focusManager.activeWindow; 1.1811 + if (win && win instanceof Ci.nsIDOMWindow) { 1.1812 + // Updating "undo" will cause a group update including "redo". 1.1813 + win.updateCommands("undo"); 1.1814 + } 1.1815 +} 1.1816 + 1.1817 + 1.1818 +/** 1.1819 + * Used to cache bookmark information in transactions. 1.1820 + * 1.1821 + * @note To avoid leaks any non-primitive property should be copied. 1.1822 + * @note Used internally, DO NOT EXPORT. 1.1823 + */ 1.1824 +function TransactionItemCache() 1.1825 +{ 1.1826 +} 1.1827 + 1.1828 +TransactionItemCache.prototype = { 1.1829 + set id(v) 1.1830 + this._id = (parseInt(v) > 0 ? v : null), 1.1831 + get id() 1.1832 + this._id || -1, 1.1833 + set parentId(v) 1.1834 + this._parentId = (parseInt(v) > 0 ? v : null), 1.1835 + get parentId() 1.1836 + this._parentId || -1, 1.1837 + keyword: null, 1.1838 + title: null, 1.1839 + dateAdded: null, 1.1840 + lastModified: null, 1.1841 + postData: null, 1.1842 + itemType: null, 1.1843 + set uri(v) 1.1844 + this._uri = (v instanceof Ci.nsIURI ? v.clone() : null), 1.1845 + get uri() 1.1846 + this._uri || null, 1.1847 + set feedURI(v) 1.1848 + this._feedURI = (v instanceof Ci.nsIURI ? v.clone() : null), 1.1849 + get feedURI() 1.1850 + this._feedURI || null, 1.1851 + set siteURI(v) 1.1852 + this._siteURI = (v instanceof Ci.nsIURI ? v.clone() : null), 1.1853 + get siteURI() 1.1854 + this._siteURI || null, 1.1855 + set index(v) 1.1856 + this._index = (parseInt(v) >= 0 ? v : null), 1.1857 + // Index can be 0. 1.1858 + get index() 1.1859 + this._index != null ? this._index : PlacesUtils.bookmarks.DEFAULT_INDEX, 1.1860 + set annotations(v) 1.1861 + this._annotations = Array.isArray(v) ? Cu.cloneInto(v, {}) : null, 1.1862 + get annotations() 1.1863 + this._annotations || null, 1.1864 + set tags(v) 1.1865 + this._tags = (v && Array.isArray(v) ? Array.slice(v) : null), 1.1866 + get tags() 1.1867 + this._tags || null, 1.1868 +}; 1.1869 + 1.1870 + 1.1871 +/** 1.1872 + * Base transaction implementation. 1.1873 + * 1.1874 + * @note used internally, DO NOT EXPORT. 1.1875 + */ 1.1876 +function BaseTransaction() 1.1877 +{ 1.1878 +} 1.1879 + 1.1880 +BaseTransaction.prototype = { 1.1881 + name: null, 1.1882 + set childTransactions(v) 1.1883 + this._childTransactions = (Array.isArray(v) ? Array.slice(v) : null), 1.1884 + get childTransactions() 1.1885 + this._childTransactions || null, 1.1886 + doTransaction: function BTXN_doTransaction() {}, 1.1887 + redoTransaction: function BTXN_redoTransaction() this.doTransaction(), 1.1888 + undoTransaction: function BTXN_undoTransaction() {}, 1.1889 + merge: function BTXN_merge() false, 1.1890 + get isTransient() false, 1.1891 + QueryInterface: XPCOMUtils.generateQI([ 1.1892 + Ci.nsITransaction 1.1893 + ]), 1.1894 +}; 1.1895 + 1.1896 + 1.1897 +/** 1.1898 + * Transaction for performing several Places Transactions in a single batch. 1.1899 + * 1.1900 + * @param aName 1.1901 + * title of the aggregate transactions 1.1902 + * @param aTransactions 1.1903 + * an array of transactions to perform 1.1904 + * 1.1905 + * @return nsITransaction object 1.1906 + */ 1.1907 +this.PlacesAggregatedTransaction = 1.1908 + function PlacesAggregatedTransaction(aName, aTransactions) 1.1909 +{ 1.1910 + // Copy the transactions array to decouple it from its prototype, which 1.1911 + // otherwise keeps alive its associated global object. 1.1912 + this.childTransactions = aTransactions; 1.1913 + this.name = aName; 1.1914 + this.item = new TransactionItemCache(); 1.1915 + 1.1916 + // Check child transactions number. We will batch if we have more than 1.1917 + // MIN_TRANSACTIONS_FOR_BATCH total number of transactions. 1.1918 + let countTransactions = function(aTransactions, aTxnCount) 1.1919 + { 1.1920 + for (let i = 0; 1.1921 + i < aTransactions.length && aTxnCount < MIN_TRANSACTIONS_FOR_BATCH; 1.1922 + ++i, ++aTxnCount) { 1.1923 + let txn = aTransactions[i]; 1.1924 + if (txn.childTransactions && txn.childTransactions.length > 0) 1.1925 + aTxnCount = countTransactions(txn.childTransactions, aTxnCount); 1.1926 + } 1.1927 + return aTxnCount; 1.1928 + } 1.1929 + 1.1930 + let txnCount = countTransactions(this.childTransactions, 0); 1.1931 + this._useBatch = txnCount >= MIN_TRANSACTIONS_FOR_BATCH; 1.1932 +} 1.1933 + 1.1934 +PlacesAggregatedTransaction.prototype = { 1.1935 + __proto__: BaseTransaction.prototype, 1.1936 + 1.1937 + doTransaction: function ATXN_doTransaction() 1.1938 + { 1.1939 + this._isUndo = false; 1.1940 + if (this._useBatch) 1.1941 + PlacesUtils.bookmarks.runInBatchMode(this, null); 1.1942 + else 1.1943 + this.runBatched(false); 1.1944 + }, 1.1945 + 1.1946 + undoTransaction: function ATXN_undoTransaction() 1.1947 + { 1.1948 + this._isUndo = true; 1.1949 + if (this._useBatch) 1.1950 + PlacesUtils.bookmarks.runInBatchMode(this, null); 1.1951 + else 1.1952 + this.runBatched(true); 1.1953 + }, 1.1954 + 1.1955 + runBatched: function ATXN_runBatched() 1.1956 + { 1.1957 + // Use a copy of the transactions array, so we won't reverse the original 1.1958 + // one on undoing. 1.1959 + let transactions = this.childTransactions.slice(0); 1.1960 + if (this._isUndo) 1.1961 + transactions.reverse(); 1.1962 + for (let i = 0; i < transactions.length; ++i) { 1.1963 + let txn = transactions[i]; 1.1964 + if (this.item.parentId != -1) 1.1965 + txn.item.parentId = this.item.parentId; 1.1966 + if (this._isUndo) 1.1967 + txn.undoTransaction(); 1.1968 + else 1.1969 + txn.doTransaction(); 1.1970 + } 1.1971 + } 1.1972 +}; 1.1973 + 1.1974 + 1.1975 +/** 1.1976 + * Transaction for creating a new folder. 1.1977 + * 1.1978 + * @param aTitle 1.1979 + * the title for the new folder 1.1980 + * @param aParentId 1.1981 + * the id of the parent folder in which the new folder should be added 1.1982 + * @param [optional] aIndex 1.1983 + * the index of the item in aParentId 1.1984 + * @param [optional] aAnnotations 1.1985 + * array of annotations to set for the new folder 1.1986 + * @param [optional] aChildTransactions 1.1987 + * array of transactions for items to be created in the new folder 1.1988 + * 1.1989 + * @return nsITransaction object 1.1990 + */ 1.1991 +this.PlacesCreateFolderTransaction = 1.1992 + function PlacesCreateFolderTransaction(aTitle, aParentId, aIndex, aAnnotations, 1.1993 + aChildTransactions) 1.1994 +{ 1.1995 + this.item = new TransactionItemCache(); 1.1996 + this.item.title = aTitle; 1.1997 + this.item.parentId = aParentId; 1.1998 + this.item.index = aIndex; 1.1999 + this.item.annotations = aAnnotations; 1.2000 + this.childTransactions = aChildTransactions; 1.2001 +} 1.2002 + 1.2003 +PlacesCreateFolderTransaction.prototype = { 1.2004 + __proto__: BaseTransaction.prototype, 1.2005 + 1.2006 + doTransaction: function CFTXN_doTransaction() 1.2007 + { 1.2008 + this.item.id = PlacesUtils.bookmarks.createFolder(this.item.parentId, 1.2009 + this.item.title, 1.2010 + this.item.index); 1.2011 + if (this.item.annotations && this.item.annotations.length > 0) 1.2012 + PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations); 1.2013 + 1.2014 + if (this.childTransactions && this.childTransactions.length > 0) { 1.2015 + // Set the new parent id into child transactions. 1.2016 + for (let i = 0; i < this.childTransactions.length; ++i) { 1.2017 + this.childTransactions[i].item.parentId = this.item.id; 1.2018 + } 1.2019 + 1.2020 + let txn = new PlacesAggregatedTransaction("Create folder childTxn", 1.2021 + this.childTransactions); 1.2022 + txn.doTransaction(); 1.2023 + } 1.2024 + }, 1.2025 + 1.2026 + undoTransaction: function CFTXN_undoTransaction() 1.2027 + { 1.2028 + if (this.childTransactions && this.childTransactions.length > 0) { 1.2029 + let txn = new PlacesAggregatedTransaction("Create folder childTxn", 1.2030 + this.childTransactions); 1.2031 + txn.undoTransaction(); 1.2032 + } 1.2033 + 1.2034 + // Remove item only after all child transactions have been reverted. 1.2035 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2036 + } 1.2037 +}; 1.2038 + 1.2039 + 1.2040 +/** 1.2041 + * Transaction for creating a new bookmark. 1.2042 + * 1.2043 + * @param aURI 1.2044 + * the nsIURI of the new bookmark 1.2045 + * @param aParentId 1.2046 + * the id of the folder in which the bookmark should be added. 1.2047 + * @param [optional] aIndex 1.2048 + * the index of the item in aParentId 1.2049 + * @param [optional] aTitle 1.2050 + * the title of the new bookmark 1.2051 + * @param [optional] aKeyword 1.2052 + * the keyword for the new bookmark 1.2053 + * @param [optional] aAnnotations 1.2054 + * array of annotations to set for the new bookmark 1.2055 + * @param [optional] aChildTransactions 1.2056 + * child transactions to commit after creating the bookmark. Prefer 1.2057 + * using any of the arguments above if possible. In general, a child 1.2058 + * transations should be used only if the change it does has to be 1.2059 + * reverted manually when removing the bookmark item. 1.2060 + * a child transaction must support setting its bookmark-item 1.2061 + * identifier via an "id" js setter. 1.2062 + * 1.2063 + * @return nsITransaction object 1.2064 + */ 1.2065 +this.PlacesCreateBookmarkTransaction = 1.2066 + function PlacesCreateBookmarkTransaction(aURI, aParentId, aIndex, aTitle, 1.2067 + aKeyword, aAnnotations, 1.2068 + aChildTransactions) 1.2069 +{ 1.2070 + this.item = new TransactionItemCache(); 1.2071 + this.item.uri = aURI; 1.2072 + this.item.parentId = aParentId; 1.2073 + this.item.index = aIndex; 1.2074 + this.item.title = aTitle; 1.2075 + this.item.keyword = aKeyword; 1.2076 + this.item.annotations = aAnnotations; 1.2077 + this.childTransactions = aChildTransactions; 1.2078 +} 1.2079 + 1.2080 +PlacesCreateBookmarkTransaction.prototype = { 1.2081 + __proto__: BaseTransaction.prototype, 1.2082 + 1.2083 + doTransaction: function CITXN_doTransaction() 1.2084 + { 1.2085 + this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId, 1.2086 + this.item.uri, 1.2087 + this.item.index, 1.2088 + this.item.title); 1.2089 + if (this.item.keyword) { 1.2090 + PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, 1.2091 + this.item.keyword); 1.2092 + } 1.2093 + if (this.item.annotations && this.item.annotations.length > 0) 1.2094 + PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations); 1.2095 + 1.2096 + if (this.childTransactions && this.childTransactions.length > 0) { 1.2097 + // Set the new item id into child transactions. 1.2098 + for (let i = 0; i < this.childTransactions.length; ++i) { 1.2099 + this.childTransactions[i].item.id = this.item.id; 1.2100 + } 1.2101 + let txn = new PlacesAggregatedTransaction("Create item childTxn", 1.2102 + this.childTransactions); 1.2103 + txn.doTransaction(); 1.2104 + } 1.2105 + }, 1.2106 + 1.2107 + undoTransaction: function CITXN_undoTransaction() 1.2108 + { 1.2109 + if (this.childTransactions && this.childTransactions.length > 0) { 1.2110 + // Undo transactions should always be done in reverse order. 1.2111 + let txn = new PlacesAggregatedTransaction("Create item childTxn", 1.2112 + this.childTransactions); 1.2113 + txn.undoTransaction(); 1.2114 + } 1.2115 + 1.2116 + // Remove item only after all child transactions have been reverted. 1.2117 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2118 + } 1.2119 +}; 1.2120 + 1.2121 + 1.2122 +/** 1.2123 + * Transaction for creating a new separator. 1.2124 + * 1.2125 + * @param aParentId 1.2126 + * the id of the folder in which the separator should be added 1.2127 + * @param [optional] aIndex 1.2128 + * the index of the item in aParentId 1.2129 + * 1.2130 + * @return nsITransaction object 1.2131 + */ 1.2132 +this.PlacesCreateSeparatorTransaction = 1.2133 + function PlacesCreateSeparatorTransaction(aParentId, aIndex) 1.2134 +{ 1.2135 + this.item = new TransactionItemCache(); 1.2136 + this.item.parentId = aParentId; 1.2137 + this.item.index = aIndex; 1.2138 +} 1.2139 + 1.2140 +PlacesCreateSeparatorTransaction.prototype = { 1.2141 + __proto__: BaseTransaction.prototype, 1.2142 + 1.2143 + doTransaction: function CSTXN_doTransaction() 1.2144 + { 1.2145 + this.item.id = 1.2146 + PlacesUtils.bookmarks.insertSeparator(this.item.parentId, this.item.index); 1.2147 + }, 1.2148 + 1.2149 + undoTransaction: function CSTXN_undoTransaction() 1.2150 + { 1.2151 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2152 + } 1.2153 +}; 1.2154 + 1.2155 + 1.2156 +/** 1.2157 + * Transaction for creating a new livemark item. 1.2158 + * 1.2159 + * @see mozIAsyncLivemarks for documentation regarding the arguments. 1.2160 + * 1.2161 + * @param aFeedURI 1.2162 + * nsIURI of the feed 1.2163 + * @param [optional] aSiteURI 1.2164 + * nsIURI of the page serving the feed 1.2165 + * @param aTitle 1.2166 + * title for the livemark 1.2167 + * @param aParentId 1.2168 + * the id of the folder in which the livemark should be added 1.2169 + * @param [optional] aIndex 1.2170 + * the index of the livemark in aParentId 1.2171 + * @param [optional] aAnnotations 1.2172 + * array of annotations to set for the new livemark. 1.2173 + * 1.2174 + * @return nsITransaction object 1.2175 + */ 1.2176 +this.PlacesCreateLivemarkTransaction = 1.2177 + function PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aTitle, aParentId, 1.2178 + aIndex, aAnnotations) 1.2179 +{ 1.2180 + this.item = new TransactionItemCache(); 1.2181 + this.item.feedURI = aFeedURI; 1.2182 + this.item.siteURI = aSiteURI; 1.2183 + this.item.title = aTitle; 1.2184 + this.item.parentId = aParentId; 1.2185 + this.item.index = aIndex; 1.2186 + this.item.annotations = aAnnotations; 1.2187 +} 1.2188 + 1.2189 +PlacesCreateLivemarkTransaction.prototype = { 1.2190 + __proto__: BaseTransaction.prototype, 1.2191 + 1.2192 + doTransaction: function CLTXN_doTransaction() 1.2193 + { 1.2194 + PlacesUtils.livemarks.addLivemark( 1.2195 + { title: this.item.title 1.2196 + , feedURI: this.item.feedURI 1.2197 + , parentId: this.item.parentId 1.2198 + , index: this.item.index 1.2199 + , siteURI: this.item.siteURI 1.2200 + }).then(aLivemark => { 1.2201 + this.item.id = aLivemark.id; 1.2202 + if (this.item.annotations && this.item.annotations.length > 0) { 1.2203 + PlacesUtils.setAnnotationsForItem(this.item.id, 1.2204 + this.item.annotations); 1.2205 + } 1.2206 + }, Cu.reportError); 1.2207 + }, 1.2208 + 1.2209 + undoTransaction: function CLTXN_undoTransaction() 1.2210 + { 1.2211 + // The getLivemark callback may fail, but it is used just to serialize, 1.2212 + // so it doesn't matter. 1.2213 + PlacesUtils.livemarks.getLivemark({ id: this.item.id }) 1.2214 + .then(null, null).then( () => { 1.2215 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2216 + }); 1.2217 + } 1.2218 +}; 1.2219 + 1.2220 + 1.2221 +/** 1.2222 + * Transaction for removing a livemark item. 1.2223 + * 1.2224 + * @param aLivemarkId 1.2225 + * the identifier of the folder for the livemark. 1.2226 + * 1.2227 + * @return nsITransaction object 1.2228 + * @note used internally by PlacesRemoveItemTransaction, DO NOT EXPORT. 1.2229 + */ 1.2230 +function PlacesRemoveLivemarkTransaction(aLivemarkId) 1.2231 +{ 1.2232 + this.item = new TransactionItemCache(); 1.2233 + this.item.id = aLivemarkId; 1.2234 + this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id); 1.2235 + this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id); 1.2236 + 1.2237 + let annos = PlacesUtils.getAnnotationsForItem(this.item.id); 1.2238 + // Exclude livemark service annotations, those will be recreated automatically 1.2239 + let annosToExclude = [PlacesUtils.LMANNO_FEEDURI, 1.2240 + PlacesUtils.LMANNO_SITEURI]; 1.2241 + this.item.annotations = annos.filter(function(aValue, aIndex, aArray) { 1.2242 + return annosToExclude.indexOf(aValue.name) == -1; 1.2243 + }); 1.2244 + this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id); 1.2245 + this.item.lastModified = 1.2246 + PlacesUtils.bookmarks.getItemLastModified(this.item.id); 1.2247 +} 1.2248 + 1.2249 +PlacesRemoveLivemarkTransaction.prototype = { 1.2250 + __proto__: BaseTransaction.prototype, 1.2251 + 1.2252 + doTransaction: function RLTXN_doTransaction() 1.2253 + { 1.2254 + PlacesUtils.livemarks.getLivemark({ id: this.item.id }) 1.2255 + .then(aLivemark => { 1.2256 + this.item.feedURI = aLivemark.feedURI; 1.2257 + this.item.siteURI = aLivemark.siteURI; 1.2258 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2259 + }, Cu.reportError); 1.2260 + }, 1.2261 + 1.2262 + undoTransaction: function RLTXN_undoTransaction() 1.2263 + { 1.2264 + // Undo work must be serialized, otherwise won't be able to know the 1.2265 + // feedURI and siteURI of the livemark. 1.2266 + // The getLivemark callback is expected to receive a failure status but it 1.2267 + // is used just to serialize, so doesn't matter. 1.2268 + PlacesUtils.livemarks.getLivemark({ id: this.item.id }) 1.2269 + .then(null, () => { 1.2270 + PlacesUtils.livemarks.addLivemark({ parentId: this.item.parentId 1.2271 + , title: this.item.title 1.2272 + , siteURI: this.item.siteURI 1.2273 + , feedURI: this.item.feedURI 1.2274 + , index: this.item.index 1.2275 + , lastModified: this.item.lastModified 1.2276 + }).then( 1.2277 + aLivemark => { 1.2278 + let itemId = aLivemark.id; 1.2279 + PlacesUtils.bookmarks.setItemDateAdded(itemId, this.item.dateAdded); 1.2280 + PlacesUtils.setAnnotationsForItem(itemId, this.item.annotations); 1.2281 + }, Cu.reportError); 1.2282 + }); 1.2283 + } 1.2284 +}; 1.2285 + 1.2286 + 1.2287 +/** 1.2288 + * Transaction for moving an Item. 1.2289 + * 1.2290 + * @param aItemId 1.2291 + * the id of the item to move 1.2292 + * @param aNewParentId 1.2293 + * id of the new parent to move to 1.2294 + * @param aNewIndex 1.2295 + * index of the new position to move to 1.2296 + * 1.2297 + * @return nsITransaction object 1.2298 + */ 1.2299 +this.PlacesMoveItemTransaction = 1.2300 + function PlacesMoveItemTransaction(aItemId, aNewParentId, aNewIndex) 1.2301 +{ 1.2302 + this.item = new TransactionItemCache(); 1.2303 + this.item.id = aItemId; 1.2304 + this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id); 1.2305 + this.new = new TransactionItemCache(); 1.2306 + this.new.parentId = aNewParentId; 1.2307 + this.new.index = aNewIndex; 1.2308 +} 1.2309 + 1.2310 +PlacesMoveItemTransaction.prototype = { 1.2311 + __proto__: BaseTransaction.prototype, 1.2312 + 1.2313 + doTransaction: function MITXN_doTransaction() 1.2314 + { 1.2315 + this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id); 1.2316 + PlacesUtils.bookmarks.moveItem(this.item.id, 1.2317 + this.new.parentId, this.new.index); 1.2318 + this._undoIndex = PlacesUtils.bookmarks.getItemIndex(this.item.id); 1.2319 + }, 1.2320 + 1.2321 + undoTransaction: function MITXN_undoTransaction() 1.2322 + { 1.2323 + // moving down in the same parent takes in count removal of the item 1.2324 + // so to revert positions we must move to oldIndex + 1 1.2325 + if (this.new.parentId == this.item.parentId && 1.2326 + this.item.index > this._undoIndex) { 1.2327 + PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId, 1.2328 + this.item.index + 1); 1.2329 + } 1.2330 + else { 1.2331 + PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId, 1.2332 + this.item.index); 1.2333 + } 1.2334 + } 1.2335 +}; 1.2336 + 1.2337 + 1.2338 +/** 1.2339 + * Transaction for removing an Item 1.2340 + * 1.2341 + * @param aItemId 1.2342 + * id of the item to remove 1.2343 + * 1.2344 + * @return nsITransaction object 1.2345 + */ 1.2346 +this.PlacesRemoveItemTransaction = 1.2347 + function PlacesRemoveItemTransaction(aItemId) 1.2348 +{ 1.2349 + if (PlacesUtils.isRootItem(aItemId)) 1.2350 + throw Cr.NS_ERROR_INVALID_ARG; 1.2351 + 1.2352 + // if the item lives within a tag container, use the tagging transactions 1.2353 + let parent = PlacesUtils.bookmarks.getFolderIdForItem(aItemId); 1.2354 + let grandparent = PlacesUtils.bookmarks.getFolderIdForItem(parent); 1.2355 + if (grandparent == PlacesUtils.tagsFolderId) { 1.2356 + let uri = PlacesUtils.bookmarks.getBookmarkURI(aItemId); 1.2357 + return new PlacesUntagURITransaction(uri, [parent]); 1.2358 + } 1.2359 + 1.2360 + // if the item is a livemark container we will not save its children. 1.2361 + if (PlacesUtils.annotations.itemHasAnnotation(aItemId, 1.2362 + PlacesUtils.LMANNO_FEEDURI)) 1.2363 + return new PlacesRemoveLivemarkTransaction(aItemId); 1.2364 + 1.2365 + this.item = new TransactionItemCache(); 1.2366 + this.item.id = aItemId; 1.2367 + this.item.itemType = PlacesUtils.bookmarks.getItemType(this.item.id); 1.2368 + if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) { 1.2369 + this.childTransactions = this._getFolderContentsTransactions(); 1.2370 + // Remove this folder itself. 1.2371 + let txn = PlacesUtils.bookmarks.getRemoveFolderTransaction(this.item.id); 1.2372 + this.childTransactions.push(txn); 1.2373 + } 1.2374 + else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) { 1.2375 + this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id); 1.2376 + this.item.keyword = 1.2377 + PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id); 1.2378 + } 1.2379 + 1.2380 + if (this.item.itemType != Ci.nsINavBookmarksService.TYPE_SEPARATOR) 1.2381 + this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id); 1.2382 + 1.2383 + this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id); 1.2384 + this.item.annotations = PlacesUtils.getAnnotationsForItem(this.item.id); 1.2385 + this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id); 1.2386 + this.item.lastModified = 1.2387 + PlacesUtils.bookmarks.getItemLastModified(this.item.id); 1.2388 +} 1.2389 + 1.2390 +PlacesRemoveItemTransaction.prototype = { 1.2391 + __proto__: BaseTransaction.prototype, 1.2392 + 1.2393 + doTransaction: function RITXN_doTransaction() 1.2394 + { 1.2395 + this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id); 1.2396 + 1.2397 + if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) { 1.2398 + let txn = new PlacesAggregatedTransaction("Remove item childTxn", 1.2399 + this.childTransactions); 1.2400 + txn.doTransaction(); 1.2401 + } 1.2402 + else { 1.2403 + // Before removing the bookmark, save its tags. 1.2404 + let tags = this.item.uri ? 1.2405 + PlacesUtils.tagging.getTagsForURI(this.item.uri) : null; 1.2406 + 1.2407 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2408 + 1.2409 + // If this was the last bookmark (excluding tag-items) for this url, 1.2410 + // persist the tags. 1.2411 + if (tags && PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) { 1.2412 + this.item.tags = tags; 1.2413 + } 1.2414 + } 1.2415 + }, 1.2416 + 1.2417 + undoTransaction: function RITXN_undoTransaction() 1.2418 + { 1.2419 + if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) { 1.2420 + this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId, 1.2421 + this.item.uri, 1.2422 + this.item.index, 1.2423 + this.item.title); 1.2424 + if (this.item.tags && this.item.tags.length > 0) 1.2425 + PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags); 1.2426 + if (this.item.keyword) { 1.2427 + PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, 1.2428 + this.item.keyword); 1.2429 + } 1.2430 + } 1.2431 + else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) { 1.2432 + let txn = new PlacesAggregatedTransaction("Remove item childTxn", 1.2433 + this.childTransactions); 1.2434 + txn.undoTransaction(); 1.2435 + } 1.2436 + else { // TYPE_SEPARATOR 1.2437 + this.item.id = PlacesUtils.bookmarks.insertSeparator(this.item.parentId, 1.2438 + this.item.index); 1.2439 + } 1.2440 + 1.2441 + if (this.item.annotations && this.item.annotations.length > 0) 1.2442 + PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations); 1.2443 + 1.2444 + PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded); 1.2445 + PlacesUtils.bookmarks.setItemLastModified(this.item.id, 1.2446 + this.item.lastModified); 1.2447 + }, 1.2448 + 1.2449 + /** 1.2450 + * Returns a flat, ordered list of transactions for a depth-first recreation 1.2451 + * of items within this folder. 1.2452 + */ 1.2453 + _getFolderContentsTransactions: 1.2454 + function RITXN__getFolderContentsTransactions() 1.2455 + { 1.2456 + let transactions = []; 1.2457 + let contents = 1.2458 + PlacesUtils.getFolderContents(this.item.id, false, false).root; 1.2459 + for (let i = 0; i < contents.childCount; ++i) { 1.2460 + let txn = new PlacesRemoveItemTransaction(contents.getChild(i).itemId); 1.2461 + transactions.push(txn); 1.2462 + } 1.2463 + contents.containerOpen = false; 1.2464 + // Reverse transactions to preserve parent-child relationship. 1.2465 + return transactions.reverse(); 1.2466 + } 1.2467 +}; 1.2468 + 1.2469 + 1.2470 +/** 1.2471 + * Transaction for editting a bookmark's title. 1.2472 + * 1.2473 + * @param aItemId 1.2474 + * id of the item to edit 1.2475 + * @param aNewTitle 1.2476 + * new title for the item to edit 1.2477 + * 1.2478 + * @return nsITransaction object 1.2479 + */ 1.2480 +this.PlacesEditItemTitleTransaction = 1.2481 + function PlacesEditItemTitleTransaction(aItemId, aNewTitle) 1.2482 +{ 1.2483 + this.item = new TransactionItemCache(); 1.2484 + this.item.id = aItemId; 1.2485 + this.new = new TransactionItemCache(); 1.2486 + this.new.title = aNewTitle; 1.2487 +} 1.2488 + 1.2489 +PlacesEditItemTitleTransaction.prototype = { 1.2490 + __proto__: BaseTransaction.prototype, 1.2491 + 1.2492 + doTransaction: function EITTXN_doTransaction() 1.2493 + { 1.2494 + this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id); 1.2495 + PlacesUtils.bookmarks.setItemTitle(this.item.id, this.new.title); 1.2496 + }, 1.2497 + 1.2498 + undoTransaction: function EITTXN_undoTransaction() 1.2499 + { 1.2500 + PlacesUtils.bookmarks.setItemTitle(this.item.id, this.item.title); 1.2501 + } 1.2502 +}; 1.2503 + 1.2504 + 1.2505 +/** 1.2506 + * Transaction for editing a bookmark's uri. 1.2507 + * 1.2508 + * @param aItemId 1.2509 + * id of the bookmark to edit 1.2510 + * @param aNewURI 1.2511 + * new uri for the bookmark 1.2512 + * 1.2513 + * @return nsITransaction object 1.2514 + */ 1.2515 +this.PlacesEditBookmarkURITransaction = 1.2516 + function PlacesEditBookmarkURITransaction(aItemId, aNewURI) { 1.2517 + this.item = new TransactionItemCache(); 1.2518 + this.item.id = aItemId; 1.2519 + this.new = new TransactionItemCache(); 1.2520 + this.new.uri = aNewURI; 1.2521 +} 1.2522 + 1.2523 +PlacesEditBookmarkURITransaction.prototype = { 1.2524 + __proto__: BaseTransaction.prototype, 1.2525 + 1.2526 + doTransaction: function EBUTXN_doTransaction() 1.2527 + { 1.2528 + this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id); 1.2529 + PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.new.uri); 1.2530 + // move tags from old URI to new URI 1.2531 + this.item.tags = PlacesUtils.tagging.getTagsForURI(this.item.uri); 1.2532 + if (this.item.tags.length != 0) { 1.2533 + // only untag the old URI if this is the only bookmark 1.2534 + if (PlacesUtils.getBookmarksForURI(this.item.uri, {}).length == 0) 1.2535 + PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags); 1.2536 + PlacesUtils.tagging.tagURI(this.new.uri, this.item.tags); 1.2537 + } 1.2538 + }, 1.2539 + 1.2540 + undoTransaction: function EBUTXN_undoTransaction() 1.2541 + { 1.2542 + PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.item.uri); 1.2543 + // move tags from new URI to old URI 1.2544 + if (this.item.tags.length != 0) { 1.2545 + // only untag the new URI if this is the only bookmark 1.2546 + if (PlacesUtils.getBookmarksForURI(this.new.uri, {}).length == 0) 1.2547 + PlacesUtils.tagging.untagURI(this.new.uri, this.item.tags); 1.2548 + PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags); 1.2549 + } 1.2550 + } 1.2551 +}; 1.2552 + 1.2553 + 1.2554 +/** 1.2555 + * Transaction for setting/unsetting an item annotation 1.2556 + * 1.2557 + * @param aItemId 1.2558 + * id of the item where to set annotation 1.2559 + * @param aAnnotationObject 1.2560 + * Object representing an annotation, containing the following 1.2561 + * properties: name, flags, expires, value. 1.2562 + * If value is null the annotation will be removed 1.2563 + * 1.2564 + * @return nsITransaction object 1.2565 + */ 1.2566 +this.PlacesSetItemAnnotationTransaction = 1.2567 + function PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject) 1.2568 +{ 1.2569 + this.item = new TransactionItemCache(); 1.2570 + this.item.id = aItemId; 1.2571 + this.new = new TransactionItemCache(); 1.2572 + this.new.annotations = [aAnnotationObject]; 1.2573 +} 1.2574 + 1.2575 +PlacesSetItemAnnotationTransaction.prototype = { 1.2576 + __proto__: BaseTransaction.prototype, 1.2577 + 1.2578 + doTransaction: function SIATXN_doTransaction() 1.2579 + { 1.2580 + let annoName = this.new.annotations[0].name; 1.2581 + if (PlacesUtils.annotations.itemHasAnnotation(this.item.id, annoName)) { 1.2582 + // fill the old anno if it is set 1.2583 + let flags = {}, expires = {}, type = {}; 1.2584 + PlacesUtils.annotations.getItemAnnotationInfo(this.item.id, annoName, flags, 1.2585 + expires, type); 1.2586 + let value = PlacesUtils.annotations.getItemAnnotation(this.item.id, 1.2587 + annoName); 1.2588 + this.item.annotations = [{ name: annoName, 1.2589 + type: type.value, 1.2590 + flags: flags.value, 1.2591 + value: value, 1.2592 + expires: expires.value }]; 1.2593 + } 1.2594 + else { 1.2595 + // create an empty old anno 1.2596 + this.item.annotations = [{ name: annoName, 1.2597 + flags: 0, 1.2598 + value: null, 1.2599 + expires: Ci.nsIAnnotationService.EXPIRE_NEVER }]; 1.2600 + } 1.2601 + 1.2602 + PlacesUtils.setAnnotationsForItem(this.item.id, this.new.annotations); 1.2603 + }, 1.2604 + 1.2605 + undoTransaction: function SIATXN_undoTransaction() 1.2606 + { 1.2607 + PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations); 1.2608 + } 1.2609 +}; 1.2610 + 1.2611 + 1.2612 +/** 1.2613 + * Transaction for setting/unsetting a page annotation 1.2614 + * 1.2615 + * @param aURI 1.2616 + * URI of the page where to set annotation 1.2617 + * @param aAnnotationObject 1.2618 + * Object representing an annotation, containing the following 1.2619 + * properties: name, flags, expires, value. 1.2620 + * If value is null the annotation will be removed 1.2621 + * 1.2622 + * @return nsITransaction object 1.2623 + */ 1.2624 +this.PlacesSetPageAnnotationTransaction = 1.2625 + function PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject) 1.2626 +{ 1.2627 + this.item = new TransactionItemCache(); 1.2628 + this.item.uri = aURI; 1.2629 + this.new = new TransactionItemCache(); 1.2630 + this.new.annotations = [aAnnotationObject]; 1.2631 +} 1.2632 + 1.2633 +PlacesSetPageAnnotationTransaction.prototype = { 1.2634 + __proto__: BaseTransaction.prototype, 1.2635 + 1.2636 + doTransaction: function SPATXN_doTransaction() 1.2637 + { 1.2638 + let annoName = this.new.annotations[0].name; 1.2639 + if (PlacesUtils.annotations.pageHasAnnotation(this.item.uri, annoName)) { 1.2640 + // fill the old anno if it is set 1.2641 + let flags = {}, expires = {}, type = {}; 1.2642 + PlacesUtils.annotations.getPageAnnotationInfo(this.item.uri, annoName, flags, 1.2643 + expires, type); 1.2644 + let value = PlacesUtils.annotations.getPageAnnotation(this.item.uri, 1.2645 + annoName); 1.2646 + this.item.annotations = [{ name: annoName, 1.2647 + flags: flags.value, 1.2648 + value: value, 1.2649 + expires: expires.value }]; 1.2650 + } 1.2651 + else { 1.2652 + // create an empty old anno 1.2653 + this.item.annotations = [{ name: annoName, 1.2654 + type: Ci.nsIAnnotationService.TYPE_STRING, 1.2655 + flags: 0, 1.2656 + value: null, 1.2657 + expires: Ci.nsIAnnotationService.EXPIRE_NEVER }]; 1.2658 + } 1.2659 + 1.2660 + PlacesUtils.setAnnotationsForURI(this.item.uri, this.new.annotations); 1.2661 + }, 1.2662 + 1.2663 + undoTransaction: function SPATXN_undoTransaction() 1.2664 + { 1.2665 + PlacesUtils.setAnnotationsForURI(this.item.uri, this.item.annotations); 1.2666 + } 1.2667 +}; 1.2668 + 1.2669 + 1.2670 +/** 1.2671 + * Transaction for editing a bookmark's keyword. 1.2672 + * 1.2673 + * @param aItemId 1.2674 + * id of the bookmark to edit 1.2675 + * @param aNewKeyword 1.2676 + * new keyword for the bookmark 1.2677 + * 1.2678 + * @return nsITransaction object 1.2679 + */ 1.2680 +this.PlacesEditBookmarkKeywordTransaction = 1.2681 + function PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword) 1.2682 +{ 1.2683 + this.item = new TransactionItemCache(); 1.2684 + this.item.id = aItemId; 1.2685 + this.new = new TransactionItemCache(); 1.2686 + this.new.keyword = aNewKeyword; 1.2687 +} 1.2688 + 1.2689 +PlacesEditBookmarkKeywordTransaction.prototype = { 1.2690 + __proto__: BaseTransaction.prototype, 1.2691 + 1.2692 + doTransaction: function EBKTXN_doTransaction() 1.2693 + { 1.2694 + this.item.keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id); 1.2695 + PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.new.keyword); 1.2696 + }, 1.2697 + 1.2698 + undoTransaction: function EBKTXN_undoTransaction() 1.2699 + { 1.2700 + PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.item.keyword); 1.2701 + } 1.2702 +}; 1.2703 + 1.2704 + 1.2705 +/** 1.2706 + * Transaction for editing the post data associated with a bookmark. 1.2707 + * 1.2708 + * @param aItemId 1.2709 + * id of the bookmark to edit 1.2710 + * @param aPostData 1.2711 + * post data 1.2712 + * 1.2713 + * @return nsITransaction object 1.2714 + */ 1.2715 +this.PlacesEditBookmarkPostDataTransaction = 1.2716 + function PlacesEditBookmarkPostDataTransaction(aItemId, aPostData) 1.2717 +{ 1.2718 + this.item = new TransactionItemCache(); 1.2719 + this.item.id = aItemId; 1.2720 + this.new = new TransactionItemCache(); 1.2721 + this.new.postData = aPostData; 1.2722 +} 1.2723 + 1.2724 +PlacesEditBookmarkPostDataTransaction.prototype = { 1.2725 + __proto__: BaseTransaction.prototype, 1.2726 + 1.2727 + doTransaction: function EBPDTXN_doTransaction() 1.2728 + { 1.2729 + this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id); 1.2730 + PlacesUtils.setPostDataForBookmark(this.item.id, this.new.postData); 1.2731 + }, 1.2732 + 1.2733 + undoTransaction: function EBPDTXN_undoTransaction() 1.2734 + { 1.2735 + PlacesUtils.setPostDataForBookmark(this.item.id, this.item.postData); 1.2736 + } 1.2737 +}; 1.2738 + 1.2739 + 1.2740 +/** 1.2741 + * Transaction for editing an item's date added property. 1.2742 + * 1.2743 + * @param aItemId 1.2744 + * id of the item to edit 1.2745 + * @param aNewDateAdded 1.2746 + * new date added for the item 1.2747 + * 1.2748 + * @return nsITransaction object 1.2749 + */ 1.2750 +this.PlacesEditItemDateAddedTransaction = 1.2751 + function PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded) 1.2752 +{ 1.2753 + this.item = new TransactionItemCache(); 1.2754 + this.item.id = aItemId; 1.2755 + this.new = new TransactionItemCache(); 1.2756 + this.new.dateAdded = aNewDateAdded; 1.2757 +} 1.2758 + 1.2759 +PlacesEditItemDateAddedTransaction.prototype = { 1.2760 + __proto__: BaseTransaction.prototype, 1.2761 + 1.2762 + doTransaction: function EIDATXN_doTransaction() 1.2763 + { 1.2764 + // Child transactions have the id set as parentId. 1.2765 + if (this.item.id == -1 && this.item.parentId != -1) 1.2766 + this.item.id = this.item.parentId; 1.2767 + this.item.dateAdded = 1.2768 + PlacesUtils.bookmarks.getItemDateAdded(this.item.id); 1.2769 + PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.new.dateAdded); 1.2770 + }, 1.2771 + 1.2772 + undoTransaction: function EIDATXN_undoTransaction() 1.2773 + { 1.2774 + PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded); 1.2775 + } 1.2776 +}; 1.2777 + 1.2778 + 1.2779 +/** 1.2780 + * Transaction for editing an item's last modified time. 1.2781 + * 1.2782 + * @param aItemId 1.2783 + * id of the item to edit 1.2784 + * @param aNewLastModified 1.2785 + * new last modified date for the item 1.2786 + * 1.2787 + * @return nsITransaction object 1.2788 + */ 1.2789 +this.PlacesEditItemLastModifiedTransaction = 1.2790 + function PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified) 1.2791 +{ 1.2792 + this.item = new TransactionItemCache(); 1.2793 + this.item.id = aItemId; 1.2794 + this.new = new TransactionItemCache(); 1.2795 + this.new.lastModified = aNewLastModified; 1.2796 +} 1.2797 + 1.2798 +PlacesEditItemLastModifiedTransaction.prototype = { 1.2799 + __proto__: BaseTransaction.prototype, 1.2800 + 1.2801 + doTransaction: 1.2802 + function EILMTXN_doTransaction() 1.2803 + { 1.2804 + // Child transactions have the id set as parentId. 1.2805 + if (this.item.id == -1 && this.item.parentId != -1) 1.2806 + this.item.id = this.item.parentId; 1.2807 + this.item.lastModified = 1.2808 + PlacesUtils.bookmarks.getItemLastModified(this.item.id); 1.2809 + PlacesUtils.bookmarks.setItemLastModified(this.item.id, 1.2810 + this.new.lastModified); 1.2811 + }, 1.2812 + 1.2813 + undoTransaction: 1.2814 + function EILMTXN_undoTransaction() 1.2815 + { 1.2816 + PlacesUtils.bookmarks.setItemLastModified(this.item.id, 1.2817 + this.item.lastModified); 1.2818 + } 1.2819 +}; 1.2820 + 1.2821 + 1.2822 +/** 1.2823 + * Transaction for sorting a folder by name 1.2824 + * 1.2825 + * @param aFolderId 1.2826 + * id of the folder to sort 1.2827 + * 1.2828 + * @return nsITransaction object 1.2829 + */ 1.2830 +this.PlacesSortFolderByNameTransaction = 1.2831 + function PlacesSortFolderByNameTransaction(aFolderId) 1.2832 +{ 1.2833 + this.item = new TransactionItemCache(); 1.2834 + this.item.id = aFolderId; 1.2835 +} 1.2836 + 1.2837 +PlacesSortFolderByNameTransaction.prototype = { 1.2838 + __proto__: BaseTransaction.prototype, 1.2839 + 1.2840 + doTransaction: function SFBNTXN_doTransaction() 1.2841 + { 1.2842 + this._oldOrder = []; 1.2843 + 1.2844 + let contents = 1.2845 + PlacesUtils.getFolderContents(this.item.id, false, false).root; 1.2846 + let count = contents.childCount; 1.2847 + 1.2848 + // sort between separators 1.2849 + let newOrder = []; 1.2850 + let preSep = []; // temporary array for sorting each group of items 1.2851 + let sortingMethod = 1.2852 + function (a, b) { 1.2853 + if (PlacesUtils.nodeIsContainer(a) && !PlacesUtils.nodeIsContainer(b)) 1.2854 + return -1; 1.2855 + if (!PlacesUtils.nodeIsContainer(a) && PlacesUtils.nodeIsContainer(b)) 1.2856 + return 1; 1.2857 + return a.title.localeCompare(b.title); 1.2858 + }; 1.2859 + 1.2860 + for (let i = 0; i < count; ++i) { 1.2861 + let item = contents.getChild(i); 1.2862 + this._oldOrder[item.itemId] = i; 1.2863 + if (PlacesUtils.nodeIsSeparator(item)) { 1.2864 + if (preSep.length > 0) { 1.2865 + preSep.sort(sortingMethod); 1.2866 + newOrder = newOrder.concat(preSep); 1.2867 + preSep.splice(0, preSep.length); 1.2868 + } 1.2869 + newOrder.push(item); 1.2870 + } 1.2871 + else 1.2872 + preSep.push(item); 1.2873 + } 1.2874 + contents.containerOpen = false; 1.2875 + 1.2876 + if (preSep.length > 0) { 1.2877 + preSep.sort(sortingMethod); 1.2878 + newOrder = newOrder.concat(preSep); 1.2879 + } 1.2880 + 1.2881 + // set the nex indexes 1.2882 + let callback = { 1.2883 + runBatched: function() { 1.2884 + for (let i = 0; i < newOrder.length; ++i) { 1.2885 + PlacesUtils.bookmarks.setItemIndex(newOrder[i].itemId, i); 1.2886 + } 1.2887 + } 1.2888 + }; 1.2889 + PlacesUtils.bookmarks.runInBatchMode(callback, null); 1.2890 + }, 1.2891 + 1.2892 + undoTransaction: function SFBNTXN_undoTransaction() 1.2893 + { 1.2894 + let callback = { 1.2895 + _self: this, 1.2896 + runBatched: function() { 1.2897 + for (item in this._self._oldOrder) 1.2898 + PlacesUtils.bookmarks.setItemIndex(item, this._self._oldOrder[item]); 1.2899 + } 1.2900 + }; 1.2901 + PlacesUtils.bookmarks.runInBatchMode(callback, null); 1.2902 + } 1.2903 +}; 1.2904 + 1.2905 + 1.2906 +/** 1.2907 + * Transaction for tagging a URL with the given set of tags. Current tags set 1.2908 + * for the URL persist. It's the caller's job to check whether or not aURI 1.2909 + * was already tagged by any of the tags in aTags, undoing this tags 1.2910 + * transaction removes them all from aURL! 1.2911 + * 1.2912 + * @param aURI 1.2913 + * the URL to tag. 1.2914 + * @param aTags 1.2915 + * Array of tags to set for the given URL. 1.2916 + */ 1.2917 +this.PlacesTagURITransaction = 1.2918 + function PlacesTagURITransaction(aURI, aTags) 1.2919 +{ 1.2920 + this.item = new TransactionItemCache(); 1.2921 + this.item.uri = aURI; 1.2922 + this.item.tags = aTags; 1.2923 +} 1.2924 + 1.2925 +PlacesTagURITransaction.prototype = { 1.2926 + __proto__: BaseTransaction.prototype, 1.2927 + 1.2928 + doTransaction: function TUTXN_doTransaction() 1.2929 + { 1.2930 + if (PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) { 1.2931 + // There is no bookmark for this uri, but we only allow to tag bookmarks. 1.2932 + // Force an unfiled bookmark first. 1.2933 + this.item.id = 1.2934 + PlacesUtils.bookmarks 1.2935 + .insertBookmark(PlacesUtils.unfiledBookmarksFolderId, 1.2936 + this.item.uri, 1.2937 + PlacesUtils.bookmarks.DEFAULT_INDEX, 1.2938 + PlacesUtils.history.getPageTitle(this.item.uri)); 1.2939 + } 1.2940 + PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags); 1.2941 + }, 1.2942 + 1.2943 + undoTransaction: function TUTXN_undoTransaction() 1.2944 + { 1.2945 + if (this.item.id != -1) { 1.2946 + PlacesUtils.bookmarks.removeItem(this.item.id); 1.2947 + this.item.id = -1; 1.2948 + } 1.2949 + PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags); 1.2950 + } 1.2951 +}; 1.2952 + 1.2953 + 1.2954 +/** 1.2955 + * Transaction for removing tags from a URL. It's the caller's job to check 1.2956 + * whether or not aURI isn't tagged by any of the tags in aTags, undoing this 1.2957 + * tags transaction adds them all to aURL! 1.2958 + * 1.2959 + * @param aURI 1.2960 + * the URL to un-tag. 1.2961 + * @param aTags 1.2962 + * Array of tags to unset. pass null to remove all tags from the given 1.2963 + * url. 1.2964 + */ 1.2965 +this.PlacesUntagURITransaction = 1.2966 + function PlacesUntagURITransaction(aURI, aTags) 1.2967 +{ 1.2968 + this.item = new TransactionItemCache(); 1.2969 + this.item.uri = aURI; 1.2970 + if (aTags) { 1.2971 + // Within this transaction, we cannot rely on tags given by itemId 1.2972 + // since the tag containers may be gone after we call untagURI. 1.2973 + // Thus, we convert each tag given by its itemId to name. 1.2974 + let tags = []; 1.2975 + for (let i = 0; i < aTags.length; ++i) { 1.2976 + if (typeof(aTags[i]) == "number") 1.2977 + tags.push(PlacesUtils.bookmarks.getItemTitle(aTags[i])); 1.2978 + else 1.2979 + tags.push(aTags[i]); 1.2980 + } 1.2981 + this.item.tags = tags; 1.2982 + } 1.2983 +} 1.2984 + 1.2985 +PlacesUntagURITransaction.prototype = { 1.2986 + __proto__: BaseTransaction.prototype, 1.2987 + 1.2988 + doTransaction: function UTUTXN_doTransaction() 1.2989 + { 1.2990 + // Filter tags existing on the bookmark, otherwise on undo we may try to 1.2991 + // set nonexistent tags. 1.2992 + let tags = PlacesUtils.tagging.getTagsForURI(this.item.uri); 1.2993 + this.item.tags = this.item.tags.filter(function (aTag) { 1.2994 + return tags.indexOf(aTag) != -1; 1.2995 + }); 1.2996 + PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags); 1.2997 + }, 1.2998 + 1.2999 + undoTransaction: function UTUTXN_undoTransaction() 1.3000 + { 1.3001 + PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags); 1.3002 + } 1.3003 +};