diff --git a/mozilla/toolkit/components/places/src/utils.js b/mozilla/toolkit/components/places/src/utils.js index cd9ad053b9e..2e8ba95cc09 100644 --- a/mozilla/toolkit/components/places/src/utils.js +++ b/mozilla/toolkit/components/places/src/utils.js @@ -1047,8 +1047,12 @@ var PlacesUtils = { }); var batch = { + _utils: this, nodes: nodes[0].children, runBatched: function restore_runBatched() { + var searchIds = []; + var folderIdMap = []; + this.nodes.forEach(function(node) { var root = node.root; // FIXME support folders other than known roots @@ -1079,23 +1083,34 @@ var PlacesUtils = { if (container != this.tagsFolderId) this.bookmarks.removeFolderChildren(container); else { - // remove tags via the tagging service - var tags = this.tagging.allTags; - var uris = []; - tags.forEach(function(aTag) { - var tagURIs = this.tagging.getURIsForTag(aTag); - for (let i in tagURIs) - this.tagging.untagURI(tagURIs[i], [aTag]); - }, this); + // remove tags via the tagging service + var tags = this.tagging.allTags; + var uris = []; + tags.forEach(function(aTag) { + var tagURIs = this.tagging.getURIsForTag(aTag); + for (let i in tagURIs) + this.tagging.untagURI(tagURIs[i], [aTag]); + }, this); } } // insert the data into the db node.children.forEach(function(child) { var index = child.index; - this.importJSONNode(child, container, index); + var [folders, searches] = this.importJSONNode(child, container, index); + folderIdMap = folderIdMap.concat(folders); + searchIds = searchIds.concat(searches); }, this); - }, PlacesUtils); + }, this._utils); + + // fixup imported place: uris that contain folders + searchIds.forEach(function(aId) { + var oldURI = this.bookmarks.getBookmarkURI(aId); + var uri = this._fixupQuery(this.bookmarks.getBookmarkURI(aId), + folderIdMap); + if (!uri.equals(oldURI)) + this.bookmarks.changeBookmarkURI(aId, uri); + }, this._utils); } }; @@ -1111,9 +1126,13 @@ var PlacesUtils = { * The container the data was dropped or pasted into * @param aIndex * The index within the container the item was dropped or pasted at + * @returns an array containing of maps of old folder ids to new folder ids, + * and an array of saved search ids that need to be fixed up. + * eg: [[[oldFolder1, newFolder1]], [search1]] */ importJSONNode: function PU_importJSONNode(aData, aContainer, aIndex) { - // create item + var folderIdMap = []; + var searchIds = []; var id = -1; switch (aData.type) { case this.TYPE_X_MOZ_PLACE_CONTAINER: @@ -1122,7 +1141,7 @@ var PlacesUtils = { aData.children.forEach(function(aChild) { this.tagging.tagURI(this._uri(aChild.uri), [aData.title]); }, this); - return; + return [folderIdMap, searchIds]; } } else if (aData.livemark && aData.annos) { @@ -1146,10 +1165,13 @@ var PlacesUtils = { } else { id = this.bookmarks.createFolder(aContainer, aData.title, aIndex); + folderIdMap.push([aData.id, id]); // process children if (aData.children) { aData.children.every(function(aChild, aIndex) { - this.importJSONNode(aChild, id, aIndex); + var [folderIds, searches] = this.importJSONNode(aChild, id, aIndex); + folderIdMap = folderIdMap.concat(folderIds); + searchIds = searchIds.concat(searches); return true; }, this); } @@ -1166,6 +1188,8 @@ var PlacesUtils = { } if (aData.charset) this.history.setCharsetForURI(this._uri(aData.uri), aData.charset); + if (aData.uri.match(/^place:/)) + searchIds.push(id); break; case this.TYPE_X_MOZ_PLACE_SEPARATOR: id = this.bookmarks.insertSeparator(aContainer, aIndex); @@ -1180,6 +1204,45 @@ var PlacesUtils = { if (aData.annos) this.setAnnotationsForItem(id, aData.annos); } + + return [folderIdMap, searchIds]; + }, + + /** + * Replaces imported folder ids with their local counterparts in a place: URI. + * + * @param aURI + * A place: URI with folder ids. + * @param aFolderIdMap + * An array mapping old folder id to new folder ids. + * @returns the fixed up URI if all matched. If some matched, it returns + * the URI with only the matching folders included. If none matched it + * returns the input URI unchanged. + */ + _fixupQuery: function PU__fixupQuery(aQueryURI, aFolderIdMap) { + var queries = {}; + var options = {}; + this.history.queryStringToQueries(aQueryURI.spec, queries, {}, options); + + var fixedQueries = []; + queries.value.forEach(function(aQuery) { + var folders = aQuery.getFolders({}); + + var newFolders = []; + aFolderIdMap.forEach(function(aMapping) { + if (folders.indexOf(aMapping[0]) != -1) + newFolders.push(aMapping[1]); + }); + + if (newFolders.length) + aQuery.setFolders(newFolders, newFolders.length); + fixedQueries.push(aQuery); + }); + + var stringURI = this.history.queriesToQueryString(fixedQueries, + fixedQueries.length, + options.value); + return this._uri(stringURI); }, /** @@ -1275,7 +1338,9 @@ var PlacesUtils = { (concreteId != aPlacesNode.itemId && !aResolveShortcuts))) { aJSNode.type = self.TYPE_X_MOZ_PLACE; aJSNode.uri = aPlacesNode.uri; - aJSNode.concreteId = concreteId; + // folder shortcut + if (aIsUICommand) + aJSNode.concreteId = concreteId; return; } else if (aJSNode.id != -1) { // bookmark folder diff --git a/mozilla/toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js b/mozilla/toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js new file mode 100644 index 00000000000..72d5efd3a1e --- /dev/null +++ b/mozilla/toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js @@ -0,0 +1,262 @@ +/* -*- 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 (Original Author) + * + * 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"); +var tests = []; + +/* + +Backup/restore tests example: + +var myTest = { + populate: function () { ... add bookmarks ... }, + validate: function () { ... query for your bookmarks ... } +} + +this.push(myTest); + +*/ + +/* + +test summary: +- create folders with content +- create a query bookmark for those folders +- backs up bookmarks +- restores bookmarks +- confirms that the query has the new ids for the same folders + +scenarios: +- 1 folder (folder shortcut) +- n folders (single query) +- n folders (multiple queries) + +*/ + +const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX; + +var test = { + _testRootId: null, + _testRootTitle: "test root", + _folderIds: [], + _bookmarkURIs: [], + _count: 3, + + populate: function populate() { + // folder to hold this test + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId); + this._testRootId = + PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId, + this._testRootTitle, DEFAULT_INDEX); + + // create test folders each with a bookmark + for (var i = 0; i < this._count; i++) { + var folderId = + PlacesUtils.bookmarks.createFolder(this._testRootId, "folder" + i, DEFAULT_INDEX); + this._folderIds.push(folderId) + + var bookmarkURI = uri("http://" + i); + PlacesUtils.bookmarks.insertBookmark(folderId, bookmarkURI, + DEFAULT_INDEX, "bookmark" + i); + this._bookmarkURIs.push(bookmarkURI); + } + + // create a query URI with 1 folder (ie: folder shortcut) + this._queryURI1 = uri("place:folder=" + this._folderIds[0] + "&queryType=1"); + this._queryTitle1 = "query1"; + PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI1, + DEFAULT_INDEX, this._queryTitle1); + + // create a query URI with _count folders + this._queryURI2 = uri("place:folder=" + this._folderIds.join("&folder=") + "&queryType=1"); + this._queryTitle2 = "query2"; + PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI2, + DEFAULT_INDEX, this._queryTitle2); + + // create a query URI with _count queries (each with a folder) + // first get a query object for each folder + var queries = this._folderIds.map(function(aFolderId) { + var query = PlacesUtils.history.getNewQuery(); + query.setFolders([aFolderId], 1); + return query; + }); + var options = PlacesUtils.history.getNewQueryOptions(); + options.queryType = options.QUERY_TYPE_BOOKMARKS; + this._queryURI3 = + uri(PlacesUtils.history.queriesToQueryString(queries, queries.length, options)); + this._queryTitle3 = "query3"; + PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI3, + DEFAULT_INDEX, this._queryTitle3); + }, + + clean: function () {}, + + validate: function validate() { + // Throw a wrench in the works by inserting some new bookmarks, + // ensuring folder ids won't be the same, when restoring. + for (var i = 0; i < 10; i++) { + PlacesUtils.bookmarks. + insertBookmark(PlacesUtils.bookmarksMenuFolderId, uri("http://aaaa"+i), DEFAULT_INDEX, ""); + } + + var toolbar = + PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId, + false, true).root; + do_check_true(toolbar.childCount, 1); + + var folderNode = toolbar.getChild(0); + do_check_eq(folderNode.type, folderNode.RESULT_TYPE_FOLDER); + do_check_eq(folderNode.title, this._testRootTitle); + folderNode.QueryInterface(Ci.nsINavHistoryQueryResultNode); + folderNode.containerOpen = true; + + // |_count| folders + the query node + do_check_eq(folderNode.childCount, this._count+3); + + for (var i = 0; i < this._count; i++) { + var subFolder = folderNode.getChild(i); + do_check_eq(subFolder.title, "folder"+i); + subFolder.QueryInterface(Ci.nsINavHistoryContainerResultNode); + subFolder.containerOpen = true; + do_check_eq(subFolder.childCount, 1); + var child = subFolder.getChild(0); + do_check_eq(child.title, "bookmark"+i); + do_check_true(uri(child.uri).equals(uri("http://" + i))) + } + + // validate folder shortcut + this.validateQueryNode1(folderNode.getChild(this._count)); + + // validate folders query + this.validateQueryNode2(folderNode.getChild(this._count + 1)); + + // validate multiple queries query + this.validateQueryNode3(folderNode.getChild(this._count + 2)); + + // clean up + folderNode.containerOpen = false; + toolbar.containerOpen = false; + }, + + validateQueryNode1: function validateQueryNode1(aNode) { + do_check_eq(aNode.title, this._queryTitle1); + do_check_true(PlacesUtils.nodeIsFolder(aNode)); + + aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); + aNode.containerOpen = true; + do_check_eq(aNode.childCount, 1); + var child = aNode.getChild(0); + do_check_true(uri(child.uri).equals(uri("http://0"))) + do_check_eq(child.title, "bookmark0") + aNode.containerOpen = false; + }, + + validateQueryNode2: function validateQueryNode2(aNode) { + do_check_eq(aNode.title, this._queryTitle2); + do_check_true(PlacesUtils.nodeIsQuery(aNode)); + + aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); + aNode.containerOpen = true; + do_check_eq(aNode.childCount, this._count); + for (var i = 0; i < aNode.childCount; i++) { + var child = aNode.getChild(i); + do_check_true(uri(child.uri).equals(uri("http://" + i))) + do_check_eq(child.title, "bookmark" + i) + } + aNode.containerOpen = false; + }, + + validateQueryNode3: function validateQueryNode3(aNode) { + do_check_eq(aNode.title, this._queryTitle3); + do_check_true(PlacesUtils.nodeIsQuery(aNode)); + + aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); + aNode.containerOpen = true; + do_check_eq(aNode.childCount, this._count); + for (var i = 0; i < aNode.childCount; i++) { + var child = aNode.getChild(i); + do_check_true(uri(child.uri).equals(uri("http://" + i))) + do_check_eq(child.title, "bookmark" + i) + } + aNode.containerOpen = false; + } +} +tests.push(test); + +function run_test() { + do_check_eq(typeof PlacesUtils, "object"); + + // make json file + var jsonFile = dirSvc.get("ProfD", Ci.nsILocalFile); + jsonFile.append("bookmarks.json"); + if (jsonFile.exists()) + jsonFile.remove(false); + jsonFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600); + if (!jsonFile.exists()) + do_throw("couldn't create file: bookmarks.exported.json"); + + // populate db + tests.forEach(function(aTest) { + aTest.populate(); + // sanity + aTest.validate(); + }); + + // export json to file + try { + PlacesUtils.backupBookmarksToFile(jsonFile); + } catch(ex) { do_throw("couldn't export to file: " + ex); } + + // clean + tests.forEach(function(aTest) { + aTest.clean(); + }); + + // restore json file + try { + PlacesUtils.restoreBookmarksFromJSONFile(jsonFile); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + + // validate + tests.forEach(function(aTest) { + aTest.validate(); + }); + + // clean up + jsonFile.remove(false); +}