browser/components/places/src/PlacesUIUtils.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/places/src/PlacesUIUtils.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1222 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +this.EXPORTED_SYMBOLS = ["PlacesUIUtils"];
    1.10 +
    1.11 +var Ci = Components.interfaces;
    1.12 +var Cc = Components.classes;
    1.13 +var Cr = Components.results;
    1.14 +var Cu = Components.utils;
    1.15 +
    1.16 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.17 +Cu.import("resource://gre/modules/Services.jsm");
    1.18 +
    1.19 +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
    1.20 +                                  "resource://gre/modules/PluralForm.jsm");
    1.21 +
    1.22 +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
    1.23 +                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
    1.24 +
    1.25 +#ifdef MOZ_SERVICES_SYNC
    1.26 +XPCOMUtils.defineLazyModuleGetter(this, "Weave",
    1.27 +                                  "resource://services-sync/main.js");
    1.28 +#endif
    1.29 +
    1.30 +XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
    1.31 +  Cu.import("resource://gre/modules/PlacesUtils.jsm");
    1.32 +  return PlacesUtils;
    1.33 +});
    1.34 +
    1.35 +this.PlacesUIUtils = {
    1.36 +  ORGANIZER_LEFTPANE_VERSION: 7,
    1.37 +  ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
    1.38 +  ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
    1.39 +
    1.40 +  LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
    1.41 +  DESCRIPTION_ANNO: "bookmarkProperties/description",
    1.42 +
    1.43 +  TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab",
    1.44 +
    1.45 +  /**
    1.46 +   * Makes a URI from a spec, and do fixup
    1.47 +   * @param   aSpec
    1.48 +   *          The string spec of the URI
    1.49 +   * @returns A URI object for the spec.
    1.50 +   */
    1.51 +  createFixedURI: function PUIU_createFixedURI(aSpec) {
    1.52 +    return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
    1.53 +  },
    1.54 +
    1.55 +  getFormattedString: function PUIU_getFormattedString(key, params) {
    1.56 +    return bundle.formatStringFromName(key, params, params.length);
    1.57 +  },
    1.58 +
    1.59 +  /**
    1.60 +   * Get a localized plural string for the specified key name and numeric value
    1.61 +   * substituting parameters.
    1.62 +   *
    1.63 +   * @param   aKey
    1.64 +   *          String, key for looking up the localized string in the bundle
    1.65 +   * @param   aNumber
    1.66 +   *          Number based on which the final localized form is looked up
    1.67 +   * @param   aParams
    1.68 +   *          Array whose items will substitute #1, #2,... #n parameters
    1.69 +   *          in the string.
    1.70 +   *
    1.71 +   * @see https://developer.mozilla.org/en/Localization_and_Plurals
    1.72 +   * @return The localized plural string.
    1.73 +   */
    1.74 +  getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) {
    1.75 +    let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey));
    1.76 +
    1.77 +    // Replace #1 with aParams[0], #2 with aParams[1], and so on.
    1.78 +    return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) {
    1.79 +      let param = aParams[parseInt(matchedNumber, 10) - 1];
    1.80 +      return param !== undefined ? param : matchedId;
    1.81 +    });
    1.82 +  },
    1.83 +
    1.84 +  getString: function PUIU_getString(key) {
    1.85 +    return bundle.GetStringFromName(key);
    1.86 +  },
    1.87 +
    1.88 +  get _copyableAnnotations() [
    1.89 +    this.DESCRIPTION_ANNO,
    1.90 +    this.LOAD_IN_SIDEBAR_ANNO,
    1.91 +    PlacesUtils.POST_DATA_ANNO,
    1.92 +    PlacesUtils.READ_ONLY_ANNO,
    1.93 +  ],
    1.94 +
    1.95 +  /**
    1.96 +   * Get a transaction for copying a uri item (either a bookmark or a history
    1.97 +   * entry) from one container to another.
    1.98 +   *
    1.99 +   * @param   aData
   1.100 +   *          JSON object of dropped or pasted item properties
   1.101 +   * @param   aContainer
   1.102 +   *          The container being copied into
   1.103 +   * @param   aIndex
   1.104 +   *          The index within the container the item is copied to
   1.105 +   * @return A nsITransaction object that performs the copy.
   1.106 +   *
   1.107 +   * @note Since a copy creates a completely new item, only some internal
   1.108 +   *       annotations are synced from the old one.
   1.109 +   * @see this._copyableAnnotations for the list of copyable annotations.
   1.110 +   */
   1.111 +  _getURIItemCopyTransaction:
   1.112 +  function PUIU__getURIItemCopyTransaction(aData, aContainer, aIndex)
   1.113 +  {
   1.114 +    let transactions = [];
   1.115 +    if (aData.dateAdded) {
   1.116 +      transactions.push(
   1.117 +        new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
   1.118 +      );
   1.119 +    }
   1.120 +    if (aData.lastModified) {
   1.121 +      transactions.push(
   1.122 +        new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
   1.123 +      );
   1.124 +    }
   1.125 +
   1.126 +    let keyword = aData.keyword || null;
   1.127 +    let annos = [];
   1.128 +    if (aData.annos) {
   1.129 +      annos = aData.annos.filter(function (aAnno) {
   1.130 +        return this._copyableAnnotations.indexOf(aAnno.name) != -1;
   1.131 +      }, this);
   1.132 +    }
   1.133 +
   1.134 +    return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri),
   1.135 +                                               aContainer, aIndex, aData.title,
   1.136 +                                               keyword, annos, transactions);
   1.137 +  },
   1.138 +
   1.139 +  /**
   1.140 +   * Gets a transaction for copying (recursively nesting to include children)
   1.141 +   * a folder (or container) and its contents from one folder to another.
   1.142 +   *
   1.143 +   * @param   aData
   1.144 +   *          Unwrapped dropped folder data - Obj containing folder and children
   1.145 +   * @param   aContainer
   1.146 +   *          The container we are copying into
   1.147 +   * @param   aIndex
   1.148 +   *          The index in the destination container to insert the new items
   1.149 +   * @return A nsITransaction object that will perform the copy.
   1.150 +   *
   1.151 +   * @note Since a copy creates a completely new item, only some internal
   1.152 +   *       annotations are synced from the old one.
   1.153 +   * @see this._copyableAnnotations for the list of copyable annotations.
   1.154 +   */
   1.155 +  _getFolderCopyTransaction:
   1.156 +  function PUIU__getFolderCopyTransaction(aData, aContainer, aIndex)
   1.157 +  {
   1.158 +    function getChildItemsTransactions(aChildren)
   1.159 +    {
   1.160 +      let transactions = [];
   1.161 +      let index = aIndex;
   1.162 +      aChildren.forEach(function (node, i) {
   1.163 +        // Make sure that items are given the correct index, this will be
   1.164 +        // passed by the transaction manager to the backend for the insertion.
   1.165 +        // Insertion behaves differently for DEFAULT_INDEX (append).
   1.166 +        if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) {
   1.167 +          index = i;
   1.168 +        }
   1.169 +
   1.170 +        if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
   1.171 +          if (node.livemark && node.annos) {
   1.172 +            transactions.push(
   1.173 +              PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index)
   1.174 +            );
   1.175 +          }
   1.176 +          else {
   1.177 +            transactions.push(
   1.178 +              PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index)
   1.179 +            );
   1.180 +          }
   1.181 +        }
   1.182 +        else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
   1.183 +          transactions.push(new PlacesCreateSeparatorTransaction(-1, index));
   1.184 +        }
   1.185 +        else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) {
   1.186 +          transactions.push(
   1.187 +            PlacesUIUtils._getURIItemCopyTransaction(node, -1, index)
   1.188 +          );
   1.189 +        }
   1.190 +        else {
   1.191 +          throw new Error("Unexpected item under a bookmarks folder");
   1.192 +        }
   1.193 +      });
   1.194 +      return transactions;
   1.195 +    }
   1.196 +
   1.197 +    if (aContainer == PlacesUtils.tagsFolderId) { // Copying a tag folder.
   1.198 +      let transactions = [];
   1.199 +      if (aData.children) {
   1.200 +        aData.children.forEach(function(aChild) {
   1.201 +          transactions.push(
   1.202 +            new PlacesTagURITransaction(PlacesUtils._uri(aChild.uri),
   1.203 +                                        [aData.title])
   1.204 +          );
   1.205 +        });
   1.206 +      }
   1.207 +      return new PlacesAggregatedTransaction("addTags", transactions);
   1.208 +    }
   1.209 +
   1.210 +    if (aData.livemark && aData.annos) { // Copying a livemark.
   1.211 +      return this._getLivemarkCopyTransaction(aData, aContainer, aIndex);
   1.212 +    }
   1.213 +
   1.214 +    let transactions = getChildItemsTransactions(aData.children);
   1.215 +    if (aData.dateAdded) {
   1.216 +      transactions.push(
   1.217 +        new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
   1.218 +      );
   1.219 +    }
   1.220 +    if (aData.lastModified) {
   1.221 +      transactions.push(
   1.222 +        new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
   1.223 +      );
   1.224 +    }
   1.225 +
   1.226 +    let annos = [];
   1.227 +    if (aData.annos) {
   1.228 +      annos = aData.annos.filter(function (aAnno) {
   1.229 +        return this._copyableAnnotations.indexOf(aAnno.name) != -1;
   1.230 +      }, this);
   1.231 +    }
   1.232 +
   1.233 +    return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex,
   1.234 +                                             annos, transactions);
   1.235 +  },
   1.236 +
   1.237 +  /**
   1.238 +   * Gets a transaction for copying a live bookmark item from one container to
   1.239 +   * another.
   1.240 +   *
   1.241 +   * @param   aData
   1.242 +   *          Unwrapped live bookmarkmark data
   1.243 +   * @param   aContainer
   1.244 +   *          The container we are copying into
   1.245 +   * @param   aIndex
   1.246 +   *          The index in the destination container to insert the new items
   1.247 +   * @return A nsITransaction object that will perform the copy.
   1.248 +   *
   1.249 +   * @note Since a copy creates a completely new item, only some internal
   1.250 +   *       annotations are synced from the old one.
   1.251 +   * @see this._copyableAnnotations for the list of copyable annotations.
   1.252 +   */
   1.253 +  _getLivemarkCopyTransaction:
   1.254 +  function PUIU__getLivemarkCopyTransaction(aData, aContainer, aIndex)
   1.255 +  {
   1.256 +    if (!aData.livemark || !aData.annos) {
   1.257 +      throw new Error("node is not a livemark");
   1.258 +    }
   1.259 +
   1.260 +    let feedURI, siteURI;
   1.261 +    let annos = [];
   1.262 +    if (aData.annos) {
   1.263 +      annos = aData.annos.filter(function (aAnno) {
   1.264 +        if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) {
   1.265 +          feedURI = PlacesUtils._uri(aAnno.value);
   1.266 +        }
   1.267 +        else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) {
   1.268 +          siteURI = PlacesUtils._uri(aAnno.value);
   1.269 +        }
   1.270 +        return this._copyableAnnotations.indexOf(aAnno.name) != -1
   1.271 +      }, this);
   1.272 +    }
   1.273 +
   1.274 +    return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title,
   1.275 +                                               aContainer, aIndex, annos);
   1.276 +  },
   1.277 +
   1.278 +  /**
   1.279 +   * Constructs a Transaction for the drop or paste of a blob of data into
   1.280 +   * a container.
   1.281 +   * @param   data
   1.282 +   *          The unwrapped data blob of dropped or pasted data.
   1.283 +   * @param   type
   1.284 +   *          The content type of the data
   1.285 +   * @param   container
   1.286 +   *          The container the data was dropped or pasted into
   1.287 +   * @param   index
   1.288 +   *          The index within the container the item was dropped or pasted at
   1.289 +   * @param   copy
   1.290 +   *          The drag action was copy, so don't move folders or links.
   1.291 +   * @returns An object implementing nsITransaction that can perform
   1.292 +   *          the move/insert.
   1.293 +   */
   1.294 +  makeTransaction:
   1.295 +  function PUIU_makeTransaction(data, type, container, index, copy)
   1.296 +  {
   1.297 +    switch (data.type) {
   1.298 +      case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
   1.299 +        if (copy) {
   1.300 +          return this._getFolderCopyTransaction(data, container, index);
   1.301 +        }
   1.302 +
   1.303 +        // Otherwise move the item.
   1.304 +        return new PlacesMoveItemTransaction(data.id, container, index);
   1.305 +        break;
   1.306 +      case PlacesUtils.TYPE_X_MOZ_PLACE:
   1.307 +        if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked.
   1.308 +          return this._getURIItemCopyTransaction(data, container, index);
   1.309 +        }
   1.310 +
   1.311 +        // Otherwise move the item.
   1.312 +        return new PlacesMoveItemTransaction(data.id, container, index);
   1.313 +        break;
   1.314 +      case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
   1.315 +        if (copy) {
   1.316 +          // There is no data in a separator, so copying it just amounts to
   1.317 +          // inserting a new separator.
   1.318 +          return new PlacesCreateSeparatorTransaction(container, index);
   1.319 +        }
   1.320 +
   1.321 +        // Otherwise move the item.
   1.322 +        return new PlacesMoveItemTransaction(data.id, container, index);
   1.323 +        break;
   1.324 +      default:
   1.325 +        if (type == PlacesUtils.TYPE_X_MOZ_URL ||
   1.326 +            type == PlacesUtils.TYPE_UNICODE ||
   1.327 +            type == this.TYPE_TAB_DROP) {
   1.328 +          let title = type != PlacesUtils.TYPE_UNICODE ? data.title
   1.329 +                                                       : data.uri;
   1.330 +          return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
   1.331 +                                                     container, index, title);
   1.332 +        }
   1.333 +    }
   1.334 +    return null;
   1.335 +  },
   1.336 +
   1.337 +  /**
   1.338 +   * Shows the bookmark dialog corresponding to the specified info.
   1.339 +   *
   1.340 +   * @param aInfo
   1.341 +   *        Describes the item to be edited/added in the dialog.
   1.342 +   *        See documentation at the top of bookmarkProperties.js
   1.343 +   * @param aWindow
   1.344 +   *        Owner window for the new dialog.
   1.345 +   *
   1.346 +   * @see documentation at the top of bookmarkProperties.js
   1.347 +   * @return true if any transaction has been performed, false otherwise.
   1.348 +   */
   1.349 +  showBookmarkDialog:
   1.350 +  function PUIU_showBookmarkDialog(aInfo, aParentWindow) {
   1.351 +    // Preserve size attributes differently based on the fact the dialog has
   1.352 +    // a folder picker or not.  If the picker is visible, the dialog should
   1.353 +    // be resizable since it may not show enough content for the folders
   1.354 +    // hierarchy.
   1.355 +    let hasFolderPicker = !("hiddenRows" in aInfo) ||
   1.356 +                          aInfo.hiddenRows.indexOf("folderPicker") == -1;
   1.357 +    // Use a different chrome url, since this allows to persist different sizes,
   1.358 +    // based on resizability of the dialog.
   1.359 +    let dialogURL = hasFolderPicker ?
   1.360 +                    "chrome://browser/content/places/bookmarkProperties2.xul" :
   1.361 +                    "chrome://browser/content/places/bookmarkProperties.xul";
   1.362 +
   1.363 +    let features =
   1.364 +      "centerscreen,chrome,modal,resizable=" + (hasFolderPicker ? "yes" : "no");
   1.365 +
   1.366 +    aParentWindow.openDialog(dialogURL, "",  features, aInfo);
   1.367 +    return ("performed" in aInfo && aInfo.performed);
   1.368 +  },
   1.369 +
   1.370 +  _getTopBrowserWin: function PUIU__getTopBrowserWin() {
   1.371 +    return Services.wm.getMostRecentWindow("navigator:browser");
   1.372 +  },
   1.373 +
   1.374 +  /**
   1.375 +   * Returns the closet ancestor places view for the given DOM node
   1.376 +   * @param aNode
   1.377 +   *        a DOM node
   1.378 +   * @return the closet ancestor places view if exists, null otherwsie.
   1.379 +   */
   1.380 +  getViewForNode: function PUIU_getViewForNode(aNode) {
   1.381 +    let node = aNode;
   1.382 +
   1.383 +    // The view for a <menu> of which its associated menupopup is a places
   1.384 +    // view, is the menupopup.
   1.385 +    if (node.localName == "menu" && !node._placesNode &&
   1.386 +        node.lastChild._placesView)
   1.387 +      return node.lastChild._placesView;
   1.388 +
   1.389 +    while (node instanceof Ci.nsIDOMElement) {
   1.390 +      if (node._placesView)
   1.391 +        return node._placesView;
   1.392 +      if (node.localName == "tree" && node.getAttribute("type") == "places")
   1.393 +        return node;
   1.394 +
   1.395 +      node = node.parentNode;
   1.396 +    }
   1.397 +
   1.398 +    return null;
   1.399 +  },
   1.400 +
   1.401 +  /**
   1.402 +   * By calling this before visiting an URL, the visit will be associated to a
   1.403 +   * TRANSITION_TYPED transition (if there is no a referrer).
   1.404 +   * This is used when visiting pages from the history menu, history sidebar,
   1.405 +   * url bar, url autocomplete results, and history searches from the places
   1.406 +   * organizer.  If this is not called visits will be marked as
   1.407 +   * TRANSITION_LINK.
   1.408 +   */
   1.409 +  markPageAsTyped: function PUIU_markPageAsTyped(aURL) {
   1.410 +    PlacesUtils.history.markPageAsTyped(this.createFixedURI(aURL));
   1.411 +  },
   1.412 +
   1.413 +  /**
   1.414 +   * By calling this before visiting an URL, the visit will be associated to a
   1.415 +   * TRANSITION_BOOKMARK transition.
   1.416 +   * This is used when visiting pages from the bookmarks menu,
   1.417 +   * personal toolbar, and bookmarks from within the places organizer.
   1.418 +   * If this is not called visits will be marked as TRANSITION_LINK.
   1.419 +   */
   1.420 +  markPageAsFollowedBookmark: function PUIU_markPageAsFollowedBookmark(aURL) {
   1.421 +    PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL));
   1.422 +  },
   1.423 +
   1.424 +  /**
   1.425 +   * By calling this before visiting an URL, any visit in frames will be
   1.426 +   * associated to a TRANSITION_FRAMED_LINK transition.
   1.427 +   * This is actually used to distinguish user-initiated visits in frames
   1.428 +   * so automatic visits can be correctly ignored.
   1.429 +   */
   1.430 +  markPageAsFollowedLink: function PUIU_markPageAsFollowedLink(aURL) {
   1.431 +    PlacesUtils.history.markPageAsFollowedLink(this.createFixedURI(aURL));
   1.432 +  },
   1.433 +
   1.434 +  /**
   1.435 +   * Allows opening of javascript/data URI only if the given node is
   1.436 +   * bookmarked (see bug 224521).
   1.437 +   * @param aURINode
   1.438 +   *        a URI node
   1.439 +   * @param aWindow
   1.440 +   *        a window on which a potential error alert is shown on.
   1.441 +   * @return true if it's safe to open the node in the browser, false otherwise.
   1.442 +   *
   1.443 +   */
   1.444 +  checkURLSecurity: function PUIU_checkURLSecurity(aURINode, aWindow) {
   1.445 +    if (PlacesUtils.nodeIsBookmark(aURINode))
   1.446 +      return true;
   1.447 +
   1.448 +    var uri = PlacesUtils._uri(aURINode.uri);
   1.449 +    if (uri.schemeIs("javascript") || uri.schemeIs("data")) {
   1.450 +      const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
   1.451 +      var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
   1.452 +                           getService(Ci.nsIStringBundleService).
   1.453 +                           createBundle(BRANDING_BUNDLE_URI).
   1.454 +                           GetStringFromName("brandShortName");
   1.455 +
   1.456 +      var errorStr = this.getString("load-js-data-url-error");
   1.457 +      Services.prompt.alert(aWindow, brandShortName, errorStr);
   1.458 +      return false;
   1.459 +    }
   1.460 +    return true;
   1.461 +  },
   1.462 +
   1.463 +  /**
   1.464 +   * Get the description associated with a document, as specified in a <META>
   1.465 +   * element.
   1.466 +   * @param   doc
   1.467 +   *          A DOM Document to get a description for
   1.468 +   * @returns A description string if a META element was discovered with a
   1.469 +   *          "description" or "httpequiv" attribute, empty string otherwise.
   1.470 +   */
   1.471 +  getDescriptionFromDocument: function PUIU_getDescriptionFromDocument(doc) {
   1.472 +    var metaElements = doc.getElementsByTagName("META");
   1.473 +    for (var i = 0; i < metaElements.length; ++i) {
   1.474 +      if (metaElements[i].name.toLowerCase() == "description" ||
   1.475 +          metaElements[i].httpEquiv.toLowerCase() == "description") {
   1.476 +        return metaElements[i].content;
   1.477 +      }
   1.478 +    }
   1.479 +    return "";
   1.480 +  },
   1.481 +
   1.482 +  /**
   1.483 +   * Retrieve the description of an item
   1.484 +   * @param aItemId
   1.485 +   *        item identifier
   1.486 +   * @returns the description of the given item, or an empty string if it is
   1.487 +   * not set.
   1.488 +   */
   1.489 +  getItemDescription: function PUIU_getItemDescription(aItemId) {
   1.490 +    if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO))
   1.491 +      return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO);
   1.492 +    return "";
   1.493 +  },
   1.494 +
   1.495 +  /**
   1.496 +   * Gives the user a chance to cancel loading lots of tabs at once
   1.497 +   */
   1.498 +  _confirmOpenInTabs:
   1.499 +  function PUIU__confirmOpenInTabs(numTabsToOpen, aWindow) {
   1.500 +    const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
   1.501 +    var reallyOpen = true;
   1.502 +
   1.503 +    if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) {
   1.504 +      if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
   1.505 +        // default to true: if it were false, we wouldn't get this far
   1.506 +        var warnOnOpen = { value: true };
   1.507 +
   1.508 +        var messageKey = "tabs.openWarningMultipleBranded";
   1.509 +        var openKey = "tabs.openButtonMultiple";
   1.510 +        const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
   1.511 +        var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
   1.512 +                             getService(Ci.nsIStringBundleService).
   1.513 +                             createBundle(BRANDING_BUNDLE_URI).
   1.514 +                             GetStringFromName("brandShortName");
   1.515 +
   1.516 +        var buttonPressed = Services.prompt.confirmEx(
   1.517 +          aWindow,
   1.518 +          this.getString("tabs.openWarningTitle"),
   1.519 +          this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]),
   1.520 +          (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
   1.521 +            (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
   1.522 +          this.getString(openKey), null, null,
   1.523 +          this.getFormattedString("tabs.openWarningPromptMeBranded",
   1.524 +                                  [brandShortName]),
   1.525 +          warnOnOpen
   1.526 +        );
   1.527 +
   1.528 +        reallyOpen = (buttonPressed == 0);
   1.529 +        // don't set the pref unless they press OK and it's false
   1.530 +        if (reallyOpen && !warnOnOpen.value)
   1.531 +          Services.prefs.setBoolPref(WARN_ON_OPEN_PREF, false);
   1.532 +      }
   1.533 +    }
   1.534 +
   1.535 +    return reallyOpen;
   1.536 +  },
   1.537 +
   1.538 +  /** aItemsToOpen needs to be an array of objects of the form:
   1.539 +    * {uri: string, isBookmark: boolean}
   1.540 +    */
   1.541 +  _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) {
   1.542 +    if (!aItemsToOpen.length)
   1.543 +      return;
   1.544 +
   1.545 +    // Prefer the caller window if it's a browser window, otherwise use
   1.546 +    // the top browser window.
   1.547 +    var browserWindow = null;
   1.548 +    browserWindow =
   1.549 +      aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ?
   1.550 +      aWindow : this._getTopBrowserWin();
   1.551 +
   1.552 +    var urls = [];
   1.553 +    let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow);
   1.554 +    for (let item of aItemsToOpen) {
   1.555 +      urls.push(item.uri);
   1.556 +      if (skipMarking) {
   1.557 +        continue;
   1.558 +      }
   1.559 +
   1.560 +      if (item.isBookmark)
   1.561 +        this.markPageAsFollowedBookmark(item.uri);
   1.562 +      else
   1.563 +        this.markPageAsTyped(item.uri);
   1.564 +    }
   1.565 +
   1.566 +    // whereToOpenLink doesn't return "window" when there's no browser window
   1.567 +    // open (Bug 630255).
   1.568 +    var where = browserWindow ?
   1.569 +                browserWindow.whereToOpenLink(aEvent, false, true) : "window";
   1.570 +    if (where == "window") {
   1.571 +      // There is no browser window open, thus open a new one.
   1.572 +      var uriList = PlacesUtils.toISupportsString(urls.join("|"));
   1.573 +      var args = Cc["@mozilla.org/supports-array;1"].
   1.574 +                  createInstance(Ci.nsISupportsArray);
   1.575 +      args.AppendElement(uriList);      
   1.576 +      browserWindow = Services.ww.openWindow(aWindow,
   1.577 +                                             "chrome://browser/content/browser.xul",
   1.578 +                                             null, "chrome,dialog=no,all", args);
   1.579 +      return;
   1.580 +    }
   1.581 +
   1.582 +    var loadInBackground = where == "tabshifted" ? true : false;
   1.583 +    // For consistency, we want all the bookmarks to open in new tabs, instead
   1.584 +    // of having one of them replace the currently focused tab.  Hence we call
   1.585 +    // loadTabs with aReplace set to false.
   1.586 +    browserWindow.gBrowser.loadTabs(urls, loadInBackground, false);
   1.587 +  },
   1.588 +
   1.589 +  openContainerNodeInTabs:
   1.590 +  function PUIU_openContainerInTabs(aNode, aEvent, aView) {
   1.591 +    let window = aView.ownerWindow;
   1.592 +
   1.593 +    let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
   1.594 +    if (!this._confirmOpenInTabs(urlsToOpen.length, window))
   1.595 +      return;
   1.596 +
   1.597 +    this._openTabset(urlsToOpen, aEvent, window);
   1.598 +  },
   1.599 +
   1.600 +  openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) {
   1.601 +    let window = aView.ownerWindow;
   1.602 +
   1.603 +    let urlsToOpen = [];
   1.604 +    for (var i=0; i < aNodes.length; i++) {
   1.605 +      // Skip over separators and folders.
   1.606 +      if (PlacesUtils.nodeIsURI(aNodes[i]))
   1.607 +        urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
   1.608 +    }
   1.609 +    this._openTabset(urlsToOpen, aEvent, window);
   1.610 +  },
   1.611 +
   1.612 +  /**
   1.613 +   * Loads the node's URL in the appropriate tab or window or as a web
   1.614 +   * panel given the user's preference specified by modifier keys tracked by a
   1.615 +   * DOM mouse/key event.
   1.616 +   * @param   aNode
   1.617 +   *          An uri result node.
   1.618 +   * @param   aEvent
   1.619 +   *          The DOM mouse/key event with modifier keys set that track the
   1.620 +   *          user's preferred destination window or tab.
   1.621 +   * @param   aView
   1.622 +   *          The controller associated with aNode.
   1.623 +   */
   1.624 +  openNodeWithEvent:
   1.625 +  function PUIU_openNodeWithEvent(aNode, aEvent, aView) {
   1.626 +    let window = aView.ownerWindow;
   1.627 +    this._openNodeIn(aNode, window.whereToOpenLink(aEvent, false, true), window);
   1.628 +  },
   1.629 +
   1.630 +  /**
   1.631 +   * Loads the node's URL in the appropriate tab or window or as a
   1.632 +   * web panel.
   1.633 +   * see also openUILinkIn
   1.634 +   */
   1.635 +  openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aView) {
   1.636 +    let window = aView.ownerWindow;
   1.637 +    this._openNodeIn(aNode, aWhere, window);
   1.638 +  },
   1.639 +
   1.640 +  _openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aWindow) {
   1.641 +    if (aNode && PlacesUtils.nodeIsURI(aNode) &&
   1.642 +        this.checkURLSecurity(aNode, aWindow)) {
   1.643 +      let isBookmark = PlacesUtils.nodeIsBookmark(aNode);
   1.644 +
   1.645 +      if (!PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
   1.646 +        if (isBookmark)
   1.647 +          this.markPageAsFollowedBookmark(aNode.uri);
   1.648 +        else
   1.649 +          this.markPageAsTyped(aNode.uri);
   1.650 +      }
   1.651 +
   1.652 +      // Check whether the node is a bookmark which should be opened as
   1.653 +      // a web panel
   1.654 +      if (aWhere == "current" && isBookmark) {
   1.655 +        if (PlacesUtils.annotations
   1.656 +                       .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) {
   1.657 +          let browserWin = this._getTopBrowserWin();
   1.658 +          if (browserWin) {
   1.659 +            browserWin.openWebPanel(aNode.title, aNode.uri);
   1.660 +            return;
   1.661 +          }
   1.662 +        }
   1.663 +      }
   1.664 +      aWindow.openUILinkIn(aNode.uri, aWhere, {
   1.665 +        inBackground: Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground")
   1.666 +      });
   1.667 +    }
   1.668 +  },
   1.669 +
   1.670 +  /**
   1.671 +   * Helper for guessing scheme from an url string.
   1.672 +   * Used to avoid nsIURI overhead in frequently called UI functions.
   1.673 +   *
   1.674 +   * @param aUrlString the url to guess the scheme from.
   1.675 +   *
   1.676 +   * @return guessed scheme for this url string.
   1.677 +   *
   1.678 +   * @note this is not supposed be perfect, so use it only for UI purposes.
   1.679 +   */
   1.680 +  guessUrlSchemeForUI: function PUIU_guessUrlSchemeForUI(aUrlString) {
   1.681 +    return aUrlString.substr(0, aUrlString.indexOf(":"));
   1.682 +  },
   1.683 +
   1.684 +  getBestTitle: function PUIU_getBestTitle(aNode, aDoNotCutTitle) {
   1.685 +    var title;
   1.686 +    if (!aNode.title && PlacesUtils.nodeIsURI(aNode)) {
   1.687 +      // if node title is empty, try to set the label using host and filename
   1.688 +      // PlacesUtils._uri() will throw if aNode.uri is not a valid URI
   1.689 +      try {
   1.690 +        var uri = PlacesUtils._uri(aNode.uri);
   1.691 +        var host = uri.host;
   1.692 +        var fileName = uri.QueryInterface(Ci.nsIURL).fileName;
   1.693 +        // if fileName is empty, use path to distinguish labels
   1.694 +        if (aDoNotCutTitle) {
   1.695 +          title = host + uri.path;
   1.696 +        } else {
   1.697 +          title = host + (fileName ?
   1.698 +                           (host ? "/" + this.ellipsis + "/" : "") + fileName :
   1.699 +                           uri.path);
   1.700 +        }
   1.701 +      }
   1.702 +      catch (e) {
   1.703 +        // Use (no title) for non-standard URIs (data:, javascript:, ...)
   1.704 +        title = "";
   1.705 +      }
   1.706 +    }
   1.707 +    else
   1.708 +      title = aNode.title;
   1.709 +
   1.710 +    return title || this.getString("noTitle");
   1.711 +  },
   1.712 +
   1.713 +  get leftPaneQueries() {
   1.714 +    // build the map
   1.715 +    this.leftPaneFolderId;
   1.716 +    return this.leftPaneQueries;
   1.717 +  },
   1.718 +
   1.719 +  // Get the folder id for the organizer left-pane folder.
   1.720 +  get leftPaneFolderId() {
   1.721 +    let leftPaneRoot = -1;
   1.722 +    let allBookmarksId;
   1.723 +
   1.724 +    // Shortcuts to services.
   1.725 +    let bs = PlacesUtils.bookmarks;
   1.726 +    let as = PlacesUtils.annotations;
   1.727 +
   1.728 +    // This is the list of the left pane queries.
   1.729 +    let queries = {
   1.730 +      "PlacesRoot": { title: "" },
   1.731 +      "History": { title: this.getString("OrganizerQueryHistory") },
   1.732 +      "Downloads": { title: this.getString("OrganizerQueryDownloads") },
   1.733 +      "Tags": { title: this.getString("OrganizerQueryTags") },
   1.734 +      "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
   1.735 +      "BookmarksToolbar":
   1.736 +        { title: null,
   1.737 +          concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
   1.738 +          concreteId: PlacesUtils.toolbarFolderId },
   1.739 +      "BookmarksMenu":
   1.740 +        { title: null,
   1.741 +          concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
   1.742 +          concreteId: PlacesUtils.bookmarksMenuFolderId },
   1.743 +      "UnfiledBookmarks":
   1.744 +        { title: null,
   1.745 +          concreteTitle: PlacesUtils.getString("UnsortedBookmarksFolderTitle"),
   1.746 +          concreteId: PlacesUtils.unfiledBookmarksFolderId },
   1.747 +    };
   1.748 +    // All queries but PlacesRoot.
   1.749 +    const EXPECTED_QUERY_COUNT = 7;
   1.750 +
   1.751 +    // Removes an item and associated annotations, ignoring eventual errors.
   1.752 +    function safeRemoveItem(aItemId) {
   1.753 +      try {
   1.754 +        if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) &&
   1.755 +            !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) {
   1.756 +          // Some extension annotated their roots with our query annotation,
   1.757 +          // so we should not delete them.
   1.758 +          return;
   1.759 +        }
   1.760 +        // removeItemAnnotation does not check if item exists, nor the anno,
   1.761 +        // so this is safe to do.
   1.762 +        as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
   1.763 +        as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO);
   1.764 +        // This will throw if the annotation is an orphan.
   1.765 +        bs.removeItem(aItemId);
   1.766 +      }
   1.767 +      catch(e) { /* orphan anno */ }
   1.768 +    }
   1.769 +
   1.770 +    // Returns true if item really exists, false otherwise.
   1.771 +    function itemExists(aItemId) {
   1.772 +      try {
   1.773 +        bs.getItemIndex(aItemId);
   1.774 +        return true;
   1.775 +      }
   1.776 +      catch(e) {
   1.777 +        return false;
   1.778 +      }
   1.779 +    }
   1.780 +
   1.781 +    // Get all items marked as being the left pane folder.
   1.782 +    let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO);
   1.783 +    if (items.length > 1) {
   1.784 +      // Something went wrong, we cannot have more than one left pane folder,
   1.785 +      // remove all left pane folders and continue.  We will create a new one.
   1.786 +      items.forEach(safeRemoveItem);
   1.787 +    }
   1.788 +    else if (items.length == 1 && items[0] != -1) {
   1.789 +      leftPaneRoot = items[0];
   1.790 +      // Check that organizer left pane root is valid.
   1.791 +      let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO);
   1.792 +      if (version != this.ORGANIZER_LEFTPANE_VERSION ||
   1.793 +          !itemExists(leftPaneRoot)) {
   1.794 +        // Invalid root, we must rebuild the left pane.
   1.795 +        safeRemoveItem(leftPaneRoot);
   1.796 +        leftPaneRoot = -1;
   1.797 +      }
   1.798 +    }
   1.799 +
   1.800 +    if (leftPaneRoot != -1) {
   1.801 +      // A valid left pane folder has been found.
   1.802 +      // Build the leftPaneQueries Map.  This is used to quickly access them,
   1.803 +      // associating a mnemonic name to the real item ids.
   1.804 +      delete this.leftPaneQueries;
   1.805 +      this.leftPaneQueries = {};
   1.806 +
   1.807 +      let items = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO);
   1.808 +      // While looping through queries we will also check for their validity.
   1.809 +      let queriesCount = 0;
   1.810 +      let corrupt = false;
   1.811 +      for (let i = 0; i < items.length; i++) {
   1.812 +        let queryName = as.getItemAnnotation(items[i], this.ORGANIZER_QUERY_ANNO);
   1.813 +
   1.814 +        // Some extension did use our annotation to decorate their items
   1.815 +        // with icons, so we should check only our elements, to avoid dataloss.
   1.816 +        if (!(queryName in queries))
   1.817 +          continue;
   1.818 +
   1.819 +        let query = queries[queryName];
   1.820 +        query.itemId = items[i];
   1.821 +
   1.822 +        if (!itemExists(query.itemId)) {
   1.823 +          // Orphan annotation, bail out and create a new left pane root.
   1.824 +          corrupt = true;
   1.825 +          break;
   1.826 +        }
   1.827 +
   1.828 +        // Check that all queries have valid parents.
   1.829 +        let parentId = bs.getFolderIdForItem(query.itemId);
   1.830 +        if (items.indexOf(parentId) == -1 && parentId != leftPaneRoot) {
   1.831 +          // The parent is not part of the left pane, bail out and create a new
   1.832 +          // left pane root.
   1.833 +          corrupt = true;
   1.834 +          break;
   1.835 +        }
   1.836 +
   1.837 +        // Titles could have been corrupted or the user could have changed his
   1.838 +        // locale.  Check title and eventually fix it.
   1.839 +        if (bs.getItemTitle(query.itemId) != query.title)
   1.840 +          bs.setItemTitle(query.itemId, query.title);
   1.841 +        if ("concreteId" in query) {
   1.842 +          if (bs.getItemTitle(query.concreteId) != query.concreteTitle)
   1.843 +            bs.setItemTitle(query.concreteId, query.concreteTitle);
   1.844 +        }
   1.845 +
   1.846 +        // Add the query to our cache.
   1.847 +        this.leftPaneQueries[queryName] = query.itemId;
   1.848 +        queriesCount++;
   1.849 +      }
   1.850 +
   1.851 +      // Note: it's not enough to just check for queriesCount, since we may
   1.852 +      // find an invalid query just after accounting for a sufficient number of
   1.853 +      // valid ones.  As well as we can't just rely on corrupt since we may find
   1.854 +      // less valid queries than expected.
   1.855 +      if (corrupt || queriesCount != EXPECTED_QUERY_COUNT) {
   1.856 +        // Queries number is wrong, so the left pane must be corrupt.
   1.857 +        // Note: we can't just remove the leftPaneRoot, because some query could
   1.858 +        // have a bad parent, so we have to remove all items one by one.
   1.859 +        items.forEach(safeRemoveItem);
   1.860 +        safeRemoveItem(leftPaneRoot);
   1.861 +      }
   1.862 +      else {
   1.863 +        // Everything is fine, return the current left pane folder.
   1.864 +        delete this.leftPaneFolderId;
   1.865 +        return this.leftPaneFolderId = leftPaneRoot;
   1.866 +      }
   1.867 +    }
   1.868 +
   1.869 +    // Create a new left pane folder.
   1.870 +    var callback = {
   1.871 +      // Helper to create an organizer special query.
   1.872 +      create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
   1.873 +        let itemId = bs.insertBookmark(aParentId,
   1.874 +                                       PlacesUtils._uri(aQueryUrl),
   1.875 +                                       bs.DEFAULT_INDEX,
   1.876 +                                       queries[aQueryName].title);
   1.877 +        // Mark as special organizer query.
   1.878 +        as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
   1.879 +                             0, as.EXPIRE_NEVER);
   1.880 +        // We should never backup this, since it changes between profiles.
   1.881 +        as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
   1.882 +                             0, as.EXPIRE_NEVER);
   1.883 +        // Add to the queries map.
   1.884 +        PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
   1.885 +        return itemId;
   1.886 +      },
   1.887 +
   1.888 +      // Helper to create an organizer special folder.
   1.889 +      create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
   1.890 +              // Left Pane Root Folder.
   1.891 +        let folderId = bs.createFolder(aParentId,
   1.892 +                                       queries[aFolderName].title,
   1.893 +                                       bs.DEFAULT_INDEX);
   1.894 +        // We should never backup this, since it changes between profiles.
   1.895 +        as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
   1.896 +                             0, as.EXPIRE_NEVER);
   1.897 +        // Disallow manipulating this folder within the organizer UI.
   1.898 +        bs.setFolderReadonly(folderId, true);
   1.899 +
   1.900 +        if (aIsRoot) {
   1.901 +          // Mark as special left pane root.
   1.902 +          as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
   1.903 +                               PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
   1.904 +                               0, as.EXPIRE_NEVER);
   1.905 +        }
   1.906 +        else {
   1.907 +          // Mark as special organizer folder.
   1.908 +          as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName,
   1.909 +                           0, as.EXPIRE_NEVER);
   1.910 +          PlacesUIUtils.leftPaneQueries[aFolderName] = folderId;
   1.911 +        }
   1.912 +        return folderId;
   1.913 +      },
   1.914 +
   1.915 +      runBatched: function CB_runBatched(aUserData) {
   1.916 +        delete PlacesUIUtils.leftPaneQueries;
   1.917 +        PlacesUIUtils.leftPaneQueries = { };
   1.918 +
   1.919 +        // Left Pane Root Folder.
   1.920 +        leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
   1.921 +
   1.922 +        // History Query.
   1.923 +        this.create_query("History", leftPaneRoot,
   1.924 +                          "place:type=" +
   1.925 +                          Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
   1.926 +                          "&sort=" +
   1.927 +                          Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
   1.928 +
   1.929 +        // Downloads.
   1.930 +        this.create_query("Downloads", leftPaneRoot,
   1.931 +                          "place:transition=" +
   1.932 +                          Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
   1.933 +                          "&sort=" +
   1.934 +                          Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
   1.935 +
   1.936 +        // Tags Query.
   1.937 +        this.create_query("Tags", leftPaneRoot,
   1.938 +                          "place:type=" +
   1.939 +                          Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
   1.940 +                          "&sort=" +
   1.941 +                          Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
   1.942 +
   1.943 +        // All Bookmarks Folder.
   1.944 +        allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
   1.945 +
   1.946 +        // All Bookmarks->Bookmarks Toolbar Query.
   1.947 +        this.create_query("BookmarksToolbar", allBookmarksId,
   1.948 +                          "place:folder=TOOLBAR");
   1.949 +
   1.950 +        // All Bookmarks->Bookmarks Menu Query.
   1.951 +        this.create_query("BookmarksMenu", allBookmarksId,
   1.952 +                          "place:folder=BOOKMARKS_MENU");
   1.953 +
   1.954 +        // All Bookmarks->Unfiled Bookmarks Query.
   1.955 +        this.create_query("UnfiledBookmarks", allBookmarksId,
   1.956 +                          "place:folder=UNFILED_BOOKMARKS");
   1.957 +      }
   1.958 +    };
   1.959 +    bs.runInBatchMode(callback, null);
   1.960 +
   1.961 +    delete this.leftPaneFolderId;
   1.962 +    return this.leftPaneFolderId = leftPaneRoot;
   1.963 +  },
   1.964 +
   1.965 +  /**
   1.966 +   * Get the folder id for the organizer left-pane folder.
   1.967 +   */
   1.968 +  get allBookmarksFolderId() {
   1.969 +    // ensure the left-pane root is initialized;
   1.970 +    this.leftPaneFolderId;
   1.971 +    delete this.allBookmarksFolderId;
   1.972 +    return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"];
   1.973 +  },
   1.974 +
   1.975 +  /**
   1.976 +   * If an item is a left-pane query, returns the name of the query
   1.977 +   * or an empty string if not.
   1.978 +   *
   1.979 +   * @param aItemId id of a container
   1.980 +   * @returns the name of the query, or empty string if not a left-pane query
   1.981 +   */
   1.982 +  getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) {
   1.983 +    var queryName = "";
   1.984 +    // If the let pane hasn't been built, use the annotation service
   1.985 +    // directly, to avoid building the left pane too early.
   1.986 +    if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) {
   1.987 +      try {
   1.988 +        queryName = PlacesUtils.annotations.
   1.989 +                                getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO);
   1.990 +      }
   1.991 +      catch (ex) {
   1.992 +        // doesn't have the annotation
   1.993 +        queryName = "";
   1.994 +      }
   1.995 +    }
   1.996 +    else {
   1.997 +      // If the left pane has already been built, use the name->id map
   1.998 +      // cached in PlacesUIUtils.
   1.999 +      for (let [name, id] in Iterator(this.leftPaneQueries)) {
  1.1000 +        if (aItemId == id)
  1.1001 +          queryName = name;
  1.1002 +      }
  1.1003 +    }
  1.1004 +    return queryName;
  1.1005 +  },
  1.1006 +
  1.1007 +  shouldShowTabsFromOtherComputersMenuitem: function() {
  1.1008 +    // If Sync isn't configured yet, then don't show the menuitem.
  1.1009 +    return Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED &&
  1.1010 +           Weave.Svc.Prefs.get("firstSync", "") != "notReady";
  1.1011 +  },
  1.1012 +
  1.1013 +  shouldEnableTabsFromOtherComputersMenuitem: function() {
  1.1014 +    // The tabs engine might never be inited (if services.sync.registerEngines
  1.1015 +    // is modified), so make sure we avoid undefined errors.
  1.1016 +    return Weave.Service.isLoggedIn &&
  1.1017 +           Weave.Service.engineManager.get("tabs") &&
  1.1018 +           Weave.Service.engineManager.get("tabs").enabled;
  1.1019 +  },
  1.1020 +};
  1.1021 +
  1.1022 +XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
  1.1023 +                                   "@mozilla.org/rdf/rdf-service;1",
  1.1024 +                                   "nsIRDFService");
  1.1025 +
  1.1026 +XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() {
  1.1027 +  return PlacesUIUtils.RDF.GetDataSource("rdf:local-store");
  1.1028 +});
  1.1029 +
  1.1030 +XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
  1.1031 +  return Services.prefs.getComplexValue("intl.ellipsis",
  1.1032 +                                        Ci.nsIPrefLocalizedString).data;
  1.1033 +});
  1.1034 +
  1.1035 +XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() {
  1.1036 +  try {
  1.1037 +    return Services.prefs.getBoolPref("browser.places.useAsyncTransactions");
  1.1038 +  }
  1.1039 +  catch(ex) { }
  1.1040 +  return false;
  1.1041 +});
  1.1042 +
  1.1043 +XPCOMUtils.defineLazyServiceGetter(this, "URIFixup",
  1.1044 +                                   "@mozilla.org/docshell/urifixup;1",
  1.1045 +                                   "nsIURIFixup");
  1.1046 +
  1.1047 +XPCOMUtils.defineLazyGetter(this, "bundle", function() {
  1.1048 +  const PLACES_STRING_BUNDLE_URI =
  1.1049 +    "chrome://browser/locale/places/places.properties";
  1.1050 +  return Cc["@mozilla.org/intl/stringbundle;1"].
  1.1051 +         getService(Ci.nsIStringBundleService).
  1.1052 +         createBundle(PLACES_STRING_BUNDLE_URI);
  1.1053 +});
  1.1054 +
  1.1055 +XPCOMUtils.defineLazyServiceGetter(this, "focusManager",
  1.1056 +                                   "@mozilla.org/focus-manager;1",
  1.1057 +                                   "nsIFocusManager");
  1.1058 +
  1.1059 +/**
  1.1060 + * This is a compatibility shim for old PUIU.ptm users.
  1.1061 + *
  1.1062 + * If you're looking for transactions and writing new code using them, directly
  1.1063 + * use the transactions objects exported by the PlacesUtils.jsm module.
  1.1064 + *
  1.1065 + * This object will be removed once enough users are converted to the new API.
  1.1066 + */
  1.1067 +XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() {
  1.1068 +  // Ensure PlacesUtils is imported in scope.
  1.1069 +  PlacesUtils;
  1.1070 +
  1.1071 +  return {
  1.1072 +    aggregateTransactions: function(aName, aTransactions)
  1.1073 +      new PlacesAggregatedTransaction(aName, aTransactions),
  1.1074 +
  1.1075 +    createFolder: function(aName, aContainer, aIndex, aAnnotations,
  1.1076 +                           aChildItemsTransactions)
  1.1077 +      new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations,
  1.1078 +                                        aChildItemsTransactions),
  1.1079 +
  1.1080 +    createItem: function(aURI, aContainer, aIndex, aTitle, aKeyword,
  1.1081 +                         aAnnotations, aChildTransactions)
  1.1082 +      new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle,
  1.1083 +                                          aKeyword, aAnnotations,
  1.1084 +                                          aChildTransactions),
  1.1085 +
  1.1086 +    createSeparator: function(aContainer, aIndex)
  1.1087 +      new PlacesCreateSeparatorTransaction(aContainer, aIndex),
  1.1088 +
  1.1089 +    createLivemark: function(aFeedURI, aSiteURI, aName, aContainer, aIndex,
  1.1090 +                             aAnnotations)
  1.1091 +      new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer,
  1.1092 +                                          aIndex, aAnnotations),
  1.1093 +
  1.1094 +    moveItem: function(aItemId, aNewContainer, aNewIndex)
  1.1095 +      new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex),
  1.1096 +
  1.1097 +    removeItem: function(aItemId)
  1.1098 +      new PlacesRemoveItemTransaction(aItemId),
  1.1099 +
  1.1100 +    editItemTitle: function(aItemId, aNewTitle)
  1.1101 +      new PlacesEditItemTitleTransaction(aItemId, aNewTitle),
  1.1102 +
  1.1103 +    editBookmarkURI: function(aItemId, aNewURI)
  1.1104 +      new PlacesEditBookmarkURITransaction(aItemId, aNewURI),
  1.1105 +
  1.1106 +    setItemAnnotation: function(aItemId, aAnnotationObject)
  1.1107 +      new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject),
  1.1108 +
  1.1109 +    setPageAnnotation: function(aURI, aAnnotationObject)
  1.1110 +      new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject),
  1.1111 +
  1.1112 +    editBookmarkKeyword: function(aItemId, aNewKeyword)
  1.1113 +      new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword),
  1.1114 +
  1.1115 +    editBookmarkPostData: function(aItemId, aPostData)
  1.1116 +      new PlacesEditBookmarkPostDataTransaction(aItemId, aPostData),
  1.1117 +
  1.1118 +    editLivemarkSiteURI: function(aLivemarkId, aSiteURI)
  1.1119 +      new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI),
  1.1120 +
  1.1121 +    editLivemarkFeedURI: function(aLivemarkId, aFeedURI)
  1.1122 +      new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI),
  1.1123 +
  1.1124 +    editItemDateAdded: function(aItemId, aNewDateAdded)
  1.1125 +      new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded),
  1.1126 +
  1.1127 +    editItemLastModified: function(aItemId, aNewLastModified)
  1.1128 +      new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified),
  1.1129 +
  1.1130 +    sortFolderByName: function(aFolderId)
  1.1131 +      new PlacesSortFolderByNameTransaction(aFolderId),
  1.1132 +
  1.1133 +    tagURI: function(aURI, aTags)
  1.1134 +      new PlacesTagURITransaction(aURI, aTags),
  1.1135 +
  1.1136 +    untagURI: function(aURI, aTags)
  1.1137 +      new PlacesUntagURITransaction(aURI, aTags),
  1.1138 +
  1.1139 +    /**
  1.1140 +     * Transaction for setting/unsetting Load-in-sidebar annotation.
  1.1141 +     *
  1.1142 +     * @param aBookmarkId
  1.1143 +     *        id of the bookmark where to set Load-in-sidebar annotation.
  1.1144 +     * @param aLoadInSidebar
  1.1145 +     *        boolean value.
  1.1146 +     * @returns nsITransaction object.
  1.1147 +     */
  1.1148 +    setLoadInSidebar: function(aItemId, aLoadInSidebar)
  1.1149 +    {
  1.1150 +      let annoObj = { name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
  1.1151 +                      type: Ci.nsIAnnotationService.TYPE_INT32,
  1.1152 +                      flags: 0,
  1.1153 +                      value: aLoadInSidebar,
  1.1154 +                      expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
  1.1155 +      return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
  1.1156 +    },
  1.1157 +
  1.1158 +   /**
  1.1159 +    * Transaction for editing the description of a bookmark or a folder.
  1.1160 +    *
  1.1161 +    * @param aItemId
  1.1162 +    *        id of the item to edit.
  1.1163 +    * @param aDescription
  1.1164 +    *        new description.
  1.1165 +    * @returns nsITransaction object.
  1.1166 +    */
  1.1167 +    editItemDescription: function(aItemId, aDescription)
  1.1168 +    {
  1.1169 +      let annoObj = { name: PlacesUIUtils.DESCRIPTION_ANNO,
  1.1170 +                      type: Ci.nsIAnnotationService.TYPE_STRING,
  1.1171 +                      flags: 0,
  1.1172 +                      value: aDescription,
  1.1173 +                      expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
  1.1174 +      return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
  1.1175 +    },
  1.1176 +
  1.1177 +    ////////////////////////////////////////////////////////////////////////////
  1.1178 +    //// nsITransactionManager forwarders.
  1.1179 +
  1.1180 +    beginBatch: function()
  1.1181 +      PlacesUtils.transactionManager.beginBatch(null),
  1.1182 +
  1.1183 +    endBatch: function()
  1.1184 +      PlacesUtils.transactionManager.endBatch(false),
  1.1185 +
  1.1186 +    doTransaction: function(txn)
  1.1187 +      PlacesUtils.transactionManager.doTransaction(txn),
  1.1188 +
  1.1189 +    undoTransaction: function()
  1.1190 +      PlacesUtils.transactionManager.undoTransaction(),
  1.1191 +
  1.1192 +    redoTransaction: function()
  1.1193 +      PlacesUtils.transactionManager.redoTransaction(),
  1.1194 +
  1.1195 +    get numberOfUndoItems()
  1.1196 +      PlacesUtils.transactionManager.numberOfUndoItems,
  1.1197 +    get numberOfRedoItems()
  1.1198 +      PlacesUtils.transactionManager.numberOfRedoItems,
  1.1199 +    get maxTransactionCount()
  1.1200 +      PlacesUtils.transactionManager.maxTransactionCount,
  1.1201 +    set maxTransactionCount(val)
  1.1202 +      PlacesUtils.transactionManager.maxTransactionCount = val,
  1.1203 +
  1.1204 +    clear: function()
  1.1205 +      PlacesUtils.transactionManager.clear(),
  1.1206 +
  1.1207 +    peekUndoStack: function()
  1.1208 +      PlacesUtils.transactionManager.peekUndoStack(),
  1.1209 +
  1.1210 +    peekRedoStack: function()
  1.1211 +      PlacesUtils.transactionManager.peekRedoStack(),
  1.1212 +
  1.1213 +    getUndoStack: function()
  1.1214 +      PlacesUtils.transactionManager.getUndoStack(),
  1.1215 +
  1.1216 +    getRedoStack: function()
  1.1217 +      PlacesUtils.transactionManager.getRedoStack(),
  1.1218 +
  1.1219 +    AddListener: function(aListener)
  1.1220 +      PlacesUtils.transactionManager.AddListener(aListener),
  1.1221 +
  1.1222 +    RemoveListener: function(aListener)
  1.1223 +      PlacesUtils.transactionManager.RemoveListener(aListener)
  1.1224 +  }
  1.1225 +});

mercurial