home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / firefox-3.0.14 / chrome / browser.jar / content / browser / places / bookmarkProperties.js next >
Encoding:
Text File  |  2008-05-01  |  40.0 KB  |  1,125 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Places Bookmark Properties dialog.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Joe Hughes <jhughes@google.com>
  23.  *   Dietrich Ayala <dietrich@mozilla.com>
  24.  *   Asaf Romano <mano@mozilla.com>
  25.  *   Marco Bonardo <mak77@supereva.it>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. /**
  42.  * The panel is initialized based on data given in the js object passed
  43.  * as window.arguments[0]. The object must have the following fields set:
  44.  *   @ action (String). Possible values:
  45.  *     - "add" - for adding a new item.
  46.  *       @ type (String). Possible values:
  47.  *         - "bookmark"
  48.  *           @ loadBookmarkInSidebar - optional, the default state for the
  49.  *             "Load this bookmark in the sidebar" field.
  50.  *         - "folder"
  51.  *           @ URIList (Array of nsIURI objects) - optional, list of uris to
  52.  *             be bookmarked under the new folder.
  53.  *         - "livemark"
  54.  *       @ uri (nsIURI object) - optional, the default uri for the new item.
  55.  *         The property is not used for the "folder with items" type.
  56.  *       @ title (String) - optional, the defualt title for the new item.
  57.  *       @ description (String) - optional, the default description for the new
  58.  *         item.
  59.  *       @ defaultInsertionPoint (InsertionPoint JS object) - optional, the
  60.  *         default insertion point for the new item.
  61.  *       @ keyword (String) - optional, the default keyword for the new item.
  62.  *       @ postData (String) - optional, POST data to accompany the keyword.
  63.  *      Notes:
  64.  *        1) If |uri| is set for a bookmark/livemark item and |title| isn't,
  65.  *           the dialog will query the history tables for the title associated
  66.  *           with the given uri. If the dialog is set to adding a folder with
  67.  *           bookmark items under it (see URIList), a default static title is
  68.  *           used ("[Folder Name]").
  69.  *        2) The index field of the the default insertion point is ignored if
  70.  *           the folder picker is shown.
  71.  *     - "edit" - for editing a bookmark item or a folder.
  72.  *       @ type (String). Possible values:
  73.  *         - "bookmark"
  74.  *           @ itemId (Integer) - the id of the bookmark item.
  75.  *         - "folder" (also applies to livemarks)
  76.  *           @ itemId (Integer) - the id of the folder.
  77.  *   @ hiddenRows (Strings array) - optional, list of rows to be hidden
  78.  *     regardless of the item edited or added by the dialog.
  79.  *     Possible values:
  80.  *     - "title"
  81.  *     - "location"
  82.  *     - "description"
  83.  *     - "keyword"
  84.  *     - "loadInSidebar"
  85.  *     - "feedURI"
  86.  *     - "siteURI"
  87.  *     - "folder picker" - hides both the tree and the menu.
  88.  *
  89.  * window.arguments[0].performed is set to true if any transaction has
  90.  * been performed by the dialog.
  91.  */
  92.  
  93. const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
  94. const STATIC_TITLE_ANNO = "bookmarks/staticTitle";
  95.  
  96. // This doesn't include "static" special folders (first two menu items)
  97. const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
  98.  
  99. const BOOKMARK_ITEM = 0;
  100. const BOOKMARK_FOLDER = 1;
  101. const LIVEMARK_CONTAINER = 2;
  102.  
  103. const ACTION_EDIT = 0;
  104. const ACTION_ADD = 1;
  105.  
  106. var BookmarkPropertiesPanel = {
  107.  
  108.   /** UI Text Strings */
  109.   __strings: null,
  110.   get _strings() {
  111.     if (!this.__strings) {
  112.       this.__strings = document.getElementById("stringBundle");
  113.     }
  114.     return this.__strings;
  115.   },
  116.  
  117.   _action: null,
  118.   _itemType: null,
  119.   _itemId: -1,
  120.   _uri: null,
  121.   _loadBookmarkInSidebar: false,
  122.   _itemTitle: "",
  123.   _itemDescription: "",
  124.   _microsummaries: null,
  125.   _URIList: null,
  126.   _postData: null,
  127.   _charSet: "",
  128.  
  129.   // sizeToContent is not usable due to bug 90276, so we'll use resizeTo
  130.   // instead and cache the bookmarks tree view size. See WSucks in the legacy
  131.   // UI code (addBookmark2.js).
  132.   //
  133.   // XXXmano: this doesn't work as expected yet, need to figure out if we're
  134.   // facing cocoa-widget resizeTo issue here.
  135.   _folderTreeHeight: null,
  136.  
  137.   /**
  138.    * This method returns the correct label for the dialog's "accept"
  139.    * button based on the variant of the dialog.
  140.    */
  141.   _getAcceptLabel: function BPP__getAcceptLabel() {
  142.     if (this._action == ACTION_ADD) {
  143.       if (this._URIList)
  144.         return this._strings.getString("dialogAcceptLabelAddMulti");
  145.  
  146.       return this._strings.getString("dialogAcceptLabelAddItem");
  147.     }
  148.     return this._strings.getString("dialogAcceptLabelEdit");
  149.   },
  150.  
  151.   /**
  152.    * This method returns the correct title for the current variant
  153.    * of this dialog.
  154.    */
  155.   _getDialogTitle: function BPP__getDialogTitle() {
  156.     if (this._action == ACTION_ADD) {
  157.       if (this._itemType == BOOKMARK_ITEM)
  158.         return this._strings.getString("dialogTitleAddBookmark");
  159.       if (this._itemType == LIVEMARK_CONTAINER)
  160.         return this._strings.getString("dialogTitleAddLivemark");
  161.  
  162.       // folder
  163.       NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "bogus item type");
  164.       if (this._URIList)
  165.         return this._strings.getString("dialogTitleAddMulti");
  166.  
  167.       return this._strings.getString("dialogTitleAddFolder");
  168.     }
  169.     if (this._action == ACTION_EDIT) {
  170.       return this._strings
  171.                  .getFormattedString("dialogTitleEdit", [this._itemTitle]);
  172.     }
  173.     return "";
  174.   },
  175.  
  176.   /**
  177.    * Determines the initial data for the item edited or added by this dialog
  178.    */
  179.   _determineItemInfo: function BPP__determineItemInfo() {
  180.     var dialogInfo = window.arguments[0];
  181.     NS_ASSERT("action" in dialogInfo, "missing action property");
  182.     var action = dialogInfo.action;
  183.  
  184.     if (action == "add") {
  185.       NS_ASSERT("type" in dialogInfo, "missing type property for add action");
  186.  
  187.       if ("title" in dialogInfo)
  188.         this._itemTitle = dialogInfo.title;
  189.       if ("defaultInsertionPoint" in dialogInfo)
  190.         this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint;
  191.       else {
  192.         // default to the bookmarks root
  193.         this._defaultInsertionPoint =
  194.           new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, -1);
  195.       }
  196.  
  197.       switch(dialogInfo.type) {
  198.         case "bookmark":
  199.           this._action = ACTION_ADD;
  200.           this._itemType = BOOKMARK_ITEM;
  201.           if ("uri" in dialogInfo) {
  202.             NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI,
  203.                       "uri property should be a uri object");
  204.             this._uri = dialogInfo.uri;
  205.           }
  206.           if (typeof(this._itemTitle) != "string") {
  207.             if (this._uri) {
  208.               this._itemTitle =
  209.                 this._getURITitleFromHistory(this._uri);
  210.               if (!this._itemTitle)
  211.                 this._itemTitle = this._uri.spec;
  212.             }
  213.             else
  214.               this._itemTitle = this._strings.getString("newBookmarkDefault");
  215.           }
  216.  
  217.           if ("loadBookmarkInSidebar" in dialogInfo)
  218.             this._loadBookmarkInSidebar = dialogInfo.loadBookmarkInSidebar;
  219.  
  220.           if ("keyword" in dialogInfo) {
  221.             this._bookmarkKeyword = dialogInfo.keyword;
  222.             if ("postData" in dialogInfo)
  223.               this._postData = dialogInfo.postData;
  224.             if ("charSet" in dialogInfo)
  225.               this._charSet = dialogInfo.charSet;
  226.           }
  227.  
  228.           break;
  229.         case "folder":
  230.           this._action = ACTION_ADD;
  231.           this._itemType = BOOKMARK_FOLDER;
  232.           if (!this._itemTitle) {
  233.             if ("URIList" in dialogInfo) {
  234.               this._itemTitle =
  235.                 this._strings.getString("bookmarkAllTabsDefault");
  236.               this._URIList = dialogInfo.URIList;
  237.             }
  238.             else
  239.               this._itemTitle = this._strings.getString("newFolderDefault");
  240.           }
  241.           break;
  242.         case "livemark":
  243.           this._action = ACTION_ADD;
  244.           this._itemType = LIVEMARK_CONTAINER;
  245.           if ("feedURI" in dialogInfo)
  246.             this._feedURI = dialogInfo.feedURI;
  247.           if ("siteURI" in dialogInfo)
  248.             this._siteURI = dialogInfo.siteURI;
  249.  
  250.           if (!this._itemTitle) {
  251.             if (this._feedURI) {
  252.               this._itemTitle =
  253.                 this._getURITitleFromHistory(this._feedURI);
  254.               if (!this._itemTitle)
  255.                 this._itemTitle = this._feedURI.spec;
  256.             }
  257.             else
  258.               this._itemTitle = this._strings.getString("newLivemarkDefault");
  259.           }
  260.       }
  261.  
  262.       if ("description" in dialogInfo)
  263.         this._itemDescription = dialogInfo.description;
  264.     }
  265.     else { // edit
  266.       const annos = PlacesUtils.annotations;
  267.       const bookmarks = PlacesUtils.bookmarks;
  268.  
  269.       switch (dialogInfo.type) {
  270.         case "bookmark":
  271.           NS_ASSERT("itemId" in dialogInfo);
  272.  
  273.           this._action = ACTION_EDIT;
  274.           this._itemType = BOOKMARK_ITEM;
  275.           this._itemId = dialogInfo.itemId;
  276.  
  277.           this._uri = bookmarks.getBookmarkURI(this._itemId);
  278.           this._itemTitle = bookmarks.getItemTitle(this._itemId);
  279.  
  280.           // keyword
  281.           this._bookmarkKeyword =
  282.             bookmarks.getKeywordForBookmark(this._itemId);
  283.  
  284.           // Load In Sidebar
  285.           this._loadBookmarkInSidebar =
  286.             annos.itemHasAnnotation(this._itemId, LOAD_IN_SIDEBAR_ANNO);
  287.  
  288.           break;
  289.         case "folder":
  290.           NS_ASSERT("itemId" in dialogInfo);
  291.  
  292.           this._action = ACTION_EDIT;
  293.           this._itemId = dialogInfo.itemId;
  294.  
  295.           const livemarks = PlacesUtils.livemarks;
  296.           if (livemarks.isLivemark(this._itemId)) {
  297.             this._itemType = LIVEMARK_CONTAINER;
  298.             this._feedURI = livemarks.getFeedURI(this._itemId);
  299.             this._siteURI = livemarks.getSiteURI(this._itemId);
  300.           }
  301.           else
  302.             this._itemType = BOOKMARK_FOLDER;
  303.           this._itemTitle = bookmarks.getItemTitle(this._itemId);
  304.           break;
  305.       }
  306.  
  307.       // Description
  308.       if (annos.itemHasAnnotation(this._itemId, DESCRIPTION_ANNO)) {
  309.         this._itemDescription = annos.getItemAnnotation(this._itemId,
  310.                                                         DESCRIPTION_ANNO);
  311.       }
  312.     }
  313.   },
  314.  
  315.   /**
  316.    * This method returns the title string corresponding to a given URI.
  317.    * If none is available from the bookmark service (probably because
  318.    * the given URI doesn't appear in bookmarks or history), we synthesize
  319.    * a title from the first 100 characters of the URI.
  320.    *
  321.    * @param aURI
  322.    *        nsIURI object for which we want the title
  323.    *
  324.    * @returns a title string
  325.    */
  326.   _getURITitleFromHistory: function BPP__getURITitleFromHistory(aURI) {
  327.     NS_ASSERT(aURI instanceof Ci.nsIURI);
  328.  
  329.     // get the title from History
  330.     return PlacesUtils.history.getPageTitle(aURI);
  331.   },
  332.  
  333.   /**
  334.    * This method should be called by the onload of the Bookmark Properties
  335.    * dialog to initialize the state of the panel.
  336.    */
  337.   onDialogLoad: function BPP_onDialogLoad() {
  338.     this._determineItemInfo();
  339.     this._populateProperties();
  340.     this.validateChanges();
  341.  
  342.     this._folderMenuList = this._element("folderMenuList");
  343.     this._folderTree = this._element("folderTree");
  344.     if (!this._element("folderRow").hidden)
  345.       this._initFolderMenuList();
  346.  
  347.     window.sizeToContent();
  348.  
  349.     // read the persisted attribute.
  350.     this._folderTreeHeight = parseInt(this._folderTree.getAttribute("height"));
  351.   },
  352.  
  353.   /**
  354.    * Appends a menu-item representing a bookmarks folder to a menu-popup.
  355.    * @param aMenupopup
  356.    *        The popup to which the menu-item should be added.
  357.    * @param aFolderId
  358.    *        The identifier of the bookmarks folder.
  359.    * @return the new menu item.
  360.    */
  361.   _appendFolderItemToMenupopup:
  362.   function BPP__appendFolderItemToMenupopup(aMenupopup, aFolderId) {
  363.     try {
  364.       var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId);
  365.     }
  366.     catch (ex) {
  367.       NS_ASSERT(folderTitle, "no title found for folderId of " + aFolderId);
  368.       return null;
  369.     }
  370.  
  371.     // First make sure the folders-separator is visible
  372.     this._element("foldersSeparator").hidden = false;
  373.  
  374.     var folderMenuItem = document.createElement("menuitem");
  375.     folderMenuItem.folderId = aFolderId;
  376.     folderMenuItem.setAttribute("label", folderTitle);
  377.     folderMenuItem.className = "menuitem-iconic folder-icon";
  378.     aMenupopup.appendChild(folderMenuItem);
  379.     return folderMenuItem;
  380.   },
  381.  
  382.   _initFolderMenuList: function BPP__initFolderMenuList() {
  383.     // Build the static list
  384.     var bms = PlacesUtils.bookmarks;
  385.     var bmMenuItem = this._element("bookmarksRootItem");
  386.     bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
  387.     bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
  388.     var toolbarItem = this._element("toolbarFolderItem");
  389.     toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
  390.     toolbarItem.folderId = PlacesUtils.toolbarFolderId;
  391.  
  392.     // List of recently used folders:
  393.     var annos = PlacesUtils.annotations;
  394.     var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO, { });
  395.  
  396.     // Hide the folders-separator if no folder is annotated as recently-used
  397.     if (folderIds.length == 0) {
  398.       this._element("foldersSeparator").hidden = true;
  399.       return;
  400.     }
  401.  
  402.     /**
  403.      * The value of the LAST_USED_ANNO annotation is the time (in the form of
  404.      * Date.getTime) at which the folder has been last used.
  405.      *
  406.      * First we build the annotated folders array, each item has both the
  407.      * folder identifier and the time at which it was last-used by this dialog
  408.      * set. Then we sort it descendingly based on the time field.
  409.      */
  410.     var folders = [];
  411.     for (var i=0; i < folderIds.length; i++) {
  412.       var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
  413.       folders.push({ folderId: folderIds[i], lastUsed: lastUsed });
  414.     }
  415.     folders.sort(function(a, b) {
  416.       if (b.lastUsed < a.lastUsed)
  417.         return -1;
  418.       if (b.lastUsed > a.lastUsed)
  419.         return 1;
  420.       return 0;
  421.     });
  422.  
  423.     var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, folders.length);
  424.     var menupopup = this._folderMenuList.menupopup;
  425.     for (i=0; i < numberOfItems; i++) {
  426.       this._appendFolderItemToMenupopup(menupopup, folders[i].folderId);
  427.     }
  428.  
  429.     var defaultItem =
  430.       this._getFolderMenuItem(this._defaultInsertionPoint.itemId);
  431.  
  432.     // if we fail to get a menuitem for the default insertion point
  433.     // use the Bookmarks root
  434.     if (!defaultItem)
  435.       defaultItem = this._element("bookmarksRootItem");
  436.  
  437.     this._folderMenuList.selectedItem = defaultItem;
  438.   },
  439.  
  440.   QueryInterface: function BPP_QueryInterface(aIID) {
  441.     if (aIID.equals(Ci.nsIMicrosummaryObserver) ||
  442.         aIID.equals(Ci.nsISupports))
  443.       return this;
  444.  
  445.     throw Cr.NS_ERROR_NO_INTERFACE;
  446.   },
  447.  
  448.   _element: function BPP__element(aID) {
  449.     return document.getElementById(aID);
  450.   },
  451.  
  452.   /**
  453.    * Show or hides fields based on item type.
  454.    */
  455.   _showHideRows: function BPP__showHideRows() {
  456.     var hiddenRows = window.arguments[0].hiddenRows || new Array();
  457.  
  458.     var isBookmark = this._itemType == BOOKMARK_ITEM;
  459.     var isLivemark = this._itemType == LIVEMARK_CONTAINER;
  460.  
  461.     var isQuery = false;
  462.     if (this._uri)
  463.       isQuery = this._uri.schemeIs("place");
  464.  
  465.     this._element("namePicker").hidden =
  466.       hiddenRows.indexOf("title") != -1;
  467.     this._element("locationRow").hidden =
  468.       hiddenRows.indexOf("location") != -1 || isQuery || !isBookmark;
  469.     this._element("keywordRow").hidden =
  470.       hiddenRows.indexOf("keyword") != -1 || isQuery || !isBookmark;
  471.     this._element("descriptionRow").hidden =
  472.       hiddenRows.indexOf("description")!= -1
  473.     this._element("folderRow").hidden =
  474.       hiddenRows.indexOf("folder picker") != -1 || this._action == ACTION_EDIT;
  475.     this._element("livemarkFeedLocationRow").hidden =
  476.       hiddenRows.indexOf("feedURI") != -1 || !isLivemark;
  477.     this._element("livemarkSiteLocationRow").hidden =
  478.       hiddenRows.indexOf("siteURI") != -1 || !isLivemark;
  479.     this._element("loadInSidebarCheckbox").hidden =
  480.       hiddenRows.indexOf("loadInSidebar") != -1 || isQuery || !isBookmark;
  481.   },
  482.  
  483.   /**
  484.    * This method fills in the data values for the fields in the dialog.
  485.    */
  486.   _populateProperties: function BPP__populateProperties() {
  487.     document.title = this._getDialogTitle();
  488.     document.documentElement.getButton("accept").label = this._getAcceptLabel();
  489.  
  490.     this._initNamePicker();
  491.     this._element("descriptionTextfield").value = this._itemDescription;
  492.  
  493.     if (this._itemType == BOOKMARK_ITEM) {
  494.       if (this._uri)
  495.         this._element("editURLBar").value = this._uri.spec;
  496.  
  497.       if (typeof(this._bookmarkKeyword) == "string")
  498.         this._element("keywordTextfield").value = this._bookmarkKeyword;
  499.  
  500.       if (this._loadBookmarkInSidebar)
  501.         this._element("loadInSidebarCheckbox").checked = true;
  502.     }
  503.  
  504.     if (this._itemType == LIVEMARK_CONTAINER) {
  505.       if (this._feedURI)
  506.         this._element("feedLocationTextfield").value = this._feedURI.spec;
  507.       if (this._siteURI)
  508.         this._element("feedSiteLocationTextfield").value = this._siteURI.spec;
  509.     }
  510.  
  511.     this._showHideRows();
  512.   },
  513.  
  514.   _createMicrosummaryMenuItem:
  515.   function BPP__createMicrosummaryMenuItem(aMicrosummary) {
  516.     var menuItem = document.createElement("menuitem");
  517.  
  518.     // Store a reference to the microsummary in the menu item, so we know
  519.     // which microsummary this menu item represents when it's time to
  520.     // save changes or load its content.
  521.     menuItem.microsummary = aMicrosummary;
  522.  
  523.     // Content may have to be generated asynchronously; we don't necessarily
  524.     // have it now.  If we do, great; otherwise, fall back to the generator
  525.     // name, then the URI, and we trigger a microsummary content update. Once
  526.     // the update completes, the microsummary will notify our observer to
  527.     // update the corresponding menu-item.
  528.     // XXX Instead of just showing the generator name or (heaven forbid)
  529.     // its URI when we don't have content, we should tell the user that
  530.     // we're loading the microsummary, perhaps with some throbbing to let
  531.     // her know it is in progress.
  532.     if (aMicrosummary.content)
  533.       menuItem.setAttribute("label", aMicrosummary.content);
  534.     else {
  535.       menuItem.setAttribute("label", aMicrosummary.generator.name ||
  536.                                      aMicrosummary.generator.uri.spec);
  537.       aMicrosummary.update();
  538.     }
  539.  
  540.     return menuItem;
  541.   },
  542.  
  543.   _initNamePicker: function BPP_initNamePicker() {
  544.     var userEnteredNameField = this._element("userEnteredName");
  545.     var namePicker = this._element("namePicker");
  546.     const annos = PlacesUtils.annotations;
  547.  
  548.     if (annos.itemHasAnnotation(this._itemId, STATIC_TITLE_ANNO)) {
  549.       userEnteredNameField.label = annos.getItemAnnotation(this._itemId,
  550.                                                            STATIC_TITLE_ANNO);
  551.     }
  552.     else
  553.       userEnteredNameField.label = this._itemTitle;
  554.  
  555.     // Non-bookmark items always use the item-title itself
  556.     if (this._itemType != BOOKMARK_ITEM || !this._uri) {
  557.       namePicker.selectedItem = userEnteredNameField;
  558.       return;
  559.     }
  560.  
  561.     var itemToSelect = userEnteredNameField;
  562.     try {
  563.       this._microsummaries =
  564.         PlacesUIUtils.microsummaries.getMicrosummaries(this._uri,
  565.                                                        this._itemId);
  566.     }
  567.     catch(ex) {
  568.       // getMicrosummaries will throw an exception if the page to which the URI
  569.       // refers isn't HTML or XML (the only two content types the service knows
  570.       // how to summarize).
  571.       this._microsummaries = null;
  572.     }
  573.     if (this._microsummaries) {
  574.       var enumerator = this._microsummaries.Enumerate();
  575.  
  576.       if (enumerator.hasMoreElements()) {
  577.         // Show the drop marker if there are microsummaries
  578.         namePicker.setAttribute("droppable", "true");
  579.  
  580.         var menupopup = namePicker.menupopup;
  581.         while (enumerator.hasMoreElements()) {
  582.           var microsummary = enumerator.getNext()
  583.                                        .QueryInterface(Ci.nsIMicrosummary);
  584.           var menuItem = this._createMicrosummaryMenuItem(microsummary);
  585.  
  586.           if (this._action == ACTION_EDIT &&
  587.               PlacesUIUtils.microsummaries
  588.                            .isMicrosummary(this._itemId, microsummary))
  589.             itemToSelect = menuItem;
  590.  
  591.           menupopup.appendChild(menuItem);
  592.         }
  593.       }
  594.  
  595.       this._microsummaries.addObserver(this);
  596.     }
  597.  
  598.     namePicker.selectedItem = itemToSelect;
  599.   },
  600.  
  601.   // nsIMicrosummaryObserver
  602.   onContentLoaded: function BPP_onContentLoaded(aMicrosummary) {
  603.     var namePicker = this._element("namePicker");
  604.     var childNodes = namePicker.menupopup.childNodes;
  605.  
  606.     // 0: user-entered item; 1: separator
  607.     for (var i = 2; i < childNodes.length; i++) {
  608.       if (childNodes[i].microsummary == aMicrosummary) {
  609.         var newLabel = aMicrosummary.content;
  610.         // XXXmano: non-editable menulist would do this for us, see bug 360220
  611.         // We should fix editable-menulsits to set the DOMAttrModified handler
  612.         // as well.
  613.         //
  614.         // Also note the order importance: if the label of the menu-item is
  615.         // set the something different than the menulist's current value,
  616.         // the menulist no longer has selectedItem set
  617.         if (namePicker.selectedItem == childNodes[i])
  618.           namePicker.value = newLabel;
  619.  
  620.         childNodes[i].label = newLabel;
  621.         return;
  622.       }
  623.     }
  624.   },
  625.  
  626.   onElementAppended: function BPP_onElementAppended(aMicrosummary) {
  627.     var namePicker = this._element("namePicker");
  628.     namePicker.menupopup
  629.               .appendChild(this._createMicrosummaryMenuItem(aMicrosummary));
  630.  
  631.     // Make sure the drop-marker is shown
  632.     namePicker.setAttribute("droppable", "true");
  633.   },
  634.  
  635.   onError: function BPP_onError(aMicrosummary) {
  636.     var namePicker = this._element("namePicker");
  637.     var childNodes = namePicker.menupopup.childNodes;
  638.  
  639.     // 0: user-entered item; 1: separator
  640.     for (var i = 2; i < childNodes.length; i++) {
  641.       if (childNodes[i].microsummary == aMicrosummary &&
  642.           aMicrosummary.needsRemoval)
  643.           namePicker.menupopup.removeChild(childNodes[i]);
  644.     }
  645.   },
  646.  
  647.   onDialogUnload: function BPP_onDialogUnload() {
  648.     if (this._microsummaries)
  649.       this._microsummaries.removeObserver(this);
  650.  
  651.     // persist the folder tree height
  652.     if (!this._folderTree.collapsed) {
  653.       this._folderTree.setAttribute("height",
  654.                                     this._folderTree.boxObject.height);
  655.     }
  656.   },
  657.  
  658.   onDialogAccept: function BPP_onDialogAccept() {
  659.     if (this._action == ACTION_ADD)
  660.       this._createNewItem();
  661.     else
  662.       this._saveChanges();
  663.   },
  664.  
  665.   /**
  666.    * This method checks the current state of the input fields in the
  667.    * dialog, and if any of them are in an invalid state, it will disable
  668.    * the submit button.  This method should be called after every
  669.    * significant change to the input.
  670.    */
  671.   validateChanges: function BPP_validateChanges() {
  672.     document.documentElement.getButton("accept").disabled = !this._inputIsValid();
  673.   },
  674.  
  675.   /**
  676.    * This method checks to see if the input fields are in a valid state.
  677.    *
  678.    * @returns  true if the input is valid, false otherwise
  679.    */
  680.   _inputIsValid: function BPP__inputIsValid() {
  681.     if (this._itemType == BOOKMARK_ITEM && !this._containsValidURI("editURLBar"))
  682.       return false;
  683.  
  684.     // Feed Location has to be a valid URI;
  685.     // Site Location has to be a valid URI or empty
  686.     if (this._itemType == LIVEMARK_CONTAINER) {
  687.       if (!this._containsValidURI("feedLocationTextfield"))
  688.         return false;
  689.       if (!this._containsValidURI("feedSiteLocationTextfield") &&
  690.           (this._element("feedSiteLocationTextfield").value.length > 0))
  691.         return false;
  692.     }
  693.  
  694.     return true;
  695.   },
  696.  
  697.   /**
  698.    * Determines whether the XUL textbox with the given ID contains a
  699.    * string that can be converted into an nsIURI.
  700.    *
  701.    * @param aTextboxID
  702.    *        the ID of the textbox element whose contents we'll test
  703.    *
  704.    * @returns true if the textbox contains a valid URI string, false otherwise
  705.    */
  706.   _containsValidURI: function BPP__containsValidURI(aTextboxID) {
  707.     try {
  708.       var value = this._element(aTextboxID).value;
  709.       if (value) {
  710.         var uri = PlacesUIUtils.createFixedURI(value);
  711.         return true;
  712.       }
  713.     } catch (e) { }
  714.     return false;
  715.   },
  716.  
  717.   /**
  718.    * Get an edit title transaction for the item edit/added in the dialog
  719.    */
  720.   _getEditTitleTransaction:
  721.   function BPP__getEditTitleTransaction(aItemId, aNewTitle) {
  722.     return PlacesUIUtils.ptm.editItemTitle(aItemId, aNewTitle);
  723.   },
  724.  
  725.   /**
  726.    * XXXmano todo:
  727.    *  1. Make setAnnotationsForURI unset a given annotation if the value field
  728.    *     is not set.
  729.    *  2. Replace PlacesEditItemDescriptionTransaction and
  730.    *     PlacesSetLoadInSidebarTransaction transaction with a generic
  731.    *     transaction to set/unset an annotation object.
  732.    *  3. Use the two helpers below with this new generic transaction in
  733.    *     _saveChanges.
  734.    */
  735.  
  736.   /**
  737.    * Returns an object which could then be used to set/unset the
  738.    * description annotation for an item (any type).
  739.    *
  740.    * @param aDescription
  741.    *        The description of the item.
  742.    * @returns an object representing the annotation which could then be used
  743.    *          with get/setAnnotationsForURI of PlacesUtils.
  744.    */
  745.   _getDescriptionAnnotation:
  746.   function BPP__getDescriptionAnnotation(aDescription) {
  747.     var anno = { name: DESCRIPTION_ANNO,
  748.                  type: Ci.nsIAnnotationService.TYPE_STRING,
  749.                  flags: 0,
  750.                  value: aDescription,
  751.                  expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
  752.  
  753.     /**
  754.      * See todo note above
  755.      * if (aDescription)
  756.      *   anno.value = aDescription;
  757.      */
  758.     return anno;
  759.   },
  760.  
  761.   /**
  762.    * Returns an object which could then be used to set/unset the
  763.    * load-in-sidebar annotation for a bookmark item.
  764.    *
  765.    * @param aLoadInSidebar
  766.    *        Whether to load the bookmark item in the sidebar in default
  767.    *        conditions.
  768.    * @returns an object representing the annotation which could then be used
  769.    *          with get/setAnnotationsForURI of PlacesUtils.
  770.    */
  771.   _getLoadInSidebarAnnotation:
  772.   function BPP__getLoadInSidebarAnnotation(aLoadInSidebar) {
  773.     var anno = { name: LOAD_IN_SIDEBAR_ANNO,
  774.                  type: Ci.nsIAnnotationService.TYPE_INT32,
  775.                  flags: 0,
  776.                  value: aLoadInSidebar,
  777.                  expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
  778.  
  779.     /**
  780.      * See todo note above
  781.      * if (anno)
  782.      *   anno.value = aLoadInSidebar;
  783.      */
  784.     return anno;
  785.   },
  786.  
  787.   /**
  788.    * Dialog-accept code path when editing an item (any type).
  789.    *
  790.    * Save any changes that might have been made while the properties dialog
  791.    * was open.
  792.    */
  793.   _saveChanges: function BPP__saveChanges() {
  794.     var itemId = this._itemId;
  795.  
  796.     var transactions = [];
  797.  
  798.     // title
  799.     var newTitle = this._element("userEnteredName").label;
  800.     if (newTitle != this._itemTitle)
  801.       transactions.push(this._getEditTitleTransaction(itemId, newTitle));
  802.  
  803.     // description
  804.     var description = this._element("descriptionTextfield").value;
  805.     if (description != this._itemDescription) {
  806.       transactions.push(PlacesUIUtils.ptm.
  807.                         editItemDescription(itemId, description,
  808.                         this._itemType != BOOKMARK_ITEM));
  809.     }
  810.  
  811.     if (this._itemType == BOOKMARK_ITEM) {
  812.       // location
  813.       var url = PlacesUIUtils.createFixedURI(this._element("editURLBar").value);
  814.       if (!this._uri.equals(url))
  815.         transactions.push(PlacesUIUtils.ptm.editBookmarkURI(itemId, url));
  816.  
  817.       // keyword transactions
  818.       var newKeyword = this._element("keywordTextfield").value;
  819.       if (newKeyword != this._bookmarkKeyword) {
  820.         transactions.push(PlacesUIUtils.ptm.
  821.                           editBookmarkKeyword(itemId, newKeyword));
  822.       }
  823.  
  824.       // microsummaries
  825.       var namePicker = this._element("namePicker");
  826.       var newMicrosummary = namePicker.selectedItem.microsummary;
  827.  
  828.       // Only add a microsummary update to the transaction if the
  829.       // microsummary has actually changed, i.e. the user selected no
  830.       // microsummary, but the bookmark previously had one, or the user
  831.       // selected a microsummary which is not the one the bookmark previously
  832.       // had.
  833.       if ((newMicrosummary == null &&
  834.            PlacesUIUtils.microsummaries.hasMicrosummary(itemId)) ||
  835.           (newMicrosummary != null &&
  836.            !PlacesUIUtils.microsummaries
  837.                          .isMicrosummary(itemId, newMicrosummary))) {
  838.         transactions.push(
  839.           PlacesUIUtils.ptm.editBookmarkMicrosummary(itemId, newMicrosummary));
  840.       }
  841.  
  842.       // load in sidebar
  843.       var loadInSidebarChecked = this._element("loadInSidebarCheckbox").checked;
  844.       if (loadInSidebarChecked != this._loadBookmarkInSidebar) {
  845.         transactions.push(
  846.           PlacesUIUtils.ptm.setLoadInSidebar(itemId, loadInSidebarChecked));
  847.       }
  848.     }
  849.     else if (this._itemType == LIVEMARK_CONTAINER) {
  850.       var feedURIString = this._element("feedLocationTextfield").value;
  851.       var feedURI = PlacesUIUtils.createFixedURI(feedURIString);
  852.       if (!this._feedURI.equals(feedURI)) {
  853.         transactions.push(
  854.           PlacesUIUtils.ptm.editLivemarkFeedURI(this._itemId, feedURI));
  855.       }
  856.  
  857.       // Site Location is empty, we can set its URI to null
  858.       var newSiteURIString = this._element("feedSiteLocationTextfield").value;
  859.       var newSiteURI = null;
  860.       if (newSiteURIString)
  861.         newSiteURI = PlacesUIUtils.createFixedURI(newSiteURIString);
  862.  
  863.       if ((!newSiteURI && this._siteURI)  ||
  864.           (newSiteURI && (!this._siteURI || !this._siteURI.equals(newSiteURI)))) {
  865.         transactions.push(
  866.           PlacesUIUtils.ptm.editLivemarkSiteURI(this._itemId, newSiteURI));
  867.       }
  868.     }
  869.  
  870.     // If we have any changes to perform, do them via the
  871.     // transaction manager passed by the opener so they can be undone.
  872.     if (transactions.length > 0) {
  873.       window.arguments[0].performed = true;
  874.       var aggregate =
  875.         PlacesUIUtils.ptm.aggregateTransactions(this._getDialogTitle(), transactions);
  876.       PlacesUIUtils.ptm.doTransaction(aggregate);
  877.     }
  878.   },
  879.  
  880.   /**
  881.    * [New Item Mode] Get the insertion point details for the new item, given
  882.    * dialog state and opening arguments.
  883.    *
  884.    * The container-identifier and insertion-index are returned separately in
  885.    * the form of [containerIdentifier, insertionIndex]
  886.    */
  887.   _getInsertionPointDetails: function BPP__getInsertionPointDetails() {
  888.     var containerId, indexInContainer = -1;
  889.     if (!this._element("folderRow").hidden)
  890.       containerId = this._getFolderIdFromMenuList();
  891.     else {
  892.       containerId = this._defaultInsertionPoint.itemId;
  893.       indexInContainer = this._defaultInsertionPoint.index;
  894.     }
  895.  
  896.     return [containerId, indexInContainer];
  897.   },
  898.  
  899.   /**
  900.    * Returns a transaction for creating a new bookmark item representing the
  901.    * various fields and opening arguments of the dialog.
  902.    */
  903.   _getCreateNewBookmarkTransaction:
  904.   function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) {
  905.     var uri = PlacesUIUtils.createFixedURI(this._element("editURLBar").value);
  906.     var title = this._element("userEnteredName").label;
  907.     var keyword = this._element("keywordTextfield").value;
  908.     var annotations = [];
  909.     var description = this._element("descriptionTextfield").value;
  910.     if (description)
  911.       annotations.push(this._getDescriptionAnnotation(description));
  912.  
  913.     var loadInSidebar = this._element("loadInSidebarCheckbox").checked;
  914.     if (loadInSidebar)
  915.       annotations.push(this._getLoadInSidebarAnnotation(true));
  916.  
  917.     var childTransactions = [];
  918.     var microsummary = this._element("namePicker").selectedItem.microsummary;
  919.     if (microsummary) {
  920.       childTransactions.push(
  921.         PlacesUIUtils.ptm.editBookmarkMicrosummary(-1, microsummary));
  922.     }
  923.  
  924.     if (this._postData) {
  925.       childTransactions.push(
  926.         PlacesUIUtils.ptm.editBookmarkPostData(-1, this._postData));
  927.     }
  928.  
  929.     if (this._charSet)
  930.       PlacesUtils.history.setCharsetForURI(this._uri, this._charSet);
  931.  
  932.     var transactions = [PlacesUIUtils.ptm.createItem(uri, aContainer, aIndex,
  933.                                                      title, keyword,
  934.                                                      annotations,
  935.                                                      childTransactions)];
  936.  
  937.     return PlacesUIUtils.ptm.aggregateTransactions(this._getDialogTitle(), transactions);
  938.   },
  939.  
  940.   /**
  941.    * Returns a childItems-transactions array representing the URIList with
  942.    * which the dialog has been opened.
  943.    */
  944.   _getTransactionsForURIList: function BPP__getTransactionsForURIList() {
  945.     var transactions = [];
  946.     for (var i = 0; i < this._URIList.length; ++i) {
  947.       var uri = this._URIList[i];
  948.       var title = this._getURITitleFromHistory(uri);
  949.       transactions.push(PlacesUIUtils.ptm.createItem(uri, -1, -1, title));
  950.     }
  951.     return transactions; 
  952.   },
  953.  
  954.   /**
  955.    * Returns a transaction for creating a new folder item representing the
  956.    * various fields and opening arguments of the dialog.
  957.    */
  958.   _getCreateNewFolderTransaction:
  959.   function BPP__getCreateNewFolderTransaction(aContainer, aIndex) {
  960.     var folderName = this._element("namePicker").value;
  961.     var annotations = [];
  962.     var childItemsTransactions;
  963.     if (this._URIList)
  964.       childItemsTransactions = this._getTransactionsForURIList();
  965.     var description = this._element("descriptionTextfield").value;
  966.     if (description)
  967.       annotations.push(this._getDescriptionAnnotation(description));
  968.  
  969.     return PlacesUIUtils.ptm.createFolder(folderName, aContainer, aIndex,
  970.                                           annotations, childItemsTransactions);
  971.   },
  972.  
  973.   /**
  974.    * Returns a transaction for creating a new live-bookmark item representing
  975.    * the various fields and opening arguments of the dialog.
  976.    */
  977.   _getCreateNewLivemarkTransaction:
  978.   function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) {
  979.     var feedURIString = this._element("feedLocationTextfield").value;
  980.     var feedURI = PlacesUIUtils.createFixedURI(feedURIString);
  981.  
  982.     var siteURIString = this._element("feedSiteLocationTextfield").value;
  983.     var siteURI = null;
  984.     if (siteURIString)
  985.       siteURI = PlacesUIUtils.createFixedURI(siteURIString);
  986.  
  987.     var name = this._element("namePicker").value;
  988.     return PlacesUIUtils.ptm.createLivemark(feedURI, siteURI, name,
  989.                                             aContainer, aIndex);
  990.   },
  991.  
  992.   /**
  993.    * Dialog-accept code-path for creating a new item (any type)
  994.    */
  995.   _createNewItem: function BPP__getCreateItemTransaction() {
  996.     var [container, index] = this._getInsertionPointDetails();
  997.     var createTxn;
  998.     if (this._itemType == BOOKMARK_FOLDER)
  999.       createTxn = this._getCreateNewFolderTransaction(container, index);
  1000.     else if (this._itemType == LIVEMARK_CONTAINER)
  1001.       createTxn = this._getCreateNewLivemarkTransaction(container, index);
  1002.     else // BOOKMARK_ITEM
  1003.       createTxn = this._getCreateNewBookmarkTransaction(container, index);
  1004.  
  1005.     // Mark the containing folder as recently-used if it isn't in the static
  1006.     // list
  1007.     if (container != PlacesUtils.toolbarFolderId &&
  1008.         container != PlacesUtils.bookmarksMenuFolderId)
  1009.       this._markFolderAsRecentlyUsed(container);
  1010.  
  1011.     // perfrom our transaction do via the transaction manager passed by the
  1012.     // opener so it can be undone.
  1013.     window.arguments[0].performed = true;
  1014.     PlacesUIUtils.ptm.doTransaction(createTxn);
  1015.   },
  1016.  
  1017.   onNamePickerInput: function BPP_onNamePickerInput() {
  1018.     this._element("userEnteredName").label = this._element("namePicker").value;
  1019.   },
  1020.  
  1021.   toggleTreeVisibility: function BPP_toggleTreeVisibility() {
  1022.     var expander = this._element("expander");
  1023.     if (!this._folderTree.collapsed) { // if (willCollapse)
  1024.       expander.className = "down";
  1025.       expander.setAttribute("tooltiptext",
  1026.                             expander.getAttribute("tooltiptextdown"));
  1027.       document.documentElement.buttons = "accept,cancel";
  1028.  
  1029.       this._folderTreeHeight = this._folderTree.boxObject.height;
  1030.       this._folderTree.setAttribute("height", this._folderTreeHeight);
  1031.       this._folderTree.collapsed = true;
  1032.       resizeTo(window.outerWidth, window.outerHeight - this._folderTreeHeight);
  1033.     }
  1034.     else {
  1035.       expander.className = "up";
  1036.       expander.setAttribute("tooltiptext",
  1037.                             expander.getAttribute("tooltiptextup"));
  1038.       document.documentElement.buttons = "accept,cancel,extra2";
  1039.  
  1040.       this._folderTree.collapsed = false;
  1041.  
  1042.       if (!this._folderTree.place) {
  1043.         const FOLDER_TREE_PLACE_URI =
  1044.           "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
  1045.           PlacesUIUtils.allBookmarksFolderId;
  1046.         this._folderTree.place = FOLDER_TREE_PLACE_URI;
  1047.       }
  1048.  
  1049.       var currentFolder = this._getFolderIdFromMenuList();
  1050.       this._folderTree.selectItems([currentFolder]);
  1051.       this._folderTree.focus();
  1052.  
  1053.       resizeTo(window.outerWidth, window.outerHeight + this._folderTreeHeight);
  1054.     }
  1055.   },
  1056.  
  1057.   _getFolderIdFromMenuList:
  1058.   function BPP__getFolderIdFromMenuList() {
  1059.     var selectedItem = this._folderMenuList.selectedItem;
  1060.     NS_ASSERT("folderId" in selectedItem,
  1061.               "Invalid menuitem in the folders-menulist");
  1062.     return selectedItem.folderId;
  1063.   },
  1064.  
  1065.   /**
  1066.    * Get the corresponding menu-item in the folder-menu-list for a bookmarks
  1067.    * folder if such an item exists. Otherwise, this creates a menu-item for the
  1068.    * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
  1069.    * the new item replaces the last menu-item.
  1070.    * @param aFolderId
  1071.    *        The identifier of the bookmarks folder.
  1072.    */
  1073.   _getFolderMenuItem:
  1074.   function BPP__getFolderMenuItem(aFolderId) {
  1075.     var menupopup = this._folderMenuList.menupopup;
  1076.  
  1077.     for (var i=0; i < menupopup.childNodes.length; i++) {
  1078.       if (menupopup.childNodes[i].folderId == aFolderId)
  1079.         return menupopup.childNodes[i];
  1080.     }
  1081.  
  1082.     // 2 special folders + separator + folder-items-count limit
  1083.     if (menupopup.childNodes.length == 3 + MAX_FOLDER_ITEM_IN_MENU_LIST)
  1084.       menupopup.removeChild(menupopup.lastChild);
  1085.  
  1086.     return this._appendFolderItemToMenupopup(menupopup, aFolderId);
  1087.   },
  1088.  
  1089.   onMenuListFolderSelect: function BPP_onMenuListFolderSelect(aEvent) {
  1090.     if (this._folderTree.hidden)
  1091.       return;
  1092.  
  1093.     this._folderTree.selectItems([this._getFolderIdFromMenuList()]);
  1094.   },
  1095.  
  1096.   onFolderTreeSelect: function BPP_onFolderTreeSelect() {
  1097.     var selectedNode = this._folderTree.selectedNode;
  1098.     if (!selectedNode)
  1099.       return;
  1100.  
  1101.     var folderId = PlacesUtils.getConcreteItemId(selectedNode);
  1102.     if (this._getFolderIdFromMenuList() == folderId)
  1103.       return;
  1104.  
  1105.     var folderItem = this._getFolderMenuItem(folderId);
  1106.     this._folderMenuList.selectedItem = folderItem;
  1107.   },
  1108.  
  1109.   _markFolderAsRecentlyUsed:
  1110.   function BPP__markFolderAsRecentlyUsed(aFolderId) {
  1111.     // We'll figure out when/if to expire the annotation if it turns out
  1112.     // we keep this recently-used-folders implementation
  1113.     PlacesUtils.annotations
  1114.                .setItemAnnotation(aFolderId, LAST_USED_ANNO,
  1115.                                   new Date().getTime(), 0,
  1116.                                   Ci.nsIAnnotationService.EXPIRE_NEVER);
  1117.   },
  1118.  
  1119.   newFolder: function BPP_newFolder() {
  1120.     // The command is disabled when the tree is not focused
  1121.     this._folderTree.focus();
  1122.     goDoCommand("placesCmd_new:folder");
  1123.   }
  1124. };
  1125.