controller.js
Go to the documentation of this file.
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@bonardo.net>
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
47 // Inserting items new to the view, select the inserted rows
49 // Removing items from the view, select the first item after the last selected
51 // Moving items within a view, don't treat the dropped items as additional
52 // rows.
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
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
65 
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 
113 function PlacesController(aView) {
114  this._view = aView;
115 }
116 
117 PlacesController.prototype = {
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 "placesCmd_cut":
131  var nodes = this._view.getSelectionNodes();
132  // If selection includes history nodes there's no reason to allow cut.
133  for (var i = 0; i < nodes.length; i++) {
134  if (nodes[i].itemId == -1)
135  return false;
136  }
137  // Otherwise fallback to cmd_delete check.
138  case "cmd_delete":
139  case "placesCmd_delete":
140  return this._hasRemovableSelection(false);
141  case "placesCmd_deleteDataHost":
142  return this._hasRemovableSelection(false) &&
143  !PlacesUIUtils.privateBrowsing.privateBrowsingEnabled;
144  case "placesCmd_moveBookmarks":
145  return this._hasRemovableSelection(true);
146  case "cmd_copy":
147  case "placesCmd_copy":
148  return this._view.hasSelection;
149  case "cmd_paste":
150  case "placesCmd_paste":
151  return this._canInsert(true) && this._isClipboardDataPasteable();
152  case "cmd_selectAll":
153  if (this._view.selType != "single") {
154  var rootNode = this._view.getResultNode();
155  if (rootNode.containerOpen && rootNode.childCount > 0)
156  return true;
157  }
158  return false;
159  case "placesCmd_open":
160  case "placesCmd_open:window":
161  case "placesCmd_open:tab":
162  var selectedNode = this._view.selectedNode;
163  return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
164  case "placesCmd_new:folder":
165  case "placesCmd_new:livemark":
166  return this._canInsert();
167  case "placesCmd_new:bookmark":
168  return this._canInsert();
169  case "placesCmd_new:separator":
170  return this._canInsert() &&
171  !asQuery(this._view.getResultNode()).queryOptions.excludeItems &&
172  this._view.getResult().sortingMode ==
173  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
174  case "placesCmd_show:info":
175  var selectedNode = this._view.selectedNode;
176  if (selectedNode &&
177  PlacesUtils.getConcreteItemId(selectedNode) != -1 &&
178  !PlacesUtils.nodeIsLivemarkItem(selectedNode))
179  return true;
180  return false;
181  case "placesCmd_reloadMicrosummary":
182  var selectedNode = this._view.selectedNode;
183  return selectedNode && PlacesUtils.nodeIsBookmark(selectedNode) &&
184  PlacesUIUtils.microsummaries.hasMicrosummary(selectedNode.itemId);
185  case "placesCmd_reload":
186  // Livemark containers
187  var selectedNode = this._view.selectedNode;
188  return selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode);
189  case "placesCmd_sortBy:name":
190  var selectedNode = this._view.selectedNode;
191  return selectedNode &&
192  PlacesUtils.nodeIsFolder(selectedNode) &&
193  !PlacesUtils.nodeIsReadOnly(selectedNode) &&
194  this._view.getResult().sortingMode ==
195  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
196  case "placesCmd_createBookmark":
197  var node = this._view.selectedNode;
198  return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1;
199  default:
200  return false;
201  }
202  },
203 
204  supportsCommand: function PC_supportsCommand(aCommand) {
205  //LOG("supportsCommand: " + command);
206  // Non-Places specific commands that we also support
207  switch (aCommand) {
208  case "cmd_undo":
209  case "cmd_redo":
210  case "cmd_cut":
211  case "cmd_copy":
212  case "cmd_paste":
213  case "cmd_delete":
214  case "cmd_selectAll":
215  return true;
216  }
217 
218  // All other Places Commands are prefixed with "placesCmd_" ... this
219  // filters out other commands that we do _not_ support (see 329587).
220  const CMD_PREFIX = "placesCmd_";
221  return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
222  },
223 
224  doCommand: function PC_doCommand(aCommand) {
225  switch (aCommand) {
226  case "cmd_undo":
227  PlacesUIUtils.ptm.undoTransaction();
228  break;
229  case "cmd_redo":
230  PlacesUIUtils.ptm.redoTransaction();
231  break;
232  case "cmd_cut":
233  case "placesCmd_cut":
234  this.cut();
235  break;
236  case "cmd_copy":
237  case "placesCmd_copy":
238  this.copy();
239  break;
240  case "cmd_paste":
241  case "placesCmd_paste":
242  this.paste();
243  break;
244  case "cmd_delete":
245  case "placesCmd_delete":
246  this.remove("Remove Selection");
247  break;
248  case "placesCmd_deleteDataHost":
249  var host;
250  if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
251  var queries = this._view.selectedNode.getQueries({});
252  host = queries[0].domain;
253  }
254  else
255  host = PlacesUtils._uri(this._view.selectedNode.uri).host;
256  PlacesUIUtils.privateBrowsing.removeDataFromDomain(host);
257  break;
258  case "cmd_selectAll":
259  this.selectAll();
260  break;
261  case "placesCmd_open":
262  PlacesUIUtils.openNodeIn(this._view.selectedNode, "current");
263  break;
264  case "placesCmd_open:window":
265  PlacesUIUtils.openNodeIn(this._view.selectedNode, "window");
266  break;
267  case "placesCmd_open:tab":
268  PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab");
269  break;
270  case "placesCmd_new:folder":
271  this.newItem("folder");
272  break;
273  case "placesCmd_new:bookmark":
274  this.newItem("bookmark");
275  break;
276  case "placesCmd_new:livemark":
277  this.newItem("livemark");
278  break;
279  case "placesCmd_new:separator":
280  this.newSeparator();
281  break;
282  case "placesCmd_show:info":
283  this.showBookmarkPropertiesForSelection();
284  break;
285  case "placesCmd_moveBookmarks":
286  this.moveSelectedBookmarks();
287  break;
288  case "placesCmd_reload":
289  this.reloadSelectedLivemark();
290  break;
291  case "placesCmd_reloadMicrosummary":
292  this.reloadSelectedMicrosummary();
293  break;
294  case "placesCmd_sortBy:name":
295  this.sortFolderByName();
296  break;
297  case "placesCmd_createBookmark":
298  var node = this._view.selectedNode;
299  PlacesUIUtils.showMinimalAddBookmarkUI(PlacesUtils._uri(node.uri), node.title);
300  break;
301  }
302  },
303 
304  onEvent: function PC_onEvent(eventName) { },
305 
306 
319  _hasRemovableSelection: function PC__hasRemovableSelection(aIsMoveCommand) {
320  var ranges = this._view.getRemovableSelectionRanges();
321  if (!ranges.length)
322  return false;
323 
324  var root = this._view.getResultNode();
325 
326  for (var j = 0; j < ranges.length; j++) {
327  var nodes = ranges[j];
328  for (var i = 0; i < nodes.length; ++i) {
329  // Disallow removing the view's root node
330  if (nodes[i] == root)
331  return false;
332 
333  if (PlacesUtils.nodeIsFolder(nodes[i]) &&
334  !PlacesControllerDragHelper.canMoveNode(nodes[i]))
335  return false;
336 
337  // We don't call nodeIsReadOnly here, because nodeIsReadOnly means that
338  // a node has children that cannot be edited, reordered or removed. Here,
339  // we don't care if a node's children can't be reordered or edited, just
340  // that they're removable. All history results have removable children
341  // (based on the principle that any URL in the history table should be
342  // removable), but some special bookmark folders may have non-removable
343  // children, e.g. live bookmark folder children. It doesn't make sense
344  // to delete a child of a live bookmark folder, since when the folder
345  // refreshes, the child will return.
346  var parent = nodes[i].parent || root;
347  if (PlacesUtils.isReadonlyFolder(parent))
348  return false;
349  }
350  }
351 
352  return true;
353  },
354 
358  _canInsert: function PC__canInsert(isPaste) {
359  var ip = this._view.insertionPoint;
360  return ip != null && (isPaste || ip.isTag != true);
361  },
362 
366  rootNodeIsSelected: function PC_rootNodeIsSelected() {
367  var nodes = this._view.getSelectionNodes();
368  var root = this._view.getResultNode();
369  for (var i = 0; i < nodes.length; ++i) {
370  if (nodes[i] == root)
371  return true;
372  }
373 
374  return false;
375  },
376 
385  _isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
386  // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
387  // pasteable, with no need to unwrap all the nodes.
388 
389  var flavors = PlacesControllerDragHelper.placesFlavors;
390  var clipboard = PlacesUIUtils.clipboard;
391  var hasPlacesData =
392  clipboard.hasDataMatchingFlavors(flavors, flavors.length,
393  Ci.nsIClipboard.kGlobalClipboard);
394  if (hasPlacesData)
395  return this._view.insertionPoint != null;
396 
397  // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow
398  // pasting of valid "text/unicode" and "text/x-moz-url" data
399  var xferable = Cc["@mozilla.org/widget/transferable;1"].
400  createInstance(Ci.nsITransferable);
401 
402  xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL);
403  xferable.addDataFlavor(PlacesUtils.TYPE_UNICODE);
404  clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
405 
406  try {
407  // getAnyTransferData will throw if no data is available.
408  var data = { }, type = { };
409  xferable.getAnyTransferData(type, data, { });
410  data = data.value.QueryInterface(Ci.nsISupportsString).data;
411  if (type.value != PlacesUtils.TYPE_X_MOZ_URL &&
412  type.value != PlacesUtils.TYPE_UNICODE)
413  return false;
414 
415  // unwrapNodes() will throw if the data blob is malformed.
416  var unwrappedNodes = PlacesUtils.unwrapNodes(data, type.value);
417  return this._view.insertionPoint != null;
418  }
419  catch (e) {
420  // getAnyTransferData or unwrapNodes failed
421  return false;
422  }
423  },
424 
447  _buildSelectionMetadata: function PC__buildSelectionMetadata() {
448  var metadata = [];
449  var root = this._view.getResultNode();
450  var nodes = this._view.getSelectionNodes();
451  if (nodes.length == 0)
452  nodes.push(root); // See the second note above
453 
454  for (var i=0; i < nodes.length; i++) {
455  var nodeData = {};
456  var node = nodes[i];
457  var nodeType = node.type;
458  var uri = null;
459 
460  // We don't use the nodeIs* methods here to avoid going through the type
461  // property way too often
462  switch(nodeType) {
463  case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
464  nodeData["query"] = true;
465  if (node.parent) {
466  switch (asQuery(node.parent).queryOptions.resultType) {
467  case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
468  nodeData["host"] = true;
469  break;
470  case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
471  case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
472  nodeData["day"] = true;
473  break;
474  }
475  }
476  break;
477  case Ci.nsINavHistoryResultNode.RESULT_TYPE_DYNAMIC_CONTAINER:
478  nodeData["dynamiccontainer"] = true;
479  break;
480  case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
481  case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT:
482  nodeData["folder"] = true;
483  break;
484  case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
485  nodeData["separator"] = true;
486  break;
487  case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
488  case Ci.nsINavHistoryResultNode.RESULT_TYPE_VISIT:
489  case Ci.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT:
490  nodeData["link"] = true;
491  uri = PlacesUtils._uri(node.uri);
492  if (PlacesUtils.nodeIsBookmark(node)) {
493  nodeData["bookmark"] = true;
494  PlacesUtils.nodeIsTagQuery(node.parent)
495  var mss = PlacesUIUtils.microsummaries;
496  if (mss.hasMicrosummary(node.itemId))
497  nodeData["microsummary"] = true;
498 
499  var parentNode = node.parent;
500  if (parentNode) {
501  if (PlacesUtils.nodeIsTagQuery(parentNode))
502  nodeData["tagChild"] = true;
503  else if (PlacesUtils.nodeIsLivemarkContainer(parentNode))
504  nodeData["livemarkChild"] = true;
505  }
506  }
507  break;
508  }
509 
510  // annotations
511  if (uri) {
512  var names = PlacesUtils.annotations.getPageAnnotationNames(uri, {});
513  for (var j = 0; j < names.length; ++j)
514  nodeData[names[j]] = true;
515  }
516 
517  // For items also include the item-specific annotations
518  if (node.itemId != -1) {
519  names = PlacesUtils.annotations
520  .getItemAnnotationNames(node.itemId, {});
521  for (j = 0; j < names.length; ++j)
522  nodeData[names[j]] = true;
523  }
524  metadata.push(nodeData);
525  }
526 
527  return metadata;
528  },
529 
539  _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
540  var selectiontype = aMenuItem.getAttribute("selectiontype");
541  if (selectiontype == "multiple" && aMetaData.length == 1)
542  return false;
543  if (selectiontype == "single" && aMetaData.length != 1)
544  return false;
545 
546  var forceHideAttr = aMenuItem.getAttribute("forcehideselection");
547  if (forceHideAttr) {
548  var forceHideRules = forceHideAttr.split("|");
549  for (var i = 0; i < aMetaData.length; ++i) {
550  for (var j=0; j < forceHideRules.length; ++j) {
551  if (forceHideRules[j] in aMetaData[i])
552  return false;
553  }
554  }
555  }
556 
557  var selectionAttr = aMenuItem.getAttribute("selection");
558  if (selectionAttr) {
559  if (selectionAttr == "any")
560  return true;
561 
562  var showRules = selectionAttr.split("|");
563  var anyMatched = false;
564  function metaDataNodeMatches(metaDataNode, rules) {
565  for (var i=0; i < rules.length; i++) {
566  if (rules[i] in metaDataNode)
567  return true;
568  }
569 
570  return false;
571  }
572  for (var i = 0; i < aMetaData.length; ++i) {
573  if (metaDataNodeMatches(aMetaData[i], showRules))
574  anyMatched = true;
575  else
576  return false;
577  }
578  return anyMatched;
579  }
580 
581  return !aMenuItem.hidden;
582  },
583 
617  buildContextMenu: function PC_buildContextMenu(aPopup) {
618  var metadata = this._buildSelectionMetadata();
619  var ip = this._view.insertionPoint;
620  var noIp = !ip || ip.isTag;
621 
622  var separator = null;
623  var visibleItemsBeforeSep = false;
624  var anyVisible = false;
625  for (var i = 0; i < aPopup.childNodes.length; ++i) {
626  var item = aPopup.childNodes[i];
627  if (item.localName != "menuseparator") {
628  // We allow pasting into tag containers, so special case that.
629  var hideIfNoIP = item.getAttribute("hideifnoinsertionpoint") == "true" &&
630  noIp && !(ip && ip.isTag && item.id == "placesContext_paste");
631  var hideIfPB = item.getAttribute("hideifprivatebrowsing") == "true" &&
632  PlacesUIUtils.privateBrowsing.privateBrowsingEnabled;
633  item.hidden = hideIfNoIP || hideIfPB ||
634  !this._shouldShowMenuItem(item, metadata);
635 
636  if (!item.hidden) {
637  visibleItemsBeforeSep = true;
638  anyVisible = true;
639 
640  // Show the separator above the menu-item if any
641  if (separator) {
642  separator.hidden = false;
643  separator = null;
644  }
645  }
646  }
647  else { // menuseparator
648  // Initially hide it. It will be unhidden if there will be at least one
649  // visible menu-item above and below it.
650  item.hidden = true;
651 
652  // We won't show the separator at all if no items are visible above it
653  if (visibleItemsBeforeSep)
654  separator = item;
655 
656  // New separator, count again:
657  visibleItemsBeforeSep = false;
658  }
659  }
660 
661  // Set Open Folder/Links In Tabs items enabled state if they're visible
662  if (anyVisible) {
663  var openContainerInTabsItem = document.getElementById("placesContext_openContainer:tabs");
664  if (!openContainerInTabsItem.hidden && this._view.selectedNode &&
665  PlacesUtils.nodeIsContainer(this._view.selectedNode)) {
666  openContainerInTabsItem.disabled =
667  !PlacesUtils.hasChildURIs(this._view.selectedNode);
668  }
669  else {
670  // see selectiontype rule in the overlay
671  var openLinksInTabsItem = document.getElementById("placesContext_openLinks:tabs");
672  openLinksInTabsItem.disabled = openLinksInTabsItem.hidden;
673  }
674  }
675 
676  return anyVisible;
677  },
678 
682  selectAll: function PC_selectAll() {
683  this._view.selectAll();
684  },
685 
689  showBookmarkPropertiesForSelection:
690  function PC_showBookmarkPropertiesForSelection() {
691  var node = this._view.selectedNode;
692  if (!node)
693  return;
694 
695  var itemType = PlacesUtils.nodeIsFolder(node) ||
696  PlacesUtils.nodeIsTagQuery(node) ? "folder" : "bookmark";
697  var concreteId = PlacesUtils.getConcreteItemId(node);
698  var isRootItem = PlacesUtils.isRootItem(concreteId);
699  var itemId = node.itemId;
700  if (isRootItem || PlacesUtils.nodeIsTagQuery(node)) {
701  // If this is a root or the Tags query we use the concrete itemId to catch
702  // the correct title for the node.
703  itemId = concreteId;
704  }
705 
706  PlacesUIUtils.showItemProperties(itemId, itemType,
707  isRootItem /* read only */);
708  },
709 
714  _assertURINotString: function PC__assertURINotString(value) {
715  NS_ASSERT((typeof(value) == "object") && !(value instanceof String),
716  "This method should be passed a URI as a nsIURI object, not as a string.");
717  },
718 
722  reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
723  var selectedNode = this._view.selectedNode;
724  if (selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode))
725  PlacesUtils.livemarks.reloadLivemarkFolder(selectedNode.itemId);
726  },
727 
731  reloadSelectedMicrosummary: function PC_reloadSelectedMicrosummary() {
732  var selectedNode = this._view.selectedNode;
733  var mss = PlacesUIUtils.microsummaries;
734  if (mss.hasMicrosummary(selectedNode.itemId))
735  mss.refreshMicrosummary(selectedNode.itemId);
736  },
737 
741  _confirmOpenTabs: function(numTabsToOpen) {
742  var pref = Cc["@mozilla.org/preferences-service;1"].
743  getService(Ci.nsIPrefBranch);
744 
745  const kWarnOnOpenPref = "browser.tabs.warnOnOpen";
746  var reallyOpen = true;
747  if (pref.getBoolPref(kWarnOnOpenPref)) {
748  if (numTabsToOpen >= pref.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
749  var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
750  getService(Ci.nsIPromptService);
751 
752  // default to true: if it were false, we wouldn't get this far
753  var warnOnOpen = { value: true };
754 
755  var messageKey = "tabs.openWarningMultipleBranded";
756  var openKey = "tabs.openButtonMultiple";
757  var strings = document.getElementById("placeBundle");
758  const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
759  var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
760  getService(Ci.nsIStringBundleService).
761  createBundle(BRANDING_BUNDLE_URI).
762  GetStringFromName("brandShortName");
763 
764  var buttonPressed = promptService.confirmEx(window,
765  PlacesUIUtils.getString("tabs.openWarningTitle"),
766  PlacesUIUtils.getFormattedString(messageKey,
767  [numTabsToOpen, brandShortName]),
768  (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
769  + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
770  PlacesUIUtils.getString(openKey),
771  null, null,
772  PlacesUIUtils.getFormattedString("tabs.openWarningPromptMeBranded",
773  [brandShortName]),
774  warnOnOpen);
775 
776  reallyOpen = (buttonPressed == 0);
777  // don't set the pref unless they press OK and it's false
778  if (reallyOpen && !warnOnOpen.value)
779  pref.setBoolPref(kWarnOnOpenPref, false);
780  }
781  }
782  return reallyOpen;
783  },
784 
788  openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
789  var node = this._view.selectedNode;
790  if (node && PlacesUtils.nodeIsContainer(node))
791  PlacesUIUtils.openContainerNodeInTabs(this._view.selectedNode, aEvent);
792  else
793  PlacesUIUtils.openURINodesInTabs(this._view.getSelectionNodes(), aEvent);
794  },
795 
802  newItem: function PC_newItem(aType) {
803  var ip = this._view.insertionPoint;
804  if (!ip)
805  throw Cr.NS_ERROR_NOT_AVAILABLE;
806 
807  var performed = false;
808  if (aType == "bookmark")
809  performed = PlacesUIUtils.showAddBookmarkUI(null, null, null, ip);
810  else if (aType == "livemark")
811  performed = PlacesUIUtils.showAddLivemarkUI(null, null, null, null, ip);
812  else // folder
813  performed = PlacesUIUtils.showAddFolderUI(null, ip);
814 
815  if (performed) {
816  // select the new item
817  var insertedNodeId = PlacesUtils.bookmarks
818  .getIdForItemAt(ip.itemId, ip.index);
819  this._view.selectItems([insertedNodeId], false);
820  }
821  },
822 
823 
828  newFolder: function PC_newFolder() {
829  var ip = this._view.insertionPoint;
830  if (!ip)
831  throw Cr.NS_ERROR_NOT_AVAILABLE;
832 
833  var performed = false;
834  performed = PlacesUIUtils.showAddFolderUI(null, ip);
835  if (performed) {
836  // select the new item
837  var insertedNodeId = PlacesUtils.bookmarks
838  .getIdForItemAt(ip.itemId, ip.index);
839  this._view.selectItems([insertedNodeId], false);
840  }
841  },
842 
846  newSeparator: function PC_newSeparator() {
847  var ip = this._view.insertionPoint;
848  if (!ip)
849  throw Cr.NS_ERROR_NOT_AVAILABLE;
850  var txn = PlacesUIUtils.ptm.createSeparator(ip.itemId, ip.index);
851  PlacesUIUtils.ptm.doTransaction(txn);
852  // select the new item
853  var insertedNodeId = PlacesUtils.bookmarks
854  .getIdForItemAt(ip.itemId, ip.index);
855  this._view.selectItems([insertedNodeId], false);
856  },
857 
861  moveSelectedBookmarks: function PC_moveBookmarks() {
862  window.openDialog("chrome://browser/content/places/moveBookmarks.xul",
863  "", "chrome, modal",
864  this._view.getSelectionNodes());
865  },
866 
870  sortFolderByName: function PC_sortFolderByName() {
871  var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
872  var txn = PlacesUIUtils.ptm.sortFolderByName(itemId);
873  PlacesUIUtils.ptm.doTransaction(txn);
874  },
875 
886  _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
895  function isContainedBy(node, parent) {
896  var cursor = node.parent;
897  while (cursor) {
898  if (cursor == parent)
899  return true;
900  cursor = cursor.parent;
901  }
902  return false;
903  }
904 
905  for (var j = 0; j < pastFolders.length; ++j) {
906  if (isContainedBy(node, pastFolders[j]))
907  return true;
908  }
909  return false;
910  },
911 
922  _removeRange: function PC__removeRange(range, transactions, removedFolders) {
923  NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
924  if (!removedFolders)
925  removedFolders = [];
926 
927  for (var i = 0; i < range.length; ++i) {
928  var node = range[i];
929  if (this._shouldSkipNode(node, removedFolders))
930  continue;
931 
932  if (PlacesUtils.nodeIsTagQuery(node.parent)) {
933  // This is a uri node inside a tag container. It needs a special
934  // untag transaction.
935  var tagItemId = PlacesUtils.getConcreteItemId(node.parent);
936  var uri = PlacesUtils._uri(node.uri);
937  transactions.push(PlacesUIUtils.ptm.untagURI(uri, [tagItemId]));
938  }
939  else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
940  PlacesUtils.nodeIsQuery(node.parent) &&
941  asQuery(node.parent).queryOptions.resultType ==
942  Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
943  // This is a tag container.
944  // Untag all URIs tagged with this tag only if the tag container is
945  // child of the "Tags" query in the library, in all other places we
946  // must only remove the query node.
947  var tag = node.title;
948  var URIs = PlacesUtils.tagging.getURIsForTag(tag);
949  for (var j = 0; j < URIs.length; j++)
950  transactions.push(PlacesUIUtils.ptm.untagURI(URIs[j], [tag]));
951  }
952  else if (PlacesUtils.nodeIsURI(node) &&
953  PlacesUtils.nodeIsQuery(node.parent) &&
954  asQuery(node.parent).queryOptions.queryType ==
955  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
956  // This is a uri node inside an history query.
957  var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
958  bhist.removePage(PlacesUtils._uri(node.uri));
959  // History deletes are not undoable, so we don't have a transaction.
960  }
961  else if (node.itemId == -1 &&
962  PlacesUtils.nodeIsQuery(node) &&
963  asQuery(node).queryOptions.queryType ==
964  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
965  // This is a dynamically generated history query, like queries
966  // grouped by site, time or both. Dynamically generated queries don't
967  // have an itemId even if they are descendants of a bookmark.
968  this._removeHistoryContainer(node);
969  // History deletes are not undoable, so we don't have a transaction.
970  }
971  else {
972  // This is a common bookmark item.
973  if (PlacesUtils.nodeIsFolder(node)) {
974  // If this is a folder we add it to our array of folders, used
975  // to skip nodes that are children of an already removed folder.
976  removedFolders.push(node);
977  }
978  transactions.push(PlacesUIUtils.ptm.removeItem(node.itemId));
979  }
980  }
981  },
982 
988  _removeRowsFromBookmarks: function PC__removeRowsFromBookmarks(txnName) {
989  var ranges = this._view.getRemovableSelectionRanges();
990  var transactions = [];
991  var removedFolders = [];
992 
993  for (var i = 0; i < ranges.length; i++)
994  this._removeRange(ranges[i], transactions, removedFolders);
995 
996  if (transactions.length > 0) {
997  var txn = PlacesUIUtils.ptm.aggregateTransactions(txnName, transactions);
998  PlacesUIUtils.ptm.doTransaction(txn);
999  }
1000  },
1001 
1005  _removeRowsFromHistory: function PC__removeRowsFromHistory() {
1006  // Other containers are history queries, just delete from history
1007  // history deletes are not undoable.
1008  var nodes = this._view.getSelectionNodes();
1009  var URIs = [];
1010  var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
1011  var root = this._view.getResultNode();
1012 
1013  for (var i = 0; i < nodes.length; ++i) {
1014  var node = nodes[i];
1015  if (PlacesUtils.nodeIsURI(node)) {
1016  var uri = PlacesUtils._uri(node.uri);
1017  // avoid trying to delete the same url twice
1018  if (URIs.indexOf(uri) < 0) {
1019  URIs.push(uri);
1020  }
1021  }
1022  else if (PlacesUtils.nodeIsQuery(node) &&
1023  asQuery(node).queryOptions.queryType ==
1024  Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
1025  this._removeHistoryContainer(node);
1026  }
1027 
1028  // if we have to delete a lot of urls RemovePage will be slow, it's better
1029  // to delete them in bunch and rebuild the full treeView
1030  if (URIs.length > REMOVE_PAGES_MAX_SINGLEREMOVES) {
1031  // do removal in chunks to avoid passing a too big array to removePages
1032  for (var i = 0; i < URIs.length; i += REMOVE_PAGES_CHUNKLEN) {
1033  var URIslice = URIs.slice(i, i + REMOVE_PAGES_CHUNKLEN);
1034  // set DoBatchNotify (third param) only on the last chunk, so we update
1035  // the treeView when we are done.
1036  bhist.removePages(URIslice, URIslice.length,
1037  (i + REMOVE_PAGES_CHUNKLEN) >= URIs.length);
1038  }
1039  }
1040  else {
1041  // if we have to delete fewer urls, removepage will allow us to avoid
1042  // rebuilding the full treeView
1043  for (var i = 0; i < URIs.length; ++i)
1044  bhist.removePage(URIs[i]);
1045  }
1046  },
1047 
1053  _removeHistoryContainer: function PC_removeHistoryContainer(aContainerNode) {
1054  var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
1055  if (PlacesUtils.nodeIsHost(aContainerNode)) {
1056  // Site container.
1057  bhist.removePagesFromHost(aContainerNode.title, true);
1058  }
1059  else if (PlacesUtils.nodeIsDay(aContainerNode)) {
1060  // Day container.
1061  var query = aContainerNode.getQueries({})[0];
1062  var beginTime = query.beginTime;
1063  var endTime = query.endTime;
1064  NS_ASSERT(query && beginTime && endTime,
1065  "A valid date container query should exist!");
1066  // We want to exclude beginTime from the removal because
1067  // removePagesByTimeframe includes both extremes, while date containers
1068  // exclude the lower extreme. So, if we would not exclude it, we would
1069  // end up removing more history than requested.
1070  bhist.removePagesByTimeframe(beginTime+1, endTime);
1071  }
1072  },
1073 
1080  remove: function PC_remove(aTxnName) {
1081  if (!this._hasRemovableSelection(false))
1082  return;
1083 
1084  NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
1085 
1086  var root = this._view.getResultNode();
1087 
1088  if (PlacesUtils.nodeIsFolder(root))
1089  this._removeRowsFromBookmarks(aTxnName);
1090  else if (PlacesUtils.nodeIsQuery(root)) {
1091  var queryType = asQuery(root).queryOptions.queryType;
1092  if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS)
1093  this._removeRowsFromBookmarks(aTxnName);
1094  else if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
1095  this._removeRowsFromHistory();
1096  else
1097  NS_ASSERT(false, "implement support for QUERY_TYPE_UNIFIED");
1098  }
1099  else
1100  NS_ASSERT(false, "unexpected root");
1101  },
1102 
1109  setDataTransfer: function PC_setDataTransfer(aEvent) {
1110  var dt = aEvent.dataTransfer;
1111  var doCopy = ["copyLink", "copy", "link"].indexOf(dt.effectAllowed) != -1;
1112 
1113  var result = this._view.getResult();
1114  var oldViewer = result.viewer;
1115  try {
1116  result.viewer = null;
1117  var nodes = this._view.getDraggableSelection();
1118 
1119  for (var i = 0; i < nodes.length; ++i) {
1120  var node = nodes[i];
1121 
1122  function addData(type, index, overrideURI) {
1123  var wrapNode = PlacesUtils.wrapNode(node, type, overrideURI, doCopy);
1124  dt.mozSetDataAt(type, wrapNode, index);
1125  }
1126 
1127  function addURIData(index, overrideURI) {
1128  addData(PlacesUtils.TYPE_X_MOZ_URL, index, overrideURI);
1129  addData(PlacesUtils.TYPE_UNICODE, index, overrideURI);
1130  addData(PlacesUtils.TYPE_HTML, index, overrideURI);
1131  }
1132 
1133  // This order is _important_! It controls how this and other
1134  // applications select data to be inserted based on type.
1135  addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
1136 
1137  // Drop the feed uri for livemark containers
1138  if (PlacesUtils.nodeIsLivemarkContainer(node))
1139  addURIData(i, PlacesUtils.livemarks.getFeedURI(node.itemId).spec);
1140  else if (node.uri)
1141  addURIData(i);
1142  }
1143  }
1144  finally {
1145  if (oldViewer)
1146  result.viewer = oldViewer;
1147  }
1148  },
1149 
1153  copy: function PC_copy() {
1154  var result = this._view.getResult();
1155  var oldViewer = result.viewer;
1156  try {
1157  result.viewer = null;
1158  var nodes = this._view.getSelectionNodes();
1159 
1160  var xferable = Cc["@mozilla.org/widget/transferable;1"].
1161  createInstance(Ci.nsITransferable);
1162  var foundFolder = false, foundLink = false;
1163  var copiedFolders = [];
1164  var placeString, mozURLString, htmlString, unicodeString;
1165  placeString = mozURLString = htmlString = unicodeString = "";
1166 
1167  for (var i = 0; i < nodes.length; ++i) {
1168  var node = nodes[i];
1169  if (this._shouldSkipNode(node, copiedFolders))
1170  continue;
1171  if (PlacesUtils.nodeIsFolder(node))
1172  copiedFolders.push(node);
1173 
1174  function generateChunk(type, overrideURI) {
1175  var suffix = i < (nodes.length - 1) ? NEWLINE : "";
1176  var uri = overrideURI;
1177 
1178  if (PlacesUtils.nodeIsLivemarkContainer(node))
1179  uri = PlacesUtils.livemarks.getFeedURI(node.itemId).spec
1180 
1181  mozURLString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_X_MOZ_URL,
1182  uri) + suffix);
1183  unicodeString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_UNICODE,
1184  uri) + suffix);
1185  htmlString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_HTML,
1186  uri) + suffix);
1187 
1188  var placeSuffix = i < (nodes.length - 1) ? "," : "";
1189  var resolveShortcuts = !PlacesControllerDragHelper.canMoveNode(node);
1190  return PlacesUtils.wrapNode(node, type, overrideURI, resolveShortcuts) + placeSuffix;
1191  }
1192 
1193  // all items wrapped as TYPE_X_MOZ_PLACE
1194  placeString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE);
1195  }
1196 
1197  function addData(type, data) {
1198  xferable.addDataFlavor(type);
1199  xferable.setTransferData(type, PlacesUIUtils._wrapString(data), data.length * 2);
1200  }
1201  // This order is _important_! It controls how this and other applications
1202  // select data to be inserted based on type.
1203  if (placeString)
1204  addData(PlacesUtils.TYPE_X_MOZ_PLACE, placeString);
1205  if (mozURLString)
1206  addData(PlacesUtils.TYPE_X_MOZ_URL, mozURLString);
1207  if (unicodeString)
1208  addData(PlacesUtils.TYPE_UNICODE, unicodeString);
1209  if (htmlString)
1210  addData(PlacesUtils.TYPE_HTML, htmlString);
1211 
1212  if (placeString || unicodeString || htmlString || mozURLString) {
1213  PlacesUIUtils.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
1214  }
1215  }
1216  finally {
1217  if (oldViewer)
1218  result.viewer = oldViewer;
1219  }
1220  },
1221 
1225  cut: function PC_cut() {
1226  this.copy();
1227  this.remove("Cut Selection");
1228  },
1229 
1233  paste: function PC_paste() {
1234  // Strategy:
1235  //
1236  // There can be data of various types (folder, separator, link) on the
1237  // clipboard. We need to get all of that data and build edit transactions
1238  // for them. This means asking the clipboard once for each type and
1239  // aggregating the results.
1240 
1248  function makeXferable(types) {
1249  var xferable =
1250  Cc["@mozilla.org/widget/transferable;1"].
1251  createInstance(Ci.nsITransferable);
1252  for (var i = 0; i < types.length; ++i)
1253  xferable.addDataFlavor(types[i]);
1254  return xferable;
1255  }
1256 
1257  var clipboard = PlacesUIUtils.clipboard;
1258 
1259  var ip = this._view.insertionPoint;
1260  if (!ip)
1261  throw Cr.NS_ERROR_NOT_AVAILABLE;
1262 
1269  function getTransactions(types) {
1270  var xferable = makeXferable(types);
1271  clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
1272 
1273  var data = { }, type = { };
1274  try {
1275  xferable.getAnyTransferData(type, data, { });
1276  data = data.value.QueryInterface(Ci.nsISupportsString).data;
1277  var items = PlacesUtils.unwrapNodes(data, type.value);
1278  var transactions = [];
1279  var index = ip.index;
1280  for (var i = 0; i < items.length; ++i) {
1281  var txn;
1282  if (ip.isTag) {
1283  var uri = PlacesUtils._uri(items[i].uri);
1284  txn = PlacesUIUtils.ptm.tagURI(uri, [ip.itemId]);
1285  }
1286  else {
1287  // adjusted to make sure that items are given the correct index
1288  // transactions insert differently if index == -1
1289  // transaction will enqueue the item.
1290  if (ip.index > -1)
1291  index = ip.index + i;
1292  txn = PlacesUIUtils.makeTransaction(items[i], type.value,
1293  ip.itemId, index, true);
1294  }
1295  transactions.push(txn);
1296  }
1297  return transactions;
1298  }
1299  catch (e) {
1300  // getAnyTransferData will throw if there is no data of the specified
1301  // type on the clipboard.
1302  // unwrapNodes will throw if the data that is present is malformed in
1303  // some way.
1304  // In either case, don't fail horribly, just return no data.
1305  }
1306  return [];
1307  }
1308 
1309  // Get transactions to paste any folders, separators or links that might
1310  // be on the clipboard, aggregate them and execute them.
1311  var transactions = getTransactions([PlacesUtils.TYPE_X_MOZ_PLACE,
1312  PlacesUtils.TYPE_X_MOZ_URL,
1313  PlacesUtils.TYPE_UNICODE]);
1314  var txn = PlacesUIUtils.ptm.aggregateTransactions("Paste", transactions);
1315  PlacesUIUtils.ptm.doTransaction(txn);
1316 
1317  // select the pasted items, they should be consecutive
1318  var insertedNodeIds = [];
1319  for (var i = 0; i < transactions.length; ++i)
1320  insertedNodeIds.push(PlacesUtils.bookmarks
1321  .getIdForItemAt(ip.itemId, ip.index + i));
1322  if (insertedNodeIds.length > 0)
1323  this._view.selectItems(insertedNodeIds, false);
1324  }
1325 };
1326 
1337  currentDropTarget: null,
1338 
1345  currentDataTransfer: null,
1346 
1356  draggingOverChildNode: function PCDH_draggingOverChildNode(node) {
1357  var currentNode = this.currentDropTarget;
1358  while (currentNode) {
1359  if (currentNode == node)
1360  return true;
1361  currentNode = currentNode.parentNode;
1362  }
1363  return false;
1364  },
1365 
1369  getSession: function PCDH__getSession() {
1370  var dragService = Cc["@mozilla.org/widget/dragservice;1"].
1371  getService(Ci.nsIDragService);
1372  return dragService.getCurrentSession();
1373  },
1374 
1380  getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) {
1381  for (var i = 0; i < aFlavors.length; i++) {
1382  if (this.GENERIC_VIEW_DROP_TYPES.indexOf(aFlavors[i]) != -1)
1383  return aFlavors[i];
1384  }
1385  return null;
1386  },
1387 
1394  canDrop: function PCDH_canDrop(ip) {
1395  var dt = this.currentDataTransfer;
1396  var dropCount = dt.mozItemCount;
1397 
1398  // Check every dragged item
1399  for (var i = 0; i < dropCount; i++) {
1400  var flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
1401  if (!flavor)
1402  return false;
1403 
1404  var data = dt.mozGetDataAt(flavor, i);
1405 
1406  // urls can be dropped on any insertionpoint
1407  // XXXmano: // Remember: this method is called for each dragover event!
1408  // Thus we shouldn't use unwrapNodes here at all if possible.
1409  // I think it would be OK to accept bogus data here (e.g. text which was
1410  // somehow wrapped as TAB_DROP_TYPE, this is not in our control, and
1411  // will just case the actual drop to be a no-op), and only rule out valid
1412  // expected cases, which are either unsupported flavors, or items which
1413  // cannot be dropped in the current insertionpoint. The last case will
1414  // likely force us to use unwrapNodes for the private data types of
1415  // places.
1416  if (flavor == TAB_DROP_TYPE)
1417  continue;
1418 
1419  try {
1420  var dragged = PlacesUtils.unwrapNodes(data, flavor)[0];
1421  } catch (e) {
1422  return false;
1423  }
1424 
1425  // Only bookmarks and urls can be dropped into tag containers
1426  if (ip.isTag && ip.orientation == Ci.nsITreeView.DROP_ON &&
1427  dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
1428  (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
1429  /^place:/.test(dragged.uri)))
1430  return false;
1431 
1432  // The following loop disallows the dropping of a folder on itself or
1433  // on any of its descendants.
1434  if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
1435  /^place:/.test(dragged.uri)) {
1436  var parentId = ip.itemId;
1437  while (parentId != PlacesUtils.placesRootId) {
1438  if (dragged.concreteId == parentId || dragged.id == parentId)
1439  return false;
1440  parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
1441  }
1442  }
1443  }
1444  return true;
1445  },
1446 
1447 
1455  canMoveNode:
1456  function PCDH_canMoveNode(aNode) {
1457  // can't move query root
1458  if (!aNode.parent)
1459  return false;
1460 
1461  var parentId = PlacesUtils.getConcreteItemId(aNode.parent);
1462  var concreteId = PlacesUtils.getConcreteItemId(aNode);
1463 
1464  // can't move children of tag containers
1465  if (PlacesUtils.nodeIsTagQuery(aNode.parent))
1466  return false;
1467 
1468  // can't move children of read-only containers
1469  if (PlacesUtils.nodeIsReadOnly(aNode.parent))
1470  return false;
1471 
1472  // check for special folders, etc
1473  if (PlacesUtils.nodeIsContainer(aNode) &&
1474  !this.canMoveContainer(aNode.itemId, parentId))
1475  return false;
1476 
1477  return true;
1478  },
1479 
1489  canMoveContainer:
1490  function PCDH_canMoveContainer(aId, aParentId) {
1491  if (aId == -1)
1492  return false;
1493 
1494  // Disallow moving of roots and special folders
1495  const ROOTS = [PlacesUtils.placesRootId, PlacesUtils.bookmarksMenuFolderId,
1496  PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
1497  PlacesUtils.toolbarFolderId];
1498  if (ROOTS.indexOf(aId) != -1)
1499  return false;
1500 
1501  // Get parent id if necessary
1502  if (aParentId == null || aParentId == -1)
1503  aParentId = PlacesUtils.bookmarks.getFolderIdForItem(aId);
1504 
1505  if (PlacesUtils.bookmarks.getFolderReadonly(aParentId))
1506  return false;
1507 
1508  return true;
1509  },
1510 
1516  onDrop: function PCDH_onDrop(insertionPoint) {
1517  var dt = this.currentDataTransfer;
1518  var doCopy = ["copy", "link"].indexOf(dt.dropEffect) != -1;
1519 
1520  var transactions = [];
1521  var dropCount = dt.mozItemCount;
1522  var movedCount = 0;
1523  for (var i = 0; i < dropCount; ++i) {
1524  var flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
1525  if (!flavor)
1526  return false;
1527 
1528  var data = dt.mozGetDataAt(flavor, i);
1529  var unwrapped;
1530  if (flavor != TAB_DROP_TYPE) {
1531  // There's only ever one in the D&D case.
1532  unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
1533  }
1534  else if (data instanceof XULElement && data.localName == "tab" &&
1535  data.ownerDocument.defaultView instanceof ChromeWindow) {
1536  var uri = data.linkedBrowser.currentURI;
1537  var spec = uri ? uri.spec : "about:blank";
1538  var title = data.label;
1539  unwrapped = { uri: spec,
1540  title: data.label,
1541  type: PlacesUtils.TYPE_X_MOZ_URL};
1542  }
1543  else
1544  throw("bogus data was passed as a tab")
1545 
1546  var index = insertionPoint.index;
1547 
1548  // Adjust insertion index to prevent reversal of dragged items. When you
1549  // drag multiple elts upward: need to increment index or each successive
1550  // elt will be inserted at the same index, each above the previous.
1551  var dragginUp = insertionPoint.itemId == unwrapped.parent &&
1552  index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
1553  if (index != -1 && dragginUp)
1554  index+= movedCount++;
1555 
1556  // if dragging over a tag container we should tag the item
1557  if (insertionPoint.isTag &&
1558  insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
1559  var uri = PlacesUtils._uri(unwrapped.uri);
1560  var tagItemId = insertionPoint.itemId;
1561  transactions.push(PlacesUIUtils.ptm.tagURI(uri,[tagItemId]));
1562  }
1563  else {
1564  transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
1565  flavor, insertionPoint.itemId,
1566  index, doCopy));
1567  }
1568  }
1569 
1570  var txn = PlacesUIUtils.ptm.aggregateTransactions("DropItems", transactions);
1571  PlacesUIUtils.ptm.doTransaction(txn);
1572  },
1573 
1579  disallowInsertion: function(aContainer) {
1580  NS_ASSERT(aContainer, "empty container");
1581  // allow dropping into Tag containers
1582  if (PlacesUtils.nodeIsTagQuery(aContainer))
1583  return false;
1584  // Disallow insertion of items under readonly folders
1585  return (!PlacesUtils.nodeIsFolder(aContainer) ||
1586  PlacesUtils.nodeIsReadOnly(aContainer));
1587  },
1588 
1589  placesFlavors: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
1590  PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
1591  PlacesUtils.TYPE_X_MOZ_PLACE],
1592 
1593  // The order matters.
1594  GENERIC_VIEW_DROP_TYPES: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
1595  PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
1596  PlacesUtils.TYPE_X_MOZ_PLACE,
1597  PlacesUtils.TYPE_X_MOZ_URL,
1598  TAB_DROP_TYPE,
1599  PlacesUtils.TYPE_UNICODE],
1600 
1604  get flavourSet() {
1605  delete this.flavourSet;
1606  var flavourSet = new FlavourSet();
1607  var acceptedDropFlavours = this.GENERIC_VIEW_DROP_TYPES;
1608  acceptedDropFlavours.forEach(flavourSet.appendFlavour, flavourSet);
1609  return this.flavourSet = flavourSet;
1610  }
1611 };
1612 
1614  // Get the controller for one of the places commands.
1615  var placesController = doGetPlacesControllerForCommand("placesCmd_open");
1616  if (!placesController)
1617  return;
1618 
1619  function updatePlacesCommand(aCommand) {
1620  goSetCommandEnabled(aCommand, placesController.isCommandEnabled(aCommand));
1621  }
1622 
1623  updatePlacesCommand("placesCmd_open");
1624  updatePlacesCommand("placesCmd_open:window");
1625  updatePlacesCommand("placesCmd_open:tab");
1626  updatePlacesCommand("placesCmd_new:folder");
1627  updatePlacesCommand("placesCmd_new:bookmark");
1628  updatePlacesCommand("placesCmd_new:livemark");
1629  updatePlacesCommand("placesCmd_new:separator");
1630  updatePlacesCommand("placesCmd_show:info");
1631  updatePlacesCommand("placesCmd_moveBookmarks");
1632  updatePlacesCommand("placesCmd_reload");
1633  updatePlacesCommand("placesCmd_reloadMicrosummary");
1634  updatePlacesCommand("placesCmd_sortBy:name");
1635  updatePlacesCommand("placesCmd_cut");
1636  updatePlacesCommand("placesCmd_copy");
1637  updatePlacesCommand("placesCmd_paste");
1638  updatePlacesCommand("placesCmd_delete");
1639 }
1640 
1642 {
1643  var placesController = top.document.commandDispatcher
1644  .getControllerForCommand(aCommand);
1645  if (!placesController) {
1646  // If building commands for a context menu, look for an element in the
1647  // current popup.
1648  var element = document.popupNode;
1649  while (element) {
1650  var isContextMenuShown = ("_contextMenuShown" in element) && element._contextMenuShown;
1651  // Check for the parent menupopup or the hbox used for toolbars
1652  if ((element.localName == "menupopup" || element.localName == "hbox") &&
1653  isContextMenuShown) {
1654  placesController = element.controllers.getControllerForCommand(aCommand);
1655  break;
1656  }
1657  element = element.parentNode;
1658  }
1659  }
1660 
1661  return placesController;
1662 }
1663 
1664 function goDoPlacesCommand(aCommand)
1665 {
1666  var controller = doGetPlacesControllerForCommand(aCommand);
1667  if (controller && controller.isCommandEnabled(aCommand))
1668  controller.doCommand(aCommand);
1669 }
1670 
var PlacesUIUtils
Definition: utils.js:85
var TAB_DROP_TYPE
function doGetPlacesControllerForCommand(aCommand)
Definition: controller.js:1641
const Cc
var pref
Definition: openLocation.js:44
const RELOAD_ACTION_REMOVE
Definition: controller.js:50
const REMOVE_PAGES_MAX_SINGLEREMOVES
Definition: controller.js:64
sidebarFactory createInstance
Definition: nsSidebar.js:351
function asQuery(aNode)
Definition: utils.js:83
const char * types
getService(Ci.sbIFaceplateManager)
function goUpdatePlacesCommands()
Definition: controller.js:1613
let window
const ORGANIZER_SUBSCRIPTIONS_QUERY
Definition: controller.js:43
var strings
Definition: Info.js:46
const REMOVE_PAGES_CHUNKLEN
Definition: controller.js:59
const RELOAD_ACTION_NOTHING
Definition: controller.js:46
function goDoPlacesCommand(aCommand)
Definition: controller.js:1664
function PlacesController(aView)
Definition: controller.js:113
const RELOAD_ACTION_INSERT
Definition: controller.js:48
this _dialogInput val(dateText)
const ORGANIZER_ROOT_BOOKMARKS
Definition: controller.js:42
return null
Definition: FeedWriter.js:1143
let node
const NEWLINE
Definition: utils.js:74
function NS_ASSERT(cond, msg)
Definition: httpd.js:70
const RELOAD_ACTION_MOVE
Definition: controller.js:53
var uri
Definition: FeedWriter.js:1135
countRef value
Definition: FeedWriter.js:1423
const Cr
let promptService
_setFaviconForWebReader aMenuItem
Definition: FeedWriter.js:1369
var PlacesControllerDragHelper
Definition: controller.js:1333
function onEvent(aEvent)
if(DEBUG_DATAREMOTES)
const Ci
dataSBGenres SBProperties tag
Definition: tuner2.js:871
observe data
Definition: FeedWriter.js:1329
function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag, aDropNearItemId)
Definition: controller.js:84
_getSelectedPageStyle s i
function range(x, y)
Definition: httpd.js:138