Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; 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 = ["PlacesUIUtils"]; |
michael@0 | 7 | |
michael@0 | 8 | var Ci = Components.interfaces; |
michael@0 | 9 | var Cc = Components.classes; |
michael@0 | 10 | var Cr = Components.results; |
michael@0 | 11 | var Cu = Components.utils; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", |
michael@0 | 17 | "resource://gre/modules/PluralForm.jsm"); |
michael@0 | 18 | |
michael@0 | 19 | XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", |
michael@0 | 20 | "resource://gre/modules/PrivateBrowsingUtils.jsm"); |
michael@0 | 21 | |
michael@0 | 22 | #ifdef MOZ_SERVICES_SYNC |
michael@0 | 23 | XPCOMUtils.defineLazyModuleGetter(this, "Weave", |
michael@0 | 24 | "resource://services-sync/main.js"); |
michael@0 | 25 | #endif |
michael@0 | 26 | |
michael@0 | 27 | XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { |
michael@0 | 28 | Cu.import("resource://gre/modules/PlacesUtils.jsm"); |
michael@0 | 29 | return PlacesUtils; |
michael@0 | 30 | }); |
michael@0 | 31 | |
michael@0 | 32 | this.PlacesUIUtils = { |
michael@0 | 33 | ORGANIZER_LEFTPANE_VERSION: 7, |
michael@0 | 34 | ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder", |
michael@0 | 35 | ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery", |
michael@0 | 36 | |
michael@0 | 37 | LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar", |
michael@0 | 38 | DESCRIPTION_ANNO: "bookmarkProperties/description", |
michael@0 | 39 | |
michael@0 | 40 | TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab", |
michael@0 | 41 | |
michael@0 | 42 | /** |
michael@0 | 43 | * Makes a URI from a spec, and do fixup |
michael@0 | 44 | * @param aSpec |
michael@0 | 45 | * The string spec of the URI |
michael@0 | 46 | * @returns A URI object for the spec. |
michael@0 | 47 | */ |
michael@0 | 48 | createFixedURI: function PUIU_createFixedURI(aSpec) { |
michael@0 | 49 | return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE); |
michael@0 | 50 | }, |
michael@0 | 51 | |
michael@0 | 52 | getFormattedString: function PUIU_getFormattedString(key, params) { |
michael@0 | 53 | return bundle.formatStringFromName(key, params, params.length); |
michael@0 | 54 | }, |
michael@0 | 55 | |
michael@0 | 56 | /** |
michael@0 | 57 | * Get a localized plural string for the specified key name and numeric value |
michael@0 | 58 | * substituting parameters. |
michael@0 | 59 | * |
michael@0 | 60 | * @param aKey |
michael@0 | 61 | * String, key for looking up the localized string in the bundle |
michael@0 | 62 | * @param aNumber |
michael@0 | 63 | * Number based on which the final localized form is looked up |
michael@0 | 64 | * @param aParams |
michael@0 | 65 | * Array whose items will substitute #1, #2,... #n parameters |
michael@0 | 66 | * in the string. |
michael@0 | 67 | * |
michael@0 | 68 | * @see https://developer.mozilla.org/en/Localization_and_Plurals |
michael@0 | 69 | * @return The localized plural string. |
michael@0 | 70 | */ |
michael@0 | 71 | getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) { |
michael@0 | 72 | let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey)); |
michael@0 | 73 | |
michael@0 | 74 | // Replace #1 with aParams[0], #2 with aParams[1], and so on. |
michael@0 | 75 | return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) { |
michael@0 | 76 | let param = aParams[parseInt(matchedNumber, 10) - 1]; |
michael@0 | 77 | return param !== undefined ? param : matchedId; |
michael@0 | 78 | }); |
michael@0 | 79 | }, |
michael@0 | 80 | |
michael@0 | 81 | getString: function PUIU_getString(key) { |
michael@0 | 82 | return bundle.GetStringFromName(key); |
michael@0 | 83 | }, |
michael@0 | 84 | |
michael@0 | 85 | get _copyableAnnotations() [ |
michael@0 | 86 | this.DESCRIPTION_ANNO, |
michael@0 | 87 | this.LOAD_IN_SIDEBAR_ANNO, |
michael@0 | 88 | PlacesUtils.POST_DATA_ANNO, |
michael@0 | 89 | PlacesUtils.READ_ONLY_ANNO, |
michael@0 | 90 | ], |
michael@0 | 91 | |
michael@0 | 92 | /** |
michael@0 | 93 | * Get a transaction for copying a uri item (either a bookmark or a history |
michael@0 | 94 | * entry) from one container to another. |
michael@0 | 95 | * |
michael@0 | 96 | * @param aData |
michael@0 | 97 | * JSON object of dropped or pasted item properties |
michael@0 | 98 | * @param aContainer |
michael@0 | 99 | * The container being copied into |
michael@0 | 100 | * @param aIndex |
michael@0 | 101 | * The index within the container the item is copied to |
michael@0 | 102 | * @return A nsITransaction object that performs the copy. |
michael@0 | 103 | * |
michael@0 | 104 | * @note Since a copy creates a completely new item, only some internal |
michael@0 | 105 | * annotations are synced from the old one. |
michael@0 | 106 | * @see this._copyableAnnotations for the list of copyable annotations. |
michael@0 | 107 | */ |
michael@0 | 108 | _getURIItemCopyTransaction: |
michael@0 | 109 | function PUIU__getURIItemCopyTransaction(aData, aContainer, aIndex) |
michael@0 | 110 | { |
michael@0 | 111 | let transactions = []; |
michael@0 | 112 | if (aData.dateAdded) { |
michael@0 | 113 | transactions.push( |
michael@0 | 114 | new PlacesEditItemDateAddedTransaction(null, aData.dateAdded) |
michael@0 | 115 | ); |
michael@0 | 116 | } |
michael@0 | 117 | if (aData.lastModified) { |
michael@0 | 118 | transactions.push( |
michael@0 | 119 | new PlacesEditItemLastModifiedTransaction(null, aData.lastModified) |
michael@0 | 120 | ); |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | let keyword = aData.keyword || null; |
michael@0 | 124 | let annos = []; |
michael@0 | 125 | if (aData.annos) { |
michael@0 | 126 | annos = aData.annos.filter(function (aAnno) { |
michael@0 | 127 | return this._copyableAnnotations.indexOf(aAnno.name) != -1; |
michael@0 | 128 | }, this); |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri), |
michael@0 | 132 | aContainer, aIndex, aData.title, |
michael@0 | 133 | keyword, annos, transactions); |
michael@0 | 134 | }, |
michael@0 | 135 | |
michael@0 | 136 | /** |
michael@0 | 137 | * Gets a transaction for copying (recursively nesting to include children) |
michael@0 | 138 | * a folder (or container) and its contents from one folder to another. |
michael@0 | 139 | * |
michael@0 | 140 | * @param aData |
michael@0 | 141 | * Unwrapped dropped folder data - Obj containing folder and children |
michael@0 | 142 | * @param aContainer |
michael@0 | 143 | * The container we are copying into |
michael@0 | 144 | * @param aIndex |
michael@0 | 145 | * The index in the destination container to insert the new items |
michael@0 | 146 | * @return A nsITransaction object that will perform the copy. |
michael@0 | 147 | * |
michael@0 | 148 | * @note Since a copy creates a completely new item, only some internal |
michael@0 | 149 | * annotations are synced from the old one. |
michael@0 | 150 | * @see this._copyableAnnotations for the list of copyable annotations. |
michael@0 | 151 | */ |
michael@0 | 152 | _getFolderCopyTransaction: |
michael@0 | 153 | function PUIU__getFolderCopyTransaction(aData, aContainer, aIndex) |
michael@0 | 154 | { |
michael@0 | 155 | function getChildItemsTransactions(aChildren) |
michael@0 | 156 | { |
michael@0 | 157 | let transactions = []; |
michael@0 | 158 | let index = aIndex; |
michael@0 | 159 | aChildren.forEach(function (node, i) { |
michael@0 | 160 | // Make sure that items are given the correct index, this will be |
michael@0 | 161 | // passed by the transaction manager to the backend for the insertion. |
michael@0 | 162 | // Insertion behaves differently for DEFAULT_INDEX (append). |
michael@0 | 163 | if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) { |
michael@0 | 164 | index = i; |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) { |
michael@0 | 168 | if (node.livemark && node.annos) { |
michael@0 | 169 | transactions.push( |
michael@0 | 170 | PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index) |
michael@0 | 171 | ); |
michael@0 | 172 | } |
michael@0 | 173 | else { |
michael@0 | 174 | transactions.push( |
michael@0 | 175 | PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index) |
michael@0 | 176 | ); |
michael@0 | 177 | } |
michael@0 | 178 | } |
michael@0 | 179 | else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) { |
michael@0 | 180 | transactions.push(new PlacesCreateSeparatorTransaction(-1, index)); |
michael@0 | 181 | } |
michael@0 | 182 | else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) { |
michael@0 | 183 | transactions.push( |
michael@0 | 184 | PlacesUIUtils._getURIItemCopyTransaction(node, -1, index) |
michael@0 | 185 | ); |
michael@0 | 186 | } |
michael@0 | 187 | else { |
michael@0 | 188 | throw new Error("Unexpected item under a bookmarks folder"); |
michael@0 | 189 | } |
michael@0 | 190 | }); |
michael@0 | 191 | return transactions; |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | if (aContainer == PlacesUtils.tagsFolderId) { // Copying a tag folder. |
michael@0 | 195 | let transactions = []; |
michael@0 | 196 | if (aData.children) { |
michael@0 | 197 | aData.children.forEach(function(aChild) { |
michael@0 | 198 | transactions.push( |
michael@0 | 199 | new PlacesTagURITransaction(PlacesUtils._uri(aChild.uri), |
michael@0 | 200 | [aData.title]) |
michael@0 | 201 | ); |
michael@0 | 202 | }); |
michael@0 | 203 | } |
michael@0 | 204 | return new PlacesAggregatedTransaction("addTags", transactions); |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | if (aData.livemark && aData.annos) { // Copying a livemark. |
michael@0 | 208 | return this._getLivemarkCopyTransaction(aData, aContainer, aIndex); |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | let transactions = getChildItemsTransactions(aData.children); |
michael@0 | 212 | if (aData.dateAdded) { |
michael@0 | 213 | transactions.push( |
michael@0 | 214 | new PlacesEditItemDateAddedTransaction(null, aData.dateAdded) |
michael@0 | 215 | ); |
michael@0 | 216 | } |
michael@0 | 217 | if (aData.lastModified) { |
michael@0 | 218 | transactions.push( |
michael@0 | 219 | new PlacesEditItemLastModifiedTransaction(null, aData.lastModified) |
michael@0 | 220 | ); |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | let annos = []; |
michael@0 | 224 | if (aData.annos) { |
michael@0 | 225 | annos = aData.annos.filter(function (aAnno) { |
michael@0 | 226 | return this._copyableAnnotations.indexOf(aAnno.name) != -1; |
michael@0 | 227 | }, this); |
michael@0 | 228 | } |
michael@0 | 229 | |
michael@0 | 230 | return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex, |
michael@0 | 231 | annos, transactions); |
michael@0 | 232 | }, |
michael@0 | 233 | |
michael@0 | 234 | /** |
michael@0 | 235 | * Gets a transaction for copying a live bookmark item from one container to |
michael@0 | 236 | * another. |
michael@0 | 237 | * |
michael@0 | 238 | * @param aData |
michael@0 | 239 | * Unwrapped live bookmarkmark data |
michael@0 | 240 | * @param aContainer |
michael@0 | 241 | * The container we are copying into |
michael@0 | 242 | * @param aIndex |
michael@0 | 243 | * The index in the destination container to insert the new items |
michael@0 | 244 | * @return A nsITransaction object that will perform the copy. |
michael@0 | 245 | * |
michael@0 | 246 | * @note Since a copy creates a completely new item, only some internal |
michael@0 | 247 | * annotations are synced from the old one. |
michael@0 | 248 | * @see this._copyableAnnotations for the list of copyable annotations. |
michael@0 | 249 | */ |
michael@0 | 250 | _getLivemarkCopyTransaction: |
michael@0 | 251 | function PUIU__getLivemarkCopyTransaction(aData, aContainer, aIndex) |
michael@0 | 252 | { |
michael@0 | 253 | if (!aData.livemark || !aData.annos) { |
michael@0 | 254 | throw new Error("node is not a livemark"); |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | let feedURI, siteURI; |
michael@0 | 258 | let annos = []; |
michael@0 | 259 | if (aData.annos) { |
michael@0 | 260 | annos = aData.annos.filter(function (aAnno) { |
michael@0 | 261 | if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) { |
michael@0 | 262 | feedURI = PlacesUtils._uri(aAnno.value); |
michael@0 | 263 | } |
michael@0 | 264 | else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) { |
michael@0 | 265 | siteURI = PlacesUtils._uri(aAnno.value); |
michael@0 | 266 | } |
michael@0 | 267 | return this._copyableAnnotations.indexOf(aAnno.name) != -1 |
michael@0 | 268 | }, this); |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title, |
michael@0 | 272 | aContainer, aIndex, annos); |
michael@0 | 273 | }, |
michael@0 | 274 | |
michael@0 | 275 | /** |
michael@0 | 276 | * Constructs a Transaction for the drop or paste of a blob of data into |
michael@0 | 277 | * a container. |
michael@0 | 278 | * @param data |
michael@0 | 279 | * The unwrapped data blob of dropped or pasted data. |
michael@0 | 280 | * @param type |
michael@0 | 281 | * The content type of the data |
michael@0 | 282 | * @param container |
michael@0 | 283 | * The container the data was dropped or pasted into |
michael@0 | 284 | * @param index |
michael@0 | 285 | * The index within the container the item was dropped or pasted at |
michael@0 | 286 | * @param copy |
michael@0 | 287 | * The drag action was copy, so don't move folders or links. |
michael@0 | 288 | * @returns An object implementing nsITransaction that can perform |
michael@0 | 289 | * the move/insert. |
michael@0 | 290 | */ |
michael@0 | 291 | makeTransaction: |
michael@0 | 292 | function PUIU_makeTransaction(data, type, container, index, copy) |
michael@0 | 293 | { |
michael@0 | 294 | switch (data.type) { |
michael@0 | 295 | case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: |
michael@0 | 296 | if (copy) { |
michael@0 | 297 | return this._getFolderCopyTransaction(data, container, index); |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | // Otherwise move the item. |
michael@0 | 301 | return new PlacesMoveItemTransaction(data.id, container, index); |
michael@0 | 302 | break; |
michael@0 | 303 | case PlacesUtils.TYPE_X_MOZ_PLACE: |
michael@0 | 304 | if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked. |
michael@0 | 305 | return this._getURIItemCopyTransaction(data, container, index); |
michael@0 | 306 | } |
michael@0 | 307 | |
michael@0 | 308 | // Otherwise move the item. |
michael@0 | 309 | return new PlacesMoveItemTransaction(data.id, container, index); |
michael@0 | 310 | break; |
michael@0 | 311 | case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: |
michael@0 | 312 | if (copy) { |
michael@0 | 313 | // There is no data in a separator, so copying it just amounts to |
michael@0 | 314 | // inserting a new separator. |
michael@0 | 315 | return new PlacesCreateSeparatorTransaction(container, index); |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | // Otherwise move the item. |
michael@0 | 319 | return new PlacesMoveItemTransaction(data.id, container, index); |
michael@0 | 320 | break; |
michael@0 | 321 | default: |
michael@0 | 322 | if (type == PlacesUtils.TYPE_X_MOZ_URL || |
michael@0 | 323 | type == PlacesUtils.TYPE_UNICODE || |
michael@0 | 324 | type == this.TYPE_TAB_DROP) { |
michael@0 | 325 | let title = type != PlacesUtils.TYPE_UNICODE ? data.title |
michael@0 | 326 | : data.uri; |
michael@0 | 327 | return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri), |
michael@0 | 328 | container, index, title); |
michael@0 | 329 | } |
michael@0 | 330 | } |
michael@0 | 331 | return null; |
michael@0 | 332 | }, |
michael@0 | 333 | |
michael@0 | 334 | /** |
michael@0 | 335 | * Shows the bookmark dialog corresponding to the specified info. |
michael@0 | 336 | * |
michael@0 | 337 | * @param aInfo |
michael@0 | 338 | * Describes the item to be edited/added in the dialog. |
michael@0 | 339 | * See documentation at the top of bookmarkProperties.js |
michael@0 | 340 | * @param aWindow |
michael@0 | 341 | * Owner window for the new dialog. |
michael@0 | 342 | * |
michael@0 | 343 | * @see documentation at the top of bookmarkProperties.js |
michael@0 | 344 | * @return true if any transaction has been performed, false otherwise. |
michael@0 | 345 | */ |
michael@0 | 346 | showBookmarkDialog: |
michael@0 | 347 | function PUIU_showBookmarkDialog(aInfo, aParentWindow) { |
michael@0 | 348 | // Preserve size attributes differently based on the fact the dialog has |
michael@0 | 349 | // a folder picker or not. If the picker is visible, the dialog should |
michael@0 | 350 | // be resizable since it may not show enough content for the folders |
michael@0 | 351 | // hierarchy. |
michael@0 | 352 | let hasFolderPicker = !("hiddenRows" in aInfo) || |
michael@0 | 353 | aInfo.hiddenRows.indexOf("folderPicker") == -1; |
michael@0 | 354 | // Use a different chrome url, since this allows to persist different sizes, |
michael@0 | 355 | // based on resizability of the dialog. |
michael@0 | 356 | let dialogURL = hasFolderPicker ? |
michael@0 | 357 | "chrome://browser/content/places/bookmarkProperties2.xul" : |
michael@0 | 358 | "chrome://browser/content/places/bookmarkProperties.xul"; |
michael@0 | 359 | |
michael@0 | 360 | let features = |
michael@0 | 361 | "centerscreen,chrome,modal,resizable=" + (hasFolderPicker ? "yes" : "no"); |
michael@0 | 362 | |
michael@0 | 363 | aParentWindow.openDialog(dialogURL, "", features, aInfo); |
michael@0 | 364 | return ("performed" in aInfo && aInfo.performed); |
michael@0 | 365 | }, |
michael@0 | 366 | |
michael@0 | 367 | _getTopBrowserWin: function PUIU__getTopBrowserWin() { |
michael@0 | 368 | return Services.wm.getMostRecentWindow("navigator:browser"); |
michael@0 | 369 | }, |
michael@0 | 370 | |
michael@0 | 371 | /** |
michael@0 | 372 | * Returns the closet ancestor places view for the given DOM node |
michael@0 | 373 | * @param aNode |
michael@0 | 374 | * a DOM node |
michael@0 | 375 | * @return the closet ancestor places view if exists, null otherwsie. |
michael@0 | 376 | */ |
michael@0 | 377 | getViewForNode: function PUIU_getViewForNode(aNode) { |
michael@0 | 378 | let node = aNode; |
michael@0 | 379 | |
michael@0 | 380 | // The view for a <menu> of which its associated menupopup is a places |
michael@0 | 381 | // view, is the menupopup. |
michael@0 | 382 | if (node.localName == "menu" && !node._placesNode && |
michael@0 | 383 | node.lastChild._placesView) |
michael@0 | 384 | return node.lastChild._placesView; |
michael@0 | 385 | |
michael@0 | 386 | while (node instanceof Ci.nsIDOMElement) { |
michael@0 | 387 | if (node._placesView) |
michael@0 | 388 | return node._placesView; |
michael@0 | 389 | if (node.localName == "tree" && node.getAttribute("type") == "places") |
michael@0 | 390 | return node; |
michael@0 | 391 | |
michael@0 | 392 | node = node.parentNode; |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | return null; |
michael@0 | 396 | }, |
michael@0 | 397 | |
michael@0 | 398 | /** |
michael@0 | 399 | * By calling this before visiting an URL, the visit will be associated to a |
michael@0 | 400 | * TRANSITION_TYPED transition (if there is no a referrer). |
michael@0 | 401 | * This is used when visiting pages from the history menu, history sidebar, |
michael@0 | 402 | * url bar, url autocomplete results, and history searches from the places |
michael@0 | 403 | * organizer. If this is not called visits will be marked as |
michael@0 | 404 | * TRANSITION_LINK. |
michael@0 | 405 | */ |
michael@0 | 406 | markPageAsTyped: function PUIU_markPageAsTyped(aURL) { |
michael@0 | 407 | PlacesUtils.history.markPageAsTyped(this.createFixedURI(aURL)); |
michael@0 | 408 | }, |
michael@0 | 409 | |
michael@0 | 410 | /** |
michael@0 | 411 | * By calling this before visiting an URL, the visit will be associated to a |
michael@0 | 412 | * TRANSITION_BOOKMARK transition. |
michael@0 | 413 | * This is used when visiting pages from the bookmarks menu, |
michael@0 | 414 | * personal toolbar, and bookmarks from within the places organizer. |
michael@0 | 415 | * If this is not called visits will be marked as TRANSITION_LINK. |
michael@0 | 416 | */ |
michael@0 | 417 | markPageAsFollowedBookmark: function PUIU_markPageAsFollowedBookmark(aURL) { |
michael@0 | 418 | PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL)); |
michael@0 | 419 | }, |
michael@0 | 420 | |
michael@0 | 421 | /** |
michael@0 | 422 | * By calling this before visiting an URL, any visit in frames will be |
michael@0 | 423 | * associated to a TRANSITION_FRAMED_LINK transition. |
michael@0 | 424 | * This is actually used to distinguish user-initiated visits in frames |
michael@0 | 425 | * so automatic visits can be correctly ignored. |
michael@0 | 426 | */ |
michael@0 | 427 | markPageAsFollowedLink: function PUIU_markPageAsFollowedLink(aURL) { |
michael@0 | 428 | PlacesUtils.history.markPageAsFollowedLink(this.createFixedURI(aURL)); |
michael@0 | 429 | }, |
michael@0 | 430 | |
michael@0 | 431 | /** |
michael@0 | 432 | * Allows opening of javascript/data URI only if the given node is |
michael@0 | 433 | * bookmarked (see bug 224521). |
michael@0 | 434 | * @param aURINode |
michael@0 | 435 | * a URI node |
michael@0 | 436 | * @param aWindow |
michael@0 | 437 | * a window on which a potential error alert is shown on. |
michael@0 | 438 | * @return true if it's safe to open the node in the browser, false otherwise. |
michael@0 | 439 | * |
michael@0 | 440 | */ |
michael@0 | 441 | checkURLSecurity: function PUIU_checkURLSecurity(aURINode, aWindow) { |
michael@0 | 442 | if (PlacesUtils.nodeIsBookmark(aURINode)) |
michael@0 | 443 | return true; |
michael@0 | 444 | |
michael@0 | 445 | var uri = PlacesUtils._uri(aURINode.uri); |
michael@0 | 446 | if (uri.schemeIs("javascript") || uri.schemeIs("data")) { |
michael@0 | 447 | const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties"; |
michael@0 | 448 | var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]. |
michael@0 | 449 | getService(Ci.nsIStringBundleService). |
michael@0 | 450 | createBundle(BRANDING_BUNDLE_URI). |
michael@0 | 451 | GetStringFromName("brandShortName"); |
michael@0 | 452 | |
michael@0 | 453 | var errorStr = this.getString("load-js-data-url-error"); |
michael@0 | 454 | Services.prompt.alert(aWindow, brandShortName, errorStr); |
michael@0 | 455 | return false; |
michael@0 | 456 | } |
michael@0 | 457 | return true; |
michael@0 | 458 | }, |
michael@0 | 459 | |
michael@0 | 460 | /** |
michael@0 | 461 | * Get the description associated with a document, as specified in a <META> |
michael@0 | 462 | * element. |
michael@0 | 463 | * @param doc |
michael@0 | 464 | * A DOM Document to get a description for |
michael@0 | 465 | * @returns A description string if a META element was discovered with a |
michael@0 | 466 | * "description" or "httpequiv" attribute, empty string otherwise. |
michael@0 | 467 | */ |
michael@0 | 468 | getDescriptionFromDocument: function PUIU_getDescriptionFromDocument(doc) { |
michael@0 | 469 | var metaElements = doc.getElementsByTagName("META"); |
michael@0 | 470 | for (var i = 0; i < metaElements.length; ++i) { |
michael@0 | 471 | if (metaElements[i].name.toLowerCase() == "description" || |
michael@0 | 472 | metaElements[i].httpEquiv.toLowerCase() == "description") { |
michael@0 | 473 | return metaElements[i].content; |
michael@0 | 474 | } |
michael@0 | 475 | } |
michael@0 | 476 | return ""; |
michael@0 | 477 | }, |
michael@0 | 478 | |
michael@0 | 479 | /** |
michael@0 | 480 | * Retrieve the description of an item |
michael@0 | 481 | * @param aItemId |
michael@0 | 482 | * item identifier |
michael@0 | 483 | * @returns the description of the given item, or an empty string if it is |
michael@0 | 484 | * not set. |
michael@0 | 485 | */ |
michael@0 | 486 | getItemDescription: function PUIU_getItemDescription(aItemId) { |
michael@0 | 487 | if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO)) |
michael@0 | 488 | return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO); |
michael@0 | 489 | return ""; |
michael@0 | 490 | }, |
michael@0 | 491 | |
michael@0 | 492 | /** |
michael@0 | 493 | * Gives the user a chance to cancel loading lots of tabs at once |
michael@0 | 494 | */ |
michael@0 | 495 | _confirmOpenInTabs: |
michael@0 | 496 | function PUIU__confirmOpenInTabs(numTabsToOpen, aWindow) { |
michael@0 | 497 | const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen"; |
michael@0 | 498 | var reallyOpen = true; |
michael@0 | 499 | |
michael@0 | 500 | if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) { |
michael@0 | 501 | if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) { |
michael@0 | 502 | // default to true: if it were false, we wouldn't get this far |
michael@0 | 503 | var warnOnOpen = { value: true }; |
michael@0 | 504 | |
michael@0 | 505 | var messageKey = "tabs.openWarningMultipleBranded"; |
michael@0 | 506 | var openKey = "tabs.openButtonMultiple"; |
michael@0 | 507 | const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties"; |
michael@0 | 508 | var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]. |
michael@0 | 509 | getService(Ci.nsIStringBundleService). |
michael@0 | 510 | createBundle(BRANDING_BUNDLE_URI). |
michael@0 | 511 | GetStringFromName("brandShortName"); |
michael@0 | 512 | |
michael@0 | 513 | var buttonPressed = Services.prompt.confirmEx( |
michael@0 | 514 | aWindow, |
michael@0 | 515 | this.getString("tabs.openWarningTitle"), |
michael@0 | 516 | this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]), |
michael@0 | 517 | (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + |
michael@0 | 518 | (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1), |
michael@0 | 519 | this.getString(openKey), null, null, |
michael@0 | 520 | this.getFormattedString("tabs.openWarningPromptMeBranded", |
michael@0 | 521 | [brandShortName]), |
michael@0 | 522 | warnOnOpen |
michael@0 | 523 | ); |
michael@0 | 524 | |
michael@0 | 525 | reallyOpen = (buttonPressed == 0); |
michael@0 | 526 | // don't set the pref unless they press OK and it's false |
michael@0 | 527 | if (reallyOpen && !warnOnOpen.value) |
michael@0 | 528 | Services.prefs.setBoolPref(WARN_ON_OPEN_PREF, false); |
michael@0 | 529 | } |
michael@0 | 530 | } |
michael@0 | 531 | |
michael@0 | 532 | return reallyOpen; |
michael@0 | 533 | }, |
michael@0 | 534 | |
michael@0 | 535 | /** aItemsToOpen needs to be an array of objects of the form: |
michael@0 | 536 | * {uri: string, isBookmark: boolean} |
michael@0 | 537 | */ |
michael@0 | 538 | _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) { |
michael@0 | 539 | if (!aItemsToOpen.length) |
michael@0 | 540 | return; |
michael@0 | 541 | |
michael@0 | 542 | // Prefer the caller window if it's a browser window, otherwise use |
michael@0 | 543 | // the top browser window. |
michael@0 | 544 | var browserWindow = null; |
michael@0 | 545 | browserWindow = |
michael@0 | 546 | aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ? |
michael@0 | 547 | aWindow : this._getTopBrowserWin(); |
michael@0 | 548 | |
michael@0 | 549 | var urls = []; |
michael@0 | 550 | let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow); |
michael@0 | 551 | for (let item of aItemsToOpen) { |
michael@0 | 552 | urls.push(item.uri); |
michael@0 | 553 | if (skipMarking) { |
michael@0 | 554 | continue; |
michael@0 | 555 | } |
michael@0 | 556 | |
michael@0 | 557 | if (item.isBookmark) |
michael@0 | 558 | this.markPageAsFollowedBookmark(item.uri); |
michael@0 | 559 | else |
michael@0 | 560 | this.markPageAsTyped(item.uri); |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | // whereToOpenLink doesn't return "window" when there's no browser window |
michael@0 | 564 | // open (Bug 630255). |
michael@0 | 565 | var where = browserWindow ? |
michael@0 | 566 | browserWindow.whereToOpenLink(aEvent, false, true) : "window"; |
michael@0 | 567 | if (where == "window") { |
michael@0 | 568 | // There is no browser window open, thus open a new one. |
michael@0 | 569 | var uriList = PlacesUtils.toISupportsString(urls.join("|")); |
michael@0 | 570 | var args = Cc["@mozilla.org/supports-array;1"]. |
michael@0 | 571 | createInstance(Ci.nsISupportsArray); |
michael@0 | 572 | args.AppendElement(uriList); |
michael@0 | 573 | browserWindow = Services.ww.openWindow(aWindow, |
michael@0 | 574 | "chrome://browser/content/browser.xul", |
michael@0 | 575 | null, "chrome,dialog=no,all", args); |
michael@0 | 576 | return; |
michael@0 | 577 | } |
michael@0 | 578 | |
michael@0 | 579 | var loadInBackground = where == "tabshifted" ? true : false; |
michael@0 | 580 | // For consistency, we want all the bookmarks to open in new tabs, instead |
michael@0 | 581 | // of having one of them replace the currently focused tab. Hence we call |
michael@0 | 582 | // loadTabs with aReplace set to false. |
michael@0 | 583 | browserWindow.gBrowser.loadTabs(urls, loadInBackground, false); |
michael@0 | 584 | }, |
michael@0 | 585 | |
michael@0 | 586 | openContainerNodeInTabs: |
michael@0 | 587 | function PUIU_openContainerInTabs(aNode, aEvent, aView) { |
michael@0 | 588 | let window = aView.ownerWindow; |
michael@0 | 589 | |
michael@0 | 590 | let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode); |
michael@0 | 591 | if (!this._confirmOpenInTabs(urlsToOpen.length, window)) |
michael@0 | 592 | return; |
michael@0 | 593 | |
michael@0 | 594 | this._openTabset(urlsToOpen, aEvent, window); |
michael@0 | 595 | }, |
michael@0 | 596 | |
michael@0 | 597 | openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) { |
michael@0 | 598 | let window = aView.ownerWindow; |
michael@0 | 599 | |
michael@0 | 600 | let urlsToOpen = []; |
michael@0 | 601 | for (var i=0; i < aNodes.length; i++) { |
michael@0 | 602 | // Skip over separators and folders. |
michael@0 | 603 | if (PlacesUtils.nodeIsURI(aNodes[i])) |
michael@0 | 604 | urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])}); |
michael@0 | 605 | } |
michael@0 | 606 | this._openTabset(urlsToOpen, aEvent, window); |
michael@0 | 607 | }, |
michael@0 | 608 | |
michael@0 | 609 | /** |
michael@0 | 610 | * Loads the node's URL in the appropriate tab or window or as a web |
michael@0 | 611 | * panel given the user's preference specified by modifier keys tracked by a |
michael@0 | 612 | * DOM mouse/key event. |
michael@0 | 613 | * @param aNode |
michael@0 | 614 | * An uri result node. |
michael@0 | 615 | * @param aEvent |
michael@0 | 616 | * The DOM mouse/key event with modifier keys set that track the |
michael@0 | 617 | * user's preferred destination window or tab. |
michael@0 | 618 | * @param aView |
michael@0 | 619 | * The controller associated with aNode. |
michael@0 | 620 | */ |
michael@0 | 621 | openNodeWithEvent: |
michael@0 | 622 | function PUIU_openNodeWithEvent(aNode, aEvent, aView) { |
michael@0 | 623 | let window = aView.ownerWindow; |
michael@0 | 624 | this._openNodeIn(aNode, window.whereToOpenLink(aEvent, false, true), window); |
michael@0 | 625 | }, |
michael@0 | 626 | |
michael@0 | 627 | /** |
michael@0 | 628 | * Loads the node's URL in the appropriate tab or window or as a |
michael@0 | 629 | * web panel. |
michael@0 | 630 | * see also openUILinkIn |
michael@0 | 631 | */ |
michael@0 | 632 | openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aView) { |
michael@0 | 633 | let window = aView.ownerWindow; |
michael@0 | 634 | this._openNodeIn(aNode, aWhere, window); |
michael@0 | 635 | }, |
michael@0 | 636 | |
michael@0 | 637 | _openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aWindow) { |
michael@0 | 638 | if (aNode && PlacesUtils.nodeIsURI(aNode) && |
michael@0 | 639 | this.checkURLSecurity(aNode, aWindow)) { |
michael@0 | 640 | let isBookmark = PlacesUtils.nodeIsBookmark(aNode); |
michael@0 | 641 | |
michael@0 | 642 | if (!PrivateBrowsingUtils.isWindowPrivate(aWindow)) { |
michael@0 | 643 | if (isBookmark) |
michael@0 | 644 | this.markPageAsFollowedBookmark(aNode.uri); |
michael@0 | 645 | else |
michael@0 | 646 | this.markPageAsTyped(aNode.uri); |
michael@0 | 647 | } |
michael@0 | 648 | |
michael@0 | 649 | // Check whether the node is a bookmark which should be opened as |
michael@0 | 650 | // a web panel |
michael@0 | 651 | if (aWhere == "current" && isBookmark) { |
michael@0 | 652 | if (PlacesUtils.annotations |
michael@0 | 653 | .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) { |
michael@0 | 654 | let browserWin = this._getTopBrowserWin(); |
michael@0 | 655 | if (browserWin) { |
michael@0 | 656 | browserWin.openWebPanel(aNode.title, aNode.uri); |
michael@0 | 657 | return; |
michael@0 | 658 | } |
michael@0 | 659 | } |
michael@0 | 660 | } |
michael@0 | 661 | aWindow.openUILinkIn(aNode.uri, aWhere, { |
michael@0 | 662 | inBackground: Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground") |
michael@0 | 663 | }); |
michael@0 | 664 | } |
michael@0 | 665 | }, |
michael@0 | 666 | |
michael@0 | 667 | /** |
michael@0 | 668 | * Helper for guessing scheme from an url string. |
michael@0 | 669 | * Used to avoid nsIURI overhead in frequently called UI functions. |
michael@0 | 670 | * |
michael@0 | 671 | * @param aUrlString the url to guess the scheme from. |
michael@0 | 672 | * |
michael@0 | 673 | * @return guessed scheme for this url string. |
michael@0 | 674 | * |
michael@0 | 675 | * @note this is not supposed be perfect, so use it only for UI purposes. |
michael@0 | 676 | */ |
michael@0 | 677 | guessUrlSchemeForUI: function PUIU_guessUrlSchemeForUI(aUrlString) { |
michael@0 | 678 | return aUrlString.substr(0, aUrlString.indexOf(":")); |
michael@0 | 679 | }, |
michael@0 | 680 | |
michael@0 | 681 | getBestTitle: function PUIU_getBestTitle(aNode, aDoNotCutTitle) { |
michael@0 | 682 | var title; |
michael@0 | 683 | if (!aNode.title && PlacesUtils.nodeIsURI(aNode)) { |
michael@0 | 684 | // if node title is empty, try to set the label using host and filename |
michael@0 | 685 | // PlacesUtils._uri() will throw if aNode.uri is not a valid URI |
michael@0 | 686 | try { |
michael@0 | 687 | var uri = PlacesUtils._uri(aNode.uri); |
michael@0 | 688 | var host = uri.host; |
michael@0 | 689 | var fileName = uri.QueryInterface(Ci.nsIURL).fileName; |
michael@0 | 690 | // if fileName is empty, use path to distinguish labels |
michael@0 | 691 | if (aDoNotCutTitle) { |
michael@0 | 692 | title = host + uri.path; |
michael@0 | 693 | } else { |
michael@0 | 694 | title = host + (fileName ? |
michael@0 | 695 | (host ? "/" + this.ellipsis + "/" : "") + fileName : |
michael@0 | 696 | uri.path); |
michael@0 | 697 | } |
michael@0 | 698 | } |
michael@0 | 699 | catch (e) { |
michael@0 | 700 | // Use (no title) for non-standard URIs (data:, javascript:, ...) |
michael@0 | 701 | title = ""; |
michael@0 | 702 | } |
michael@0 | 703 | } |
michael@0 | 704 | else |
michael@0 | 705 | title = aNode.title; |
michael@0 | 706 | |
michael@0 | 707 | return title || this.getString("noTitle"); |
michael@0 | 708 | }, |
michael@0 | 709 | |
michael@0 | 710 | get leftPaneQueries() { |
michael@0 | 711 | // build the map |
michael@0 | 712 | this.leftPaneFolderId; |
michael@0 | 713 | return this.leftPaneQueries; |
michael@0 | 714 | }, |
michael@0 | 715 | |
michael@0 | 716 | // Get the folder id for the organizer left-pane folder. |
michael@0 | 717 | get leftPaneFolderId() { |
michael@0 | 718 | let leftPaneRoot = -1; |
michael@0 | 719 | let allBookmarksId; |
michael@0 | 720 | |
michael@0 | 721 | // Shortcuts to services. |
michael@0 | 722 | let bs = PlacesUtils.bookmarks; |
michael@0 | 723 | let as = PlacesUtils.annotations; |
michael@0 | 724 | |
michael@0 | 725 | // This is the list of the left pane queries. |
michael@0 | 726 | let queries = { |
michael@0 | 727 | "PlacesRoot": { title: "" }, |
michael@0 | 728 | "History": { title: this.getString("OrganizerQueryHistory") }, |
michael@0 | 729 | "Downloads": { title: this.getString("OrganizerQueryDownloads") }, |
michael@0 | 730 | "Tags": { title: this.getString("OrganizerQueryTags") }, |
michael@0 | 731 | "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") }, |
michael@0 | 732 | "BookmarksToolbar": |
michael@0 | 733 | { title: null, |
michael@0 | 734 | concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"), |
michael@0 | 735 | concreteId: PlacesUtils.toolbarFolderId }, |
michael@0 | 736 | "BookmarksMenu": |
michael@0 | 737 | { title: null, |
michael@0 | 738 | concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"), |
michael@0 | 739 | concreteId: PlacesUtils.bookmarksMenuFolderId }, |
michael@0 | 740 | "UnfiledBookmarks": |
michael@0 | 741 | { title: null, |
michael@0 | 742 | concreteTitle: PlacesUtils.getString("UnsortedBookmarksFolderTitle"), |
michael@0 | 743 | concreteId: PlacesUtils.unfiledBookmarksFolderId }, |
michael@0 | 744 | }; |
michael@0 | 745 | // All queries but PlacesRoot. |
michael@0 | 746 | const EXPECTED_QUERY_COUNT = 7; |
michael@0 | 747 | |
michael@0 | 748 | // Removes an item and associated annotations, ignoring eventual errors. |
michael@0 | 749 | function safeRemoveItem(aItemId) { |
michael@0 | 750 | try { |
michael@0 | 751 | if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) && |
michael@0 | 752 | !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) { |
michael@0 | 753 | // Some extension annotated their roots with our query annotation, |
michael@0 | 754 | // so we should not delete them. |
michael@0 | 755 | return; |
michael@0 | 756 | } |
michael@0 | 757 | // removeItemAnnotation does not check if item exists, nor the anno, |
michael@0 | 758 | // so this is safe to do. |
michael@0 | 759 | as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO); |
michael@0 | 760 | as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO); |
michael@0 | 761 | // This will throw if the annotation is an orphan. |
michael@0 | 762 | bs.removeItem(aItemId); |
michael@0 | 763 | } |
michael@0 | 764 | catch(e) { /* orphan anno */ } |
michael@0 | 765 | } |
michael@0 | 766 | |
michael@0 | 767 | // Returns true if item really exists, false otherwise. |
michael@0 | 768 | function itemExists(aItemId) { |
michael@0 | 769 | try { |
michael@0 | 770 | bs.getItemIndex(aItemId); |
michael@0 | 771 | return true; |
michael@0 | 772 | } |
michael@0 | 773 | catch(e) { |
michael@0 | 774 | return false; |
michael@0 | 775 | } |
michael@0 | 776 | } |
michael@0 | 777 | |
michael@0 | 778 | // Get all items marked as being the left pane folder. |
michael@0 | 779 | let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO); |
michael@0 | 780 | if (items.length > 1) { |
michael@0 | 781 | // Something went wrong, we cannot have more than one left pane folder, |
michael@0 | 782 | // remove all left pane folders and continue. We will create a new one. |
michael@0 | 783 | items.forEach(safeRemoveItem); |
michael@0 | 784 | } |
michael@0 | 785 | else if (items.length == 1 && items[0] != -1) { |
michael@0 | 786 | leftPaneRoot = items[0]; |
michael@0 | 787 | // Check that organizer left pane root is valid. |
michael@0 | 788 | let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO); |
michael@0 | 789 | if (version != this.ORGANIZER_LEFTPANE_VERSION || |
michael@0 | 790 | !itemExists(leftPaneRoot)) { |
michael@0 | 791 | // Invalid root, we must rebuild the left pane. |
michael@0 | 792 | safeRemoveItem(leftPaneRoot); |
michael@0 | 793 | leftPaneRoot = -1; |
michael@0 | 794 | } |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | if (leftPaneRoot != -1) { |
michael@0 | 798 | // A valid left pane folder has been found. |
michael@0 | 799 | // Build the leftPaneQueries Map. This is used to quickly access them, |
michael@0 | 800 | // associating a mnemonic name to the real item ids. |
michael@0 | 801 | delete this.leftPaneQueries; |
michael@0 | 802 | this.leftPaneQueries = {}; |
michael@0 | 803 | |
michael@0 | 804 | let items = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO); |
michael@0 | 805 | // While looping through queries we will also check for their validity. |
michael@0 | 806 | let queriesCount = 0; |
michael@0 | 807 | let corrupt = false; |
michael@0 | 808 | for (let i = 0; i < items.length; i++) { |
michael@0 | 809 | let queryName = as.getItemAnnotation(items[i], this.ORGANIZER_QUERY_ANNO); |
michael@0 | 810 | |
michael@0 | 811 | // Some extension did use our annotation to decorate their items |
michael@0 | 812 | // with icons, so we should check only our elements, to avoid dataloss. |
michael@0 | 813 | if (!(queryName in queries)) |
michael@0 | 814 | continue; |
michael@0 | 815 | |
michael@0 | 816 | let query = queries[queryName]; |
michael@0 | 817 | query.itemId = items[i]; |
michael@0 | 818 | |
michael@0 | 819 | if (!itemExists(query.itemId)) { |
michael@0 | 820 | // Orphan annotation, bail out and create a new left pane root. |
michael@0 | 821 | corrupt = true; |
michael@0 | 822 | break; |
michael@0 | 823 | } |
michael@0 | 824 | |
michael@0 | 825 | // Check that all queries have valid parents. |
michael@0 | 826 | let parentId = bs.getFolderIdForItem(query.itemId); |
michael@0 | 827 | if (items.indexOf(parentId) == -1 && parentId != leftPaneRoot) { |
michael@0 | 828 | // The parent is not part of the left pane, bail out and create a new |
michael@0 | 829 | // left pane root. |
michael@0 | 830 | corrupt = true; |
michael@0 | 831 | break; |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | // Titles could have been corrupted or the user could have changed his |
michael@0 | 835 | // locale. Check title and eventually fix it. |
michael@0 | 836 | if (bs.getItemTitle(query.itemId) != query.title) |
michael@0 | 837 | bs.setItemTitle(query.itemId, query.title); |
michael@0 | 838 | if ("concreteId" in query) { |
michael@0 | 839 | if (bs.getItemTitle(query.concreteId) != query.concreteTitle) |
michael@0 | 840 | bs.setItemTitle(query.concreteId, query.concreteTitle); |
michael@0 | 841 | } |
michael@0 | 842 | |
michael@0 | 843 | // Add the query to our cache. |
michael@0 | 844 | this.leftPaneQueries[queryName] = query.itemId; |
michael@0 | 845 | queriesCount++; |
michael@0 | 846 | } |
michael@0 | 847 | |
michael@0 | 848 | // Note: it's not enough to just check for queriesCount, since we may |
michael@0 | 849 | // find an invalid query just after accounting for a sufficient number of |
michael@0 | 850 | // valid ones. As well as we can't just rely on corrupt since we may find |
michael@0 | 851 | // less valid queries than expected. |
michael@0 | 852 | if (corrupt || queriesCount != EXPECTED_QUERY_COUNT) { |
michael@0 | 853 | // Queries number is wrong, so the left pane must be corrupt. |
michael@0 | 854 | // Note: we can't just remove the leftPaneRoot, because some query could |
michael@0 | 855 | // have a bad parent, so we have to remove all items one by one. |
michael@0 | 856 | items.forEach(safeRemoveItem); |
michael@0 | 857 | safeRemoveItem(leftPaneRoot); |
michael@0 | 858 | } |
michael@0 | 859 | else { |
michael@0 | 860 | // Everything is fine, return the current left pane folder. |
michael@0 | 861 | delete this.leftPaneFolderId; |
michael@0 | 862 | return this.leftPaneFolderId = leftPaneRoot; |
michael@0 | 863 | } |
michael@0 | 864 | } |
michael@0 | 865 | |
michael@0 | 866 | // Create a new left pane folder. |
michael@0 | 867 | var callback = { |
michael@0 | 868 | // Helper to create an organizer special query. |
michael@0 | 869 | create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) { |
michael@0 | 870 | let itemId = bs.insertBookmark(aParentId, |
michael@0 | 871 | PlacesUtils._uri(aQueryUrl), |
michael@0 | 872 | bs.DEFAULT_INDEX, |
michael@0 | 873 | queries[aQueryName].title); |
michael@0 | 874 | // Mark as special organizer query. |
michael@0 | 875 | as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName, |
michael@0 | 876 | 0, as.EXPIRE_NEVER); |
michael@0 | 877 | // We should never backup this, since it changes between profiles. |
michael@0 | 878 | as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, |
michael@0 | 879 | 0, as.EXPIRE_NEVER); |
michael@0 | 880 | // Add to the queries map. |
michael@0 | 881 | PlacesUIUtils.leftPaneQueries[aQueryName] = itemId; |
michael@0 | 882 | return itemId; |
michael@0 | 883 | }, |
michael@0 | 884 | |
michael@0 | 885 | // Helper to create an organizer special folder. |
michael@0 | 886 | create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) { |
michael@0 | 887 | // Left Pane Root Folder. |
michael@0 | 888 | let folderId = bs.createFolder(aParentId, |
michael@0 | 889 | queries[aFolderName].title, |
michael@0 | 890 | bs.DEFAULT_INDEX); |
michael@0 | 891 | // We should never backup this, since it changes between profiles. |
michael@0 | 892 | as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, |
michael@0 | 893 | 0, as.EXPIRE_NEVER); |
michael@0 | 894 | // Disallow manipulating this folder within the organizer UI. |
michael@0 | 895 | bs.setFolderReadonly(folderId, true); |
michael@0 | 896 | |
michael@0 | 897 | if (aIsRoot) { |
michael@0 | 898 | // Mark as special left pane root. |
michael@0 | 899 | as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO, |
michael@0 | 900 | PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, |
michael@0 | 901 | 0, as.EXPIRE_NEVER); |
michael@0 | 902 | } |
michael@0 | 903 | else { |
michael@0 | 904 | // Mark as special organizer folder. |
michael@0 | 905 | as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName, |
michael@0 | 906 | 0, as.EXPIRE_NEVER); |
michael@0 | 907 | PlacesUIUtils.leftPaneQueries[aFolderName] = folderId; |
michael@0 | 908 | } |
michael@0 | 909 | return folderId; |
michael@0 | 910 | }, |
michael@0 | 911 | |
michael@0 | 912 | runBatched: function CB_runBatched(aUserData) { |
michael@0 | 913 | delete PlacesUIUtils.leftPaneQueries; |
michael@0 | 914 | PlacesUIUtils.leftPaneQueries = { }; |
michael@0 | 915 | |
michael@0 | 916 | // Left Pane Root Folder. |
michael@0 | 917 | leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true); |
michael@0 | 918 | |
michael@0 | 919 | // History Query. |
michael@0 | 920 | this.create_query("History", leftPaneRoot, |
michael@0 | 921 | "place:type=" + |
michael@0 | 922 | Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY + |
michael@0 | 923 | "&sort=" + |
michael@0 | 924 | Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING); |
michael@0 | 925 | |
michael@0 | 926 | // Downloads. |
michael@0 | 927 | this.create_query("Downloads", leftPaneRoot, |
michael@0 | 928 | "place:transition=" + |
michael@0 | 929 | Ci.nsINavHistoryService.TRANSITION_DOWNLOAD + |
michael@0 | 930 | "&sort=" + |
michael@0 | 931 | Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING); |
michael@0 | 932 | |
michael@0 | 933 | // Tags Query. |
michael@0 | 934 | this.create_query("Tags", leftPaneRoot, |
michael@0 | 935 | "place:type=" + |
michael@0 | 936 | Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY + |
michael@0 | 937 | "&sort=" + |
michael@0 | 938 | Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING); |
michael@0 | 939 | |
michael@0 | 940 | // All Bookmarks Folder. |
michael@0 | 941 | allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false); |
michael@0 | 942 | |
michael@0 | 943 | // All Bookmarks->Bookmarks Toolbar Query. |
michael@0 | 944 | this.create_query("BookmarksToolbar", allBookmarksId, |
michael@0 | 945 | "place:folder=TOOLBAR"); |
michael@0 | 946 | |
michael@0 | 947 | // All Bookmarks->Bookmarks Menu Query. |
michael@0 | 948 | this.create_query("BookmarksMenu", allBookmarksId, |
michael@0 | 949 | "place:folder=BOOKMARKS_MENU"); |
michael@0 | 950 | |
michael@0 | 951 | // All Bookmarks->Unfiled Bookmarks Query. |
michael@0 | 952 | this.create_query("UnfiledBookmarks", allBookmarksId, |
michael@0 | 953 | "place:folder=UNFILED_BOOKMARKS"); |
michael@0 | 954 | } |
michael@0 | 955 | }; |
michael@0 | 956 | bs.runInBatchMode(callback, null); |
michael@0 | 957 | |
michael@0 | 958 | delete this.leftPaneFolderId; |
michael@0 | 959 | return this.leftPaneFolderId = leftPaneRoot; |
michael@0 | 960 | }, |
michael@0 | 961 | |
michael@0 | 962 | /** |
michael@0 | 963 | * Get the folder id for the organizer left-pane folder. |
michael@0 | 964 | */ |
michael@0 | 965 | get allBookmarksFolderId() { |
michael@0 | 966 | // ensure the left-pane root is initialized; |
michael@0 | 967 | this.leftPaneFolderId; |
michael@0 | 968 | delete this.allBookmarksFolderId; |
michael@0 | 969 | return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"]; |
michael@0 | 970 | }, |
michael@0 | 971 | |
michael@0 | 972 | /** |
michael@0 | 973 | * If an item is a left-pane query, returns the name of the query |
michael@0 | 974 | * or an empty string if not. |
michael@0 | 975 | * |
michael@0 | 976 | * @param aItemId id of a container |
michael@0 | 977 | * @returns the name of the query, or empty string if not a left-pane query |
michael@0 | 978 | */ |
michael@0 | 979 | getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) { |
michael@0 | 980 | var queryName = ""; |
michael@0 | 981 | // If the let pane hasn't been built, use the annotation service |
michael@0 | 982 | // directly, to avoid building the left pane too early. |
michael@0 | 983 | if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) { |
michael@0 | 984 | try { |
michael@0 | 985 | queryName = PlacesUtils.annotations. |
michael@0 | 986 | getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO); |
michael@0 | 987 | } |
michael@0 | 988 | catch (ex) { |
michael@0 | 989 | // doesn't have the annotation |
michael@0 | 990 | queryName = ""; |
michael@0 | 991 | } |
michael@0 | 992 | } |
michael@0 | 993 | else { |
michael@0 | 994 | // If the left pane has already been built, use the name->id map |
michael@0 | 995 | // cached in PlacesUIUtils. |
michael@0 | 996 | for (let [name, id] in Iterator(this.leftPaneQueries)) { |
michael@0 | 997 | if (aItemId == id) |
michael@0 | 998 | queryName = name; |
michael@0 | 999 | } |
michael@0 | 1000 | } |
michael@0 | 1001 | return queryName; |
michael@0 | 1002 | }, |
michael@0 | 1003 | |
michael@0 | 1004 | shouldShowTabsFromOtherComputersMenuitem: function() { |
michael@0 | 1005 | // If Sync isn't configured yet, then don't show the menuitem. |
michael@0 | 1006 | return Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED && |
michael@0 | 1007 | Weave.Svc.Prefs.get("firstSync", "") != "notReady"; |
michael@0 | 1008 | }, |
michael@0 | 1009 | |
michael@0 | 1010 | shouldEnableTabsFromOtherComputersMenuitem: function() { |
michael@0 | 1011 | // The tabs engine might never be inited (if services.sync.registerEngines |
michael@0 | 1012 | // is modified), so make sure we avoid undefined errors. |
michael@0 | 1013 | return Weave.Service.isLoggedIn && |
michael@0 | 1014 | Weave.Service.engineManager.get("tabs") && |
michael@0 | 1015 | Weave.Service.engineManager.get("tabs").enabled; |
michael@0 | 1016 | }, |
michael@0 | 1017 | }; |
michael@0 | 1018 | |
michael@0 | 1019 | XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF", |
michael@0 | 1020 | "@mozilla.org/rdf/rdf-service;1", |
michael@0 | 1021 | "nsIRDFService"); |
michael@0 | 1022 | |
michael@0 | 1023 | XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() { |
michael@0 | 1024 | return PlacesUIUtils.RDF.GetDataSource("rdf:local-store"); |
michael@0 | 1025 | }); |
michael@0 | 1026 | |
michael@0 | 1027 | XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() { |
michael@0 | 1028 | return Services.prefs.getComplexValue("intl.ellipsis", |
michael@0 | 1029 | Ci.nsIPrefLocalizedString).data; |
michael@0 | 1030 | }); |
michael@0 | 1031 | |
michael@0 | 1032 | XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() { |
michael@0 | 1033 | try { |
michael@0 | 1034 | return Services.prefs.getBoolPref("browser.places.useAsyncTransactions"); |
michael@0 | 1035 | } |
michael@0 | 1036 | catch(ex) { } |
michael@0 | 1037 | return false; |
michael@0 | 1038 | }); |
michael@0 | 1039 | |
michael@0 | 1040 | XPCOMUtils.defineLazyServiceGetter(this, "URIFixup", |
michael@0 | 1041 | "@mozilla.org/docshell/urifixup;1", |
michael@0 | 1042 | "nsIURIFixup"); |
michael@0 | 1043 | |
michael@0 | 1044 | XPCOMUtils.defineLazyGetter(this, "bundle", function() { |
michael@0 | 1045 | const PLACES_STRING_BUNDLE_URI = |
michael@0 | 1046 | "chrome://browser/locale/places/places.properties"; |
michael@0 | 1047 | return Cc["@mozilla.org/intl/stringbundle;1"]. |
michael@0 | 1048 | getService(Ci.nsIStringBundleService). |
michael@0 | 1049 | createBundle(PLACES_STRING_BUNDLE_URI); |
michael@0 | 1050 | }); |
michael@0 | 1051 | |
michael@0 | 1052 | XPCOMUtils.defineLazyServiceGetter(this, "focusManager", |
michael@0 | 1053 | "@mozilla.org/focus-manager;1", |
michael@0 | 1054 | "nsIFocusManager"); |
michael@0 | 1055 | |
michael@0 | 1056 | /** |
michael@0 | 1057 | * This is a compatibility shim for old PUIU.ptm users. |
michael@0 | 1058 | * |
michael@0 | 1059 | * If you're looking for transactions and writing new code using them, directly |
michael@0 | 1060 | * use the transactions objects exported by the PlacesUtils.jsm module. |
michael@0 | 1061 | * |
michael@0 | 1062 | * This object will be removed once enough users are converted to the new API. |
michael@0 | 1063 | */ |
michael@0 | 1064 | XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() { |
michael@0 | 1065 | // Ensure PlacesUtils is imported in scope. |
michael@0 | 1066 | PlacesUtils; |
michael@0 | 1067 | |
michael@0 | 1068 | return { |
michael@0 | 1069 | aggregateTransactions: function(aName, aTransactions) |
michael@0 | 1070 | new PlacesAggregatedTransaction(aName, aTransactions), |
michael@0 | 1071 | |
michael@0 | 1072 | createFolder: function(aName, aContainer, aIndex, aAnnotations, |
michael@0 | 1073 | aChildItemsTransactions) |
michael@0 | 1074 | new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations, |
michael@0 | 1075 | aChildItemsTransactions), |
michael@0 | 1076 | |
michael@0 | 1077 | createItem: function(aURI, aContainer, aIndex, aTitle, aKeyword, |
michael@0 | 1078 | aAnnotations, aChildTransactions) |
michael@0 | 1079 | new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle, |
michael@0 | 1080 | aKeyword, aAnnotations, |
michael@0 | 1081 | aChildTransactions), |
michael@0 | 1082 | |
michael@0 | 1083 | createSeparator: function(aContainer, aIndex) |
michael@0 | 1084 | new PlacesCreateSeparatorTransaction(aContainer, aIndex), |
michael@0 | 1085 | |
michael@0 | 1086 | createLivemark: function(aFeedURI, aSiteURI, aName, aContainer, aIndex, |
michael@0 | 1087 | aAnnotations) |
michael@0 | 1088 | new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer, |
michael@0 | 1089 | aIndex, aAnnotations), |
michael@0 | 1090 | |
michael@0 | 1091 | moveItem: function(aItemId, aNewContainer, aNewIndex) |
michael@0 | 1092 | new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex), |
michael@0 | 1093 | |
michael@0 | 1094 | removeItem: function(aItemId) |
michael@0 | 1095 | new PlacesRemoveItemTransaction(aItemId), |
michael@0 | 1096 | |
michael@0 | 1097 | editItemTitle: function(aItemId, aNewTitle) |
michael@0 | 1098 | new PlacesEditItemTitleTransaction(aItemId, aNewTitle), |
michael@0 | 1099 | |
michael@0 | 1100 | editBookmarkURI: function(aItemId, aNewURI) |
michael@0 | 1101 | new PlacesEditBookmarkURITransaction(aItemId, aNewURI), |
michael@0 | 1102 | |
michael@0 | 1103 | setItemAnnotation: function(aItemId, aAnnotationObject) |
michael@0 | 1104 | new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject), |
michael@0 | 1105 | |
michael@0 | 1106 | setPageAnnotation: function(aURI, aAnnotationObject) |
michael@0 | 1107 | new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject), |
michael@0 | 1108 | |
michael@0 | 1109 | editBookmarkKeyword: function(aItemId, aNewKeyword) |
michael@0 | 1110 | new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword), |
michael@0 | 1111 | |
michael@0 | 1112 | editBookmarkPostData: function(aItemId, aPostData) |
michael@0 | 1113 | new PlacesEditBookmarkPostDataTransaction(aItemId, aPostData), |
michael@0 | 1114 | |
michael@0 | 1115 | editLivemarkSiteURI: function(aLivemarkId, aSiteURI) |
michael@0 | 1116 | new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI), |
michael@0 | 1117 | |
michael@0 | 1118 | editLivemarkFeedURI: function(aLivemarkId, aFeedURI) |
michael@0 | 1119 | new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI), |
michael@0 | 1120 | |
michael@0 | 1121 | editItemDateAdded: function(aItemId, aNewDateAdded) |
michael@0 | 1122 | new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded), |
michael@0 | 1123 | |
michael@0 | 1124 | editItemLastModified: function(aItemId, aNewLastModified) |
michael@0 | 1125 | new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified), |
michael@0 | 1126 | |
michael@0 | 1127 | sortFolderByName: function(aFolderId) |
michael@0 | 1128 | new PlacesSortFolderByNameTransaction(aFolderId), |
michael@0 | 1129 | |
michael@0 | 1130 | tagURI: function(aURI, aTags) |
michael@0 | 1131 | new PlacesTagURITransaction(aURI, aTags), |
michael@0 | 1132 | |
michael@0 | 1133 | untagURI: function(aURI, aTags) |
michael@0 | 1134 | new PlacesUntagURITransaction(aURI, aTags), |
michael@0 | 1135 | |
michael@0 | 1136 | /** |
michael@0 | 1137 | * Transaction for setting/unsetting Load-in-sidebar annotation. |
michael@0 | 1138 | * |
michael@0 | 1139 | * @param aBookmarkId |
michael@0 | 1140 | * id of the bookmark where to set Load-in-sidebar annotation. |
michael@0 | 1141 | * @param aLoadInSidebar |
michael@0 | 1142 | * boolean value. |
michael@0 | 1143 | * @returns nsITransaction object. |
michael@0 | 1144 | */ |
michael@0 | 1145 | setLoadInSidebar: function(aItemId, aLoadInSidebar) |
michael@0 | 1146 | { |
michael@0 | 1147 | let annoObj = { name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO, |
michael@0 | 1148 | type: Ci.nsIAnnotationService.TYPE_INT32, |
michael@0 | 1149 | flags: 0, |
michael@0 | 1150 | value: aLoadInSidebar, |
michael@0 | 1151 | expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; |
michael@0 | 1152 | return new PlacesSetItemAnnotationTransaction(aItemId, annoObj); |
michael@0 | 1153 | }, |
michael@0 | 1154 | |
michael@0 | 1155 | /** |
michael@0 | 1156 | * Transaction for editing the description of a bookmark or a folder. |
michael@0 | 1157 | * |
michael@0 | 1158 | * @param aItemId |
michael@0 | 1159 | * id of the item to edit. |
michael@0 | 1160 | * @param aDescription |
michael@0 | 1161 | * new description. |
michael@0 | 1162 | * @returns nsITransaction object. |
michael@0 | 1163 | */ |
michael@0 | 1164 | editItemDescription: function(aItemId, aDescription) |
michael@0 | 1165 | { |
michael@0 | 1166 | let annoObj = { name: PlacesUIUtils.DESCRIPTION_ANNO, |
michael@0 | 1167 | type: Ci.nsIAnnotationService.TYPE_STRING, |
michael@0 | 1168 | flags: 0, |
michael@0 | 1169 | value: aDescription, |
michael@0 | 1170 | expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; |
michael@0 | 1171 | return new PlacesSetItemAnnotationTransaction(aItemId, annoObj); |
michael@0 | 1172 | }, |
michael@0 | 1173 | |
michael@0 | 1174 | //////////////////////////////////////////////////////////////////////////// |
michael@0 | 1175 | //// nsITransactionManager forwarders. |
michael@0 | 1176 | |
michael@0 | 1177 | beginBatch: function() |
michael@0 | 1178 | PlacesUtils.transactionManager.beginBatch(null), |
michael@0 | 1179 | |
michael@0 | 1180 | endBatch: function() |
michael@0 | 1181 | PlacesUtils.transactionManager.endBatch(false), |
michael@0 | 1182 | |
michael@0 | 1183 | doTransaction: function(txn) |
michael@0 | 1184 | PlacesUtils.transactionManager.doTransaction(txn), |
michael@0 | 1185 | |
michael@0 | 1186 | undoTransaction: function() |
michael@0 | 1187 | PlacesUtils.transactionManager.undoTransaction(), |
michael@0 | 1188 | |
michael@0 | 1189 | redoTransaction: function() |
michael@0 | 1190 | PlacesUtils.transactionManager.redoTransaction(), |
michael@0 | 1191 | |
michael@0 | 1192 | get numberOfUndoItems() |
michael@0 | 1193 | PlacesUtils.transactionManager.numberOfUndoItems, |
michael@0 | 1194 | get numberOfRedoItems() |
michael@0 | 1195 | PlacesUtils.transactionManager.numberOfRedoItems, |
michael@0 | 1196 | get maxTransactionCount() |
michael@0 | 1197 | PlacesUtils.transactionManager.maxTransactionCount, |
michael@0 | 1198 | set maxTransactionCount(val) |
michael@0 | 1199 | PlacesUtils.transactionManager.maxTransactionCount = val, |
michael@0 | 1200 | |
michael@0 | 1201 | clear: function() |
michael@0 | 1202 | PlacesUtils.transactionManager.clear(), |
michael@0 | 1203 | |
michael@0 | 1204 | peekUndoStack: function() |
michael@0 | 1205 | PlacesUtils.transactionManager.peekUndoStack(), |
michael@0 | 1206 | |
michael@0 | 1207 | peekRedoStack: function() |
michael@0 | 1208 | PlacesUtils.transactionManager.peekRedoStack(), |
michael@0 | 1209 | |
michael@0 | 1210 | getUndoStack: function() |
michael@0 | 1211 | PlacesUtils.transactionManager.getUndoStack(), |
michael@0 | 1212 | |
michael@0 | 1213 | getRedoStack: function() |
michael@0 | 1214 | PlacesUtils.transactionManager.getRedoStack(), |
michael@0 | 1215 | |
michael@0 | 1216 | AddListener: function(aListener) |
michael@0 | 1217 | PlacesUtils.transactionManager.AddListener(aListener), |
michael@0 | 1218 | |
michael@0 | 1219 | RemoveListener: function(aListener) |
michael@0 | 1220 | PlacesUtils.transactionManager.RemoveListener(aListener) |
michael@0 | 1221 | } |
michael@0 | 1222 | }); |