toolkit/components/places/PlacesUtils.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 this.EXPORTED_SYMBOLS = [
michael@0 7 "PlacesUtils"
michael@0 8 , "PlacesAggregatedTransaction"
michael@0 9 , "PlacesCreateFolderTransaction"
michael@0 10 , "PlacesCreateBookmarkTransaction"
michael@0 11 , "PlacesCreateSeparatorTransaction"
michael@0 12 , "PlacesCreateLivemarkTransaction"
michael@0 13 , "PlacesMoveItemTransaction"
michael@0 14 , "PlacesRemoveItemTransaction"
michael@0 15 , "PlacesEditItemTitleTransaction"
michael@0 16 , "PlacesEditBookmarkURITransaction"
michael@0 17 , "PlacesSetItemAnnotationTransaction"
michael@0 18 , "PlacesSetPageAnnotationTransaction"
michael@0 19 , "PlacesEditBookmarkKeywordTransaction"
michael@0 20 , "PlacesEditBookmarkPostDataTransaction"
michael@0 21 , "PlacesEditItemDateAddedTransaction"
michael@0 22 , "PlacesEditItemLastModifiedTransaction"
michael@0 23 , "PlacesSortFolderByNameTransaction"
michael@0 24 , "PlacesTagURITransaction"
michael@0 25 , "PlacesUntagURITransaction"
michael@0 26 ];
michael@0 27
michael@0 28 const Ci = Components.interfaces;
michael@0 29 const Cc = Components.classes;
michael@0 30 const Cr = Components.results;
michael@0 31 const Cu = Components.utils;
michael@0 32
michael@0 33 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 34
michael@0 35 XPCOMUtils.defineLazyModuleGetter(this, "Services",
michael@0 36 "resource://gre/modules/Services.jsm");
michael@0 37
michael@0 38 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
michael@0 39 "resource://gre/modules/NetUtil.jsm");
michael@0 40
michael@0 41 XPCOMUtils.defineLazyModuleGetter(this, "Task",
michael@0 42 "resource://gre/modules/Task.jsm");
michael@0 43
michael@0 44 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
michael@0 45 "resource://gre/modules/Promise.jsm");
michael@0 46
michael@0 47 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
michael@0 48 "resource://gre/modules/Deprecated.jsm");
michael@0 49
michael@0 50 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
michael@0 51 "resource://gre/modules/BookmarkJSONUtils.jsm");
michael@0 52
michael@0 53 XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
michael@0 54 "resource://gre/modules/PlacesBackups.jsm");
michael@0 55
michael@0 56 // The minimum amount of transactions before starting a batch. Usually we do
michael@0 57 // do incremental updates, a batch will cause views to completely
michael@0 58 // refresh instead.
michael@0 59 const MIN_TRANSACTIONS_FOR_BATCH = 5;
michael@0 60
michael@0 61 #ifdef XP_MACOSX
michael@0 62 // On Mac OSX, the transferable system converts "\r\n" to "\n\n", where we
michael@0 63 // really just want "\n".
michael@0 64 const NEWLINE= "\n";
michael@0 65 #else
michael@0 66 // On other platforms, the transferable system converts "\r\n" to "\n".
michael@0 67 const NEWLINE = "\r\n";
michael@0 68 #endif
michael@0 69
michael@0 70 function QI_node(aNode, aIID) {
michael@0 71 var result = null;
michael@0 72 try {
michael@0 73 result = aNode.QueryInterface(aIID);
michael@0 74 }
michael@0 75 catch (e) {
michael@0 76 }
michael@0 77 return result;
michael@0 78 }
michael@0 79 function asContainer(aNode) QI_node(aNode, Ci.nsINavHistoryContainerResultNode);
michael@0 80 function asQuery(aNode) QI_node(aNode, Ci.nsINavHistoryQueryResultNode);
michael@0 81
michael@0 82 this.PlacesUtils = {
michael@0 83 // Place entries that are containers, e.g. bookmark folders or queries.
michael@0 84 TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container",
michael@0 85 // Place entries that are bookmark separators.
michael@0 86 TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator",
michael@0 87 // Place entries that are not containers or separators
michael@0 88 TYPE_X_MOZ_PLACE: "text/x-moz-place",
michael@0 89 // Place entries in shortcut url format (url\ntitle)
michael@0 90 TYPE_X_MOZ_URL: "text/x-moz-url",
michael@0 91 // Place entries formatted as HTML anchors
michael@0 92 TYPE_HTML: "text/html",
michael@0 93 // Place entries as raw URL text
michael@0 94 TYPE_UNICODE: "text/unicode",
michael@0 95 // Used to track the action that populated the clipboard.
michael@0 96 TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action",
michael@0 97
michael@0 98 EXCLUDE_FROM_BACKUP_ANNO: "places/excludeFromBackup",
michael@0 99 LMANNO_FEEDURI: "livemark/feedURI",
michael@0 100 LMANNO_SITEURI: "livemark/siteURI",
michael@0 101 POST_DATA_ANNO: "bookmarkProperties/POSTData",
michael@0 102 READ_ONLY_ANNO: "placesInternal/READ_ONLY",
michael@0 103 CHARSET_ANNO: "URIProperties/characterSet",
michael@0 104
michael@0 105 TOPIC_SHUTDOWN: "places-shutdown",
michael@0 106 TOPIC_INIT_COMPLETE: "places-init-complete",
michael@0 107 TOPIC_DATABASE_LOCKED: "places-database-locked",
michael@0 108 TOPIC_EXPIRATION_FINISHED: "places-expiration-finished",
michael@0 109 TOPIC_FEEDBACK_UPDATED: "places-autocomplete-feedback-updated",
michael@0 110 TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
michael@0 111 TOPIC_VACUUM_STARTING: "places-vacuum-starting",
michael@0 112 TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin",
michael@0 113 TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success",
michael@0 114 TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed",
michael@0 115
michael@0 116 asContainer: function(aNode) asContainer(aNode),
michael@0 117 asQuery: function(aNode) asQuery(aNode),
michael@0 118
michael@0 119 endl: NEWLINE,
michael@0 120
michael@0 121 /**
michael@0 122 * Makes a URI from a spec.
michael@0 123 * @param aSpec
michael@0 124 * The string spec of the URI
michael@0 125 * @returns A URI object for the spec.
michael@0 126 */
michael@0 127 _uri: function PU__uri(aSpec) {
michael@0 128 return NetUtil.newURI(aSpec);
michael@0 129 },
michael@0 130
michael@0 131 /**
michael@0 132 * Wraps a string in a nsISupportsString wrapper.
michael@0 133 * @param aString
michael@0 134 * The string to wrap.
michael@0 135 * @returns A nsISupportsString object containing a string.
michael@0 136 */
michael@0 137 toISupportsString: function PU_toISupportsString(aString) {
michael@0 138 let s = Cc["@mozilla.org/supports-string;1"].
michael@0 139 createInstance(Ci.nsISupportsString);
michael@0 140 s.data = aString;
michael@0 141 return s;
michael@0 142 },
michael@0 143
michael@0 144 getFormattedString: function PU_getFormattedString(key, params) {
michael@0 145 return bundle.formatStringFromName(key, params, params.length);
michael@0 146 },
michael@0 147
michael@0 148 getString: function PU_getString(key) {
michael@0 149 return bundle.GetStringFromName(key);
michael@0 150 },
michael@0 151
michael@0 152 /**
michael@0 153 * Determines whether or not a ResultNode is a Bookmark folder.
michael@0 154 * @param aNode
michael@0 155 * A result node
michael@0 156 * @returns true if the node is a Bookmark folder, false otherwise
michael@0 157 */
michael@0 158 nodeIsFolder: function PU_nodeIsFolder(aNode) {
michael@0 159 return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
michael@0 160 aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT);
michael@0 161 },
michael@0 162
michael@0 163 /**
michael@0 164 * Determines whether or not a ResultNode represents a bookmarked URI.
michael@0 165 * @param aNode
michael@0 166 * A result node
michael@0 167 * @returns true if the node represents a bookmarked URI, false otherwise
michael@0 168 */
michael@0 169 nodeIsBookmark: function PU_nodeIsBookmark(aNode) {
michael@0 170 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI &&
michael@0 171 aNode.itemId != -1;
michael@0 172 },
michael@0 173
michael@0 174 /**
michael@0 175 * Determines whether or not a ResultNode is a Bookmark separator.
michael@0 176 * @param aNode
michael@0 177 * A result node
michael@0 178 * @returns true if the node is a Bookmark separator, false otherwise
michael@0 179 */
michael@0 180 nodeIsSeparator: function PU_nodeIsSeparator(aNode) {
michael@0 181 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR;
michael@0 182 },
michael@0 183
michael@0 184 /**
michael@0 185 * Determines whether or not a ResultNode is a URL item.
michael@0 186 * @param aNode
michael@0 187 * A result node
michael@0 188 * @returns true if the node is a URL item, false otherwise
michael@0 189 */
michael@0 190 nodeIsURI: function PU_nodeIsURI(aNode) {
michael@0 191 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
michael@0 192 },
michael@0 193
michael@0 194 /**
michael@0 195 * Determines whether or not a ResultNode is a Query item.
michael@0 196 * @param aNode
michael@0 197 * A result node
michael@0 198 * @returns true if the node is a Query item, false otherwise
michael@0 199 */
michael@0 200 nodeIsQuery: function PU_nodeIsQuery(aNode) {
michael@0 201 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
michael@0 202 },
michael@0 203
michael@0 204 /**
michael@0 205 * Generator for a node's ancestors.
michael@0 206 * @param aNode
michael@0 207 * A result node
michael@0 208 */
michael@0 209 nodeAncestors: function PU_nodeAncestors(aNode) {
michael@0 210 let node = aNode.parent;
michael@0 211 while (node) {
michael@0 212 yield node;
michael@0 213 node = node.parent;
michael@0 214 }
michael@0 215 },
michael@0 216
michael@0 217 /**
michael@0 218 * Cache array of read-only item IDs.
michael@0 219 *
michael@0 220 * The first time this property is called:
michael@0 221 * - the cache is filled with all ids with the RO annotation
michael@0 222 * - an annotation observer is added
michael@0 223 * - a shutdown observer is added
michael@0 224 *
michael@0 225 * When the annotation observer detects annotations added or
michael@0 226 * removed that are the RO annotation name, it adds/removes
michael@0 227 * the ids from the cache.
michael@0 228 *
michael@0 229 * At shutdown, the annotation and shutdown observers are removed.
michael@0 230 */
michael@0 231 get _readOnly() {
michael@0 232 // Add annotations observer.
michael@0 233 this.annotations.addObserver(this, false);
michael@0 234 this.registerShutdownFunction(function () {
michael@0 235 this.annotations.removeObserver(this);
michael@0 236 });
michael@0 237
michael@0 238 var readOnly = this.annotations.getItemsWithAnnotation(this.READ_ONLY_ANNO);
michael@0 239 this.__defineGetter__("_readOnly", function() readOnly);
michael@0 240 return this._readOnly;
michael@0 241 },
michael@0 242
michael@0 243 QueryInterface: XPCOMUtils.generateQI([
michael@0 244 Ci.nsIAnnotationObserver
michael@0 245 , Ci.nsIObserver
michael@0 246 , Ci.nsITransactionListener
michael@0 247 ]),
michael@0 248
michael@0 249 _shutdownFunctions: [],
michael@0 250 registerShutdownFunction: function PU_registerShutdownFunction(aFunc)
michael@0 251 {
michael@0 252 // If this is the first registered function, add the shutdown observer.
michael@0 253 if (this._shutdownFunctions.length == 0) {
michael@0 254 Services.obs.addObserver(this, this.TOPIC_SHUTDOWN, false);
michael@0 255 }
michael@0 256 this._shutdownFunctions.push(aFunc);
michael@0 257 },
michael@0 258
michael@0 259 //////////////////////////////////////////////////////////////////////////////
michael@0 260 //// nsIObserver
michael@0 261 observe: function PU_observe(aSubject, aTopic, aData)
michael@0 262 {
michael@0 263 switch (aTopic) {
michael@0 264 case this.TOPIC_SHUTDOWN:
michael@0 265 Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN);
michael@0 266 while (this._shutdownFunctions.length > 0) {
michael@0 267 this._shutdownFunctions.shift().apply(this);
michael@0 268 }
michael@0 269 if (this._bookmarksServiceObserversQueue.length > 0) {
michael@0 270 // Since we are shutting down, there's no reason to add the observers.
michael@0 271 this._bookmarksServiceObserversQueue.length = 0;
michael@0 272 }
michael@0 273 break;
michael@0 274 case "bookmarks-service-ready":
michael@0 275 this._bookmarksServiceReady = true;
michael@0 276 while (this._bookmarksServiceObserversQueue.length > 0) {
michael@0 277 let observerInfo = this._bookmarksServiceObserversQueue.shift();
michael@0 278 this.bookmarks.addObserver(observerInfo.observer, observerInfo.weak);
michael@0 279 }
michael@0 280 break;
michael@0 281 }
michael@0 282 },
michael@0 283
michael@0 284 //////////////////////////////////////////////////////////////////////////////
michael@0 285 //// nsIAnnotationObserver
michael@0 286
michael@0 287 onItemAnnotationSet: function PU_onItemAnnotationSet(aItemId, aAnnotationName)
michael@0 288 {
michael@0 289 if (aAnnotationName == this.READ_ONLY_ANNO &&
michael@0 290 this._readOnly.indexOf(aItemId) == -1)
michael@0 291 this._readOnly.push(aItemId);
michael@0 292 },
michael@0 293
michael@0 294 onItemAnnotationRemoved:
michael@0 295 function PU_onItemAnnotationRemoved(aItemId, aAnnotationName)
michael@0 296 {
michael@0 297 var index = this._readOnly.indexOf(aItemId);
michael@0 298 if (aAnnotationName == this.READ_ONLY_ANNO && index > -1)
michael@0 299 delete this._readOnly[index];
michael@0 300 },
michael@0 301
michael@0 302 onPageAnnotationSet: function() {},
michael@0 303 onPageAnnotationRemoved: function() {},
michael@0 304
michael@0 305
michael@0 306 //////////////////////////////////////////////////////////////////////////////
michael@0 307 //// nsITransactionListener
michael@0 308
michael@0 309 didDo: function PU_didDo(aManager, aTransaction, aDoResult)
michael@0 310 {
michael@0 311 updateCommandsOnActiveWindow();
michael@0 312 },
michael@0 313
michael@0 314 didUndo: function PU_didUndo(aManager, aTransaction, aUndoResult)
michael@0 315 {
michael@0 316 updateCommandsOnActiveWindow();
michael@0 317 },
michael@0 318
michael@0 319 didRedo: function PU_didRedo(aManager, aTransaction, aRedoResult)
michael@0 320 {
michael@0 321 updateCommandsOnActiveWindow();
michael@0 322 },
michael@0 323
michael@0 324 didBeginBatch: function PU_didBeginBatch(aManager, aResult)
michael@0 325 {
michael@0 326 // A no-op transaction is pushed to the stack, in order to make safe and
michael@0 327 // easy to implement "Undo" an unknown number of transactions (including 0),
michael@0 328 // "above" beginBatch and endBatch. Otherwise,implementing Undo that way
michael@0 329 // head to dataloss: for example, if no changes were done in the
michael@0 330 // edit-item panel, the last transaction on the undo stack would be the
michael@0 331 // initial createItem transaction, or even worse, the batched editing of
michael@0 332 // some other item.
michael@0 333 // DO NOT MOVE this to the window scope, that would leak (bug 490068)!
michael@0 334 this.transactionManager.doTransaction({ doTransaction: function() {},
michael@0 335 undoTransaction: function() {},
michael@0 336 redoTransaction: function() {},
michael@0 337 isTransient: false,
michael@0 338 merge: function() { return false; }
michael@0 339 });
michael@0 340 },
michael@0 341
michael@0 342 willDo: function PU_willDo() {},
michael@0 343 willUndo: function PU_willUndo() {},
michael@0 344 willRedo: function PU_willRedo() {},
michael@0 345 willBeginBatch: function PU_willBeginBatch() {},
michael@0 346 willEndBatch: function PU_willEndBatch() {},
michael@0 347 didEndBatch: function PU_didEndBatch() {},
michael@0 348 willMerge: function PU_willMerge() {},
michael@0 349 didMerge: function PU_didMerge() {},
michael@0 350
michael@0 351
michael@0 352 /**
michael@0 353 * Determines if a node is read only (children cannot be inserted, sometimes
michael@0 354 * they cannot be removed depending on the circumstance)
michael@0 355 * @param aNode
michael@0 356 * A result node
michael@0 357 * @returns true if the node is readonly, false otherwise
michael@0 358 */
michael@0 359 nodeIsReadOnly: function PU_nodeIsReadOnly(aNode) {
michael@0 360 let itemId = aNode.itemId;
michael@0 361 if (itemId != -1) {
michael@0 362 return this._readOnly.indexOf(itemId) != -1;
michael@0 363 }
michael@0 364
michael@0 365 if (this.nodeIsQuery(aNode) &&
michael@0 366 asQuery(aNode).queryOptions.resultType !=
michael@0 367 Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS)
michael@0 368 return aNode.childrenReadOnly;
michael@0 369 return false;
michael@0 370 },
michael@0 371
michael@0 372 /**
michael@0 373 * Determines whether or not a ResultNode is a host container.
michael@0 374 * @param aNode
michael@0 375 * A result node
michael@0 376 * @returns true if the node is a host container, false otherwise
michael@0 377 */
michael@0 378 nodeIsHost: function PU_nodeIsHost(aNode) {
michael@0 379 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
michael@0 380 aNode.parent &&
michael@0 381 asQuery(aNode.parent).queryOptions.resultType ==
michael@0 382 Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY;
michael@0 383 },
michael@0 384
michael@0 385 /**
michael@0 386 * Determines whether or not a ResultNode is a day container.
michael@0 387 * @param node
michael@0 388 * A NavHistoryResultNode
michael@0 389 * @returns true if the node is a day container, false otherwise
michael@0 390 */
michael@0 391 nodeIsDay: function PU_nodeIsDay(aNode) {
michael@0 392 var resultType;
michael@0 393 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
michael@0 394 aNode.parent &&
michael@0 395 ((resultType = asQuery(aNode.parent).queryOptions.resultType) ==
michael@0 396 Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
michael@0 397 resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY);
michael@0 398 },
michael@0 399
michael@0 400 /**
michael@0 401 * Determines whether or not a result-node is a tag container.
michael@0 402 * @param aNode
michael@0 403 * A result-node
michael@0 404 * @returns true if the node is a tag container, false otherwise
michael@0 405 */
michael@0 406 nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
michael@0 407 return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
michael@0 408 asQuery(aNode).queryOptions.resultType ==
michael@0 409 Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS;
michael@0 410 },
michael@0 411
michael@0 412 /**
michael@0 413 * Determines whether or not a ResultNode is a container.
michael@0 414 * @param aNode
michael@0 415 * A result node
michael@0 416 * @returns true if the node is a container item, false otherwise
michael@0 417 */
michael@0 418 containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
michael@0 419 Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
michael@0 420 Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY],
michael@0 421 nodeIsContainer: function PU_nodeIsContainer(aNode) {
michael@0 422 return this.containerTypes.indexOf(aNode.type) != -1;
michael@0 423 },
michael@0 424
michael@0 425 /**
michael@0 426 * Determines whether or not a ResultNode is an history related container.
michael@0 427 * @param node
michael@0 428 * A result node
michael@0 429 * @returns true if the node is an history related container, false otherwise
michael@0 430 */
michael@0 431 nodeIsHistoryContainer: function PU_nodeIsHistoryContainer(aNode) {
michael@0 432 var resultType;
michael@0 433 return this.nodeIsQuery(aNode) &&
michael@0 434 ((resultType = asQuery(aNode).queryOptions.resultType) ==
michael@0 435 Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY ||
michael@0 436 resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
michael@0 437 resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY ||
michael@0 438 this.nodeIsDay(aNode) ||
michael@0 439 this.nodeIsHost(aNode));
michael@0 440 },
michael@0 441
michael@0 442 /**
michael@0 443 * Determines whether or not a node is a readonly folder.
michael@0 444 * @param aNode
michael@0 445 * The node to test.
michael@0 446 * @returns true if the node is a readonly folder.
michael@0 447 */
michael@0 448 isReadonlyFolder: function(aNode) {
michael@0 449 return this.nodeIsFolder(aNode) &&
michael@0 450 this._readOnly.indexOf(asQuery(aNode).folderItemId) != -1;
michael@0 451 },
michael@0 452
michael@0 453 /**
michael@0 454 * Gets the concrete item-id for the given node. Generally, this is just
michael@0 455 * node.itemId, but for folder-shortcuts that's node.folderItemId.
michael@0 456 */
michael@0 457 getConcreteItemId: function PU_getConcreteItemId(aNode) {
michael@0 458 if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
michael@0 459 return asQuery(aNode).folderItemId;
michael@0 460 else if (PlacesUtils.nodeIsTagQuery(aNode)) {
michael@0 461 // RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts
michael@0 462 // so we can still get the concrete itemId for them.
michael@0 463 var queries = aNode.getQueries();
michael@0 464 var folders = queries[0].getFolders();
michael@0 465 return folders[0];
michael@0 466 }
michael@0 467 return aNode.itemId;
michael@0 468 },
michael@0 469
michael@0 470 /**
michael@0 471 * String-wraps a result node according to the rules of the specified
michael@0 472 * content type.
michael@0 473 * @param aNode
michael@0 474 * The Result node to wrap (serialize)
michael@0 475 * @param aType
michael@0 476 * The content type to serialize as
michael@0 477 * @param [optional] aOverrideURI
michael@0 478 * Used instead of the node's URI if provided.
michael@0 479 * This is useful for wrapping a container as TYPE_X_MOZ_URL,
michael@0 480 * TYPE_HTML or TYPE_UNICODE.
michael@0 481 * @return A string serialization of the node
michael@0 482 */
michael@0 483 wrapNode: function PU_wrapNode(aNode, aType, aOverrideURI) {
michael@0 484 // when wrapping a node, we want all the items, even if the original
michael@0 485 // query options are excluding them.
michael@0 486 // this can happen when copying from the left hand pane of the bookmarks
michael@0 487 // organizer
michael@0 488 // @return [node, shouldClose]
michael@0 489 function convertNode(cNode) {
michael@0 490 if (PlacesUtils.nodeIsFolder(cNode) &&
michael@0 491 cNode.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT &&
michael@0 492 asQuery(cNode).queryOptions.excludeItems) {
michael@0 493 return [PlacesUtils.getFolderContents(cNode.itemId, false, true).root, true];
michael@0 494 }
michael@0 495
michael@0 496 // If we didn't create our own query, do not alter the node's open state.
michael@0 497 return [cNode, false];
michael@0 498 }
michael@0 499
michael@0 500 function gatherLivemarkUrl(aNode) {
michael@0 501 try {
michael@0 502 return PlacesUtils.annotations
michael@0 503 .getItemAnnotation(aNode.itemId,
michael@0 504 PlacesUtils.LMANNO_SITEURI);
michael@0 505 } catch (ex) {
michael@0 506 return PlacesUtils.annotations
michael@0 507 .getItemAnnotation(aNode.itemId,
michael@0 508 PlacesUtils.LMANNO_FEEDURI);
michael@0 509 }
michael@0 510 }
michael@0 511
michael@0 512 function isLivemark(aNode) {
michael@0 513 return PlacesUtils.nodeIsFolder(aNode) &&
michael@0 514 PlacesUtils.annotations
michael@0 515 .itemHasAnnotation(aNode.itemId,
michael@0 516 PlacesUtils.LMANNO_FEEDURI);
michael@0 517 }
michael@0 518
michael@0 519 switch (aType) {
michael@0 520 case this.TYPE_X_MOZ_PLACE:
michael@0 521 case this.TYPE_X_MOZ_PLACE_SEPARATOR:
michael@0 522 case this.TYPE_X_MOZ_PLACE_CONTAINER: {
michael@0 523 let writer = {
michael@0 524 value: "",
michael@0 525 write: function PU_wrapNode__write(aStr, aLen) {
michael@0 526 this.value += aStr;
michael@0 527 }
michael@0 528 };
michael@0 529
michael@0 530 let [node, shouldClose] = convertNode(aNode);
michael@0 531 this._serializeNodeAsJSONToOutputStream(node, writer);
michael@0 532 if (shouldClose)
michael@0 533 node.containerOpen = false;
michael@0 534
michael@0 535 return writer.value;
michael@0 536 }
michael@0 537 case this.TYPE_X_MOZ_URL: {
michael@0 538 function gatherDataUrl(bNode) {
michael@0 539 if (isLivemark(bNode)) {
michael@0 540 return gatherLivemarkUrl(bNode) + NEWLINE + bNode.title;
michael@0 541 }
michael@0 542
michael@0 543 if (PlacesUtils.nodeIsURI(bNode))
michael@0 544 return (aOverrideURI || bNode.uri) + NEWLINE + bNode.title;
michael@0 545 // ignore containers and separators - items without valid URIs
michael@0 546 return "";
michael@0 547 }
michael@0 548
michael@0 549 let [node, shouldClose] = convertNode(aNode);
michael@0 550 let dataUrl = gatherDataUrl(node);
michael@0 551 if (shouldClose)
michael@0 552 node.containerOpen = false;
michael@0 553
michael@0 554 return dataUrl;
michael@0 555 }
michael@0 556 case this.TYPE_HTML: {
michael@0 557 function gatherDataHtml(bNode) {
michael@0 558 function htmlEscape(s) {
michael@0 559 s = s.replace(/&/g, "&amp;");
michael@0 560 s = s.replace(/>/g, "&gt;");
michael@0 561 s = s.replace(/</g, "&lt;");
michael@0 562 s = s.replace(/"/g, "&quot;");
michael@0 563 s = s.replace(/'/g, "&apos;");
michael@0 564 return s;
michael@0 565 }
michael@0 566 // escape out potential HTML in the title
michael@0 567 let escapedTitle = bNode.title ? htmlEscape(bNode.title) : "";
michael@0 568
michael@0 569 if (isLivemark(bNode)) {
michael@0 570 return "<A HREF=\"" + gatherLivemarkUrl(bNode) + "\">" + escapedTitle + "</A>" + NEWLINE;
michael@0 571 }
michael@0 572
michael@0 573 if (PlacesUtils.nodeIsContainer(bNode)) {
michael@0 574 asContainer(bNode);
michael@0 575 let wasOpen = bNode.containerOpen;
michael@0 576 if (!wasOpen)
michael@0 577 bNode.containerOpen = true;
michael@0 578
michael@0 579 let childString = "<DL><DT>" + escapedTitle + "</DT>" + NEWLINE;
michael@0 580 let cc = bNode.childCount;
michael@0 581 for (let i = 0; i < cc; ++i)
michael@0 582 childString += "<DD>"
michael@0 583 + NEWLINE
michael@0 584 + gatherDataHtml(bNode.getChild(i))
michael@0 585 + "</DD>"
michael@0 586 + NEWLINE;
michael@0 587 bNode.containerOpen = wasOpen;
michael@0 588 return childString + "</DL>" + NEWLINE;
michael@0 589 }
michael@0 590 if (PlacesUtils.nodeIsURI(bNode))
michael@0 591 return "<A HREF=\"" + bNode.uri + "\">" + escapedTitle + "</A>" + NEWLINE;
michael@0 592 if (PlacesUtils.nodeIsSeparator(bNode))
michael@0 593 return "<HR>" + NEWLINE;
michael@0 594 return "";
michael@0 595 }
michael@0 596
michael@0 597 let [node, shouldClose] = convertNode(aNode);
michael@0 598 let dataHtml = gatherDataHtml(node);
michael@0 599 if (shouldClose)
michael@0 600 node.containerOpen = false;
michael@0 601
michael@0 602 return dataHtml;
michael@0 603 }
michael@0 604 }
michael@0 605
michael@0 606 // Otherwise, we wrap as TYPE_UNICODE.
michael@0 607 function gatherDataText(bNode) {
michael@0 608 if (isLivemark(bNode)) {
michael@0 609 return gatherLivemarkUrl(bNode);
michael@0 610 }
michael@0 611
michael@0 612 if (PlacesUtils.nodeIsContainer(bNode)) {
michael@0 613 asContainer(bNode);
michael@0 614 let wasOpen = bNode.containerOpen;
michael@0 615 if (!wasOpen)
michael@0 616 bNode.containerOpen = true;
michael@0 617
michael@0 618 let childString = bNode.title + NEWLINE;
michael@0 619 let cc = bNode.childCount;
michael@0 620 for (let i = 0; i < cc; ++i) {
michael@0 621 let child = bNode.getChild(i);
michael@0 622 let suffix = i < (cc - 1) ? NEWLINE : "";
michael@0 623 childString += gatherDataText(child) + suffix;
michael@0 624 }
michael@0 625 bNode.containerOpen = wasOpen;
michael@0 626 return childString;
michael@0 627 }
michael@0 628 if (PlacesUtils.nodeIsURI(bNode))
michael@0 629 return (aOverrideURI || bNode.uri);
michael@0 630 if (PlacesUtils.nodeIsSeparator(bNode))
michael@0 631 return "--------------------";
michael@0 632 return "";
michael@0 633 }
michael@0 634
michael@0 635 let [node, shouldClose] = convertNode(aNode);
michael@0 636 let dataText = gatherDataText(node);
michael@0 637 // Convert node could pass an open container node.
michael@0 638 if (shouldClose)
michael@0 639 node.containerOpen = false;
michael@0 640
michael@0 641 return dataText;
michael@0 642 },
michael@0 643
michael@0 644 /**
michael@0 645 * Unwraps data from the Clipboard or the current Drag Session.
michael@0 646 * @param blob
michael@0 647 * A blob (string) of data, in some format we potentially know how
michael@0 648 * to parse.
michael@0 649 * @param type
michael@0 650 * The content type of the blob.
michael@0 651 * @returns An array of objects representing each item contained by the source.
michael@0 652 */
michael@0 653 unwrapNodes: function PU_unwrapNodes(blob, type) {
michael@0 654 // We split on "\n" because the transferable system converts "\r\n" to "\n"
michael@0 655 var nodes = [];
michael@0 656 switch(type) {
michael@0 657 case this.TYPE_X_MOZ_PLACE:
michael@0 658 case this.TYPE_X_MOZ_PLACE_SEPARATOR:
michael@0 659 case this.TYPE_X_MOZ_PLACE_CONTAINER:
michael@0 660 nodes = JSON.parse("[" + blob + "]");
michael@0 661 break;
michael@0 662 case this.TYPE_X_MOZ_URL:
michael@0 663 var parts = blob.split("\n");
michael@0 664 // data in this type has 2 parts per entry, so if there are fewer
michael@0 665 // than 2 parts left, the blob is malformed and we should stop
michael@0 666 // but drag and drop of files from the shell has parts.length = 1
michael@0 667 if (parts.length != 1 && parts.length % 2)
michael@0 668 break;
michael@0 669 for (var i = 0; i < parts.length; i=i+2) {
michael@0 670 var uriString = parts[i];
michael@0 671 var titleString = "";
michael@0 672 if (parts.length > i+1)
michael@0 673 titleString = parts[i+1];
michael@0 674 else {
michael@0 675 // for drag and drop of files, try to use the leafName as title
michael@0 676 try {
michael@0 677 titleString = this._uri(uriString).QueryInterface(Ci.nsIURL)
michael@0 678 .fileName;
michael@0 679 }
michael@0 680 catch (e) {}
michael@0 681 }
michael@0 682 // note: this._uri() will throw if uriString is not a valid URI
michael@0 683 if (this._uri(uriString)) {
michael@0 684 nodes.push({ uri: uriString,
michael@0 685 title: titleString ? titleString : uriString ,
michael@0 686 type: this.TYPE_X_MOZ_URL });
michael@0 687 }
michael@0 688 }
michael@0 689 break;
michael@0 690 case this.TYPE_UNICODE:
michael@0 691 var parts = blob.split("\n");
michael@0 692 for (var i = 0; i < parts.length; i++) {
michael@0 693 var uriString = parts[i];
michael@0 694 // text/uri-list is converted to TYPE_UNICODE but it could contain
michael@0 695 // comments line prepended by #, we should skip them
michael@0 696 if (uriString.substr(0, 1) == '\x23')
michael@0 697 continue;
michael@0 698 // note: this._uri() will throw if uriString is not a valid URI
michael@0 699 if (uriString != "" && this._uri(uriString))
michael@0 700 nodes.push({ uri: uriString,
michael@0 701 title: uriString,
michael@0 702 type: this.TYPE_X_MOZ_URL });
michael@0 703 }
michael@0 704 break;
michael@0 705 default:
michael@0 706 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 707 }
michael@0 708 return nodes;
michael@0 709 },
michael@0 710
michael@0 711 /**
michael@0 712 * Generates a nsINavHistoryResult for the contents of a folder.
michael@0 713 * @param folderId
michael@0 714 * The folder to open
michael@0 715 * @param [optional] excludeItems
michael@0 716 * True to hide all items (individual bookmarks). This is used on
michael@0 717 * the left places pane so you just get a folder hierarchy.
michael@0 718 * @param [optional] expandQueries
michael@0 719 * True to make query items expand as new containers. For managing,
michael@0 720 * you want this to be false, for menus and such, you want this to
michael@0 721 * be true.
michael@0 722 * @returns A nsINavHistoryResult containing the contents of the
michael@0 723 * folder. The result.root is guaranteed to be open.
michael@0 724 */
michael@0 725 getFolderContents:
michael@0 726 function PU_getFolderContents(aFolderId, aExcludeItems, aExpandQueries) {
michael@0 727 var query = this.history.getNewQuery();
michael@0 728 query.setFolders([aFolderId], 1);
michael@0 729 var options = this.history.getNewQueryOptions();
michael@0 730 options.excludeItems = aExcludeItems;
michael@0 731 options.expandQueries = aExpandQueries;
michael@0 732
michael@0 733 var result = this.history.executeQuery(query, options);
michael@0 734 result.root.containerOpen = true;
michael@0 735 return result;
michael@0 736 },
michael@0 737
michael@0 738 /**
michael@0 739 * Fetch all annotations for a URI, including all properties of each
michael@0 740 * annotation which would be required to recreate it.
michael@0 741 * @param aURI
michael@0 742 * The URI for which annotations are to be retrieved.
michael@0 743 * @return Array of objects, each containing the following properties:
michael@0 744 * name, flags, expires, value
michael@0 745 */
michael@0 746 getAnnotationsForURI: function PU_getAnnotationsForURI(aURI) {
michael@0 747 var annosvc = this.annotations;
michael@0 748 var annos = [], val = null;
michael@0 749 var annoNames = annosvc.getPageAnnotationNames(aURI);
michael@0 750 for (var i = 0; i < annoNames.length; i++) {
michael@0 751 var flags = {}, exp = {}, storageType = {};
michael@0 752 annosvc.getPageAnnotationInfo(aURI, annoNames[i], flags, exp, storageType);
michael@0 753 val = annosvc.getPageAnnotation(aURI, annoNames[i]);
michael@0 754 annos.push({name: annoNames[i],
michael@0 755 flags: flags.value,
michael@0 756 expires: exp.value,
michael@0 757 value: val});
michael@0 758 }
michael@0 759 return annos;
michael@0 760 },
michael@0 761
michael@0 762 /**
michael@0 763 * Fetch all annotations for an item, including all properties of each
michael@0 764 * annotation which would be required to recreate it.
michael@0 765 * @param aItemId
michael@0 766 * The identifier of the itme for which annotations are to be
michael@0 767 * retrieved.
michael@0 768 * @return Array of objects, each containing the following properties:
michael@0 769 * name, flags, expires, mimeType, type, value
michael@0 770 */
michael@0 771 getAnnotationsForItem: function PU_getAnnotationsForItem(aItemId) {
michael@0 772 var annosvc = this.annotations;
michael@0 773 var annos = [], val = null;
michael@0 774 var annoNames = annosvc.getItemAnnotationNames(aItemId);
michael@0 775 for (var i = 0; i < annoNames.length; i++) {
michael@0 776 var flags = {}, exp = {}, storageType = {};
michael@0 777 annosvc.getItemAnnotationInfo(aItemId, annoNames[i], flags, exp, storageType);
michael@0 778 val = annosvc.getItemAnnotation(aItemId, annoNames[i]);
michael@0 779 annos.push({name: annoNames[i],
michael@0 780 flags: flags.value,
michael@0 781 expires: exp.value,
michael@0 782 value: val});
michael@0 783 }
michael@0 784 return annos;
michael@0 785 },
michael@0 786
michael@0 787 /**
michael@0 788 * Annotate a URI with a batch of annotations.
michael@0 789 * @param aURI
michael@0 790 * The URI for which annotations are to be set.
michael@0 791 * @param aAnnotations
michael@0 792 * Array of objects, each containing the following properties:
michael@0 793 * name, flags, expires.
michael@0 794 * If the value for an annotation is not set it will be removed.
michael@0 795 */
michael@0 796 setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) {
michael@0 797 var annosvc = this.annotations;
michael@0 798 aAnnos.forEach(function(anno) {
michael@0 799 if (anno.value === undefined || anno.value === null) {
michael@0 800 annosvc.removePageAnnotation(aURI, anno.name);
michael@0 801 }
michael@0 802 else {
michael@0 803 let flags = ("flags" in anno) ? anno.flags : 0;
michael@0 804 let expires = ("expires" in anno) ?
michael@0 805 anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
michael@0 806 annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires);
michael@0 807 }
michael@0 808 });
michael@0 809 },
michael@0 810
michael@0 811 /**
michael@0 812 * Annotate an item with a batch of annotations.
michael@0 813 * @param aItemId
michael@0 814 * The identifier of the item for which annotations are to be set
michael@0 815 * @param aAnnotations
michael@0 816 * Array of objects, each containing the following properties:
michael@0 817 * name, flags, expires.
michael@0 818 * If the value for an annotation is not set it will be removed.
michael@0 819 */
michael@0 820 setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos) {
michael@0 821 var annosvc = this.annotations;
michael@0 822
michael@0 823 aAnnos.forEach(function(anno) {
michael@0 824 if (anno.value === undefined || anno.value === null) {
michael@0 825 annosvc.removeItemAnnotation(aItemId, anno.name);
michael@0 826 }
michael@0 827 else {
michael@0 828 let flags = ("flags" in anno) ? anno.flags : 0;
michael@0 829 let expires = ("expires" in anno) ?
michael@0 830 anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
michael@0 831 annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags,
michael@0 832 expires);
michael@0 833 }
michael@0 834 });
michael@0 835 },
michael@0 836
michael@0 837 // Identifier getters for special folders.
michael@0 838 // You should use these everywhere PlacesUtils is available to avoid XPCOM
michael@0 839 // traversal just to get roots' ids.
michael@0 840 get placesRootId() {
michael@0 841 delete this.placesRootId;
michael@0 842 return this.placesRootId = this.bookmarks.placesRoot;
michael@0 843 },
michael@0 844
michael@0 845 get bookmarksMenuFolderId() {
michael@0 846 delete this.bookmarksMenuFolderId;
michael@0 847 return this.bookmarksMenuFolderId = this.bookmarks.bookmarksMenuFolder;
michael@0 848 },
michael@0 849
michael@0 850 get toolbarFolderId() {
michael@0 851 delete this.toolbarFolderId;
michael@0 852 return this.toolbarFolderId = this.bookmarks.toolbarFolder;
michael@0 853 },
michael@0 854
michael@0 855 get tagsFolderId() {
michael@0 856 delete this.tagsFolderId;
michael@0 857 return this.tagsFolderId = this.bookmarks.tagsFolder;
michael@0 858 },
michael@0 859
michael@0 860 get unfiledBookmarksFolderId() {
michael@0 861 delete this.unfiledBookmarksFolderId;
michael@0 862 return this.unfiledBookmarksFolderId = this.bookmarks.unfiledBookmarksFolder;
michael@0 863 },
michael@0 864
michael@0 865 /**
michael@0 866 * Checks if aItemId is a root.
michael@0 867 *
michael@0 868 * @param aItemId
michael@0 869 * item id to look for.
michael@0 870 * @returns true if aItemId is a root, false otherwise.
michael@0 871 */
michael@0 872 isRootItem: function PU_isRootItem(aItemId) {
michael@0 873 return aItemId == PlacesUtils.bookmarksMenuFolderId ||
michael@0 874 aItemId == PlacesUtils.toolbarFolderId ||
michael@0 875 aItemId == PlacesUtils.unfiledBookmarksFolderId ||
michael@0 876 aItemId == PlacesUtils.tagsFolderId ||
michael@0 877 aItemId == PlacesUtils.placesRootId;
michael@0 878 },
michael@0 879
michael@0 880 /**
michael@0 881 * Set the POST data associated with a bookmark, if any.
michael@0 882 * Used by POST keywords.
michael@0 883 * @param aBookmarkId
michael@0 884 * @returns string of POST data
michael@0 885 */
michael@0 886 setPostDataForBookmark: function PU_setPostDataForBookmark(aBookmarkId, aPostData) {
michael@0 887 const annos = this.annotations;
michael@0 888 if (aPostData)
michael@0 889 annos.setItemAnnotation(aBookmarkId, this.POST_DATA_ANNO, aPostData,
michael@0 890 0, Ci.nsIAnnotationService.EXPIRE_NEVER);
michael@0 891 else if (annos.itemHasAnnotation(aBookmarkId, this.POST_DATA_ANNO))
michael@0 892 annos.removeItemAnnotation(aBookmarkId, this.POST_DATA_ANNO);
michael@0 893 },
michael@0 894
michael@0 895 /**
michael@0 896 * Get the POST data associated with a bookmark, if any.
michael@0 897 * @param aBookmarkId
michael@0 898 * @returns string of POST data if set for aBookmarkId. null otherwise.
michael@0 899 */
michael@0 900 getPostDataForBookmark: function PU_getPostDataForBookmark(aBookmarkId) {
michael@0 901 const annos = this.annotations;
michael@0 902 if (annos.itemHasAnnotation(aBookmarkId, this.POST_DATA_ANNO))
michael@0 903 return annos.getItemAnnotation(aBookmarkId, this.POST_DATA_ANNO);
michael@0 904
michael@0 905 return null;
michael@0 906 },
michael@0 907
michael@0 908 /**
michael@0 909 * Get the URI (and any associated POST data) for a given keyword.
michael@0 910 * @param aKeyword string keyword
michael@0 911 * @returns an array containing a string URL and a string of POST data
michael@0 912 */
michael@0 913 getURLAndPostDataForKeyword: function PU_getURLAndPostDataForKeyword(aKeyword) {
michael@0 914 var url = null, postdata = null;
michael@0 915 try {
michael@0 916 var uri = this.bookmarks.getURIForKeyword(aKeyword);
michael@0 917 if (uri) {
michael@0 918 url = uri.spec;
michael@0 919 var bookmarks = this.bookmarks.getBookmarkIdsForURI(uri);
michael@0 920 for (let i = 0; i < bookmarks.length; i++) {
michael@0 921 var bookmark = bookmarks[i];
michael@0 922 var kw = this.bookmarks.getKeywordForBookmark(bookmark);
michael@0 923 if (kw == aKeyword) {
michael@0 924 postdata = this.getPostDataForBookmark(bookmark);
michael@0 925 break;
michael@0 926 }
michael@0 927 }
michael@0 928 }
michael@0 929 } catch(ex) {}
michael@0 930 return [url, postdata];
michael@0 931 },
michael@0 932
michael@0 933 /**
michael@0 934 * Get all bookmarks for a URL, excluding items under tags.
michael@0 935 */
michael@0 936 getBookmarksForURI:
michael@0 937 function PU_getBookmarksForURI(aURI) {
michael@0 938 var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI);
michael@0 939
michael@0 940 // filter the ids list
michael@0 941 return bmkIds.filter(function(aID) {
michael@0 942 var parentId = this.bookmarks.getFolderIdForItem(aID);
michael@0 943 var grandparentId = this.bookmarks.getFolderIdForItem(parentId);
michael@0 944 // item under a tag container
michael@0 945 if (grandparentId == this.tagsFolderId)
michael@0 946 return false;
michael@0 947 return true;
michael@0 948 }, this);
michael@0 949 },
michael@0 950
michael@0 951 /**
michael@0 952 * Get the most recently added/modified bookmark for a URL, excluding items
michael@0 953 * under tags.
michael@0 954 *
michael@0 955 * @param aURI
michael@0 956 * nsIURI of the page we will look for.
michael@0 957 * @returns itemId of the found bookmark, or -1 if nothing is found.
michael@0 958 */
michael@0 959 getMostRecentBookmarkForURI:
michael@0 960 function PU_getMostRecentBookmarkForURI(aURI) {
michael@0 961 var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI);
michael@0 962 for (var i = 0; i < bmkIds.length; i++) {
michael@0 963 // Find the first folder which isn't a tag container
michael@0 964 var itemId = bmkIds[i];
michael@0 965 var parentId = this.bookmarks.getFolderIdForItem(itemId);
michael@0 966 // Optimization: if this is a direct child of a root we don't need to
michael@0 967 // check if its grandparent is a tag.
michael@0 968 if (parentId == this.unfiledBookmarksFolderId ||
michael@0 969 parentId == this.toolbarFolderId ||
michael@0 970 parentId == this.bookmarksMenuFolderId)
michael@0 971 return itemId;
michael@0 972
michael@0 973 var grandparentId = this.bookmarks.getFolderIdForItem(parentId);
michael@0 974 if (grandparentId != this.tagsFolderId)
michael@0 975 return itemId;
michael@0 976 }
michael@0 977 return -1;
michael@0 978 },
michael@0 979
michael@0 980 /**
michael@0 981 * Returns a nsNavHistoryContainerResultNode with forced excludeItems and
michael@0 982 * expandQueries.
michael@0 983 * @param aNode
michael@0 984 * The node to convert
michael@0 985 * @param [optional] excludeItems
michael@0 986 * True to hide all items (individual bookmarks). This is used on
michael@0 987 * the left places pane so you just get a folder hierarchy.
michael@0 988 * @param [optional] expandQueries
michael@0 989 * True to make query items expand as new containers. For managing,
michael@0 990 * you want this to be false, for menus and such, you want this to
michael@0 991 * be true.
michael@0 992 * @returns A nsINavHistoryContainerResultNode containing the unfiltered
michael@0 993 * contents of the container.
michael@0 994 * @note The returned container node could be open or closed, we don't
michael@0 995 * guarantee its status.
michael@0 996 */
michael@0 997 getContainerNodeWithOptions:
michael@0 998 function PU_getContainerNodeWithOptions(aNode, aExcludeItems, aExpandQueries) {
michael@0 999 if (!this.nodeIsContainer(aNode))
michael@0 1000 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 1001
michael@0 1002 // excludeItems is inherited by child containers in an excludeItems view.
michael@0 1003 var excludeItems = asQuery(aNode).queryOptions.excludeItems ||
michael@0 1004 asQuery(aNode.parentResult.root).queryOptions.excludeItems;
michael@0 1005 // expandQueries is inherited by child containers in an expandQueries view.
michael@0 1006 var expandQueries = asQuery(aNode).queryOptions.expandQueries &&
michael@0 1007 asQuery(aNode.parentResult.root).queryOptions.expandQueries;
michael@0 1008
michael@0 1009 // If our options are exactly what we expect, directly return the node.
michael@0 1010 if (excludeItems == aExcludeItems && expandQueries == aExpandQueries)
michael@0 1011 return aNode;
michael@0 1012
michael@0 1013 // Otherwise, get contents manually.
michael@0 1014 var queries = {}, options = {};
michael@0 1015 this.history.queryStringToQueries(aNode.uri, queries, {}, options);
michael@0 1016 options.value.excludeItems = aExcludeItems;
michael@0 1017 options.value.expandQueries = aExpandQueries;
michael@0 1018 return this.history.executeQueries(queries.value,
michael@0 1019 queries.value.length,
michael@0 1020 options.value).root;
michael@0 1021 },
michael@0 1022
michael@0 1023 /**
michael@0 1024 * Returns true if a container has uri nodes in its first level.
michael@0 1025 * Has better performance than (getURLsForContainerNode(node).length > 0).
michael@0 1026 * @param aNode
michael@0 1027 * The container node to search through.
michael@0 1028 * @returns true if the node contains uri nodes, false otherwise.
michael@0 1029 */
michael@0 1030 hasChildURIs: function PU_hasChildURIs(aNode) {
michael@0 1031 if (!this.nodeIsContainer(aNode))
michael@0 1032 return false;
michael@0 1033
michael@0 1034 let root = this.getContainerNodeWithOptions(aNode, false, true);
michael@0 1035 let result = root.parentResult;
michael@0 1036 let didSuppressNotifications = false;
michael@0 1037 let wasOpen = root.containerOpen;
michael@0 1038 if (!wasOpen) {
michael@0 1039 didSuppressNotifications = result.suppressNotifications;
michael@0 1040 if (!didSuppressNotifications)
michael@0 1041 result.suppressNotifications = true;
michael@0 1042
michael@0 1043 root.containerOpen = true;
michael@0 1044 }
michael@0 1045
michael@0 1046 let found = false;
michael@0 1047 for (let i = 0; i < root.childCount && !found; i++) {
michael@0 1048 let child = root.getChild(i);
michael@0 1049 if (this.nodeIsURI(child))
michael@0 1050 found = true;
michael@0 1051 }
michael@0 1052
michael@0 1053 if (!wasOpen) {
michael@0 1054 root.containerOpen = false;
michael@0 1055 if (!didSuppressNotifications)
michael@0 1056 result.suppressNotifications = false;
michael@0 1057 }
michael@0 1058 return found;
michael@0 1059 },
michael@0 1060
michael@0 1061 /**
michael@0 1062 * Returns an array containing all the uris in the first level of the
michael@0 1063 * passed in container.
michael@0 1064 * If you only need to know if the node contains uris, use hasChildURIs.
michael@0 1065 * @param aNode
michael@0 1066 * The container node to search through
michael@0 1067 * @returns array of uris in the first level of the container.
michael@0 1068 */
michael@0 1069 getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) {
michael@0 1070 let urls = [];
michael@0 1071 if (!this.nodeIsContainer(aNode))
michael@0 1072 return urls;
michael@0 1073
michael@0 1074 let root = this.getContainerNodeWithOptions(aNode, false, true);
michael@0 1075 let result = root.parentResult;
michael@0 1076 let wasOpen = root.containerOpen;
michael@0 1077 let didSuppressNotifications = false;
michael@0 1078 if (!wasOpen) {
michael@0 1079 didSuppressNotifications = result.suppressNotifications;
michael@0 1080 if (!didSuppressNotifications)
michael@0 1081 result.suppressNotifications = true;
michael@0 1082
michael@0 1083 root.containerOpen = true;
michael@0 1084 }
michael@0 1085
michael@0 1086 for (let i = 0; i < root.childCount; ++i) {
michael@0 1087 let child = root.getChild(i);
michael@0 1088 if (this.nodeIsURI(child))
michael@0 1089 urls.push({uri: child.uri, isBookmark: this.nodeIsBookmark(child)});
michael@0 1090 }
michael@0 1091
michael@0 1092 if (!wasOpen) {
michael@0 1093 root.containerOpen = false;
michael@0 1094 if (!didSuppressNotifications)
michael@0 1095 result.suppressNotifications = false;
michael@0 1096 }
michael@0 1097 return urls;
michael@0 1098 },
michael@0 1099
michael@0 1100 /**
michael@0 1101 * Serializes the given node (and all its descendents) as JSON
michael@0 1102 * and writes the serialization to the given output stream.
michael@0 1103 *
michael@0 1104 * @param aNode
michael@0 1105 * An nsINavHistoryResultNode
michael@0 1106 * @param aStream
michael@0 1107 * An nsIOutputStream. NOTE: it only uses the write(str, len)
michael@0 1108 * method of nsIOutputStream. The caller is responsible for
michael@0 1109 * closing the stream.
michael@0 1110 */
michael@0 1111 _serializeNodeAsJSONToOutputStream: function (aNode, aStream) {
michael@0 1112 function addGenericProperties(aPlacesNode, aJSNode) {
michael@0 1113 aJSNode.title = aPlacesNode.title;
michael@0 1114 aJSNode.id = aPlacesNode.itemId;
michael@0 1115 if (aJSNode.id != -1) {
michael@0 1116 var parent = aPlacesNode.parent;
michael@0 1117 if (parent) {
michael@0 1118 aJSNode.parent = parent.itemId;
michael@0 1119 aJSNode.parentReadOnly = PlacesUtils.nodeIsReadOnly(parent);
michael@0 1120 }
michael@0 1121 var dateAdded = aPlacesNode.dateAdded;
michael@0 1122 if (dateAdded)
michael@0 1123 aJSNode.dateAdded = dateAdded;
michael@0 1124 var lastModified = aPlacesNode.lastModified;
michael@0 1125 if (lastModified)
michael@0 1126 aJSNode.lastModified = lastModified;
michael@0 1127
michael@0 1128 // XXX need a hasAnnos api
michael@0 1129 var annos = [];
michael@0 1130 try {
michael@0 1131 annos = PlacesUtils.getAnnotationsForItem(aJSNode.id).filter(function(anno) {
michael@0 1132 // XXX should whitelist this instead, w/ a pref for
michael@0 1133 // backup/restore of non-whitelisted annos
michael@0 1134 // XXX causes JSON encoding errors, so utf-8 encode
michael@0 1135 //anno.value = unescape(encodeURIComponent(anno.value));
michael@0 1136 if (anno.name == PlacesUtils.LMANNO_FEEDURI)
michael@0 1137 aJSNode.livemark = 1;
michael@0 1138 return true;
michael@0 1139 });
michael@0 1140 } catch(ex) {}
michael@0 1141 if (annos.length != 0)
michael@0 1142 aJSNode.annos = annos;
michael@0 1143 }
michael@0 1144 // XXXdietrich - store annos for non-bookmark items
michael@0 1145 }
michael@0 1146
michael@0 1147 function addURIProperties(aPlacesNode, aJSNode) {
michael@0 1148 aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
michael@0 1149 aJSNode.uri = aPlacesNode.uri;
michael@0 1150 if (aJSNode.id && aJSNode.id != -1) {
michael@0 1151 // harvest bookmark-specific properties
michael@0 1152 var keyword = PlacesUtils.bookmarks.getKeywordForBookmark(aJSNode.id);
michael@0 1153 if (keyword)
michael@0 1154 aJSNode.keyword = keyword;
michael@0 1155 }
michael@0 1156
michael@0 1157 if (aPlacesNode.tags)
michael@0 1158 aJSNode.tags = aPlacesNode.tags;
michael@0 1159
michael@0 1160 // last character-set
michael@0 1161 var uri = PlacesUtils._uri(aPlacesNode.uri);
michael@0 1162 try {
michael@0 1163 var lastCharset = PlacesUtils.annotations.getPageAnnotation(
michael@0 1164 uri, PlacesUtils.CHARSET_ANNO);
michael@0 1165 aJSNode.charset = lastCharset;
michael@0 1166 } catch (e) {}
michael@0 1167 }
michael@0 1168
michael@0 1169 function addSeparatorProperties(aPlacesNode, aJSNode) {
michael@0 1170 aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
michael@0 1171 }
michael@0 1172
michael@0 1173 function addContainerProperties(aPlacesNode, aJSNode) {
michael@0 1174 var concreteId = PlacesUtils.getConcreteItemId(aPlacesNode);
michael@0 1175 if (concreteId != -1) {
michael@0 1176 // This is a bookmark or a tag container.
michael@0 1177 if (PlacesUtils.nodeIsQuery(aPlacesNode) ||
michael@0 1178 concreteId != aPlacesNode.itemId) {
michael@0 1179 aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
michael@0 1180 aJSNode.uri = aPlacesNode.uri;
michael@0 1181 // folder shortcut
michael@0 1182 aJSNode.concreteId = concreteId;
michael@0 1183 }
michael@0 1184 else { // Bookmark folder or a shortcut we should convert to folder.
michael@0 1185 aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
michael@0 1186
michael@0 1187 // Mark root folders.
michael@0 1188 if (aJSNode.id == PlacesUtils.placesRootId)
michael@0 1189 aJSNode.root = "placesRoot";
michael@0 1190 else if (aJSNode.id == PlacesUtils.bookmarksMenuFolderId)
michael@0 1191 aJSNode.root = "bookmarksMenuFolder";
michael@0 1192 else if (aJSNode.id == PlacesUtils.tagsFolderId)
michael@0 1193 aJSNode.root = "tagsFolder";
michael@0 1194 else if (aJSNode.id == PlacesUtils.unfiledBookmarksFolderId)
michael@0 1195 aJSNode.root = "unfiledBookmarksFolder";
michael@0 1196 else if (aJSNode.id == PlacesUtils.toolbarFolderId)
michael@0 1197 aJSNode.root = "toolbarFolder";
michael@0 1198 }
michael@0 1199 }
michael@0 1200 else {
michael@0 1201 // This is a grouped container query, generated on the fly.
michael@0 1202 aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
michael@0 1203 aJSNode.uri = aPlacesNode.uri;
michael@0 1204 }
michael@0 1205 }
michael@0 1206
michael@0 1207 function appendConvertedComplexNode(aNode, aSourceNode, aArray) {
michael@0 1208 var repr = {};
michael@0 1209
michael@0 1210 for (let [name, value] in Iterator(aNode))
michael@0 1211 repr[name] = value;
michael@0 1212
michael@0 1213 // write child nodes
michael@0 1214 var children = repr.children = [];
michael@0 1215 if (!aNode.livemark) {
michael@0 1216 asContainer(aSourceNode);
michael@0 1217 var wasOpen = aSourceNode.containerOpen;
michael@0 1218 if (!wasOpen)
michael@0 1219 aSourceNode.containerOpen = true;
michael@0 1220 var cc = aSourceNode.childCount;
michael@0 1221 for (var i = 0; i < cc; ++i) {
michael@0 1222 var childNode = aSourceNode.getChild(i);
michael@0 1223 appendConvertedNode(aSourceNode.getChild(i), i, children);
michael@0 1224 }
michael@0 1225 if (!wasOpen)
michael@0 1226 aSourceNode.containerOpen = false;
michael@0 1227 }
michael@0 1228
michael@0 1229 aArray.push(repr);
michael@0 1230 return true;
michael@0 1231 }
michael@0 1232
michael@0 1233 function appendConvertedNode(bNode, aIndex, aArray) {
michael@0 1234 var node = {};
michael@0 1235
michael@0 1236 // set index in order received
michael@0 1237 // XXX handy shortcut, but are there cases where we don't want
michael@0 1238 // to export using the sorting provided by the query?
michael@0 1239 if (aIndex)
michael@0 1240 node.index = aIndex;
michael@0 1241
michael@0 1242 addGenericProperties(bNode, node);
michael@0 1243
michael@0 1244 var parent = bNode.parent;
michael@0 1245 var grandParent = parent ? parent.parent : null;
michael@0 1246 if (grandParent)
michael@0 1247 node.grandParentId = grandParent.itemId;
michael@0 1248
michael@0 1249 if (PlacesUtils.nodeIsURI(bNode)) {
michael@0 1250 // Tag root accept only folder nodes
michael@0 1251 if (parent && parent.itemId == PlacesUtils.tagsFolderId)
michael@0 1252 return false;
michael@0 1253
michael@0 1254 // Check for url validity, since we can't halt while writing a backup.
michael@0 1255 // This will throw if we try to serialize an invalid url and it does
michael@0 1256 // not make sense saving a wrong or corrupt uri node.
michael@0 1257 try {
michael@0 1258 PlacesUtils._uri(bNode.uri);
michael@0 1259 } catch (ex) {
michael@0 1260 return false;
michael@0 1261 }
michael@0 1262
michael@0 1263 addURIProperties(bNode, node);
michael@0 1264 }
michael@0 1265 else if (PlacesUtils.nodeIsContainer(bNode)) {
michael@0 1266 // Tag containers accept only uri nodes
michael@0 1267 if (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId)
michael@0 1268 return false;
michael@0 1269
michael@0 1270 addContainerProperties(bNode, node);
michael@0 1271 }
michael@0 1272 else if (PlacesUtils.nodeIsSeparator(bNode)) {
michael@0 1273 // Tag root accept only folder nodes
michael@0 1274 // Tag containers accept only uri nodes
michael@0 1275 if ((parent && parent.itemId == PlacesUtils.tagsFolderId) ||
michael@0 1276 (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId))
michael@0 1277 return false;
michael@0 1278
michael@0 1279 addSeparatorProperties(bNode, node);
michael@0 1280 }
michael@0 1281
michael@0 1282 if (!node.feedURI && node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
michael@0 1283 return appendConvertedComplexNode(node, bNode, aArray);
michael@0 1284
michael@0 1285 aArray.push(node);
michael@0 1286 return true;
michael@0 1287 }
michael@0 1288
michael@0 1289 // serialize to stream
michael@0 1290 var array = [];
michael@0 1291 if (appendConvertedNode(aNode, null, array)) {
michael@0 1292 var json = JSON.stringify(array[0]);
michael@0 1293 aStream.write(json, json.length);
michael@0 1294 }
michael@0 1295 else {
michael@0 1296 throw Cr.NS_ERROR_UNEXPECTED;
michael@0 1297 }
michael@0 1298 },
michael@0 1299
michael@0 1300 /**
michael@0 1301 * Given a uri returns list of itemIds associated to it.
michael@0 1302 *
michael@0 1303 * @param aURI
michael@0 1304 * nsIURI or spec of the page.
michael@0 1305 * @param aCallback
michael@0 1306 * Function to be called when done.
michael@0 1307 * The function will receive an array of itemIds associated to aURI and
michael@0 1308 * aURI itself.
michael@0 1309 *
michael@0 1310 * @return A object with a .cancel() method allowing to cancel the request.
michael@0 1311 *
michael@0 1312 * @note Children of live bookmarks folders are excluded. The callback function is
michael@0 1313 * not invoked if the request is cancelled or hits an error.
michael@0 1314 */
michael@0 1315 asyncGetBookmarkIds: function PU_asyncGetBookmarkIds(aURI, aCallback)
michael@0 1316 {
michael@0 1317 if (!this._asyncGetBookmarksStmt) {
michael@0 1318 let db = this.history.DBConnection;
michael@0 1319 this._asyncGetBookmarksStmt = db.createAsyncStatement(
michael@0 1320 "SELECT b.id "
michael@0 1321 + "FROM moz_bookmarks b "
michael@0 1322 + "JOIN moz_places h on h.id = b.fk "
michael@0 1323 + "WHERE h.url = :url "
michael@0 1324 );
michael@0 1325 this.registerShutdownFunction(function () {
michael@0 1326 this._asyncGetBookmarksStmt.finalize();
michael@0 1327 });
michael@0 1328 }
michael@0 1329
michael@0 1330 let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
michael@0 1331 this._asyncGetBookmarksStmt.params.url = url;
michael@0 1332
michael@0 1333 // Storage does not guarantee that invoking cancel() on a statement
michael@0 1334 // will cause a REASON_CANCELED. Thus we wrap the statement.
michael@0 1335 let stmt = new AsyncStatementCancelWrapper(this._asyncGetBookmarksStmt);
michael@0 1336 return stmt.executeAsync({
michael@0 1337 _callback: aCallback,
michael@0 1338 _itemIds: [],
michael@0 1339 handleResult: function(aResultSet) {
michael@0 1340 for (let row; (row = aResultSet.getNextRow());) {
michael@0 1341 this._itemIds.push(row.getResultByIndex(0));
michael@0 1342 }
michael@0 1343 },
michael@0 1344 handleCompletion: function(aReason)
michael@0 1345 {
michael@0 1346 if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 1347 this._callback(this._itemIds, aURI);
michael@0 1348 }
michael@0 1349 }
michael@0 1350 });
michael@0 1351 },
michael@0 1352
michael@0 1353 /**
michael@0 1354 * Lazily adds a bookmarks observer, waiting for the bookmarks service to be
michael@0 1355 * alive before registering the observer. This is especially useful in the
michael@0 1356 * startup path, to avoid initializing the service just to add an observer.
michael@0 1357 *
michael@0 1358 * @param aObserver
michael@0 1359 * Object implementing nsINavBookmarkObserver
michael@0 1360 * @param [optional]aWeakOwner
michael@0 1361 * Whether to use weak ownership.
michael@0 1362 *
michael@0 1363 * @note Correct functionality of lazy observers relies on the fact Places
michael@0 1364 * notifies categories before real observers, and uses
michael@0 1365 * PlacesCategoriesStarter component to kick-off the registration.
michael@0 1366 */
michael@0 1367 _bookmarksServiceReady: false,
michael@0 1368 _bookmarksServiceObserversQueue: [],
michael@0 1369 addLazyBookmarkObserver:
michael@0 1370 function PU_addLazyBookmarkObserver(aObserver, aWeakOwner) {
michael@0 1371 if (this._bookmarksServiceReady) {
michael@0 1372 this.bookmarks.addObserver(aObserver, aWeakOwner === true);
michael@0 1373 return;
michael@0 1374 }
michael@0 1375 this._bookmarksServiceObserversQueue.push({ observer: aObserver,
michael@0 1376 weak: aWeakOwner === true });
michael@0 1377 },
michael@0 1378
michael@0 1379 /**
michael@0 1380 * Removes a bookmarks observer added through addLazyBookmarkObserver.
michael@0 1381 *
michael@0 1382 * @param aObserver
michael@0 1383 * Object implementing nsINavBookmarkObserver
michael@0 1384 */
michael@0 1385 removeLazyBookmarkObserver:
michael@0 1386 function PU_removeLazyBookmarkObserver(aObserver) {
michael@0 1387 if (this._bookmarksServiceReady) {
michael@0 1388 this.bookmarks.removeObserver(aObserver);
michael@0 1389 return;
michael@0 1390 }
michael@0 1391 let index = -1;
michael@0 1392 for (let i = 0;
michael@0 1393 i < this._bookmarksServiceObserversQueue.length && index == -1; i++) {
michael@0 1394 if (this._bookmarksServiceObserversQueue[i].observer === aObserver)
michael@0 1395 index = i;
michael@0 1396 }
michael@0 1397 if (index != -1) {
michael@0 1398 this._bookmarksServiceObserversQueue.splice(index, 1);
michael@0 1399 }
michael@0 1400 },
michael@0 1401
michael@0 1402 /**
michael@0 1403 * Sets the character-set for a URI.
michael@0 1404 *
michael@0 1405 * @param aURI nsIURI
michael@0 1406 * @param aCharset character-set value.
michael@0 1407 * @return {Promise}
michael@0 1408 */
michael@0 1409 setCharsetForURI: function PU_setCharsetForURI(aURI, aCharset) {
michael@0 1410 let deferred = Promise.defer();
michael@0 1411
michael@0 1412 // Delaying to catch issues with asynchronous behavior while waiting
michael@0 1413 // to implement asynchronous annotations in bug 699844.
michael@0 1414 Services.tm.mainThread.dispatch(function() {
michael@0 1415 if (aCharset && aCharset.length > 0) {
michael@0 1416 PlacesUtils.annotations.setPageAnnotation(
michael@0 1417 aURI, PlacesUtils.CHARSET_ANNO, aCharset, 0,
michael@0 1418 Ci.nsIAnnotationService.EXPIRE_NEVER);
michael@0 1419 } else {
michael@0 1420 PlacesUtils.annotations.removePageAnnotation(
michael@0 1421 aURI, PlacesUtils.CHARSET_ANNO);
michael@0 1422 }
michael@0 1423 deferred.resolve();
michael@0 1424 }, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 1425
michael@0 1426 return deferred.promise;
michael@0 1427 },
michael@0 1428
michael@0 1429 /**
michael@0 1430 * Gets the last saved character-set for a URI.
michael@0 1431 *
michael@0 1432 * @param aURI nsIURI
michael@0 1433 * @return {Promise}
michael@0 1434 * @resolve a character-set or null.
michael@0 1435 */
michael@0 1436 getCharsetForURI: function PU_getCharsetForURI(aURI) {
michael@0 1437 let deferred = Promise.defer();
michael@0 1438
michael@0 1439 Services.tm.mainThread.dispatch(function() {
michael@0 1440 let charset = null;
michael@0 1441
michael@0 1442 try {
michael@0 1443 charset = PlacesUtils.annotations.getPageAnnotation(aURI,
michael@0 1444 PlacesUtils.CHARSET_ANNO);
michael@0 1445 } catch (ex) { }
michael@0 1446
michael@0 1447 deferred.resolve(charset);
michael@0 1448 }, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 1449
michael@0 1450 return deferred.promise;
michael@0 1451 },
michael@0 1452
michael@0 1453 /**
michael@0 1454 * Promised wrapper for mozIAsyncHistory::updatePlaces for a single place.
michael@0 1455 *
michael@0 1456 * @param aPlaces
michael@0 1457 * a single mozIPlaceInfo object
michael@0 1458 * @resolves {Promise}
michael@0 1459 */
michael@0 1460 promiseUpdatePlace: function PU_promiseUpdatePlaces(aPlace) {
michael@0 1461 let deferred = Promise.defer();
michael@0 1462 PlacesUtils.asyncHistory.updatePlaces(aPlace, {
michael@0 1463 _placeInfo: null,
michael@0 1464 handleResult: function handleResult(aPlaceInfo) {
michael@0 1465 this._placeInfo = aPlaceInfo;
michael@0 1466 },
michael@0 1467 handleError: function handleError(aResultCode, aPlaceInfo) {
michael@0 1468 deferred.reject(new Components.Exception("Error", aResultCode));
michael@0 1469 },
michael@0 1470 handleCompletion: function() {
michael@0 1471 deferred.resolve(this._placeInfo);
michael@0 1472 }
michael@0 1473 });
michael@0 1474
michael@0 1475 return deferred.promise;
michael@0 1476 },
michael@0 1477
michael@0 1478 /**
michael@0 1479 * Promised wrapper for mozIAsyncHistory::getPlacesInfo for a single place.
michael@0 1480 *
michael@0 1481 * @param aPlaceIdentifier
michael@0 1482 * either an nsIURI or a GUID (@see getPlacesInfo)
michael@0 1483 * @resolves to the place info object handed to handleResult.
michael@0 1484 */
michael@0 1485 promisePlaceInfo: function PU_promisePlaceInfo(aPlaceIdentifier) {
michael@0 1486 let deferred = Promise.defer();
michael@0 1487 PlacesUtils.asyncHistory.getPlacesInfo(aPlaceIdentifier, {
michael@0 1488 _placeInfo: null,
michael@0 1489 handleResult: function handleResult(aPlaceInfo) {
michael@0 1490 this._placeInfo = aPlaceInfo;
michael@0 1491 },
michael@0 1492 handleError: function handleError(aResultCode, aPlaceInfo) {
michael@0 1493 deferred.reject(new Components.Exception("Error", aResultCode));
michael@0 1494 },
michael@0 1495 handleCompletion: function() {
michael@0 1496 deferred.resolve(this._placeInfo);
michael@0 1497 }
michael@0 1498 });
michael@0 1499
michael@0 1500 return deferred.promise;
michael@0 1501 },
michael@0 1502
michael@0 1503 /**
michael@0 1504 * Gets favicon data for a given page url.
michael@0 1505 *
michael@0 1506 * @param aPageUrl url of the page to look favicon for.
michael@0 1507 * @resolves to an object representing a favicon entry, having the following
michael@0 1508 * properties: { uri, dataLen, data, mimeType }
michael@0 1509 * @rejects JavaScript exception if the given url has no associated favicon.
michael@0 1510 */
michael@0 1511 promiseFaviconData: function (aPageUrl) {
michael@0 1512 let deferred = Promise.defer();
michael@0 1513 PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(aPageUrl),
michael@0 1514 function (aURI, aDataLen, aData, aMimeType) {
michael@0 1515 if (aURI) {
michael@0 1516 deferred.resolve({ uri: aURI,
michael@0 1517 dataLen: aDataLen,
michael@0 1518 data: aData,
michael@0 1519 mimeType: aMimeType });
michael@0 1520 } else {
michael@0 1521 deferred.reject();
michael@0 1522 }
michael@0 1523 });
michael@0 1524 return deferred.promise;
michael@0 1525 },
michael@0 1526
michael@0 1527 /**
michael@0 1528 * Get the unique id for an item (a bookmark, a folder or a separator) given
michael@0 1529 * its item id.
michael@0 1530 *
michael@0 1531 * @param aItemId
michael@0 1532 * an item id
michael@0 1533 * @return {Promise}
michael@0 1534 * @resolves to the GUID.
michael@0 1535 * @rejects if aItemId is invalid.
michael@0 1536 */
michael@0 1537 promiseItemGUID: function (aItemId) GUIDHelper.getItemGUID(aItemId),
michael@0 1538
michael@0 1539 /**
michael@0 1540 * Get the item id for an item (a bookmark, a folder or a separator) given
michael@0 1541 * its unique id.
michael@0 1542 *
michael@0 1543 * @param aGUID
michael@0 1544 * an item GUID
michael@0 1545 * @retrun {Promise}
michael@0 1546 * @resolves to the GUID.
michael@0 1547 * @rejects if there's no item for the given GUID.
michael@0 1548 */
michael@0 1549 promiseItemId: function (aGUID) GUIDHelper.getItemId(aGUID)
michael@0 1550 };
michael@0 1551
michael@0 1552 /**
michael@0 1553 * Wraps the provided statement so that invoking cancel() on the pending
michael@0 1554 * statement object will always cause a REASON_CANCELED.
michael@0 1555 */
michael@0 1556 function AsyncStatementCancelWrapper(aStmt) {
michael@0 1557 this._stmt = aStmt;
michael@0 1558 }
michael@0 1559 AsyncStatementCancelWrapper.prototype = {
michael@0 1560 _canceled: false,
michael@0 1561 _cancel: function() {
michael@0 1562 this._canceled = true;
michael@0 1563 this._pendingStmt.cancel();
michael@0 1564 },
michael@0 1565 handleResult: function(aResultSet) {
michael@0 1566 this._callback.handleResult(aResultSet);
michael@0 1567 },
michael@0 1568 handleError: function(aError) {
michael@0 1569 Cu.reportError("Async statement execution returned (" + aError.result +
michael@0 1570 "): " + aError.message);
michael@0 1571 },
michael@0 1572 handleCompletion: function(aReason)
michael@0 1573 {
michael@0 1574 let reason = this._canceled ?
michael@0 1575 Ci.mozIStorageStatementCallback.REASON_CANCELED :
michael@0 1576 aReason;
michael@0 1577 this._callback.handleCompletion(reason);
michael@0 1578 },
michael@0 1579 executeAsync: function(aCallback) {
michael@0 1580 this._pendingStmt = this._stmt.executeAsync(this);
michael@0 1581 this._callback = aCallback;
michael@0 1582 let self = this;
michael@0 1583 return { cancel: function () { self._cancel(); } }
michael@0 1584 }
michael@0 1585 }
michael@0 1586
michael@0 1587 XPCOMUtils.defineLazyGetter(PlacesUtils, "history", function() {
michael@0 1588 return Cc["@mozilla.org/browser/nav-history-service;1"]
michael@0 1589 .getService(Ci.nsINavHistoryService)
michael@0 1590 .QueryInterface(Ci.nsIBrowserHistory)
michael@0 1591 .QueryInterface(Ci.nsPIPlacesDatabase);
michael@0 1592 });
michael@0 1593
michael@0 1594 XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "asyncHistory",
michael@0 1595 "@mozilla.org/browser/history;1",
michael@0 1596 "mozIAsyncHistory");
michael@0 1597
michael@0 1598 XPCOMUtils.defineLazyGetter(PlacesUtils, "bhistory", function() {
michael@0 1599 return PlacesUtils.history;
michael@0 1600 });
michael@0 1601
michael@0 1602 XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "favicons",
michael@0 1603 "@mozilla.org/browser/favicon-service;1",
michael@0 1604 "mozIAsyncFavicons");
michael@0 1605
michael@0 1606 XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "bookmarks",
michael@0 1607 "@mozilla.org/browser/nav-bookmarks-service;1",
michael@0 1608 "nsINavBookmarksService");
michael@0 1609
michael@0 1610 XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "annotations",
michael@0 1611 "@mozilla.org/browser/annotation-service;1",
michael@0 1612 "nsIAnnotationService");
michael@0 1613
michael@0 1614 XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "tagging",
michael@0 1615 "@mozilla.org/browser/tagging-service;1",
michael@0 1616 "nsITaggingService");
michael@0 1617
michael@0 1618 XPCOMUtils.defineLazyGetter(PlacesUtils, "livemarks", function() {
michael@0 1619 return Cc["@mozilla.org/browser/livemark-service;2"].
michael@0 1620 getService(Ci.mozIAsyncLivemarks);
michael@0 1621 });
michael@0 1622
michael@0 1623 XPCOMUtils.defineLazyGetter(PlacesUtils, "transactionManager", function() {
michael@0 1624 let tm = Cc["@mozilla.org/transactionmanager;1"].
michael@0 1625 createInstance(Ci.nsITransactionManager);
michael@0 1626 tm.AddListener(PlacesUtils);
michael@0 1627 this.registerShutdownFunction(function () {
michael@0 1628 // Clear all references to local transactions in the transaction manager,
michael@0 1629 // this prevents from leaking it.
michael@0 1630 this.transactionManager.RemoveListener(this);
michael@0 1631 this.transactionManager.clear();
michael@0 1632 });
michael@0 1633
michael@0 1634 // Bug 750269
michael@0 1635 // The transaction manager keeps strong references to transactions, and by
michael@0 1636 // that, also to the global for each transaction. A transaction, however,
michael@0 1637 // could be either the transaction itself (for which the global is this
michael@0 1638 // module) or some js-proxy in another global, usually a window. The later
michael@0 1639 // would leak because the transaction lifetime (in the manager's stacks)
michael@0 1640 // is independent of the global from which doTransaction was called.
michael@0 1641 // To avoid such a leak, we hide the native doTransaction from callers,
michael@0 1642 // and let each doTransaction call go through this module.
michael@0 1643 // Doing so ensures that, as long as the transaction is any of the
michael@0 1644 // PlacesXXXTransaction objects declared in this module, the object
michael@0 1645 // referenced by the transaction manager has the module itself as global.
michael@0 1646 return Object.create(tm, {
michael@0 1647 "doTransaction": {
michael@0 1648 value: function(aTransaction) {
michael@0 1649 tm.doTransaction(aTransaction);
michael@0 1650 }
michael@0 1651 }
michael@0 1652 });
michael@0 1653 });
michael@0 1654
michael@0 1655 XPCOMUtils.defineLazyGetter(this, "bundle", function() {
michael@0 1656 const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
michael@0 1657 return Cc["@mozilla.org/intl/stringbundle;1"].
michael@0 1658 getService(Ci.nsIStringBundleService).
michael@0 1659 createBundle(PLACES_STRING_BUNDLE_URI);
michael@0 1660 });
michael@0 1661
michael@0 1662 XPCOMUtils.defineLazyServiceGetter(this, "focusManager",
michael@0 1663 "@mozilla.org/focus-manager;1",
michael@0 1664 "nsIFocusManager");
michael@0 1665
michael@0 1666 // Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
michael@0 1667 // itemIds will be deprecated in favour of GUIDs, which play much better
michael@0 1668 // with multiple undo/redo operations. Because these GUIDs are already stored,
michael@0 1669 // and because we don't want to revise the transactions API once more when this
michael@0 1670 // happens, transactions are set to work with GUIDs exclusively, in the sense
michael@0 1671 // that they may never expose itemIds, nor do they accept them as input.
michael@0 1672 // More importantly, transactions which add or remove items guarantee to
michael@0 1673 // restore the guids on undo/redo, so that the following transactions that may
michael@0 1674 // done or undo can assume the items they're interested in are stil accessible
michael@0 1675 // through the same GUID.
michael@0 1676 // The current bookmarks API, however, doesn't expose the necessary means for
michael@0 1677 // working with GUIDs. So, until it does, this helper object accesses the
michael@0 1678 // Places database directly in order to switch between GUIDs and itemIds, and
michael@0 1679 // "restore" GUIDs on items re-created items.
michael@0 1680 const REASON_FINISHED = Ci.mozIStorageStatementCallback.REASON_FINISHED;
michael@0 1681 let GUIDHelper = {
michael@0 1682 // Cache for guid<->itemId paris.
michael@0 1683 GUIDsForIds: new Map(),
michael@0 1684 idsForGUIDs: new Map(),
michael@0 1685
michael@0 1686 getItemId: function (aGUID) {
michael@0 1687 if (this.idsForGUIDs.has(aGUID))
michael@0 1688 return Promise.resolve(this.idsForGUIDs.get(aGUID));
michael@0 1689
michael@0 1690 let deferred = Promise.defer();
michael@0 1691 let itemId = -1;
michael@0 1692
michael@0 1693 this._getIDStatement.params.guid = aGUID;
michael@0 1694 this._getIDStatement.executeAsync({
michael@0 1695 handleResult: function (aResultSet) {
michael@0 1696 let row = aResultSet.getNextRow();
michael@0 1697 if (row)
michael@0 1698 itemId = row.getResultByIndex(0);
michael@0 1699 },
michael@0 1700 handleCompletion: aReason => {
michael@0 1701 if (aReason == REASON_FINISHED && itemId != -1) {
michael@0 1702 this.ensureObservingRemovedItems();
michael@0 1703 this.idsForGUIDs.set(aGUID, itemId);
michael@0 1704
michael@0 1705 deferred.resolve(itemId);
michael@0 1706 }
michael@0 1707 else if (itemId != -1) {
michael@0 1708 deferred.reject("no item found for the given guid");
michael@0 1709 }
michael@0 1710 else {
michael@0 1711 deferred.reject("SQLite Error: " + aReason);
michael@0 1712 }
michael@0 1713 }
michael@0 1714 });
michael@0 1715
michael@0 1716 return deferred.promise;
michael@0 1717 },
michael@0 1718
michael@0 1719 getItemGUID: function (aItemId) {
michael@0 1720 if (this.GUIDsForIds.has(aItemId))
michael@0 1721 return Promise.resolve(this.GUIDsForIds.get(aItemId));
michael@0 1722
michael@0 1723 let deferred = Promise.defer();
michael@0 1724 let guid = "";
michael@0 1725
michael@0 1726 this._getGUIDStatement.params.id = aItemId;
michael@0 1727 this._getGUIDStatement.executeAsync({
michael@0 1728 handleResult: function (aResultSet) {
michael@0 1729 let row = aResultSet.getNextRow();
michael@0 1730 if (row) {
michael@0 1731 guid = row.getResultByIndex(1);
michael@0 1732 }
michael@0 1733 },
michael@0 1734 handleCompletion: aReason => {
michael@0 1735 if (aReason == REASON_FINISHED && guid) {
michael@0 1736 this.ensureObservingRemovedItems();
michael@0 1737 this.GUIDsForIds.set(aItemId, guid);
michael@0 1738
michael@0 1739 deferred.resolve(guid);
michael@0 1740 }
michael@0 1741 else if (!guid) {
michael@0 1742 deferred.reject("no item found for the given itemId");
michael@0 1743 }
michael@0 1744 else {
michael@0 1745 deferred.reject("SQLite Error: " + aReason);
michael@0 1746 }
michael@0 1747 }
michael@0 1748 });
michael@0 1749
michael@0 1750 return deferred.promise;
michael@0 1751 },
michael@0 1752
michael@0 1753 ensureObservingRemovedItems: function () {
michael@0 1754 if (!("observer" in this)) {
michael@0 1755 /**
michael@0 1756 * This observers serves two purposes:
michael@0 1757 * (1) Invalidate cached id<->guid paris on when items are removed.
michael@0 1758 * (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved.
michael@0 1759 * So, for exmaple, when the NewBookmark needs the new GUID, we already
michael@0 1760 * have it cached.
michael@0 1761 */
michael@0 1762 this.observer = {
michael@0 1763 onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle,
michael@0 1764 aDateAdded, aGUID, aParentGUID) => {
michael@0 1765 this.GUIDsForIds.set(aItemId, aGUID);
michael@0 1766 this.GUIDsForIds.set(aParentId, aParentGUID);
michael@0 1767 },
michael@0 1768 onItemRemoved:
michael@0 1769 (aItemId, aParentId, aIndex, aItemTyep, aURI, aGUID, aParentGUID) => {
michael@0 1770 this.GUIDsForIds.delete(aItemId);
michael@0 1771 this.idsForGUIDs.delete(aGUID);
michael@0 1772 this.GUIDsForIds.set(aParentId, aParentGUID);
michael@0 1773 },
michael@0 1774
michael@0 1775 QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
michael@0 1776 __noSuchMethod__: () => {}, // Catch all all onItem* methods.
michael@0 1777 };
michael@0 1778 PlacesUtils.bookmarks.addObserver(this.observer, false);
michael@0 1779 PlacesUtils.registerShutdownFunction(() => {
michael@0 1780 PlacesUtils.bookmarks.removeObserver(this.observer);
michael@0 1781 });
michael@0 1782 }
michael@0 1783 }
michael@0 1784 };
michael@0 1785 XPCOMUtils.defineLazyGetter(GUIDHelper, "_getIDStatement", () => {
michael@0 1786 let s = PlacesUtils.history.DBConnection.createAsyncStatement(
michael@0 1787 "SELECT b.id, b.guid from moz_bookmarks b WHERE b.guid = :guid");
michael@0 1788 PlacesUtils.registerShutdownFunction( () => s.finalize() );
michael@0 1789 return s;
michael@0 1790 });
michael@0 1791 XPCOMUtils.defineLazyGetter(GUIDHelper, "_getGUIDStatement", () => {
michael@0 1792 let s = PlacesUtils.history.DBConnection.createAsyncStatement(
michael@0 1793 "SELECT b.id, b.guid from moz_bookmarks b WHERE b.id = :id");
michael@0 1794 PlacesUtils.registerShutdownFunction( () => s.finalize() );
michael@0 1795 return s;
michael@0 1796 });
michael@0 1797
michael@0 1798 ////////////////////////////////////////////////////////////////////////////////
michael@0 1799 //// Transactions handlers.
michael@0 1800
michael@0 1801 /**
michael@0 1802 * Updates commands in the undo group of the active window commands.
michael@0 1803 * Inactive windows commands will be updated on focus.
michael@0 1804 */
michael@0 1805 function updateCommandsOnActiveWindow()
michael@0 1806 {
michael@0 1807 let win = focusManager.activeWindow;
michael@0 1808 if (win && win instanceof Ci.nsIDOMWindow) {
michael@0 1809 // Updating "undo" will cause a group update including "redo".
michael@0 1810 win.updateCommands("undo");
michael@0 1811 }
michael@0 1812 }
michael@0 1813
michael@0 1814
michael@0 1815 /**
michael@0 1816 * Used to cache bookmark information in transactions.
michael@0 1817 *
michael@0 1818 * @note To avoid leaks any non-primitive property should be copied.
michael@0 1819 * @note Used internally, DO NOT EXPORT.
michael@0 1820 */
michael@0 1821 function TransactionItemCache()
michael@0 1822 {
michael@0 1823 }
michael@0 1824
michael@0 1825 TransactionItemCache.prototype = {
michael@0 1826 set id(v)
michael@0 1827 this._id = (parseInt(v) > 0 ? v : null),
michael@0 1828 get id()
michael@0 1829 this._id || -1,
michael@0 1830 set parentId(v)
michael@0 1831 this._parentId = (parseInt(v) > 0 ? v : null),
michael@0 1832 get parentId()
michael@0 1833 this._parentId || -1,
michael@0 1834 keyword: null,
michael@0 1835 title: null,
michael@0 1836 dateAdded: null,
michael@0 1837 lastModified: null,
michael@0 1838 postData: null,
michael@0 1839 itemType: null,
michael@0 1840 set uri(v)
michael@0 1841 this._uri = (v instanceof Ci.nsIURI ? v.clone() : null),
michael@0 1842 get uri()
michael@0 1843 this._uri || null,
michael@0 1844 set feedURI(v)
michael@0 1845 this._feedURI = (v instanceof Ci.nsIURI ? v.clone() : null),
michael@0 1846 get feedURI()
michael@0 1847 this._feedURI || null,
michael@0 1848 set siteURI(v)
michael@0 1849 this._siteURI = (v instanceof Ci.nsIURI ? v.clone() : null),
michael@0 1850 get siteURI()
michael@0 1851 this._siteURI || null,
michael@0 1852 set index(v)
michael@0 1853 this._index = (parseInt(v) >= 0 ? v : null),
michael@0 1854 // Index can be 0.
michael@0 1855 get index()
michael@0 1856 this._index != null ? this._index : PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 1857 set annotations(v)
michael@0 1858 this._annotations = Array.isArray(v) ? Cu.cloneInto(v, {}) : null,
michael@0 1859 get annotations()
michael@0 1860 this._annotations || null,
michael@0 1861 set tags(v)
michael@0 1862 this._tags = (v && Array.isArray(v) ? Array.slice(v) : null),
michael@0 1863 get tags()
michael@0 1864 this._tags || null,
michael@0 1865 };
michael@0 1866
michael@0 1867
michael@0 1868 /**
michael@0 1869 * Base transaction implementation.
michael@0 1870 *
michael@0 1871 * @note used internally, DO NOT EXPORT.
michael@0 1872 */
michael@0 1873 function BaseTransaction()
michael@0 1874 {
michael@0 1875 }
michael@0 1876
michael@0 1877 BaseTransaction.prototype = {
michael@0 1878 name: null,
michael@0 1879 set childTransactions(v)
michael@0 1880 this._childTransactions = (Array.isArray(v) ? Array.slice(v) : null),
michael@0 1881 get childTransactions()
michael@0 1882 this._childTransactions || null,
michael@0 1883 doTransaction: function BTXN_doTransaction() {},
michael@0 1884 redoTransaction: function BTXN_redoTransaction() this.doTransaction(),
michael@0 1885 undoTransaction: function BTXN_undoTransaction() {},
michael@0 1886 merge: function BTXN_merge() false,
michael@0 1887 get isTransient() false,
michael@0 1888 QueryInterface: XPCOMUtils.generateQI([
michael@0 1889 Ci.nsITransaction
michael@0 1890 ]),
michael@0 1891 };
michael@0 1892
michael@0 1893
michael@0 1894 /**
michael@0 1895 * Transaction for performing several Places Transactions in a single batch.
michael@0 1896 *
michael@0 1897 * @param aName
michael@0 1898 * title of the aggregate transactions
michael@0 1899 * @param aTransactions
michael@0 1900 * an array of transactions to perform
michael@0 1901 *
michael@0 1902 * @return nsITransaction object
michael@0 1903 */
michael@0 1904 this.PlacesAggregatedTransaction =
michael@0 1905 function PlacesAggregatedTransaction(aName, aTransactions)
michael@0 1906 {
michael@0 1907 // Copy the transactions array to decouple it from its prototype, which
michael@0 1908 // otherwise keeps alive its associated global object.
michael@0 1909 this.childTransactions = aTransactions;
michael@0 1910 this.name = aName;
michael@0 1911 this.item = new TransactionItemCache();
michael@0 1912
michael@0 1913 // Check child transactions number. We will batch if we have more than
michael@0 1914 // MIN_TRANSACTIONS_FOR_BATCH total number of transactions.
michael@0 1915 let countTransactions = function(aTransactions, aTxnCount)
michael@0 1916 {
michael@0 1917 for (let i = 0;
michael@0 1918 i < aTransactions.length && aTxnCount < MIN_TRANSACTIONS_FOR_BATCH;
michael@0 1919 ++i, ++aTxnCount) {
michael@0 1920 let txn = aTransactions[i];
michael@0 1921 if (txn.childTransactions && txn.childTransactions.length > 0)
michael@0 1922 aTxnCount = countTransactions(txn.childTransactions, aTxnCount);
michael@0 1923 }
michael@0 1924 return aTxnCount;
michael@0 1925 }
michael@0 1926
michael@0 1927 let txnCount = countTransactions(this.childTransactions, 0);
michael@0 1928 this._useBatch = txnCount >= MIN_TRANSACTIONS_FOR_BATCH;
michael@0 1929 }
michael@0 1930
michael@0 1931 PlacesAggregatedTransaction.prototype = {
michael@0 1932 __proto__: BaseTransaction.prototype,
michael@0 1933
michael@0 1934 doTransaction: function ATXN_doTransaction()
michael@0 1935 {
michael@0 1936 this._isUndo = false;
michael@0 1937 if (this._useBatch)
michael@0 1938 PlacesUtils.bookmarks.runInBatchMode(this, null);
michael@0 1939 else
michael@0 1940 this.runBatched(false);
michael@0 1941 },
michael@0 1942
michael@0 1943 undoTransaction: function ATXN_undoTransaction()
michael@0 1944 {
michael@0 1945 this._isUndo = true;
michael@0 1946 if (this._useBatch)
michael@0 1947 PlacesUtils.bookmarks.runInBatchMode(this, null);
michael@0 1948 else
michael@0 1949 this.runBatched(true);
michael@0 1950 },
michael@0 1951
michael@0 1952 runBatched: function ATXN_runBatched()
michael@0 1953 {
michael@0 1954 // Use a copy of the transactions array, so we won't reverse the original
michael@0 1955 // one on undoing.
michael@0 1956 let transactions = this.childTransactions.slice(0);
michael@0 1957 if (this._isUndo)
michael@0 1958 transactions.reverse();
michael@0 1959 for (let i = 0; i < transactions.length; ++i) {
michael@0 1960 let txn = transactions[i];
michael@0 1961 if (this.item.parentId != -1)
michael@0 1962 txn.item.parentId = this.item.parentId;
michael@0 1963 if (this._isUndo)
michael@0 1964 txn.undoTransaction();
michael@0 1965 else
michael@0 1966 txn.doTransaction();
michael@0 1967 }
michael@0 1968 }
michael@0 1969 };
michael@0 1970
michael@0 1971
michael@0 1972 /**
michael@0 1973 * Transaction for creating a new folder.
michael@0 1974 *
michael@0 1975 * @param aTitle
michael@0 1976 * the title for the new folder
michael@0 1977 * @param aParentId
michael@0 1978 * the id of the parent folder in which the new folder should be added
michael@0 1979 * @param [optional] aIndex
michael@0 1980 * the index of the item in aParentId
michael@0 1981 * @param [optional] aAnnotations
michael@0 1982 * array of annotations to set for the new folder
michael@0 1983 * @param [optional] aChildTransactions
michael@0 1984 * array of transactions for items to be created in the new folder
michael@0 1985 *
michael@0 1986 * @return nsITransaction object
michael@0 1987 */
michael@0 1988 this.PlacesCreateFolderTransaction =
michael@0 1989 function PlacesCreateFolderTransaction(aTitle, aParentId, aIndex, aAnnotations,
michael@0 1990 aChildTransactions)
michael@0 1991 {
michael@0 1992 this.item = new TransactionItemCache();
michael@0 1993 this.item.title = aTitle;
michael@0 1994 this.item.parentId = aParentId;
michael@0 1995 this.item.index = aIndex;
michael@0 1996 this.item.annotations = aAnnotations;
michael@0 1997 this.childTransactions = aChildTransactions;
michael@0 1998 }
michael@0 1999
michael@0 2000 PlacesCreateFolderTransaction.prototype = {
michael@0 2001 __proto__: BaseTransaction.prototype,
michael@0 2002
michael@0 2003 doTransaction: function CFTXN_doTransaction()
michael@0 2004 {
michael@0 2005 this.item.id = PlacesUtils.bookmarks.createFolder(this.item.parentId,
michael@0 2006 this.item.title,
michael@0 2007 this.item.index);
michael@0 2008 if (this.item.annotations && this.item.annotations.length > 0)
michael@0 2009 PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
michael@0 2010
michael@0 2011 if (this.childTransactions && this.childTransactions.length > 0) {
michael@0 2012 // Set the new parent id into child transactions.
michael@0 2013 for (let i = 0; i < this.childTransactions.length; ++i) {
michael@0 2014 this.childTransactions[i].item.parentId = this.item.id;
michael@0 2015 }
michael@0 2016
michael@0 2017 let txn = new PlacesAggregatedTransaction("Create folder childTxn",
michael@0 2018 this.childTransactions);
michael@0 2019 txn.doTransaction();
michael@0 2020 }
michael@0 2021 },
michael@0 2022
michael@0 2023 undoTransaction: function CFTXN_undoTransaction()
michael@0 2024 {
michael@0 2025 if (this.childTransactions && this.childTransactions.length > 0) {
michael@0 2026 let txn = new PlacesAggregatedTransaction("Create folder childTxn",
michael@0 2027 this.childTransactions);
michael@0 2028 txn.undoTransaction();
michael@0 2029 }
michael@0 2030
michael@0 2031 // Remove item only after all child transactions have been reverted.
michael@0 2032 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2033 }
michael@0 2034 };
michael@0 2035
michael@0 2036
michael@0 2037 /**
michael@0 2038 * Transaction for creating a new bookmark.
michael@0 2039 *
michael@0 2040 * @param aURI
michael@0 2041 * the nsIURI of the new bookmark
michael@0 2042 * @param aParentId
michael@0 2043 * the id of the folder in which the bookmark should be added.
michael@0 2044 * @param [optional] aIndex
michael@0 2045 * the index of the item in aParentId
michael@0 2046 * @param [optional] aTitle
michael@0 2047 * the title of the new bookmark
michael@0 2048 * @param [optional] aKeyword
michael@0 2049 * the keyword for the new bookmark
michael@0 2050 * @param [optional] aAnnotations
michael@0 2051 * array of annotations to set for the new bookmark
michael@0 2052 * @param [optional] aChildTransactions
michael@0 2053 * child transactions to commit after creating the bookmark. Prefer
michael@0 2054 * using any of the arguments above if possible. In general, a child
michael@0 2055 * transations should be used only if the change it does has to be
michael@0 2056 * reverted manually when removing the bookmark item.
michael@0 2057 * a child transaction must support setting its bookmark-item
michael@0 2058 * identifier via an "id" js setter.
michael@0 2059 *
michael@0 2060 * @return nsITransaction object
michael@0 2061 */
michael@0 2062 this.PlacesCreateBookmarkTransaction =
michael@0 2063 function PlacesCreateBookmarkTransaction(aURI, aParentId, aIndex, aTitle,
michael@0 2064 aKeyword, aAnnotations,
michael@0 2065 aChildTransactions)
michael@0 2066 {
michael@0 2067 this.item = new TransactionItemCache();
michael@0 2068 this.item.uri = aURI;
michael@0 2069 this.item.parentId = aParentId;
michael@0 2070 this.item.index = aIndex;
michael@0 2071 this.item.title = aTitle;
michael@0 2072 this.item.keyword = aKeyword;
michael@0 2073 this.item.annotations = aAnnotations;
michael@0 2074 this.childTransactions = aChildTransactions;
michael@0 2075 }
michael@0 2076
michael@0 2077 PlacesCreateBookmarkTransaction.prototype = {
michael@0 2078 __proto__: BaseTransaction.prototype,
michael@0 2079
michael@0 2080 doTransaction: function CITXN_doTransaction()
michael@0 2081 {
michael@0 2082 this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId,
michael@0 2083 this.item.uri,
michael@0 2084 this.item.index,
michael@0 2085 this.item.title);
michael@0 2086 if (this.item.keyword) {
michael@0 2087 PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id,
michael@0 2088 this.item.keyword);
michael@0 2089 }
michael@0 2090 if (this.item.annotations && this.item.annotations.length > 0)
michael@0 2091 PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
michael@0 2092
michael@0 2093 if (this.childTransactions && this.childTransactions.length > 0) {
michael@0 2094 // Set the new item id into child transactions.
michael@0 2095 for (let i = 0; i < this.childTransactions.length; ++i) {
michael@0 2096 this.childTransactions[i].item.id = this.item.id;
michael@0 2097 }
michael@0 2098 let txn = new PlacesAggregatedTransaction("Create item childTxn",
michael@0 2099 this.childTransactions);
michael@0 2100 txn.doTransaction();
michael@0 2101 }
michael@0 2102 },
michael@0 2103
michael@0 2104 undoTransaction: function CITXN_undoTransaction()
michael@0 2105 {
michael@0 2106 if (this.childTransactions && this.childTransactions.length > 0) {
michael@0 2107 // Undo transactions should always be done in reverse order.
michael@0 2108 let txn = new PlacesAggregatedTransaction("Create item childTxn",
michael@0 2109 this.childTransactions);
michael@0 2110 txn.undoTransaction();
michael@0 2111 }
michael@0 2112
michael@0 2113 // Remove item only after all child transactions have been reverted.
michael@0 2114 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2115 }
michael@0 2116 };
michael@0 2117
michael@0 2118
michael@0 2119 /**
michael@0 2120 * Transaction for creating a new separator.
michael@0 2121 *
michael@0 2122 * @param aParentId
michael@0 2123 * the id of the folder in which the separator should be added
michael@0 2124 * @param [optional] aIndex
michael@0 2125 * the index of the item in aParentId
michael@0 2126 *
michael@0 2127 * @return nsITransaction object
michael@0 2128 */
michael@0 2129 this.PlacesCreateSeparatorTransaction =
michael@0 2130 function PlacesCreateSeparatorTransaction(aParentId, aIndex)
michael@0 2131 {
michael@0 2132 this.item = new TransactionItemCache();
michael@0 2133 this.item.parentId = aParentId;
michael@0 2134 this.item.index = aIndex;
michael@0 2135 }
michael@0 2136
michael@0 2137 PlacesCreateSeparatorTransaction.prototype = {
michael@0 2138 __proto__: BaseTransaction.prototype,
michael@0 2139
michael@0 2140 doTransaction: function CSTXN_doTransaction()
michael@0 2141 {
michael@0 2142 this.item.id =
michael@0 2143 PlacesUtils.bookmarks.insertSeparator(this.item.parentId, this.item.index);
michael@0 2144 },
michael@0 2145
michael@0 2146 undoTransaction: function CSTXN_undoTransaction()
michael@0 2147 {
michael@0 2148 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2149 }
michael@0 2150 };
michael@0 2151
michael@0 2152
michael@0 2153 /**
michael@0 2154 * Transaction for creating a new livemark item.
michael@0 2155 *
michael@0 2156 * @see mozIAsyncLivemarks for documentation regarding the arguments.
michael@0 2157 *
michael@0 2158 * @param aFeedURI
michael@0 2159 * nsIURI of the feed
michael@0 2160 * @param [optional] aSiteURI
michael@0 2161 * nsIURI of the page serving the feed
michael@0 2162 * @param aTitle
michael@0 2163 * title for the livemark
michael@0 2164 * @param aParentId
michael@0 2165 * the id of the folder in which the livemark should be added
michael@0 2166 * @param [optional] aIndex
michael@0 2167 * the index of the livemark in aParentId
michael@0 2168 * @param [optional] aAnnotations
michael@0 2169 * array of annotations to set for the new livemark.
michael@0 2170 *
michael@0 2171 * @return nsITransaction object
michael@0 2172 */
michael@0 2173 this.PlacesCreateLivemarkTransaction =
michael@0 2174 function PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aTitle, aParentId,
michael@0 2175 aIndex, aAnnotations)
michael@0 2176 {
michael@0 2177 this.item = new TransactionItemCache();
michael@0 2178 this.item.feedURI = aFeedURI;
michael@0 2179 this.item.siteURI = aSiteURI;
michael@0 2180 this.item.title = aTitle;
michael@0 2181 this.item.parentId = aParentId;
michael@0 2182 this.item.index = aIndex;
michael@0 2183 this.item.annotations = aAnnotations;
michael@0 2184 }
michael@0 2185
michael@0 2186 PlacesCreateLivemarkTransaction.prototype = {
michael@0 2187 __proto__: BaseTransaction.prototype,
michael@0 2188
michael@0 2189 doTransaction: function CLTXN_doTransaction()
michael@0 2190 {
michael@0 2191 PlacesUtils.livemarks.addLivemark(
michael@0 2192 { title: this.item.title
michael@0 2193 , feedURI: this.item.feedURI
michael@0 2194 , parentId: this.item.parentId
michael@0 2195 , index: this.item.index
michael@0 2196 , siteURI: this.item.siteURI
michael@0 2197 }).then(aLivemark => {
michael@0 2198 this.item.id = aLivemark.id;
michael@0 2199 if (this.item.annotations && this.item.annotations.length > 0) {
michael@0 2200 PlacesUtils.setAnnotationsForItem(this.item.id,
michael@0 2201 this.item.annotations);
michael@0 2202 }
michael@0 2203 }, Cu.reportError);
michael@0 2204 },
michael@0 2205
michael@0 2206 undoTransaction: function CLTXN_undoTransaction()
michael@0 2207 {
michael@0 2208 // The getLivemark callback may fail, but it is used just to serialize,
michael@0 2209 // so it doesn't matter.
michael@0 2210 PlacesUtils.livemarks.getLivemark({ id: this.item.id })
michael@0 2211 .then(null, null).then( () => {
michael@0 2212 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2213 });
michael@0 2214 }
michael@0 2215 };
michael@0 2216
michael@0 2217
michael@0 2218 /**
michael@0 2219 * Transaction for removing a livemark item.
michael@0 2220 *
michael@0 2221 * @param aLivemarkId
michael@0 2222 * the identifier of the folder for the livemark.
michael@0 2223 *
michael@0 2224 * @return nsITransaction object
michael@0 2225 * @note used internally by PlacesRemoveItemTransaction, DO NOT EXPORT.
michael@0 2226 */
michael@0 2227 function PlacesRemoveLivemarkTransaction(aLivemarkId)
michael@0 2228 {
michael@0 2229 this.item = new TransactionItemCache();
michael@0 2230 this.item.id = aLivemarkId;
michael@0 2231 this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
michael@0 2232 this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
michael@0 2233
michael@0 2234 let annos = PlacesUtils.getAnnotationsForItem(this.item.id);
michael@0 2235 // Exclude livemark service annotations, those will be recreated automatically
michael@0 2236 let annosToExclude = [PlacesUtils.LMANNO_FEEDURI,
michael@0 2237 PlacesUtils.LMANNO_SITEURI];
michael@0 2238 this.item.annotations = annos.filter(function(aValue, aIndex, aArray) {
michael@0 2239 return annosToExclude.indexOf(aValue.name) == -1;
michael@0 2240 });
michael@0 2241 this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
michael@0 2242 this.item.lastModified =
michael@0 2243 PlacesUtils.bookmarks.getItemLastModified(this.item.id);
michael@0 2244 }
michael@0 2245
michael@0 2246 PlacesRemoveLivemarkTransaction.prototype = {
michael@0 2247 __proto__: BaseTransaction.prototype,
michael@0 2248
michael@0 2249 doTransaction: function RLTXN_doTransaction()
michael@0 2250 {
michael@0 2251 PlacesUtils.livemarks.getLivemark({ id: this.item.id })
michael@0 2252 .then(aLivemark => {
michael@0 2253 this.item.feedURI = aLivemark.feedURI;
michael@0 2254 this.item.siteURI = aLivemark.siteURI;
michael@0 2255 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2256 }, Cu.reportError);
michael@0 2257 },
michael@0 2258
michael@0 2259 undoTransaction: function RLTXN_undoTransaction()
michael@0 2260 {
michael@0 2261 // Undo work must be serialized, otherwise won't be able to know the
michael@0 2262 // feedURI and siteURI of the livemark.
michael@0 2263 // The getLivemark callback is expected to receive a failure status but it
michael@0 2264 // is used just to serialize, so doesn't matter.
michael@0 2265 PlacesUtils.livemarks.getLivemark({ id: this.item.id })
michael@0 2266 .then(null, () => {
michael@0 2267 PlacesUtils.livemarks.addLivemark({ parentId: this.item.parentId
michael@0 2268 , title: this.item.title
michael@0 2269 , siteURI: this.item.siteURI
michael@0 2270 , feedURI: this.item.feedURI
michael@0 2271 , index: this.item.index
michael@0 2272 , lastModified: this.item.lastModified
michael@0 2273 }).then(
michael@0 2274 aLivemark => {
michael@0 2275 let itemId = aLivemark.id;
michael@0 2276 PlacesUtils.bookmarks.setItemDateAdded(itemId, this.item.dateAdded);
michael@0 2277 PlacesUtils.setAnnotationsForItem(itemId, this.item.annotations);
michael@0 2278 }, Cu.reportError);
michael@0 2279 });
michael@0 2280 }
michael@0 2281 };
michael@0 2282
michael@0 2283
michael@0 2284 /**
michael@0 2285 * Transaction for moving an Item.
michael@0 2286 *
michael@0 2287 * @param aItemId
michael@0 2288 * the id of the item to move
michael@0 2289 * @param aNewParentId
michael@0 2290 * id of the new parent to move to
michael@0 2291 * @param aNewIndex
michael@0 2292 * index of the new position to move to
michael@0 2293 *
michael@0 2294 * @return nsITransaction object
michael@0 2295 */
michael@0 2296 this.PlacesMoveItemTransaction =
michael@0 2297 function PlacesMoveItemTransaction(aItemId, aNewParentId, aNewIndex)
michael@0 2298 {
michael@0 2299 this.item = new TransactionItemCache();
michael@0 2300 this.item.id = aItemId;
michael@0 2301 this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
michael@0 2302 this.new = new TransactionItemCache();
michael@0 2303 this.new.parentId = aNewParentId;
michael@0 2304 this.new.index = aNewIndex;
michael@0 2305 }
michael@0 2306
michael@0 2307 PlacesMoveItemTransaction.prototype = {
michael@0 2308 __proto__: BaseTransaction.prototype,
michael@0 2309
michael@0 2310 doTransaction: function MITXN_doTransaction()
michael@0 2311 {
michael@0 2312 this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id);
michael@0 2313 PlacesUtils.bookmarks.moveItem(this.item.id,
michael@0 2314 this.new.parentId, this.new.index);
michael@0 2315 this._undoIndex = PlacesUtils.bookmarks.getItemIndex(this.item.id);
michael@0 2316 },
michael@0 2317
michael@0 2318 undoTransaction: function MITXN_undoTransaction()
michael@0 2319 {
michael@0 2320 // moving down in the same parent takes in count removal of the item
michael@0 2321 // so to revert positions we must move to oldIndex + 1
michael@0 2322 if (this.new.parentId == this.item.parentId &&
michael@0 2323 this.item.index > this._undoIndex) {
michael@0 2324 PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId,
michael@0 2325 this.item.index + 1);
michael@0 2326 }
michael@0 2327 else {
michael@0 2328 PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId,
michael@0 2329 this.item.index);
michael@0 2330 }
michael@0 2331 }
michael@0 2332 };
michael@0 2333
michael@0 2334
michael@0 2335 /**
michael@0 2336 * Transaction for removing an Item
michael@0 2337 *
michael@0 2338 * @param aItemId
michael@0 2339 * id of the item to remove
michael@0 2340 *
michael@0 2341 * @return nsITransaction object
michael@0 2342 */
michael@0 2343 this.PlacesRemoveItemTransaction =
michael@0 2344 function PlacesRemoveItemTransaction(aItemId)
michael@0 2345 {
michael@0 2346 if (PlacesUtils.isRootItem(aItemId))
michael@0 2347 throw Cr.NS_ERROR_INVALID_ARG;
michael@0 2348
michael@0 2349 // if the item lives within a tag container, use the tagging transactions
michael@0 2350 let parent = PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
michael@0 2351 let grandparent = PlacesUtils.bookmarks.getFolderIdForItem(parent);
michael@0 2352 if (grandparent == PlacesUtils.tagsFolderId) {
michael@0 2353 let uri = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
michael@0 2354 return new PlacesUntagURITransaction(uri, [parent]);
michael@0 2355 }
michael@0 2356
michael@0 2357 // if the item is a livemark container we will not save its children.
michael@0 2358 if (PlacesUtils.annotations.itemHasAnnotation(aItemId,
michael@0 2359 PlacesUtils.LMANNO_FEEDURI))
michael@0 2360 return new PlacesRemoveLivemarkTransaction(aItemId);
michael@0 2361
michael@0 2362 this.item = new TransactionItemCache();
michael@0 2363 this.item.id = aItemId;
michael@0 2364 this.item.itemType = PlacesUtils.bookmarks.getItemType(this.item.id);
michael@0 2365 if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
michael@0 2366 this.childTransactions = this._getFolderContentsTransactions();
michael@0 2367 // Remove this folder itself.
michael@0 2368 let txn = PlacesUtils.bookmarks.getRemoveFolderTransaction(this.item.id);
michael@0 2369 this.childTransactions.push(txn);
michael@0 2370 }
michael@0 2371 else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
michael@0 2372 this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id);
michael@0 2373 this.item.keyword =
michael@0 2374 PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id);
michael@0 2375 }
michael@0 2376
michael@0 2377 if (this.item.itemType != Ci.nsINavBookmarksService.TYPE_SEPARATOR)
michael@0 2378 this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
michael@0 2379
michael@0 2380 this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
michael@0 2381 this.item.annotations = PlacesUtils.getAnnotationsForItem(this.item.id);
michael@0 2382 this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
michael@0 2383 this.item.lastModified =
michael@0 2384 PlacesUtils.bookmarks.getItemLastModified(this.item.id);
michael@0 2385 }
michael@0 2386
michael@0 2387 PlacesRemoveItemTransaction.prototype = {
michael@0 2388 __proto__: BaseTransaction.prototype,
michael@0 2389
michael@0 2390 doTransaction: function RITXN_doTransaction()
michael@0 2391 {
michael@0 2392 this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id);
michael@0 2393
michael@0 2394 if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
michael@0 2395 let txn = new PlacesAggregatedTransaction("Remove item childTxn",
michael@0 2396 this.childTransactions);
michael@0 2397 txn.doTransaction();
michael@0 2398 }
michael@0 2399 else {
michael@0 2400 // Before removing the bookmark, save its tags.
michael@0 2401 let tags = this.item.uri ?
michael@0 2402 PlacesUtils.tagging.getTagsForURI(this.item.uri) : null;
michael@0 2403
michael@0 2404 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2405
michael@0 2406 // If this was the last bookmark (excluding tag-items) for this url,
michael@0 2407 // persist the tags.
michael@0 2408 if (tags && PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) {
michael@0 2409 this.item.tags = tags;
michael@0 2410 }
michael@0 2411 }
michael@0 2412 },
michael@0 2413
michael@0 2414 undoTransaction: function RITXN_undoTransaction()
michael@0 2415 {
michael@0 2416 if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
michael@0 2417 this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId,
michael@0 2418 this.item.uri,
michael@0 2419 this.item.index,
michael@0 2420 this.item.title);
michael@0 2421 if (this.item.tags && this.item.tags.length > 0)
michael@0 2422 PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
michael@0 2423 if (this.item.keyword) {
michael@0 2424 PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id,
michael@0 2425 this.item.keyword);
michael@0 2426 }
michael@0 2427 }
michael@0 2428 else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
michael@0 2429 let txn = new PlacesAggregatedTransaction("Remove item childTxn",
michael@0 2430 this.childTransactions);
michael@0 2431 txn.undoTransaction();
michael@0 2432 }
michael@0 2433 else { // TYPE_SEPARATOR
michael@0 2434 this.item.id = PlacesUtils.bookmarks.insertSeparator(this.item.parentId,
michael@0 2435 this.item.index);
michael@0 2436 }
michael@0 2437
michael@0 2438 if (this.item.annotations && this.item.annotations.length > 0)
michael@0 2439 PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
michael@0 2440
michael@0 2441 PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded);
michael@0 2442 PlacesUtils.bookmarks.setItemLastModified(this.item.id,
michael@0 2443 this.item.lastModified);
michael@0 2444 },
michael@0 2445
michael@0 2446 /**
michael@0 2447 * Returns a flat, ordered list of transactions for a depth-first recreation
michael@0 2448 * of items within this folder.
michael@0 2449 */
michael@0 2450 _getFolderContentsTransactions:
michael@0 2451 function RITXN__getFolderContentsTransactions()
michael@0 2452 {
michael@0 2453 let transactions = [];
michael@0 2454 let contents =
michael@0 2455 PlacesUtils.getFolderContents(this.item.id, false, false).root;
michael@0 2456 for (let i = 0; i < contents.childCount; ++i) {
michael@0 2457 let txn = new PlacesRemoveItemTransaction(contents.getChild(i).itemId);
michael@0 2458 transactions.push(txn);
michael@0 2459 }
michael@0 2460 contents.containerOpen = false;
michael@0 2461 // Reverse transactions to preserve parent-child relationship.
michael@0 2462 return transactions.reverse();
michael@0 2463 }
michael@0 2464 };
michael@0 2465
michael@0 2466
michael@0 2467 /**
michael@0 2468 * Transaction for editting a bookmark's title.
michael@0 2469 *
michael@0 2470 * @param aItemId
michael@0 2471 * id of the item to edit
michael@0 2472 * @param aNewTitle
michael@0 2473 * new title for the item to edit
michael@0 2474 *
michael@0 2475 * @return nsITransaction object
michael@0 2476 */
michael@0 2477 this.PlacesEditItemTitleTransaction =
michael@0 2478 function PlacesEditItemTitleTransaction(aItemId, aNewTitle)
michael@0 2479 {
michael@0 2480 this.item = new TransactionItemCache();
michael@0 2481 this.item.id = aItemId;
michael@0 2482 this.new = new TransactionItemCache();
michael@0 2483 this.new.title = aNewTitle;
michael@0 2484 }
michael@0 2485
michael@0 2486 PlacesEditItemTitleTransaction.prototype = {
michael@0 2487 __proto__: BaseTransaction.prototype,
michael@0 2488
michael@0 2489 doTransaction: function EITTXN_doTransaction()
michael@0 2490 {
michael@0 2491 this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
michael@0 2492 PlacesUtils.bookmarks.setItemTitle(this.item.id, this.new.title);
michael@0 2493 },
michael@0 2494
michael@0 2495 undoTransaction: function EITTXN_undoTransaction()
michael@0 2496 {
michael@0 2497 PlacesUtils.bookmarks.setItemTitle(this.item.id, this.item.title);
michael@0 2498 }
michael@0 2499 };
michael@0 2500
michael@0 2501
michael@0 2502 /**
michael@0 2503 * Transaction for editing a bookmark's uri.
michael@0 2504 *
michael@0 2505 * @param aItemId
michael@0 2506 * id of the bookmark to edit
michael@0 2507 * @param aNewURI
michael@0 2508 * new uri for the bookmark
michael@0 2509 *
michael@0 2510 * @return nsITransaction object
michael@0 2511 */
michael@0 2512 this.PlacesEditBookmarkURITransaction =
michael@0 2513 function PlacesEditBookmarkURITransaction(aItemId, aNewURI) {
michael@0 2514 this.item = new TransactionItemCache();
michael@0 2515 this.item.id = aItemId;
michael@0 2516 this.new = new TransactionItemCache();
michael@0 2517 this.new.uri = aNewURI;
michael@0 2518 }
michael@0 2519
michael@0 2520 PlacesEditBookmarkURITransaction.prototype = {
michael@0 2521 __proto__: BaseTransaction.prototype,
michael@0 2522
michael@0 2523 doTransaction: function EBUTXN_doTransaction()
michael@0 2524 {
michael@0 2525 this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id);
michael@0 2526 PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.new.uri);
michael@0 2527 // move tags from old URI to new URI
michael@0 2528 this.item.tags = PlacesUtils.tagging.getTagsForURI(this.item.uri);
michael@0 2529 if (this.item.tags.length != 0) {
michael@0 2530 // only untag the old URI if this is the only bookmark
michael@0 2531 if (PlacesUtils.getBookmarksForURI(this.item.uri, {}).length == 0)
michael@0 2532 PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
michael@0 2533 PlacesUtils.tagging.tagURI(this.new.uri, this.item.tags);
michael@0 2534 }
michael@0 2535 },
michael@0 2536
michael@0 2537 undoTransaction: function EBUTXN_undoTransaction()
michael@0 2538 {
michael@0 2539 PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.item.uri);
michael@0 2540 // move tags from new URI to old URI
michael@0 2541 if (this.item.tags.length != 0) {
michael@0 2542 // only untag the new URI if this is the only bookmark
michael@0 2543 if (PlacesUtils.getBookmarksForURI(this.new.uri, {}).length == 0)
michael@0 2544 PlacesUtils.tagging.untagURI(this.new.uri, this.item.tags);
michael@0 2545 PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
michael@0 2546 }
michael@0 2547 }
michael@0 2548 };
michael@0 2549
michael@0 2550
michael@0 2551 /**
michael@0 2552 * Transaction for setting/unsetting an item annotation
michael@0 2553 *
michael@0 2554 * @param aItemId
michael@0 2555 * id of the item where to set annotation
michael@0 2556 * @param aAnnotationObject
michael@0 2557 * Object representing an annotation, containing the following
michael@0 2558 * properties: name, flags, expires, value.
michael@0 2559 * If value is null the annotation will be removed
michael@0 2560 *
michael@0 2561 * @return nsITransaction object
michael@0 2562 */
michael@0 2563 this.PlacesSetItemAnnotationTransaction =
michael@0 2564 function PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject)
michael@0 2565 {
michael@0 2566 this.item = new TransactionItemCache();
michael@0 2567 this.item.id = aItemId;
michael@0 2568 this.new = new TransactionItemCache();
michael@0 2569 this.new.annotations = [aAnnotationObject];
michael@0 2570 }
michael@0 2571
michael@0 2572 PlacesSetItemAnnotationTransaction.prototype = {
michael@0 2573 __proto__: BaseTransaction.prototype,
michael@0 2574
michael@0 2575 doTransaction: function SIATXN_doTransaction()
michael@0 2576 {
michael@0 2577 let annoName = this.new.annotations[0].name;
michael@0 2578 if (PlacesUtils.annotations.itemHasAnnotation(this.item.id, annoName)) {
michael@0 2579 // fill the old anno if it is set
michael@0 2580 let flags = {}, expires = {}, type = {};
michael@0 2581 PlacesUtils.annotations.getItemAnnotationInfo(this.item.id, annoName, flags,
michael@0 2582 expires, type);
michael@0 2583 let value = PlacesUtils.annotations.getItemAnnotation(this.item.id,
michael@0 2584 annoName);
michael@0 2585 this.item.annotations = [{ name: annoName,
michael@0 2586 type: type.value,
michael@0 2587 flags: flags.value,
michael@0 2588 value: value,
michael@0 2589 expires: expires.value }];
michael@0 2590 }
michael@0 2591 else {
michael@0 2592 // create an empty old anno
michael@0 2593 this.item.annotations = [{ name: annoName,
michael@0 2594 flags: 0,
michael@0 2595 value: null,
michael@0 2596 expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
michael@0 2597 }
michael@0 2598
michael@0 2599 PlacesUtils.setAnnotationsForItem(this.item.id, this.new.annotations);
michael@0 2600 },
michael@0 2601
michael@0 2602 undoTransaction: function SIATXN_undoTransaction()
michael@0 2603 {
michael@0 2604 PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
michael@0 2605 }
michael@0 2606 };
michael@0 2607
michael@0 2608
michael@0 2609 /**
michael@0 2610 * Transaction for setting/unsetting a page annotation
michael@0 2611 *
michael@0 2612 * @param aURI
michael@0 2613 * URI of the page where to set annotation
michael@0 2614 * @param aAnnotationObject
michael@0 2615 * Object representing an annotation, containing the following
michael@0 2616 * properties: name, flags, expires, value.
michael@0 2617 * If value is null the annotation will be removed
michael@0 2618 *
michael@0 2619 * @return nsITransaction object
michael@0 2620 */
michael@0 2621 this.PlacesSetPageAnnotationTransaction =
michael@0 2622 function PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject)
michael@0 2623 {
michael@0 2624 this.item = new TransactionItemCache();
michael@0 2625 this.item.uri = aURI;
michael@0 2626 this.new = new TransactionItemCache();
michael@0 2627 this.new.annotations = [aAnnotationObject];
michael@0 2628 }
michael@0 2629
michael@0 2630 PlacesSetPageAnnotationTransaction.prototype = {
michael@0 2631 __proto__: BaseTransaction.prototype,
michael@0 2632
michael@0 2633 doTransaction: function SPATXN_doTransaction()
michael@0 2634 {
michael@0 2635 let annoName = this.new.annotations[0].name;
michael@0 2636 if (PlacesUtils.annotations.pageHasAnnotation(this.item.uri, annoName)) {
michael@0 2637 // fill the old anno if it is set
michael@0 2638 let flags = {}, expires = {}, type = {};
michael@0 2639 PlacesUtils.annotations.getPageAnnotationInfo(this.item.uri, annoName, flags,
michael@0 2640 expires, type);
michael@0 2641 let value = PlacesUtils.annotations.getPageAnnotation(this.item.uri,
michael@0 2642 annoName);
michael@0 2643 this.item.annotations = [{ name: annoName,
michael@0 2644 flags: flags.value,
michael@0 2645 value: value,
michael@0 2646 expires: expires.value }];
michael@0 2647 }
michael@0 2648 else {
michael@0 2649 // create an empty old anno
michael@0 2650 this.item.annotations = [{ name: annoName,
michael@0 2651 type: Ci.nsIAnnotationService.TYPE_STRING,
michael@0 2652 flags: 0,
michael@0 2653 value: null,
michael@0 2654 expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
michael@0 2655 }
michael@0 2656
michael@0 2657 PlacesUtils.setAnnotationsForURI(this.item.uri, this.new.annotations);
michael@0 2658 },
michael@0 2659
michael@0 2660 undoTransaction: function SPATXN_undoTransaction()
michael@0 2661 {
michael@0 2662 PlacesUtils.setAnnotationsForURI(this.item.uri, this.item.annotations);
michael@0 2663 }
michael@0 2664 };
michael@0 2665
michael@0 2666
michael@0 2667 /**
michael@0 2668 * Transaction for editing a bookmark's keyword.
michael@0 2669 *
michael@0 2670 * @param aItemId
michael@0 2671 * id of the bookmark to edit
michael@0 2672 * @param aNewKeyword
michael@0 2673 * new keyword for the bookmark
michael@0 2674 *
michael@0 2675 * @return nsITransaction object
michael@0 2676 */
michael@0 2677 this.PlacesEditBookmarkKeywordTransaction =
michael@0 2678 function PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword)
michael@0 2679 {
michael@0 2680 this.item = new TransactionItemCache();
michael@0 2681 this.item.id = aItemId;
michael@0 2682 this.new = new TransactionItemCache();
michael@0 2683 this.new.keyword = aNewKeyword;
michael@0 2684 }
michael@0 2685
michael@0 2686 PlacesEditBookmarkKeywordTransaction.prototype = {
michael@0 2687 __proto__: BaseTransaction.prototype,
michael@0 2688
michael@0 2689 doTransaction: function EBKTXN_doTransaction()
michael@0 2690 {
michael@0 2691 this.item.keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id);
michael@0 2692 PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.new.keyword);
michael@0 2693 },
michael@0 2694
michael@0 2695 undoTransaction: function EBKTXN_undoTransaction()
michael@0 2696 {
michael@0 2697 PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id, this.item.keyword);
michael@0 2698 }
michael@0 2699 };
michael@0 2700
michael@0 2701
michael@0 2702 /**
michael@0 2703 * Transaction for editing the post data associated with a bookmark.
michael@0 2704 *
michael@0 2705 * @param aItemId
michael@0 2706 * id of the bookmark to edit
michael@0 2707 * @param aPostData
michael@0 2708 * post data
michael@0 2709 *
michael@0 2710 * @return nsITransaction object
michael@0 2711 */
michael@0 2712 this.PlacesEditBookmarkPostDataTransaction =
michael@0 2713 function PlacesEditBookmarkPostDataTransaction(aItemId, aPostData)
michael@0 2714 {
michael@0 2715 this.item = new TransactionItemCache();
michael@0 2716 this.item.id = aItemId;
michael@0 2717 this.new = new TransactionItemCache();
michael@0 2718 this.new.postData = aPostData;
michael@0 2719 }
michael@0 2720
michael@0 2721 PlacesEditBookmarkPostDataTransaction.prototype = {
michael@0 2722 __proto__: BaseTransaction.prototype,
michael@0 2723
michael@0 2724 doTransaction: function EBPDTXN_doTransaction()
michael@0 2725 {
michael@0 2726 this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id);
michael@0 2727 PlacesUtils.setPostDataForBookmark(this.item.id, this.new.postData);
michael@0 2728 },
michael@0 2729
michael@0 2730 undoTransaction: function EBPDTXN_undoTransaction()
michael@0 2731 {
michael@0 2732 PlacesUtils.setPostDataForBookmark(this.item.id, this.item.postData);
michael@0 2733 }
michael@0 2734 };
michael@0 2735
michael@0 2736
michael@0 2737 /**
michael@0 2738 * Transaction for editing an item's date added property.
michael@0 2739 *
michael@0 2740 * @param aItemId
michael@0 2741 * id of the item to edit
michael@0 2742 * @param aNewDateAdded
michael@0 2743 * new date added for the item
michael@0 2744 *
michael@0 2745 * @return nsITransaction object
michael@0 2746 */
michael@0 2747 this.PlacesEditItemDateAddedTransaction =
michael@0 2748 function PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded)
michael@0 2749 {
michael@0 2750 this.item = new TransactionItemCache();
michael@0 2751 this.item.id = aItemId;
michael@0 2752 this.new = new TransactionItemCache();
michael@0 2753 this.new.dateAdded = aNewDateAdded;
michael@0 2754 }
michael@0 2755
michael@0 2756 PlacesEditItemDateAddedTransaction.prototype = {
michael@0 2757 __proto__: BaseTransaction.prototype,
michael@0 2758
michael@0 2759 doTransaction: function EIDATXN_doTransaction()
michael@0 2760 {
michael@0 2761 // Child transactions have the id set as parentId.
michael@0 2762 if (this.item.id == -1 && this.item.parentId != -1)
michael@0 2763 this.item.id = this.item.parentId;
michael@0 2764 this.item.dateAdded =
michael@0 2765 PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
michael@0 2766 PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.new.dateAdded);
michael@0 2767 },
michael@0 2768
michael@0 2769 undoTransaction: function EIDATXN_undoTransaction()
michael@0 2770 {
michael@0 2771 PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded);
michael@0 2772 }
michael@0 2773 };
michael@0 2774
michael@0 2775
michael@0 2776 /**
michael@0 2777 * Transaction for editing an item's last modified time.
michael@0 2778 *
michael@0 2779 * @param aItemId
michael@0 2780 * id of the item to edit
michael@0 2781 * @param aNewLastModified
michael@0 2782 * new last modified date for the item
michael@0 2783 *
michael@0 2784 * @return nsITransaction object
michael@0 2785 */
michael@0 2786 this.PlacesEditItemLastModifiedTransaction =
michael@0 2787 function PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified)
michael@0 2788 {
michael@0 2789 this.item = new TransactionItemCache();
michael@0 2790 this.item.id = aItemId;
michael@0 2791 this.new = new TransactionItemCache();
michael@0 2792 this.new.lastModified = aNewLastModified;
michael@0 2793 }
michael@0 2794
michael@0 2795 PlacesEditItemLastModifiedTransaction.prototype = {
michael@0 2796 __proto__: BaseTransaction.prototype,
michael@0 2797
michael@0 2798 doTransaction:
michael@0 2799 function EILMTXN_doTransaction()
michael@0 2800 {
michael@0 2801 // Child transactions have the id set as parentId.
michael@0 2802 if (this.item.id == -1 && this.item.parentId != -1)
michael@0 2803 this.item.id = this.item.parentId;
michael@0 2804 this.item.lastModified =
michael@0 2805 PlacesUtils.bookmarks.getItemLastModified(this.item.id);
michael@0 2806 PlacesUtils.bookmarks.setItemLastModified(this.item.id,
michael@0 2807 this.new.lastModified);
michael@0 2808 },
michael@0 2809
michael@0 2810 undoTransaction:
michael@0 2811 function EILMTXN_undoTransaction()
michael@0 2812 {
michael@0 2813 PlacesUtils.bookmarks.setItemLastModified(this.item.id,
michael@0 2814 this.item.lastModified);
michael@0 2815 }
michael@0 2816 };
michael@0 2817
michael@0 2818
michael@0 2819 /**
michael@0 2820 * Transaction for sorting a folder by name
michael@0 2821 *
michael@0 2822 * @param aFolderId
michael@0 2823 * id of the folder to sort
michael@0 2824 *
michael@0 2825 * @return nsITransaction object
michael@0 2826 */
michael@0 2827 this.PlacesSortFolderByNameTransaction =
michael@0 2828 function PlacesSortFolderByNameTransaction(aFolderId)
michael@0 2829 {
michael@0 2830 this.item = new TransactionItemCache();
michael@0 2831 this.item.id = aFolderId;
michael@0 2832 }
michael@0 2833
michael@0 2834 PlacesSortFolderByNameTransaction.prototype = {
michael@0 2835 __proto__: BaseTransaction.prototype,
michael@0 2836
michael@0 2837 doTransaction: function SFBNTXN_doTransaction()
michael@0 2838 {
michael@0 2839 this._oldOrder = [];
michael@0 2840
michael@0 2841 let contents =
michael@0 2842 PlacesUtils.getFolderContents(this.item.id, false, false).root;
michael@0 2843 let count = contents.childCount;
michael@0 2844
michael@0 2845 // sort between separators
michael@0 2846 let newOrder = [];
michael@0 2847 let preSep = []; // temporary array for sorting each group of items
michael@0 2848 let sortingMethod =
michael@0 2849 function (a, b) {
michael@0 2850 if (PlacesUtils.nodeIsContainer(a) && !PlacesUtils.nodeIsContainer(b))
michael@0 2851 return -1;
michael@0 2852 if (!PlacesUtils.nodeIsContainer(a) && PlacesUtils.nodeIsContainer(b))
michael@0 2853 return 1;
michael@0 2854 return a.title.localeCompare(b.title);
michael@0 2855 };
michael@0 2856
michael@0 2857 for (let i = 0; i < count; ++i) {
michael@0 2858 let item = contents.getChild(i);
michael@0 2859 this._oldOrder[item.itemId] = i;
michael@0 2860 if (PlacesUtils.nodeIsSeparator(item)) {
michael@0 2861 if (preSep.length > 0) {
michael@0 2862 preSep.sort(sortingMethod);
michael@0 2863 newOrder = newOrder.concat(preSep);
michael@0 2864 preSep.splice(0, preSep.length);
michael@0 2865 }
michael@0 2866 newOrder.push(item);
michael@0 2867 }
michael@0 2868 else
michael@0 2869 preSep.push(item);
michael@0 2870 }
michael@0 2871 contents.containerOpen = false;
michael@0 2872
michael@0 2873 if (preSep.length > 0) {
michael@0 2874 preSep.sort(sortingMethod);
michael@0 2875 newOrder = newOrder.concat(preSep);
michael@0 2876 }
michael@0 2877
michael@0 2878 // set the nex indexes
michael@0 2879 let callback = {
michael@0 2880 runBatched: function() {
michael@0 2881 for (let i = 0; i < newOrder.length; ++i) {
michael@0 2882 PlacesUtils.bookmarks.setItemIndex(newOrder[i].itemId, i);
michael@0 2883 }
michael@0 2884 }
michael@0 2885 };
michael@0 2886 PlacesUtils.bookmarks.runInBatchMode(callback, null);
michael@0 2887 },
michael@0 2888
michael@0 2889 undoTransaction: function SFBNTXN_undoTransaction()
michael@0 2890 {
michael@0 2891 let callback = {
michael@0 2892 _self: this,
michael@0 2893 runBatched: function() {
michael@0 2894 for (item in this._self._oldOrder)
michael@0 2895 PlacesUtils.bookmarks.setItemIndex(item, this._self._oldOrder[item]);
michael@0 2896 }
michael@0 2897 };
michael@0 2898 PlacesUtils.bookmarks.runInBatchMode(callback, null);
michael@0 2899 }
michael@0 2900 };
michael@0 2901
michael@0 2902
michael@0 2903 /**
michael@0 2904 * Transaction for tagging a URL with the given set of tags. Current tags set
michael@0 2905 * for the URL persist. It's the caller's job to check whether or not aURI
michael@0 2906 * was already tagged by any of the tags in aTags, undoing this tags
michael@0 2907 * transaction removes them all from aURL!
michael@0 2908 *
michael@0 2909 * @param aURI
michael@0 2910 * the URL to tag.
michael@0 2911 * @param aTags
michael@0 2912 * Array of tags to set for the given URL.
michael@0 2913 */
michael@0 2914 this.PlacesTagURITransaction =
michael@0 2915 function PlacesTagURITransaction(aURI, aTags)
michael@0 2916 {
michael@0 2917 this.item = new TransactionItemCache();
michael@0 2918 this.item.uri = aURI;
michael@0 2919 this.item.tags = aTags;
michael@0 2920 }
michael@0 2921
michael@0 2922 PlacesTagURITransaction.prototype = {
michael@0 2923 __proto__: BaseTransaction.prototype,
michael@0 2924
michael@0 2925 doTransaction: function TUTXN_doTransaction()
michael@0 2926 {
michael@0 2927 if (PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) {
michael@0 2928 // There is no bookmark for this uri, but we only allow to tag bookmarks.
michael@0 2929 // Force an unfiled bookmark first.
michael@0 2930 this.item.id =
michael@0 2931 PlacesUtils.bookmarks
michael@0 2932 .insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
michael@0 2933 this.item.uri,
michael@0 2934 PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 2935 PlacesUtils.history.getPageTitle(this.item.uri));
michael@0 2936 }
michael@0 2937 PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
michael@0 2938 },
michael@0 2939
michael@0 2940 undoTransaction: function TUTXN_undoTransaction()
michael@0 2941 {
michael@0 2942 if (this.item.id != -1) {
michael@0 2943 PlacesUtils.bookmarks.removeItem(this.item.id);
michael@0 2944 this.item.id = -1;
michael@0 2945 }
michael@0 2946 PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
michael@0 2947 }
michael@0 2948 };
michael@0 2949
michael@0 2950
michael@0 2951 /**
michael@0 2952 * Transaction for removing tags from a URL. It's the caller's job to check
michael@0 2953 * whether or not aURI isn't tagged by any of the tags in aTags, undoing this
michael@0 2954 * tags transaction adds them all to aURL!
michael@0 2955 *
michael@0 2956 * @param aURI
michael@0 2957 * the URL to un-tag.
michael@0 2958 * @param aTags
michael@0 2959 * Array of tags to unset. pass null to remove all tags from the given
michael@0 2960 * url.
michael@0 2961 */
michael@0 2962 this.PlacesUntagURITransaction =
michael@0 2963 function PlacesUntagURITransaction(aURI, aTags)
michael@0 2964 {
michael@0 2965 this.item = new TransactionItemCache();
michael@0 2966 this.item.uri = aURI;
michael@0 2967 if (aTags) {
michael@0 2968 // Within this transaction, we cannot rely on tags given by itemId
michael@0 2969 // since the tag containers may be gone after we call untagURI.
michael@0 2970 // Thus, we convert each tag given by its itemId to name.
michael@0 2971 let tags = [];
michael@0 2972 for (let i = 0; i < aTags.length; ++i) {
michael@0 2973 if (typeof(aTags[i]) == "number")
michael@0 2974 tags.push(PlacesUtils.bookmarks.getItemTitle(aTags[i]));
michael@0 2975 else
michael@0 2976 tags.push(aTags[i]);
michael@0 2977 }
michael@0 2978 this.item.tags = tags;
michael@0 2979 }
michael@0 2980 }
michael@0 2981
michael@0 2982 PlacesUntagURITransaction.prototype = {
michael@0 2983 __proto__: BaseTransaction.prototype,
michael@0 2984
michael@0 2985 doTransaction: function UTUTXN_doTransaction()
michael@0 2986 {
michael@0 2987 // Filter tags existing on the bookmark, otherwise on undo we may try to
michael@0 2988 // set nonexistent tags.
michael@0 2989 let tags = PlacesUtils.tagging.getTagsForURI(this.item.uri);
michael@0 2990 this.item.tags = this.item.tags.filter(function (aTag) {
michael@0 2991 return tags.indexOf(aTag) != -1;
michael@0 2992 });
michael@0 2993 PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
michael@0 2994 },
michael@0 2995
michael@0 2996 undoTransaction: function UTUTXN_undoTransaction()
michael@0 2997 {
michael@0 2998 PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
michael@0 2999 }
michael@0 3000 };

mercurial