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 / controller.js < prev    next >
Encoding:
Text File  |  2008-05-02  |  53.4 KB  |  1,510 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 Command Controller.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Ben Goodger <beng@google.com>
  23.  *   Myk Melez <myk@mozilla.org>
  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. // XXXmano: we should move most/all of these constants to PlacesUtils
  42. const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
  43. const ORGANIZER_SUBSCRIPTIONS_QUERY = "place:annotation=livemark%2FfeedURI";
  44.  
  45. // No change to the view, preserve current selection
  46. const RELOAD_ACTION_NOTHING = 0;
  47. // Inserting items new to the view, select the inserted rows
  48. const RELOAD_ACTION_INSERT = 1;
  49. // Removing items from the view, select the first item after the last selected
  50. const RELOAD_ACTION_REMOVE = 2;
  51. // Moving items within a view, don't treat the dropped items as additional 
  52. // rows.
  53. const RELOAD_ACTION_MOVE = 3;
  54.  
  55. // when removing a bunch of pages we split them in chunks to avoid passing
  56. // a too big array to RemovePages
  57. // 300 is the best choice with an history of about 150000 visits
  58. // smaller chunks could cause a Slow Script warning with a huge history
  59. const REMOVE_PAGES_CHUNKLEN = 300;
  60. // if we are removing less than this pages we will remove them one by one
  61. // since it will be reflected faster on the UI
  62. // 10 is a good compromise, since allows the user to delete a little amount of
  63. // urls for privacy reasons, but does not cause heavy disk access
  64. const REMOVE_PAGES_MAX_SINGLEREMOVES = 10;
  65.  
  66. /**
  67.  * Represents an insertion point within a container where we can insert
  68.  * items. 
  69.  * @param   aItemId
  70.  *          The identifier of the parent container
  71.  * @param   aIndex
  72.  *          The index within the container where we should insert
  73.  * @param   aOrientation
  74.  *          The orientation of the insertion. NOTE: the adjustments to the
  75.  *          insertion point to accommodate the orientation should be done by
  76.  *          the person who constructs the IP, not the user. The orientation
  77.  *          is provided for informational purposes only!
  78.  * @param   [optional] aIsTag
  79.  *          Indicates if parent container is a tag
  80.  * @param   [optional] aDropNearItemId
  81.  *          When defined we will calculate index based on this itemId
  82.  * @constructor
  83.  */
  84. function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag,
  85.                         aDropNearItemId) {
  86.   this.itemId = aItemId;
  87.   this._index = aIndex;
  88.   this.orientation = aOrientation;
  89.   this.isTag = aIsTag;
  90.   this.dropNearItemId = aDropNearItemId;
  91. }
  92.  
  93. InsertionPoint.prototype = {
  94.   set index(val) {
  95.     return this._index = val;
  96.   },
  97.  
  98.   get index() {
  99.     if (this.dropNearItemId > 0) {
  100.       // If dropNearItemId is set up we must calculate the real index of
  101.       // the item near which we will drop.
  102.       var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId);
  103.       return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
  104.     }
  105.     return this._index;
  106.   }
  107. };
  108.  
  109. /**
  110.  * Places Controller
  111.  */
  112.  
  113. function PlacesController(aView) {
  114.   this._view = aView;
  115. }
  116.  
  117. PlacesController.prototype = {
  118.   /**
  119.    * The places view.
  120.    */
  121.   _view: null,
  122.  
  123.   isCommandEnabled: function PC_isCommandEnabled(aCommand) {
  124.     switch (aCommand) {
  125.     case "cmd_undo":
  126.       return PlacesUIUtils.ptm.numberOfUndoItems > 0;
  127.     case "cmd_redo":
  128.       return PlacesUIUtils.ptm.numberOfRedoItems > 0;
  129.     case "cmd_cut":
  130.     case "cmd_delete":
  131.       return this._hasRemovableSelection(false);
  132.     case "placesCmd_moveBookmarks":
  133.       return this._hasRemovableSelection(true);
  134.     case "cmd_copy":
  135.       return this._view.hasSelection;
  136.     case "cmd_paste":
  137.       return this._canInsert() && this._isClipboardDataPasteable();
  138.     case "cmd_selectAll":
  139.       if (this._view.selType != "single") {
  140.         var result = this._view.getResult();
  141.         if (result) {
  142.           var container = asContainer(result.root);
  143.           if (container.childCount > 0);
  144.             return true;
  145.         }
  146.       }
  147.       return false;
  148.     case "placesCmd_open":
  149.     case "placesCmd_open:window":
  150.     case "placesCmd_open:tab":
  151.       var selectedNode = this._view.selectedNode;
  152.       return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
  153.     case "placesCmd_new:folder":
  154.     case "placesCmd_new:livemark":
  155.       return this._canInsert();
  156.     case "placesCmd_new:bookmark":
  157.       return this._canInsert();
  158.     case "placesCmd_new:separator":
  159.       return this._canInsert() &&
  160.              !asQuery(this._view.getResult().root).queryOptions.excludeItems &&
  161.              this._view.getResult().sortingMode ==
  162.                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
  163.     case "placesCmd_show:info":
  164.       var selectedNode = this._view.selectedNode;
  165.       if (selectedNode) {
  166.         if (PlacesUtils.nodeIsFolder(selectedNode) ||
  167.             (PlacesUtils.nodeIsQuery(selectedNode) &&
  168.              selectedNode.itemId != -1) ||
  169.             (PlacesUtils.nodeIsBookmark(selectedNode) &&
  170.             !PlacesUtils.nodeIsLivemarkItem(selectedNode)))
  171.           return true;
  172.       }
  173.       return false;
  174.     case "placesCmd_reloadMicrosummary":
  175.       var selectedNode = this._view.selectedNode;
  176.       return selectedNode && PlacesUtils.nodeIsBookmark(selectedNode) &&
  177.              PlacesUIUtils.microsummaries.hasMicrosummary(selectedNode.itemId);
  178.     case "placesCmd_reload":
  179.       // Livemark containers
  180.       var selectedNode = this._view.selectedNode;
  181.       return selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode);
  182.     case "placesCmd_sortBy:name":
  183.       var selectedNode = this._view.selectedNode;
  184.       return selectedNode &&
  185.              PlacesUtils.nodeIsFolder(selectedNode) &&
  186.              !PlacesUtils.nodeIsReadOnly(selectedNode) &&
  187.              this._view.getResult().sortingMode ==
  188.                  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
  189.     default:
  190.       return false;
  191.     }
  192.   },
  193.  
  194.   supportsCommand: function PC_supportsCommand(aCommand) {
  195.     //LOG("supportsCommand: " + command);
  196.     // Non-Places specific commands that we also support
  197.     switch (aCommand) {
  198.     case "cmd_undo":
  199.     case "cmd_redo":
  200.     case "cmd_cut":
  201.     case "cmd_copy":
  202.     case "cmd_paste":
  203.     case "cmd_delete":
  204.     case "cmd_selectAll":
  205.       return true;
  206.     }
  207.  
  208.     // All other Places Commands are prefixed with "placesCmd_" ... this 
  209.     // filters out other commands that we do _not_ support (see 329587).
  210.     const CMD_PREFIX = "placesCmd_";
  211.     return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
  212.   },
  213.  
  214.   doCommand: function PC_doCommand(aCommand) {
  215.     switch (aCommand) {
  216.     case "cmd_undo":
  217.       PlacesUIUtils.ptm.undoTransaction();
  218.       break;
  219.     case "cmd_redo":
  220.       PlacesUIUtils.ptm.redoTransaction();
  221.       break;
  222.     case "cmd_cut":
  223.       this.cut();
  224.       break;
  225.     case "cmd_copy":
  226.       this.copy();
  227.       break;
  228.     case "cmd_paste":
  229.       this.paste();
  230.       break;
  231.     case "cmd_delete":
  232.       this.remove("Remove Selection");
  233.       break;
  234.     case "cmd_selectAll":
  235.       this.selectAll();
  236.       break;
  237.     case "placesCmd_open":
  238.       PlacesUIUtils.openNodeIn(this._view.selectedNode, "current");
  239.       break;
  240.     case "placesCmd_open:window":
  241.       PlacesUIUtils.openNodeIn(this._view.selectedNode, "window");
  242.       break;
  243.     case "placesCmd_open:tab":
  244.       PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab");
  245.       break;
  246.     case "placesCmd_new:folder":
  247.       this.newItem("folder");
  248.       break;
  249.     case "placesCmd_new:bookmark":
  250.       this.newItem("bookmark");
  251.       break;
  252.     case "placesCmd_new:livemark":
  253.       this.newItem("livemark");
  254.       break;
  255.     case "placesCmd_new:separator":
  256.       this.newSeparator();
  257.       break;
  258.     case "placesCmd_show:info":
  259.       this.showBookmarkPropertiesForSelection();
  260.       break;
  261.     case "placesCmd_moveBookmarks":
  262.       this.moveSelectedBookmarks();
  263.       break;
  264.     case "placesCmd_reload":
  265.       this.reloadSelectedLivemark();
  266.       break;
  267.     case "placesCmd_reloadMicrosummary":
  268.       this.reloadSelectedMicrosummary();
  269.       break;
  270.     case "placesCmd_sortBy:name":
  271.       this.sortFolderByName();
  272.       break;
  273.     }
  274.   },
  275.  
  276.   onEvent: function PC_onEvent(eventName) { },
  277.  
  278.   
  279.   /**
  280.    * Determine whether or not the selection can be removed, either by the 
  281.    * delete or cut operations based on whether or not any of its contents
  282.    * are non-removable. We don't need to worry about recursion here since it
  283.    * is a policy decision that a removable item not be placed inside a non-
  284.    * removable item.
  285.    * @param aIsMoveCommand
  286.    *        True if the command for which this method is called only moves the
  287.    *        selected items to another container, false otherwise.
  288.    * @returns true if all nodes in the selection can be removed,
  289.    *          false otherwise.
  290.    */
  291.   _hasRemovableSelection: function PC__hasRemovableSelection(aIsMoveCommand) {
  292.     var nodes = this._view.getSelectionNodes();
  293.     var root = this._view.getResultNode();
  294.  
  295.     for (var i = 0; i < nodes.length; ++i) {
  296.       // Disallow removing the view's root node
  297.       if (nodes[i] == root)
  298.         return false;
  299.  
  300.       if (PlacesUtils.nodeIsFolder(nodes[i]) &&
  301.           !PlacesControllerDragHelper.canMoveContainerNode(nodes[i]))
  302.         return false;
  303.  
  304.       // We don't call nodeIsReadOnly here, because nodeIsReadOnly means that
  305.       // a node has children that cannot be edited, reordered or removed. Here,
  306.       // we don't care if a node's children can't be reordered or edited, just
  307.       // that they're removable. All history results have removable children
  308.       // (based on the principle that any URL in the history table should be
  309.       // removable), but some special bookmark folders may have non-removable
  310.       // children, e.g. live bookmark folder children. It doesn't make sense
  311.       // to delete a child of a live bookmark folder, since when the folder
  312.       // refreshes, the child will return.
  313.       var parent = nodes[i].parent || root;
  314.       if (PlacesUtils.isReadonlyFolder(parent))
  315.         return false;
  316.     }
  317.     return true;
  318.   },
  319.  
  320.   /**
  321.    * Determines whether or not nodes can be inserted relative to the selection.
  322.    */
  323.   _canInsert: function PC__canInsert() {
  324.     var ip = this._view.insertionPoint;
  325.     return ip != null && ip.isTag != true;
  326.   },
  327.  
  328.   /**
  329.    * Determines whether or not the root node for the view is selected
  330.    */
  331.   rootNodeIsSelected: function PC_rootNodeIsSelected() {
  332.     var nodes = this._view.getSelectionNodes();
  333.     var root = this._view.getResultNode();
  334.     for (var i = 0; i < nodes.length; ++i) {
  335.       if (nodes[i] == root)
  336.         return true;      
  337.     }
  338.  
  339.     return false;
  340.   },
  341.  
  342.   /**
  343.    * Looks at the data on the clipboard to see if it is paste-able. 
  344.    * Paste-able data is:
  345.    *   - in a format that the view can receive
  346.    * @returns true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
  347.                        - clipboard data is of type TEXT_UNICODE and
  348.                          is a valid URI.
  349.    */
  350.   _isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
  351.     // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
  352.     // pasteable, with no need to unwrap all the nodes.
  353.  
  354.     var flavors = PlacesUIUtils.placesFlavors;
  355.     var clipboard = PlacesUIUtils.clipboard;
  356.     var hasPlacesData =
  357.       clipboard.hasDataMatchingFlavors(flavors, flavors.length,
  358.                                        Ci.nsIClipboard.kGlobalClipboard);
  359.     if (hasPlacesData)
  360.       return this._view.insertionPoint != null;
  361.  
  362.     // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow
  363.     // pasting of valid "text/unicode" and "text/x-moz-url" data
  364.     var xferable = Cc["@mozilla.org/widget/transferable;1"].
  365.                    createInstance(Ci.nsITransferable);
  366.  
  367.     xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL);
  368.     xferable.addDataFlavor(PlacesUtils.TYPE_UNICODE);
  369.     clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
  370.  
  371.     try {
  372.       // getAnyTransferData will throw if no data is available.
  373.       var data = { }, type = { };
  374.       xferable.getAnyTransferData(type, data, { });
  375.       data = data.value.QueryInterface(Ci.nsISupportsString).data;
  376.       if (type.value != PlacesUtils.TYPE_X_MOZ_URL &&
  377.           type.value != PlacesUtils.TYPE_UNICODE)
  378.         return false;
  379.  
  380.       // unwrapNodes() will throw if the data blob is malformed.
  381.       var unwrappedNodes = PlacesUtils.unwrapNodes(data, type.value);
  382.       return this._view.insertionPoint != null;
  383.     }
  384.     catch (e) {
  385.       // getAnyTransferData or unwrapNodes failed
  386.       return false;
  387.     }
  388.   },
  389.  
  390.   /** 
  391.    * Gathers information about the selected nodes according to the following
  392.    * rules:
  393.    *    "link"              node is a URI
  394.    *    "bookmark"          node is a bookamrk
  395.    *    "livemarkChild"     node is a child of a livemark
  396.    *    "tagChild"          node is a child of a tag
  397.    *    "folder"            node is a folder
  398.    *    "query"             node is a query
  399.    *    "dynamiccontainer"  node is a dynamic container
  400.    *    "separator"         node is a separator line
  401.    *    "host"              node is a host
  402.    *
  403.    * @returns an array of objects corresponding the selected nodes. Each
  404.    *          object has each of the properties above set if its corresponding
  405.    *          node matches the rule. In addition, the annotations names for each 
  406.    *          node are set on its corresponding object as properties.
  407.    * Notes:
  408.    *   1) This can be slow, so don't call it anywhere performance critical!
  409.    *   2) A single-object array corresponding the root node is returned if
  410.    *      there's no selection.
  411.    */
  412.   _buildSelectionMetadata: function PC__buildSelectionMetadata() {
  413.     var metadata = [];
  414.     var root = this._view.getResult().root;
  415.     var nodes = this._view.getSelectionNodes();
  416.     if (nodes.length == 0)
  417.       nodes.push(root); // See the second note above
  418.  
  419.     for (var i=0; i < nodes.length; i++) {
  420.       var nodeData = {};
  421.       var node = nodes[i];
  422.       var nodeType = node.type;
  423.       var uri = null;
  424.  
  425.       // We don't use the nodeIs* methods here to avoid going through the type
  426.       // property way too often
  427.       switch(nodeType) {
  428.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
  429.           nodeData["query"] = true;
  430.           if (node.parent) {
  431.             switch (asQuery(node.parent).queryOptions.resultType) {
  432.               case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
  433.                 nodeData["host"] = true;
  434.                 break;
  435.               case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
  436.               case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
  437.                 nodeData["day"] = true;
  438.                 break;
  439.             }
  440.           }
  441.           break;
  442.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_DYNAMIC_CONTAINER:
  443.           nodeData["dynamiccontainer"] = true;
  444.           break;
  445.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
  446.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT:
  447.           nodeData["folder"] = true;
  448.           break;
  449.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
  450.           nodeData["separator"] = true;
  451.           break;
  452.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
  453.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_VISIT:
  454.         case Ci.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT:
  455.           nodeData["link"] = true;
  456.           uri = PlacesUtils._uri(node.uri);
  457.           if (PlacesUtils.nodeIsBookmark(node)) {
  458.             nodeData["bookmark"] = true;
  459.             PlacesUtils.nodeIsTagQuery(node.parent)
  460.             var mss = PlacesUIUtils.microsummaries;
  461.             if (mss.hasMicrosummary(node.itemId))
  462.               nodeData["microsummary"] = true;
  463.  
  464.             var parentNode = node.parent;
  465.             if (parentNode) {
  466.               if (PlacesUtils.nodeIsTagQuery(parentNode))
  467.                 nodeData["tagChild"] = true;
  468.               else if (PlacesUtils.nodeIsLivemarkContainer(parentNode))
  469.                 nodeData["livemarkChild"] = true;
  470.             }
  471.           }
  472.           break;
  473.       }
  474.  
  475.       // annotations
  476.       if (uri) {
  477.         var names = PlacesUtils.annotations.getPageAnnotationNames(uri, {});
  478.         for (var j = 0; j < names.length; ++j)
  479.           nodeData[names[j]] = true;
  480.       }
  481.  
  482.       // For items also include the item-specific annotations
  483.       if (node.itemId != -1) {
  484.         names = PlacesUtils.annotations
  485.                            .getItemAnnotationNames(node.itemId, {});
  486.         for (j = 0; j < names.length; ++j)
  487.           nodeData[names[j]] = true;
  488.       }
  489.       metadata.push(nodeData);
  490.     }
  491.  
  492.     return metadata;
  493.   },
  494.  
  495.   /** 
  496.    * Determines if a context-menu item should be shown
  497.    * @param   aMenuItem
  498.    *          the context menu item 
  499.    * @param   aMetaData
  500.    *          meta data about the selection
  501.    * @returns true if the conditions (see buildContextMenu) are satisfied
  502.    *          and the item can be displayed, false otherwise. 
  503.    */
  504.   _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
  505.     var selectiontype = aMenuItem.getAttribute("selectiontype");
  506.     if (selectiontype == "multiple" && aMetaData.length == 1)
  507.       return false;
  508.     if (selectiontype == "single" && aMetaData.length != 1)
  509.       return false;
  510.  
  511.     var forceHideRules = aMenuItem.getAttribute("forcehideselection").split("|");
  512.     for (var i = 0; i < aMetaData.length; ++i) {
  513.       for (var j=0; j < forceHideRules.length; ++j) {
  514.         if (forceHideRules[j] in aMetaData[i])
  515.           return false;
  516.       }
  517.     }
  518.  
  519.     var selectionAttr = aMenuItem.getAttribute("selection");
  520.     if (selectionAttr) {
  521.       if (selectionAttr == "any")
  522.         return true;
  523.  
  524.       var showRules = selectionAttr.split("|");
  525.       var anyMatched = false;
  526.       function metaDataNodeMatches(metaDataNode, rules) {
  527.         for (var i=0; i < rules.length; i++) {
  528.           if (rules[i] in metaDataNode)
  529.             return true;
  530.         }
  531.  
  532.         return false;
  533.       }
  534.       for (var i = 0; i < aMetaData.length; ++i) {
  535.         if (metaDataNodeMatches(aMetaData[i], showRules))
  536.           anyMatched = true;
  537.         else
  538.           return false;
  539.       }
  540.       return anyMatched;
  541.     }
  542.  
  543.     return !aMenuItem.hidden;
  544.   },
  545.  
  546.   /**
  547.    * Detects information (meta-data rules) about the current selection in the
  548.    * view (see _buildSelectionMetadata) and sets the visibility state for each
  549.    * of the menu-items in the given popup with the following rules applied:
  550.    *  1) The "selectiontype" attribute may be set on a menu-item to "single"
  551.    *     if the menu-item should be visible only if there is a single node
  552.    *     selected, or to "multiple" if the menu-item should be visible only if
  553.    *     multiple nodes are selected. If the attribute is not set or if it is
  554.    *     set to an invalid value, the menu-item may be visible for both types of
  555.    *     selection.
  556.    *  2) The "selection" attribute may be set on a menu-item to the various
  557.    *     meta-data rules for which it may be visible. The rules should be
  558.    *     separated with the | character.
  559.    *  3) A menu-item may be visible only if at least one of the rules set in
  560.    *     its selection attribute apply to each of the selected nodes in the
  561.    *     view.
  562.    *  4) The "forcehideselection" attribute may be set on a menu-item to rules
  563.    *     for which it should be hidden. This attribute takes priority over the
  564.    *     selection attribute. A menu-item would be hidden if at least one of the
  565.    *     given rules apply to one of the selected nodes. The rules should be
  566.    *     separated with the | character.
  567.    *  5) The "hideifnoinsetionpoint" attribute may be set on a menu-item to
  568.    *     true if it should be hidden when there's no insertion point
  569.    *  6) The visibility state of a menu-item is unchanged if none of these
  570.    *     attribute are set.
  571.    *  7) These attributes should not be set on separators for which the
  572.    *     visibility state is "auto-detected."
  573.    * @param   aPopup
  574.    *          The menupopup to build children into.
  575.    * @return true if at least one item is visible, false otherwise.
  576.    */
  577.   buildContextMenu: function PC_buildContextMenu(aPopup) {
  578.     var metadata = this._buildSelectionMetadata();
  579.     var ip = this._view.insertionPoint;
  580.     var noIp = !ip || ip.isTag;
  581.  
  582.     var separator = null;
  583.     var visibleItemsBeforeSep = false;
  584.     var anyVisible = false;
  585.     for (var i = 0; i < aPopup.childNodes.length; ++i) {
  586.       var item = aPopup.childNodes[i];
  587.       if (item.localName != "menuseparator") {
  588.         item.hidden = (item.getAttribute("hideifnoinsetionpoint") == "true" && noIp) ||
  589.                       !this._shouldShowMenuItem(item, metadata);
  590.  
  591.         if (!item.hidden) {
  592.           visibleItemsBeforeSep = true;
  593.           anyVisible = true;
  594.  
  595.           // Show the separator above the menu-item if any
  596.           if (separator) {
  597.             separator.hidden = false;
  598.             separator = null;
  599.           }
  600.         }
  601.       }
  602.       else { // menuseparator
  603.         // Initially hide it. It will be unhidden if there will be at least one
  604.         // visible menu-item above and below it.
  605.         item.hidden = true;
  606.  
  607.         // We won't show the separator at all if no items are visible above it
  608.         if (visibleItemsBeforeSep)
  609.           separator = item;
  610.  
  611.         // New separator, count again:
  612.         visibleItemsBeforeSep = false;
  613.       }
  614.     }
  615.  
  616.     // Set Open Folder/Links In Tabs items enabled state if they're visible
  617.     if (anyVisible) {
  618.       var openContainerInTabsItem = document.getElementById("placesContext_openContainer:tabs");
  619.       if (!openContainerInTabsItem.hidden && this._view.selectedNode &&
  620.           PlacesUtils.nodeIsContainer(this._view.selectedNode)) {
  621.         openContainerInTabsItem.disabled =
  622.           !PlacesUtils.hasChildURIs(this._view.selectedNode);
  623.       }
  624.       else {
  625.         // see selectiontype rule in the overlay
  626.         var openLinksInTabsItem = document.getElementById("placesContext_openLinks:tabs");
  627.         openLinksInTabsItem.disabled = openLinksInTabsItem.hidden;
  628.       }
  629.     }
  630.  
  631.     return anyVisible;
  632.   },
  633.  
  634.   /**
  635.    * Select all links in the current view. 
  636.    */
  637.   selectAll: function PC_selectAll() {
  638.     this._view.selectAll();
  639.   },
  640.  
  641.   /**
  642.    * Opens the bookmark properties for the selected URI Node.
  643.    */
  644.   showBookmarkPropertiesForSelection: 
  645.   function PC_showBookmarkPropertiesForSelection() {
  646.     var node = this._view.selectedNode;
  647.     if (!node)
  648.       return;
  649.  
  650.     if (PlacesUtils.nodeIsFolder(node))
  651.       PlacesUIUtils.showItemProperties(node.itemId, "folder");
  652.     else if (PlacesUtils.nodeIsBookmark(node) ||
  653.              PlacesUtils.nodeIsQuery(node))
  654.       PlacesUIUtils.showItemProperties(node.itemId, "bookmark");
  655.   },
  656.  
  657.   /**
  658.    * This method can be run on a URI parameter to ensure that it didn't
  659.    * receive a string instead of an nsIURI object.
  660.    */
  661.   _assertURINotString: function PC__assertURINotString(value) {
  662.     NS_ASSERT((typeof(value) == "object") && !(value instanceof String), 
  663.            "This method should be passed a URI as a nsIURI object, not as a string.");
  664.   },
  665.  
  666.   /**
  667.    * Reloads the selected livemark if any.
  668.    */
  669.   reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
  670.     var selectedNode = this._view.selectedNode;
  671.     if (selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode))
  672.       PlacesUtils.livemarks.reloadLivemarkFolder(selectedNode.itemId);
  673.   },
  674.  
  675.   /**
  676.    * Reload the microsummary associated with the selection
  677.    */
  678.   reloadSelectedMicrosummary: function PC_reloadSelectedMicrosummary() {
  679.     var selectedNode = this._view.selectedNode;
  680.     var mss = PlacesUIUtils.microsummaries;
  681.     if (mss.hasMicrosummary(selectedNode.itemId))
  682.       mss.refreshMicrosummary(selectedNode.itemId);
  683.   },
  684.  
  685.   /**
  686.    * Gives the user a chance to cancel loading lots of tabs at once
  687.    */
  688.   _confirmOpenTabs: function(numTabsToOpen) {
  689.     var pref = Cc["@mozilla.org/preferences-service;1"].
  690.                getService(Ci.nsIPrefBranch);
  691.  
  692.     const kWarnOnOpenPref = "browser.tabs.warnOnOpen";
  693.     var reallyOpen = true;
  694.     if (pref.getBoolPref(kWarnOnOpenPref)) {
  695.       if (numTabsToOpen >= pref.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
  696.         var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  697.                             getService(Ci.nsIPromptService);
  698.  
  699.         // default to true: if it were false, we wouldn't get this far
  700.         var warnOnOpen = { value: true };
  701.  
  702.         var messageKey = "tabs.openWarningMultipleBranded";
  703.         var openKey = "tabs.openButtonMultiple";
  704.         var strings = document.getElementById("placeBundle");
  705.         const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
  706.         var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
  707.                              getService(Ci.nsIStringBundleService).
  708.                              createBundle(BRANDING_BUNDLE_URI).
  709.                              GetStringFromName("brandShortName");
  710.        
  711.         var buttonPressed = promptService.confirmEx(window,
  712.           PlacesUIUtils.getString("tabs.openWarningTitle"),
  713.           PlacesUIUtils.getFormattedString(messageKey, 
  714.             [numTabsToOpen, brandShortName]),
  715.           (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
  716.           + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
  717.           PlacesUIUtils.getString(openKey),
  718.           null, null,
  719.           PlacesUIUtils.getFormattedString("tabs.openWarningPromptMeBranded",
  720.             [brandShortName]),
  721.           warnOnOpen);
  722.  
  723.          reallyOpen = (buttonPressed == 0);
  724.          // don't set the pref unless they press OK and it's false
  725.          if (reallyOpen && !warnOnOpen.value)
  726.            pref.setBoolPref(kWarnOnOpenPref, false);
  727.       }
  728.     }
  729.     return reallyOpen;
  730.   },
  731.  
  732.   /**
  733.    * Opens the links in the selected folder, or the selected links in new tabs. 
  734.    */
  735.   openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
  736.     var node = this._view.selectedNode;
  737.     if (node && PlacesUtils.nodeIsContainer(node))
  738.       PlacesUIUtils.openContainerNodeInTabs(this._view.selectedNode, aEvent);
  739.     else
  740.       PlacesUIUtils.openURINodesInTabs(this._view.getSelectionNodes(), aEvent);
  741.   },
  742.  
  743.   /**
  744.    * Shows the Add Bookmark UI for the current insertion point.
  745.    *
  746.    * @param aType
  747.    *        the type of the new item (bookmark/livemark/folder)
  748.    */
  749.   newItem: function PC_newItem(aType) {
  750.     var ip = this._view.insertionPoint;
  751.     if (!ip)
  752.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  753.  
  754.     var performed = false;
  755.     if (aType == "bookmark")
  756.       performed = PlacesUIUtils.showAddBookmarkUI(null, null, null, ip);
  757.     else if (aType == "livemark")
  758.       performed = PlacesUIUtils.showAddLivemarkUI(null, null, null, null, ip);
  759.     else // folder
  760.       performed = PlacesUIUtils.showAddFolderUI(null, ip);
  761.  
  762.     if (performed) {
  763.       // select the new item
  764.       var insertedNodeId = PlacesUtils.bookmarks
  765.                                       .getIdForItemAt(ip.itemId, ip.index);
  766.       this._view.selectItems([insertedNodeId], ip.itemId);
  767.     }
  768.   },
  769.  
  770.  
  771.   /**
  772.    * Create a new Bookmark folder somewhere. Prompts the user for the name
  773.    * of the folder. 
  774.    */
  775.   newFolder: function PC_newFolder() {
  776.     var ip = this._view.insertionPoint;
  777.     if (!ip)
  778.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  779.  
  780.     var performed = false;
  781.     performed = PlacesUIUtils.showAddFolderUI(null, ip);
  782.     if (performed) {
  783.       // select the new item
  784.       var insertedNodeId = PlacesUtils.bookmarks
  785.                                       .getIdForItemAt(ip.itemId, ip.index);
  786.       this._view.selectItems([insertedNodeId]);
  787.     }
  788.   },
  789.  
  790.   /**
  791.    * Create a new Bookmark separator somewhere.
  792.    */
  793.   newSeparator: function PC_newSeparator() {
  794.     var ip = this._view.insertionPoint;
  795.     if (!ip)
  796.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  797.     var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index);
  798.     PlacesUIUtils.ptm.doTransaction(txn);
  799.     // select the new item
  800.     var insertedNodeId = PlacesUtils.bookmarks
  801.                                     .getIdForItemAt(ip.itemId, ip.index);
  802.     this._view.selectItems([insertedNodeId]);
  803.   },
  804.  
  805.   /**
  806.    * Opens a dialog for moving the selected nodes.
  807.    */
  808.   moveSelectedBookmarks: function PC_moveBookmarks() {
  809.     window.openDialog("chrome://browser/content/places/moveBookmarks.xul",
  810.                       "", "chrome, modal",
  811.                       this._view.getSelectionNodes());
  812.   },
  813.  
  814.   /**
  815.    * Sort the selected folder by name
  816.    */
  817.   sortFolderByName: function PC_sortFolderByName() {
  818.     var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
  819.     var txn = PlacesUIUtils.ptm.sortFolderByName(itemId);
  820.     PlacesUIUtils.ptm.doTransaction(txn);
  821.   },
  822.  
  823.   /**
  824.    * Walk the list of folders we're removing in this delete operation, and
  825.    * see if the selected node specified is already implicitly being removed 
  826.    * because it is a child of that folder. 
  827.    * @param   node
  828.    *          Node to check for containment. 
  829.    * @param   pastFolders
  830.    *          List of folders the calling function has already traversed
  831.    * @returns true if the node should be skipped, false otherwise. 
  832.    */
  833.   _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
  834.     /**
  835.      * Determines if a node is contained by another node within a resultset. 
  836.      * @param   node
  837.      *          The node to check for containment for
  838.      * @param   parent
  839.      *          The parent container to check for containment in
  840.      * @returns true if node is a member of parent's children, false otherwise.
  841.      */
  842.     function isContainedBy(node, parent) {
  843.       var cursor = node.parent;
  844.       while (cursor) {
  845.         if (cursor == parent)
  846.           return true;
  847.         cursor = cursor.parent;
  848.       }
  849.       return false;
  850.     }
  851.   
  852.       for (var j = 0; j < pastFolders.length; ++j) {
  853.         if (isContainedBy(node, pastFolders[j]))
  854.           return true;
  855.       }
  856.       return false;
  857.   },
  858.  
  859.   /**
  860.    * Creates a set of transactions for the removal of a range of items. 
  861.    * A range is an array of adjacent nodes in a view.
  862.    * @param   [in] range
  863.    *          An array of nodes to remove. Should all be adjacent. 
  864.    * @param   [out] transactions
  865.    *          An array of transactions.
  866.    */
  867.   _removeRange: function PC__removeRange(range, transactions) {
  868.     NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
  869.  
  870.     var removedFolders = [];
  871.  
  872.     for (var i = 0; i < range.length; ++i) {
  873.       var node = range[i];
  874.       if (this._shouldSkipNode(node, removedFolders))
  875.         continue;
  876.  
  877.       if (PlacesUtils.nodeIsFolder(node))
  878.         removedFolders.push(node);
  879.       else if (PlacesUtils.nodeIsTagQuery(node.parent)) {
  880.         var queries = asQuery(node.parent).getQueries({});
  881.         var folders = queries[0].getFolders({});
  882.         var uri = PlacesUtils._uri(node.uri);
  883.         var tagItemId = folders[0];
  884.         transactions.push(PlacesUIUtils.ptm.untagURI(uri, [tagItemId]));
  885.         continue;
  886.       }
  887.       else if (PlacesUtils.nodeIsQuery(node.parent) &&
  888.                asQuery(node.parent).queryOptions.queryType ==
  889.                 Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY &&
  890.                node.uri) {
  891.         // remove page from history, history deletes are not undoable
  892.         var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
  893.         bhist.removePage(PlacesUtils._uri(node.uri));
  894.         continue;
  895.       }
  896.  
  897.       transactions.push(PlacesUIUtils.ptm.removeItem(node.itemId));
  898.     }
  899.   },
  900.  
  901.   /**
  902.    * Removes the set of selected ranges from bookmarks.
  903.    * @param   txnName
  904.    *          See |remove|.
  905.    */
  906.   _removeRowsFromBookmarks: function PC__removeRowsFromBookmarks(txnName) {
  907.     var ranges = this._view.getRemovableSelectionRanges();
  908.     var transactions = [];
  909.     // Delete the selected rows. Do this by walking the selection backward, so
  910.     // that when undo is performed they are re-inserted in the correct order.
  911.     for (var i = ranges.length - 1; i >= 0 ; --i)
  912.       this._removeRange(ranges[i], transactions);
  913.     if (transactions.length > 0) {
  914.       var txn = PlacesUIUtils.ptm.aggregateTransactions(txnName, transactions);
  915.       PlacesUIUtils.ptm.doTransaction(txn);
  916.     }
  917.   },
  918.  
  919.   /**
  920.    * Removes the set of selected ranges from history.
  921.    */
  922.   _removeRowsFromHistory: function PC__removeRowsFromHistory() {
  923.     // Other containers are history queries, just delete from history
  924.     // history deletes are not undoable.
  925.     var nodes = this._view.getSelectionNodes();
  926.     var URIs = [];
  927.     var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
  928.     var resultView = this._view.getResultView();
  929.     var root = this._view.getResultNode();
  930.  
  931.     for (var i = 0; i < nodes.length; ++i) {
  932.       var node = nodes[i];
  933.       if (PlacesUtils.nodeIsHost(node))
  934.         bhist.removePagesFromHost(node.title, true);
  935.       else if (PlacesUtils.nodeIsURI(node)) {
  936.         var uri = PlacesUtils._uri(node.uri);
  937.         // avoid trying to delete the same url twice
  938.         if (URIs.indexOf(uri) < 0) {
  939.           URIs.push(uri);
  940.         }
  941.       }
  942.       else if (PlacesUtils.nodeIsDay(node)) {
  943.         // this is the oldest date
  944.         // for the last node endDate is end of epoch
  945.         var beginDate = 0;
  946.         // this is the newest date
  947.         // day nodes have time property set to the last day in the interval
  948.         var endDate = node.time;
  949.  
  950.         var nodeIdx = 0;
  951.         var cc = root.childCount;
  952.  
  953.         // Find index of current day node
  954.         while (nodeIdx < cc && root.getChild(nodeIdx) != node)
  955.           ++nodeIdx;
  956.  
  957.         // We have an older day
  958.         if (nodeIdx+1 < cc)
  959.           beginDate = root.getChild(nodeIdx+1).time;
  960.  
  961.         // we want to exclude beginDate from the removal
  962.         bhist.removePagesByTimeframe(beginDate+1, endDate);
  963.       }
  964.     }
  965.  
  966.     // if we have to delete a lot of urls RemovePage will be slow, it's better
  967.     // to delete them in bunch and rebuild the full treeView
  968.     if (URIs.length > REMOVE_PAGES_MAX_SINGLEREMOVES) {
  969.       // do removal in chunks to avoid passing a too big array to removePages
  970.       for (var i = 0; i < URIs.length; i += REMOVE_PAGES_CHUNKLEN) {
  971.         var URIslice = URIs.slice(i, Math.max(i + REMOVE_PAGES_CHUNKLEN, URIs.length));
  972.         // set DoBatchNotify only on the last chunk
  973.         bhist.removePages(URIslice, URIslice.length,
  974.                           (i + REMOVE_PAGES_CHUNKLEN) >= URIs.length);
  975.       }
  976.     }
  977.     else {
  978.       // if we have to delete fewer urls, removepage will allow us to avoid
  979.       // rebuilding the full treeView
  980.       for (var i = 0; i < URIs.length; ++i)
  981.         bhist.removePage(URIs[i]);
  982.     }
  983.   },
  984.  
  985.   /**
  986.    * Removes the selection
  987.    * @param   aTxnName
  988.    *          A name for the transaction if this is being performed
  989.    *          as part of another operation.
  990.    */
  991.   remove: function PC_remove(aTxnName) {
  992.     if (!this._hasRemovableSelection(false))
  993.       return;
  994.  
  995.     NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
  996.  
  997.     var root = this._view.getResult().root;
  998.  
  999.     if (PlacesUtils.nodeIsFolder(root)) 
  1000.       this._removeRowsFromBookmarks(aTxnName);
  1001.     else if (PlacesUtils.nodeIsQuery(root)) {
  1002.       var queryType = asQuery(root).queryOptions.queryType;
  1003.       if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS)
  1004.         this._removeRowsFromBookmarks(aTxnName);
  1005.       else if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
  1006.         this._removeRowsFromHistory();
  1007.       else
  1008.         NS_ASSERT(false, "implement support for QUERY_TYPE_UNIFIED");
  1009.     }
  1010.     else
  1011.       NS_ASSERT(false, "unexpected root");
  1012.   },
  1013.  
  1014.   /**
  1015.    * Get a TransferDataSet containing the content of the selection that can be
  1016.    * dropped elsewhere. 
  1017.    * @param   dragAction
  1018.    *          The action to happen when dragging, i.e. copy
  1019.    * @returns A TransferDataSet object that can be dragged and dropped 
  1020.    *          elsewhere.
  1021.    */
  1022.   getTransferData: function PC_getTransferData(dragAction) {
  1023.     var copy = dragAction == Ci.nsIDragService.DRAGDROP_ACTION_COPY;
  1024.     var result = this._view.getResult();
  1025.     var oldViewer = result.viewer;
  1026.     try {
  1027.       result.viewer = null;
  1028.       var nodes = this._view.getDragableSelection();
  1029.       if (dragAction == Ci.nsIDragService.DRAGDROP_ACTION_MOVE) {
  1030.         nodes = nodes.filter(function(node) {
  1031.           var parent = node.parent;
  1032.           return parent && !PlacesUtils.nodeIsReadOnly(parent);
  1033.         });
  1034.       }
  1035.  
  1036.       var dataSet = new TransferDataSet();
  1037.       for (var i = 0; i < nodes.length; ++i) {
  1038.         var node = nodes[i];
  1039.  
  1040.         var data = new TransferData();
  1041.         function addData(type, overrideURI) {
  1042.           data.addDataForFlavour(type, PlacesUIUtils._wrapString(
  1043.                                  PlacesUtils.wrapNode(node, type, overrideURI, copy)));
  1044.         }
  1045.  
  1046.         function addURIData(overrideURI) {
  1047.           addData(PlacesUtils.TYPE_X_MOZ_URL, overrideURI);
  1048.           addData(PlacesUtils.TYPE_UNICODE, overrideURI);
  1049.           addData(PlacesUtils.TYPE_HTML, overrideURI);
  1050.         }
  1051.  
  1052.         // This order is _important_! It controls how this and other 
  1053.         // applications select data to be inserted based on type.
  1054.         addData(PlacesUtils.TYPE_X_MOZ_PLACE);
  1055.       
  1056.         var uri;
  1057.       
  1058.         // Allow dropping the feed uri of live-bookmark folders
  1059.         if (PlacesUtils.nodeIsLivemarkContainer(node))
  1060.           uri = PlacesUtils.livemarks.getFeedURI(node.itemId).spec;
  1061.       
  1062.         addURIData(uri);
  1063.         dataSet.push(data);
  1064.       }
  1065.     }
  1066.     finally {
  1067.       if (oldViewer)
  1068.         result.viewer = oldViewer;
  1069.     }
  1070.     return dataSet;
  1071.   },
  1072.  
  1073.   /**
  1074.    * Copy Bookmarks and Folders to the clipboard
  1075.    */
  1076.   copy: function PC_copy() {
  1077.     var result = this._view.getResult();
  1078.     var oldViewer = result.viewer;
  1079.     try {
  1080.       result.viewer = null;
  1081.       var nodes = this._view.getSelectionNodes();
  1082.  
  1083.       var xferable =  Cc["@mozilla.org/widget/transferable;1"].
  1084.                       createInstance(Ci.nsITransferable);
  1085.       var foundFolder = false, foundLink = false;
  1086.       var copiedFolders = [];
  1087.       var placeString = mozURLString = htmlString = unicodeString = "";
  1088.  
  1089.       for (var i = 0; i < nodes.length; ++i) {
  1090.         var node = nodes[i];
  1091.         if (this._shouldSkipNode(node, copiedFolders))
  1092.           continue;
  1093.         if (PlacesUtils.nodeIsFolder(node))
  1094.           copiedFolders.push(node);
  1095.         
  1096.         function generateChunk(type, overrideURI) {
  1097.           var suffix = i < (nodes.length - 1) ? NEWLINE : "";
  1098.           var uri = overrideURI;
  1099.         
  1100.           if (PlacesUtils.nodeIsLivemarkContainer(node))
  1101.             uri = PlacesUtils.livemarks.getFeedURI(node.itemId).spec
  1102.  
  1103.           mozURLString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_X_MOZ_URL,
  1104.                                                  uri) + suffix);
  1105.           unicodeString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_UNICODE,
  1106.                                                  uri) + suffix);
  1107.           htmlString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_HTML,
  1108.                                                  uri) + suffix);
  1109.  
  1110.           var placeSuffix = i < (nodes.length - 1) ? "," : "";
  1111.           var resolveShortcuts = !PlacesControllerDragHelper.canMoveContainerNode(node);
  1112.           return PlacesUtils.wrapNode(node, type, overrideURI, resolveShortcuts) + placeSuffix;
  1113.         }
  1114.  
  1115.         // all items wrapped as TYPE_X_MOZ_PLACE
  1116.         placeString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE);
  1117.       }
  1118.  
  1119.       function addData(type, data) {
  1120.         xferable.addDataFlavor(type);
  1121.         xferable.setTransferData(type, PlacesUIUtils._wrapString(data), data.length * 2);
  1122.       }
  1123.       // This order is _important_! It controls how this and other applications 
  1124.       // select data to be inserted based on type.
  1125.       if (placeString)
  1126.         addData(PlacesUtils.TYPE_X_MOZ_PLACE, placeString);
  1127.       if (mozURLString)
  1128.         addData(PlacesUtils.TYPE_X_MOZ_URL, mozURLString);
  1129.       if (unicodeString)
  1130.         addData(PlacesUtils.TYPE_UNICODE, unicodeString);
  1131.       if (htmlString)
  1132.         addData(PlacesUtils.TYPE_HTML, htmlString);
  1133.  
  1134.       if (placeString || unicodeString || htmlString || mozURLString) {
  1135.         PlacesUIUtils.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
  1136.       }
  1137.     }
  1138.     finally {
  1139.       if (oldViewer)
  1140.         result.viewer = oldViewer;
  1141.     }
  1142.   },
  1143.  
  1144.   /**
  1145.    * Cut Bookmarks and Folders to the clipboard
  1146.    */
  1147.   cut: function PC_cut() {
  1148.     this.copy();
  1149.     this.remove("Cut Selection");
  1150.   },
  1151.  
  1152.   /**
  1153.    * Paste Bookmarks and Folders from the clipboard
  1154.    */
  1155.   paste: function PC_paste() {
  1156.     // Strategy:
  1157.     // 
  1158.     // There can be data of various types (folder, separator, link) on the 
  1159.     // clipboard. We need to get all of that data and build edit transactions
  1160.     // for them. This means asking the clipboard once for each type and 
  1161.     // aggregating the results. 
  1162.  
  1163.     /**
  1164.      * Constructs a transferable that can receive data of specific types.
  1165.      * @param   types
  1166.      *          The types of data the transferable can hold, in order of
  1167.      *          preference.
  1168.      * @returns The transferable.
  1169.      */
  1170.     function makeXferable(types) {
  1171.       var xferable = 
  1172.           Cc["@mozilla.org/widget/transferable;1"].
  1173.           createInstance(Ci.nsITransferable);
  1174.       for (var i = 0; i < types.length; ++i) 
  1175.         xferable.addDataFlavor(types[i]);
  1176.       return xferable;
  1177.     }
  1178.  
  1179.     var clipboard = PlacesUIUtils.clipboard;
  1180.  
  1181.     var ip = this._view.insertionPoint;
  1182.     if (!ip)
  1183.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  1184.  
  1185.     /**
  1186.      * Gets a list of transactions to perform the paste of specific types.
  1187.      * @param   types
  1188.      *          The types of data to form paste transactions for
  1189.      * @returns An array of transactions that perform the paste.
  1190.      */
  1191.     function getTransactions(types) {
  1192.       var xferable = makeXferable(types);
  1193.       clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
  1194.  
  1195.       var data = { }, type = { };
  1196.       try {
  1197.         xferable.getAnyTransferData(type, data, { });
  1198.         data = data.value.QueryInterface(Ci.nsISupportsString).data;
  1199.         var items = PlacesUtils.unwrapNodes(data, type.value);
  1200.         var transactions = [];
  1201.         var index = ip.index;
  1202.         for (var i = 0; i < items.length; ++i) {
  1203.           // adjusted to make sure that items are given the correct index -
  1204.           // transactions insert differently if index == -1
  1205.           if (ip.index > -1)
  1206.             index = ip.index + i;
  1207.           transactions.push(PlacesUIUtils.makeTransaction(items[i], type.value,
  1208.                                                           ip.itemId, index,
  1209.                                                           true));
  1210.         }
  1211.         return transactions;
  1212.       }
  1213.       catch (e) {
  1214.         // getAnyTransferData will throw if there is no data of the specified
  1215.         // type on the clipboard. 
  1216.         // unwrapNodes will throw if the data that is present is malformed in
  1217.         // some way. 
  1218.         // In either case, don't fail horribly, just return no data.
  1219.       }
  1220.       return [];
  1221.     }
  1222.  
  1223.     // Get transactions to paste any folders, separators or links that might
  1224.     // be on the clipboard, aggregate them and execute them. 
  1225.     var transactions = getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE,
  1226.                                         PlacesUtils.TYPE_X_MOZ_URL, 
  1227.                                         PlacesUtils.TYPE_UNICODE]);
  1228.     var txn = PlacesUIUtils.ptm.aggregateTransactions("Paste", transactions);
  1229.     PlacesUIUtils.ptm.doTransaction(txn);
  1230.  
  1231.     // select the pasted items, they should be consecutive
  1232.     var insertedNodeIds = [];
  1233.     for (var i = 0; i < transactions.length; ++i)
  1234.       insertedNodeIds.push(PlacesUtils.bookmarks
  1235.                                       .getIdForItemAt(ip.itemId, ip.index + i));
  1236.     if (insertedNodeIds.length > 0)
  1237.       this._view.selectItems(insertedNodeIds);
  1238.   }
  1239. };
  1240.  
  1241. /**
  1242.  * Handles drag and drop operations for views. Note that this is view agnostic!
  1243.  * You should not use PlacesController._view within these methods, since
  1244.  * the view that the item(s) have been dropped on was not necessarily active. 
  1245.  * Drop functions are passed the view that is being dropped on. 
  1246.  */
  1247. var PlacesControllerDragHelper = {
  1248.  
  1249.   /**
  1250.    * Determines if the mouse is currently being dragged over a child node of
  1251.    * this menu. This is necessary so that the menu doesn't close while the
  1252.    * mouse is dragging over one of its submenus
  1253.    * @param   node
  1254.    *          The container node
  1255.    * @returns true if the user is dragging over a node within the hierarchy of
  1256.    *          the container, false otherwise.
  1257.    */
  1258.   draggingOverChildNode: function PCDH_draggingOverChildNode(node) {
  1259.     var currentNode = this.currentDropTarget;
  1260.     while (currentNode) {
  1261.       if (currentNode == node)
  1262.         return true;
  1263.       currentNode = currentNode.parentNode;
  1264.     }
  1265.     return false;
  1266.   },
  1267.  
  1268.   /**
  1269.    * DOM Element currently being dragged over
  1270.    */
  1271.   currentDropTarget: null,
  1272.  
  1273.   /**
  1274.    * @returns The current active drag session. Returns null if there is none.
  1275.    */
  1276.   getSession: function PCDH__getSession() {
  1277.     var dragService = Cc["@mozilla.org/widget/dragservice;1"].
  1278.                       getService(Ci.nsIDragService);
  1279.     return dragService.getCurrentSession();
  1280.   },
  1281.  
  1282.   /**
  1283.    * Determines whether or not the data currently being dragged can be dropped
  1284.    * on a places view.
  1285.    * @param ip
  1286.    *        The insertion point where the items should be dropped
  1287.    */
  1288.   canDrop: function PCDH_canDrop(ip) {
  1289.     var session = this.getSession();
  1290.     if (!session)
  1291.       return false;
  1292.  
  1293.     var types = PlacesUIUtils.GENERIC_VIEW_DROP_TYPES;
  1294.     var foundType = false;
  1295.     for (var i = 0; i < types.length && !foundType; ++i) {
  1296.       if (session.isDataFlavorSupported(types[i]))
  1297.         foundType = true;
  1298.     }
  1299.  
  1300.     if (!foundType)
  1301.       return false;
  1302.  
  1303.     // Check every dragged item
  1304.     var xferable = this._initTransferable(session);
  1305.     var dropCount = session.numDropItems;
  1306.     for (i = 0; i < dropCount; i++) {
  1307.       // Get the information of the dragged item
  1308.       session.getData(xferable, i);
  1309.       var data = { }, flavor = { };
  1310.       xferable.getAnyTransferData(flavor, data, { });
  1311.       data.value.QueryInterface(Ci.nsISupportsString);
  1312.       var dragged = PlacesUtils.unwrapNodes(data.value.data, flavor.value)[0];
  1313.  
  1314.       // Only bookmarks and urls can be dropped into tag containers
  1315.       if (ip.isTag && dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
  1316.                       (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
  1317.                        /^place:/.test(dragged.uri)))
  1318.         return false;
  1319.  
  1320.       // The following loop disallows the dropping of a folder on itself or
  1321.       // on any of its descendants.
  1322.       if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
  1323.           /^place:/.test(dragged.uri)) {
  1324.         var parentId = ip.itemId;
  1325.         while (parentId != PlacesUtils.placesRootId) {
  1326.           if (dragged.concreteId == parentId || dragged.id == parentId)
  1327.             return false;
  1328.           parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
  1329.         }
  1330.       }
  1331.     }
  1332.  
  1333.     return true;
  1334.   },
  1335.  
  1336.   /**
  1337.    * Determines if a container node can be moved.
  1338.    * 
  1339.    * @param   aNode
  1340.    *          A bookmark folder node.
  1341.    * @param   [optional] aInsertionPoint
  1342.    *          The insertion point of the drop target.
  1343.    * @returns True if the container can be moved.
  1344.    */
  1345.   canMoveContainerNode:
  1346.   function PCDH_canMoveContainerNode(aNode, aInsertionPoint) {
  1347.     // can't move query root
  1348.     if (!aNode.parent)
  1349.       return false;
  1350.  
  1351.     var targetId = aInsertionPoint ? aInsertionPoint.itemId : -1;
  1352.     var parentId = PlacesUtils.getConcreteItemId(aNode.parent);
  1353.     var concreteId = PlacesUtils.getConcreteItemId(aNode);
  1354.  
  1355.     // can't move tag containers 
  1356.     if (PlacesUtils.nodeIsTagQuery(aNode))
  1357.       return false;
  1358.  
  1359.     // check is child of a read-only container 
  1360.     if (PlacesUtils.nodeIsReadOnly(aNode.parent))
  1361.       return false;
  1362.  
  1363.     // check for special folders, etc
  1364.     if (!this.canMoveContainer(aNode.itemId, parentId))
  1365.       return false;
  1366.  
  1367.     return true;
  1368.   },
  1369.  
  1370.   /**
  1371.    * Determines if a container node can be moved.
  1372.    * 
  1373.    * @param   aId
  1374.    *          A bookmark folder id.
  1375.    * @param   [optional] aParentId
  1376.    *          The parent id of the folder.
  1377.    * @returns True if the container can be moved to the target.
  1378.    */
  1379.   canMoveContainer:
  1380.   function PCDH_canMoveContainer(aId, aParentId) {
  1381.     if (aId == -1)
  1382.       return false;
  1383.  
  1384.     // Disallow moving of roots and special folders
  1385.     const ROOTS = [PlacesUtils.placesRootId, PlacesUtils.bookmarksMenuFolderId,
  1386.                    PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
  1387.                    PlacesUtils.toolbarFolderId];
  1388.     if (ROOTS.indexOf(aId) != -1)
  1389.       return false;
  1390.  
  1391.     // Get parent id if necessary
  1392.     if (aParentId == null || aParentId == -1)
  1393.       aParentId = PlacesUtils.bookmarks.getFolderIdForItem(aId);
  1394.  
  1395.     if(PlacesUtils.bookmarks.getFolderReadonly(aParentId))
  1396.       return false;
  1397.  
  1398.     return true;
  1399.   },
  1400.  
  1401.   /** 
  1402.    * Creates a Transferable object that can be filled with data of types
  1403.    * supported by a view. 
  1404.    * @param   session
  1405.    *          The active drag session
  1406.    * @returns An object implementing nsITransferable that can receive data
  1407.    *          dropped onto a view. 
  1408.    */
  1409.   _initTransferable: function PCDH__initTransferable(session) {
  1410.     var xferable = Cc["@mozilla.org/widget/transferable;1"].
  1411.                    createInstance(Ci.nsITransferable);
  1412.     var types = PlacesUIUtils.GENERIC_VIEW_DROP_TYPES;
  1413.     for (var i = 0; i < types.length; ++i) {
  1414.       if (session.isDataFlavorSupported(types[i]))
  1415.         xferable.addDataFlavor(types[i]);
  1416.     }
  1417.     return xferable;
  1418.   },
  1419.  
  1420.   /**
  1421.    * Handles the drop of one or more items onto a view.
  1422.    * @param   insertionPoint
  1423.    *          The insertion point where the items should be dropped
  1424.    */
  1425.   onDrop: function PCDH_onDrop(insertionPoint) {
  1426.     var session = this.getSession();
  1427.     // XXX dragAction is not valid, so we also set copy below by checking
  1428.     // whether the dropped item is moveable, before creating the transaction
  1429.     var copy = session.dragAction & Ci.nsIDragService.DRAGDROP_ACTION_COPY;
  1430.     var transactions = [];
  1431.     var xferable = this._initTransferable(session);
  1432.     var dropCount = session.numDropItems;
  1433.  
  1434.     var movedCount = 0;
  1435.  
  1436.     for (var i = 0; i < dropCount; ++i) {
  1437.       session.getData(xferable, i);
  1438.  
  1439.       var data = { }, flavor = { };
  1440.       xferable.getAnyTransferData(flavor, data, { });
  1441.       data.value.QueryInterface(Ci.nsISupportsString);
  1442.  
  1443.       // There's only ever one in the D&D case. 
  1444.       var unwrapped = PlacesUtils.unwrapNodes(data.value.data, 
  1445.                                               flavor.value)[0];
  1446.  
  1447.       var index = insertionPoint.index;
  1448.  
  1449.       // Adjust insertion index to prevent reversal of dragged items. When you
  1450.       // drag multiple elts upward: need to increment index or each successive
  1451.       // elt will be inserted at the same index, each above the previous.
  1452.       if (index != -1 && index < unwrapped.index) {
  1453.         index = index + movedCount;
  1454.         movedCount++;
  1455.       }
  1456.  
  1457.       // if dragging over a tag container we should tag the item
  1458.       if (insertionPoint.isTag) {
  1459.         var uri = PlacesUtils._uri(unwrapped.uri);
  1460.         var tagItemId = insertionPoint.itemId;
  1461.         transactions.push(PlacesUIUtils.ptm.tagURI(uri,[tagItemId]));
  1462.       }
  1463.       else {
  1464.         if (unwrapped.id && !this.canMoveContainer(unwrapped.id, null))
  1465.           copy = true;
  1466.         else if (unwrapped.concreteId &&
  1467.                  !this.canMoveContainer(unwrapped.concreteId, null))
  1468.           copy = true;
  1469.  
  1470.         transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
  1471.                           flavor.value, insertionPoint.itemId,
  1472.                           index, copy));
  1473.       }
  1474.     }
  1475.  
  1476.     var txn = PlacesUIUtils.ptm.aggregateTransactions("DropItems", transactions);
  1477.     PlacesUIUtils.ptm.doTransaction(txn);
  1478.   }
  1479. };
  1480.  
  1481. function goUpdatePlacesCommands() {
  1482.   var placesController;
  1483.   try {
  1484.     // Or any other command...
  1485.     placesController = top.document.commandDispatcher
  1486.                           .getControllerForCommand("placesCmd_open");
  1487.   }
  1488.   catch(ex) { return; }
  1489.  
  1490.   function updatePlacesCommand(aCommand) {
  1491.     var enabled = false;
  1492.     if (placesController)
  1493.       enabled = placesController.isCommandEnabled(aCommand);
  1494.     goSetCommandEnabled(aCommand, enabled);
  1495.   }
  1496.  
  1497.   updatePlacesCommand("placesCmd_open");
  1498.   updatePlacesCommand("placesCmd_open:window");
  1499.   updatePlacesCommand("placesCmd_open:tab");
  1500.   updatePlacesCommand("placesCmd_new:folder");
  1501.   updatePlacesCommand("placesCmd_new:bookmark");
  1502.   updatePlacesCommand("placesCmd_new:livemark");
  1503.   updatePlacesCommand("placesCmd_new:separator");
  1504.   updatePlacesCommand("placesCmd_show:info");
  1505.   updatePlacesCommand("placesCmd_moveBookmarks");
  1506.   updatePlacesCommand("placesCmd_reload");
  1507.   updatePlacesCommand("placesCmd_reloadMicrosummary");
  1508.   updatePlacesCommand("placesCmd_sortBy:name");
  1509. }
  1510.