diff --git a/browser/components/places/content/controller.js b/browser/components/places/content/controller.js
index cf6c0abeaee0..8455eb2c9257 100755
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -264,9 +264,9 @@ PlacesController.prototype = {
* is a policy decision that a removable item not be placed inside a non-
* removable item.
* @param aIsMoveCommand
- * True if thecommand for which this method is called only moves the
+ * True if the command for which this method is called only moves the
* selected items to another container, false otherwise.
- * @returns true if the there's a selection which has no nodes that cannot be removed,
+ * @returns true if all nodes in the selection can be removed,
* false otherwise.
*/
_hasRemovableSelection: function PC__hasRemovableSelection(aIsMoveCommand) {
@@ -278,17 +278,8 @@ PlacesController.prototype = {
if (nodes[i] == root)
return false;
- // Disallow removing shortcuts from the left pane
- var nodeItemId = nodes[i].itemId;
- if (PlacesUtils.annotations
- .itemHasAnnotation(nodeItemId, ORGANIZER_QUERY_ANNO))
- return false;
-
- // Disallow removing the toolbar, menu and unfiled-bookmarks folders
- if (!aIsMoveCommand &&
- (nodeItemId == PlacesUtils.toolbarFolderId ||
- nodeItemId == PlacesUtils.unfiledBookmarksFolderId ||
- nodeItemId == PlacesUtils.bookmarksMenuFolderId))
+ if (PlacesUtils.nodeIsFolder(nodes[i]) &&
+ !PlacesControllerDragHelper.canMoveContainerNode(nodes[i]))
return false;
// We don't call nodeIsReadOnly here, because nodeIsReadOnly means that
@@ -1006,6 +997,7 @@ PlacesController.prototype = {
* elsewhere.
*/
getTransferData: function PC_getTransferData(dragAction) {
+ var copy = dragAction == Ci.nsIDragService.DRAGDROP_ACTION_COPY;
var result = this._view.getResult();
var oldViewer = result.viewer;
try {
@@ -1025,7 +1017,7 @@ PlacesController.prototype = {
var data = new TransferData();
function addData(type, overrideURI) {
data.addDataForFlavour(type, PlacesUIUtils._wrapString(
- PlacesUtils.wrapNode(node, type, overrideURI)));
+ PlacesUtils.wrapNode(node, type, overrideURI, copy)));
}
function addURIData(overrideURI) {
@@ -1093,7 +1085,8 @@ PlacesController.prototype = {
uri) + suffix);
var placeSuffix = i < (nodes.length - 1) ? "," : "";
- return PlacesUtils.wrapNode(node, type, overrideURI) + placeSuffix;
+ var resolveShortcuts = !PlacesControllerDragHelper.canMoveContainerNode(node);
+ return PlacesUtils.wrapNode(node, type, overrideURI, resolveShortcuts) + placeSuffix;
}
// all items wrapped as TYPE_X_MOZ_PLACE
@@ -1317,6 +1310,71 @@ var PlacesControllerDragHelper = {
return true;
},
+ /**
+ * Determines if a container node can be moved.
+ *
+ * @param aNode
+ * A bookmark folder node.
+ * @param [optional] aInsertionPoint
+ * The insertion point of the drop target.
+ * @returns True if the container can be moved.
+ */
+ canMoveContainerNode:
+ function PCDH_canMoveContainerNode(aNode, aInsertionPoint) {
+ // can't move query root
+ if (!aNode.parent)
+ return false;
+
+ var targetId = aInsertionPoint ? aInsertionPoint.itemId : -1;
+ var parentId = PlacesUtils.getConcreteItemId(aNode.parent);
+ var concreteId = PlacesUtils.getConcreteItemId(aNode);
+
+ // can't move tag containers
+ if (PlacesUtils.nodeIsTagQuery(aNode))
+ return false;
+
+ // check is child of a read-only container
+ if (PlacesUtils.nodeIsReadOnly(aNode.parent))
+ return false;
+
+ // check for special folders, etc
+ if (!this.canMoveContainer(aNode.itemId, parentId))
+ return false;
+
+ return true;
+ },
+
+ /**
+ * Determines if a container node can be moved.
+ *
+ * @param aId
+ * A bookmark folder id.
+ * @param [optional] aParentId
+ * The parent id of the folder.
+ * @returns True if the container can be moved to the target.
+ */
+ canMoveContainer:
+ function PCDH_canMoveContainer(aId, aParentId) {
+ if (aId == -1)
+ return false;
+
+ // Disallow moving of roots and special folders
+ const ROOTS = [PlacesUtils.placesRootId, PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId];
+ if (ROOTS.indexOf(aId) != -1)
+ return false;
+
+ // Get parent id if necessary
+ if (aParentId == null || aParentId == -1)
+ aParentId = PlacesUtils.bookmarks.getFolderIdForItem(aId);
+
+ if(PlacesUtils.bookmarks.getFolderReadonly(aParentId))
+ return false;
+
+ return true;
+ },
+
/**
* Creates a Transferable object that can be filled with data of types
* supported by a view.
@@ -1343,6 +1401,8 @@ var PlacesControllerDragHelper = {
*/
onDrop: function PCDH_onDrop(insertionPoint) {
var session = this.getSession();
+ // XXX dragAction is not valid, so we also set copy below by checking
+ // whether the dropped item is moveable, before creating the transaction
var copy = session.dragAction & Ci.nsIDragService.DRAGDROP_ACTION_COPY;
var transactions = [];
var xferable = this._initTransferable(session);
@@ -1360,13 +1420,13 @@ var PlacesControllerDragHelper = {
// There's only ever one in the D&D case.
var unwrapped = PlacesUtils.unwrapNodes(data.value.data,
flavor.value)[0];
+
var index = insertionPoint.index;
// Adjust insertion index to prevent reversal of dragged items. When you
// drag multiple elts upward: need to increment index or each successive
// elt will be inserted at the same index, each above the previous.
- if ((index != -1) && ((index < unwrapped.index) ||
- (unwrapped.folder && (index < unwrapped.folder.index)))) {
+ if (index != -1 && index < unwrapped.index) {
index = index + movedCount;
movedCount++;
}
@@ -1378,6 +1438,12 @@ var PlacesControllerDragHelper = {
transactions.push(PlacesUIUtils.ptm.tagURI(uri,[tagItemId]));
}
else {
+ if (!this.canMoveContainer(unwrapped.id, null))
+ copy = true;
+ else if (unwrapped.concreteId &&
+ !this.canMoveContainer(unwrapped.concreteId, null))
+ copy = true;
+
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
flavor.value, insertionPoint.itemId,
index, copy));
diff --git a/browser/components/places/content/menu.xml b/browser/components/places/content/menu.xml
index c87988047880..ead46e5363f4 100755
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -189,9 +189,10 @@
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function test() {
+ // sanity check
+ ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
+ ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?");
+ ok(PlacesControllerDragHelper, "checking PlacesControllerDragHelper, running in chrome context?");
+
+ const IDX = PlacesUtils.bookmarks.DEFAULT_INDEX;
+
+ // setup
+ var rootId = PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId, "", IDX);
+ var rootNode = PlacesUtils.getFolderContents(rootId, false, true).root;
+ is(rootNode.childCount, 0, "confirm test root is empty");
+
+ var tests = [];
+
+ // add a regular folder, should be moveable
+ tests.push({
+ populate: function() {
+ this.id =
+ PlacesUtils.bookmarks.createFolder(rootId, "", IDX);
+ },
+ validate: function() {
+ is(rootNode.childCount, 1,
+ "populate added data to the test root");
+ is(PlacesControllerDragHelper.canMoveContainer(this.id),
+ true, "can move regular folder id");
+ is(PlacesControllerDragHelper.canMoveContainerNode(rootNode.getChild(0)),
+ true, "can move regular folder node");
+ }
+ });
+
+ // add a regular folder shortcut, should be moveable
+ tests.push({
+ populate: function() {
+ this.folderId =
+ PlacesUtils.bookmarks.createFolder(rootId, "foo", IDX);
+ this.shortcutId =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:folder="+this.folderId), IDX, "bar");
+ },
+ validate: function() {
+ is(rootNode.childCount, 2,
+ "populated data to the test root");
+
+ var folderNode = rootNode.getChild(0);
+ is(folderNode.type, 6, "node is folder");
+ is(this.folderId, folderNode.itemId, "folder id and folder node item id match");
+
+ var shortcutNode = rootNode.getChild(1);
+ is(shortcutNode.type, 9, "node is folder shortcut");
+ is(this.shortcutId, shortcutNode.itemId, "shortcut id and shortcut node item id match");
+
+ var concreteId = PlacesUtils.getConcreteItemId(shortcutNode);
+ is(concreteId, folderNode.itemId, "shortcut node id and concrete id match");
+
+ is(PlacesControllerDragHelper.canMoveContainer(this.shortcutId),
+ true, "can move folder shortcut id");
+
+ is(PlacesControllerDragHelper.canMoveContainerNode(shortcutNode),
+ true, "can move folder shortcut node");
+ }
+ });
+
+ // add a regular query, should be moveable
+ tests.push({
+ populate: function() {
+ this.bookmarkId =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("http://foo.com"), IDX, "foo");
+ this.queryId =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:terms=foo"), IDX, "bar");
+ },
+ validate: function() {
+ is(rootNode.childCount, 2,
+ "populated data to the test root");
+
+ var bmNode = rootNode.getChild(0);
+ is(bmNode.itemId, this.bookmarkId, "bookmark id and bookmark node item id match");
+
+ var queryNode = rootNode.getChild(1);
+ is(queryNode.itemId, this.queryId, "query id and query node item id match");
+
+ is(PlacesControllerDragHelper.canMoveContainer(this.queryId),
+ true, "can move query id");
+
+ is(PlacesControllerDragHelper.canMoveContainerNode(queryNode),
+ true, "can move query node");
+ }
+ });
+
+ // test that special folders cannot be moved
+ // test that special folders shortcuts can be moved
+ tests.push({
+ folders: [PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId],
+ shortcuts: {},
+ populate: function() {
+ for (var i = 0; i < this.folders.length; i++) {
+ var id = this.folders[i];
+ this.shortcuts[id] =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:folder=" + id), IDX, "");
+ }
+ },
+ validate: function() {
+ // test toolbar shortcut node
+ is(rootNode.childCount, this.folders.length,
+ "populated data to the test root");
+
+ function getRootChildNode(aId) {
+ var node = PlacesUtils.getFolderContents(PlacesUtils.placesRootId, false, true).root;
+ for (var i = 0; i < node.childCount; i++) {
+ var child = node.getChild(i);
+ if (child.itemId == aId)
+ return child;
+ }
+ }
+
+ for (var i = 0; i < this.folders.length; i++) {
+ var id = this.folders[i];
+
+ is(PlacesControllerDragHelper.canMoveContainer(id),
+ false, "shouldn't be able to move special folder id");
+
+ //var node = PlacesUtils.getFolderContents(id, false, true).root;
+ var node = getRootChildNode(id);
+ is(PlacesControllerDragHelper.canMoveContainerNode(node),
+ false, "shouldn't be able to move special folder node");
+
+ var shortcutId = this.shortcuts[id];
+ var shortcutNode = rootNode.getChild(i);
+
+ is(shortcutNode.itemId, shortcutId, "shortcut id and shortcut node item id match");
+
+ LOG("can move shortcut id?");
+ is(PlacesControllerDragHelper.canMoveContainer(shortcutId),
+ true, "should be able to move special folder shortcut id");
+
+ LOG("can move shortcut node?");
+ is(PlacesControllerDragHelper.canMoveContainerNode(shortcutNode),
+ true, "should be able to move special folder shortcut node");
+ }
+ }
+ });
+
+ // test that a tag container cannot be moved
+ tests.push({
+ populate: function() {
+ // tag a uri
+ this.uri = makeURI("http://foo.com");
+ PlacesUtils.tagging.tagURI(this.uri, ["bar"]);
+ },
+ validate: function() {
+ // get tag root
+ var query = PlacesUtils.history.getNewQuery();
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY;
+ var tagsNode = PlacesUtils.history.executeQuery(query, options).root;
+
+ tagsNode.containerOpen = true;
+ is(tagsNode.childCount, 1, "has new tag");
+
+ var tagNode = tagsNode.getChild(0);
+
+ is(PlacesControllerDragHelper.canMoveContainerNode(tagNode),
+ false, "should not be able to move tag container node");
+ }
+ });
+
+ // test that any child of a read-only node cannot be moved
+ tests.push({
+ populate: function() {
+ this.id =
+ PlacesUtils.bookmarks.createFolder(rootId, "foo", IDX);
+ PlacesUtils.bookmarks.createFolder(this.id, "bar", IDX);
+ PlacesUtils.bookmarks.setFolderReadonly(this.id, true);
+ },
+ validate: function() {
+ is(rootNode.childCount, 1,
+ "populate added data to the test root");
+ var readOnlyFolder = rootNode.getChild(0);
+
+ // test that we can move the read-only folder
+ is(PlacesControllerDragHelper.canMoveContainer(this.id),
+ true, "can move read-only folder id");
+ is(PlacesControllerDragHelper.canMoveContainerNode(readOnlyFolder),
+ true, "can move read-only folder node");
+
+ // test that we cannot move the child of a read-only folder
+ readOnlyFolder.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ readOnlyFolder.containerOpen = true;
+ var childFolder = readOnlyFolder.getChild(0);
+
+ is(PlacesControllerDragHelper.canMoveContainer(childFolder.itemId),
+ false, "cannot move a child of a read-only folder");
+ is(PlacesControllerDragHelper.canMoveContainerNode(childFolder),
+ false, "cannot move a child node of a read-only folder node");
+ }
+ });
+
+ tests.forEach(function(aTest) {
+ PlacesUtils.bookmarks.removeFolderChildren(rootId);
+ aTest.populate();
+ aTest.validate();
+ });
+
+ PlacesUtils.bookmarks.removeItem(rootId);
+}
diff --git a/toolkit/components/places/src/utils.js b/toolkit/components/places/src/utils.js
index 976c46842aa0..cd9ad053b9e8 100644
--- a/toolkit/components/places/src/utils.js
+++ b/toolkit/components/places/src/utils.js
@@ -50,6 +50,7 @@ var Cc = Components.classes;
var Cr = Components.results;
const POST_DATA_ANNO = "bookmarkProperties/POSTData";
+const READ_ONLY_ANNO = "placesInternal/READ_ONLY";
const LMANNO_FEEDURI = "livemark/feedURI";
const LMANNO_SITEURI = "livemark/siteURI";
@@ -439,9 +440,11 @@ var PlacesUtils = {
* Used instead of the node's URI if provided.
* This is useful for wrapping a container as TYPE_X_MOZ_URL,
* TYPE_HTML or TYPE_UNICODE.
+ * @param aForceCopy
+ * Does a full copy, resolving folder shortcuts.
* @returns A string serialization of the node
*/
- wrapNode: function PU_wrapNode(aNode, aType, aOverrideURI) {
+ wrapNode: function PU_wrapNode(aNode, aType, aOverrideURI, aForceCopy) {
var self = this;
// when wrapping a node, we want all the items, even if the original
@@ -449,8 +452,10 @@ var PlacesUtils = {
// this can happen when copying from the left hand pane of the bookmarks
// organizer
function convertNode(cNode) {
- if (self.nodeIsFolder(cNode) && asQuery(cNode).queryOptions.excludeItems)
- return self.getFolderContents(cNode.itemId, false, true).root;
+ if (self.nodeIsFolder(cNode) && asQuery(cNode).queryOptions.excludeItems) {
+ var concreteId = self.getConcreteItemId(cNode);
+ return self.getFolderContents(concreteId, false, true).root;
+ }
return cNode;
}
@@ -464,7 +469,7 @@ var PlacesUtils = {
this.value += aStr;
}
};
- self.serializeNodeAsJSONToOutputStream(convertNode(aNode), writer, true);
+ self.serializeNodeAsJSONToOutputStream(convertNode(aNode), writer, true, aForceCopy);
return writer.value;
case this.TYPE_X_MOZ_URL:
function gatherDataUrl(bNode) {
@@ -490,7 +495,7 @@ var PlacesUtils = {
return s;
}
// escape out potential HTML in the title
- var escapedTitle = htmlEscape(bNode.title);
+ var escapedTitle = bNode.title ? htmlEscape(bNode.title) : "";
if (self.nodeIsLivemarkContainer(bNode)) {
var siteURI = self.livemarks.getSiteURI(bNode.itemId).spec;
return "" + escapedTitle + "" + NEWLINE;
@@ -1190,9 +1195,11 @@ var PlacesUtils = {
* @param aIsUICommand
* Boolean - If true, modifies serialization so that each node self-contained.
* For Example, tags are serialized inline with each bookmark.
+ * @param aResolveShortcuts
+ * Converts folder shortcuts into actual folders.
*/
serializeNodeAsJSONToOutputStream:
- function PU_serializeNodeAsJSONToOutputStream(aNode, aStream, aIsUICommand) {
+ function PU_serializeNodeAsJSONToOutputStream(aNode, aStream, aIsUICommand, aResolveShortcuts) {
var self = this;
function addGenericProperties(aPlacesNode, aJSNode) {
@@ -1219,8 +1226,12 @@ var PlacesUtils = {
// backup/restore of non-whitelisted annos
// XXX causes JSON encoding errors, so utf-8 encode
//anno.value = unescape(encodeURIComponent(anno.value));
- if (anno.name == "livemark/feedURI")
+ if (anno.name == LMANNO_FEEDURI)
aJSNode.livemark = 1;
+ if (anno.name == READ_ONLY_ANNO && aResolveShortcuts) {
+ // When copying a read-only node, remove the read-only annotation.
+ return false;
+ }
return anno.name != "placesInternal/GUID";
});
} catch(ex) {
@@ -1259,14 +1270,17 @@ var PlacesUtils = {
function addContainerProperties(aPlacesNode, aJSNode) {
// saved queries
- if (aJSNode.id != -1 &&
- self.bookmarks.getItemType(aJSNode.id) == self.bookmarks.TYPE_BOOKMARK) {
+ var concreteId = PlacesUtils.getConcreteItemId(aPlacesNode);
+ if (aJSNode.id != -1 && (PlacesUtils.nodeIsQuery(aPlacesNode) ||
+ (concreteId != aPlacesNode.itemId && !aResolveShortcuts))) {
aJSNode.type = self.TYPE_X_MOZ_PLACE;
aJSNode.uri = aPlacesNode.uri;
- aJSNode.concreteId = PlacesUtils.getConcreteItemId(aPlacesNode);
+ aJSNode.concreteId = concreteId;
return;
}
else if (aJSNode.id != -1) { // bookmark folder
+ if (concreteId != aPlacesNode.itemId)
+ aJSNode.type = self.TYPE_X_MOZ_PLACE;
aJSNode.type = self.TYPE_X_MOZ_PLACE_CONTAINER;
// mark special folders
if (aJSNode.id == self.bookmarks.placesRoot)
diff --git a/toolkit/components/places/tests/bookmarks/test_423515_forceCopyShortcuts.js b/toolkit/components/places/tests/bookmarks/test_423515_forceCopyShortcuts.js
new file mode 100644
index 000000000000..106303f027f7
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_423515_forceCopyShortcuts.js
@@ -0,0 +1,147 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bug 384370 code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Corp.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dietrich Ayala
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource://gre/modules/utils.js");
+
+const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX;
+
+function run_test() {
+ /*
+ - create folder A
+ - add a bookmark to it
+ - create a bookmark that's a place: folder shortcut to the new folder
+ - serialize it to JSON, forcing copy
+ - import JSON
+ - confirm that the newly imported folder is a full copy and not a shortcut
+ */
+
+ var folderA =
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
+ "test folder", DEFAULT_INDEX);
+ var bookmarkURI = uri("http://test");
+ PlacesUtils.bookmarks.insertBookmark(folderA, bookmarkURI,
+ DEFAULT_INDEX, "");
+
+ // create the query
+ var queryURI = uri("place:folder=" + folderA);
+ var queryTitle = "test query";
+ var queryId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
+ queryURI, DEFAULT_INDEX, queryTitle);
+ LOG("queryId: " + queryId);
+
+ // create a query that's *not* a folder shortcut
+ var queryURI2 = uri("place:");
+ var queryTitle2 = "non-folder test query";
+ var queryId2 =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
+ queryURI2, DEFAULT_INDEX, queryTitle2);
+
+ // check default state
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.toolbarFolderId], 1);
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.expandQueries = true;
+ var result = PlacesUtils.history.executeQuery(query, options);
+ var root = result.root;
+ root.containerOpen = true;
+
+ // check folder query node
+ var queryNode = root.getChild(root.childCount-2);
+ do_check_eq(queryNode.type, queryNode.RESULT_TYPE_FOLDER_SHORTCUT);
+ do_check_eq(queryNode.title, queryTitle);
+ do_check_true(queryURI.equals(uri(queryNode.uri)));
+ queryNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ queryNode.containerOpen = true;
+ do_check_eq(queryNode.childCount, 1);
+ var bookmark = queryNode.getChild(0);
+ do_check_true(bookmarkURI.equals(uri(bookmark.uri)));
+ queryNode.containerOpen = false;
+
+ // check non-folder query node
+ var queryNode2 = root.getChild(root.childCount-1);
+ do_check_eq(queryNode2.type, queryNode2.RESULT_TYPE_QUERY);
+ do_check_eq(queryNode2.title, queryTitle2);
+ do_check_true(queryURI2.equals(uri(queryNode2.uri)));
+ queryNode2.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ queryNode2.containerOpen = true;
+ do_check_eq(queryNode2.childCount, 0);
+ queryNode2.containerOpen = false;
+
+ // clean up
+ root.containerOpen = false;
+
+ // serialize
+ var stream = {
+ _str: "",
+ write: function(aData, aLen) {
+ this._str += aData;
+ }
+ };
+ PlacesUtils.serializeNodeAsJSONToOutputStream(queryNode, stream, false, true);
+
+ LOG("SERIALIZED: " + stream._str);
+
+ PlacesUtils.bookmarks.removeItem(queryId);
+
+ // import
+ PlacesUtils.importJSONNode(stream._str, PlacesUtils.toolbarFolderId, -1);
+
+ // query for node
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.toolbarFolderId], 1);
+ var options = PlacesUtils.history.getNewQueryOptions();
+ var result = PlacesUtils.history.executeQuery(query, options);
+ var root = result.root;
+ root.containerOpen = true;
+
+ // check folder node (no longer shortcut)
+ var queryNode = root.getChild(root.childCount-2);
+ do_check_eq(queryNode.type, queryNode.RESULT_TYPE_FOLDER);
+ queryNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ queryNode.containerOpen = true;
+ do_check_eq(queryNode.childCount, 1);
+ var child = queryNode.getChild(0);
+ do_check_true(bookmarkURI.equals(uri(child.uri)));
+
+ var queryNode2 = root.getChild(root.childCount-1);
+ do_check_eq(queryNode2.type, queryNode2.RESULT_TYPE_QUERY);
+ queryNode2.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ queryNode2.containerOpen = true;
+ do_check_eq(queryNode2.childCount, 0);
+}