зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 4c465fe2d7c9 (bug 1095426)
This commit is contained in:
Родитель
57a332bfa6
Коммит
7ca69ab78f
|
@ -11,11 +11,11 @@ const Cr = Components.results;
|
|||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
Cu.import("resource://gre/modules/PromiseUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
|
||||
"resource://gre/modules/PlacesBackups.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
|
@ -243,85 +243,325 @@ BookmarkImporter.prototype = {
|
|||
/**
|
||||
* Import bookmarks from a JSON string.
|
||||
*
|
||||
* @param {String} aString JSON string of serialized bookmark data.
|
||||
* @return {Promise}
|
||||
* @resolves When the new bookmarks have been created.
|
||||
* @rejects JavaScript exception.
|
||||
* @param aString
|
||||
* JSON string of serialized bookmark data.
|
||||
*/
|
||||
async importFromJSON(aString) {
|
||||
this._importPromises = [];
|
||||
let deferred = PromiseUtils.defer();
|
||||
let nodes =
|
||||
PlacesUtils.unwrapNodes(aString, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
|
||||
|
||||
if (nodes.length == 0 || !nodes[0].children ||
|
||||
nodes[0].children.length == 0) {
|
||||
return;
|
||||
deferred.resolve(); // Nothing to restore
|
||||
} else {
|
||||
// Ensure tag folder gets processed last
|
||||
nodes[0].children.sort(function sortRoots(aNode, bNode) {
|
||||
if (aNode.root && aNode.root == "tagsFolder")
|
||||
return 1;
|
||||
if (bNode.root && bNode.root == "tagsFolder")
|
||||
return -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
let batch = {
|
||||
nodes: nodes[0].children,
|
||||
runBatched: () => {
|
||||
if (this._replace) {
|
||||
// Get roots excluded from the backup, we will not remove them
|
||||
// before restoring.
|
||||
let excludeItems = PlacesUtils.annotations.getItemsWithAnnotation(
|
||||
PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
|
||||
// Delete existing children of the root node, excepting:
|
||||
// 1. special folders: delete the child nodes
|
||||
// 2. tags folder: untag via the tagging api
|
||||
let root = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
|
||||
false, false).root;
|
||||
let childIds = [];
|
||||
for (let i = 0; i < root.childCount; i++) {
|
||||
let childId = root.getChild(i).itemId;
|
||||
if (!excludeItems.includes(childId) &&
|
||||
childId != PlacesUtils.tagsFolderId) {
|
||||
childIds.push(childId);
|
||||
}
|
||||
}
|
||||
root.containerOpen = false;
|
||||
|
||||
for (let i = 0; i < childIds.length; i++) {
|
||||
let rootItemId = childIds[i];
|
||||
if (PlacesUtils.isRootItem(rootItemId)) {
|
||||
PlacesUtils.bookmarks.removeFolderChildren(rootItemId,
|
||||
this._source);
|
||||
} else {
|
||||
PlacesUtils.bookmarks.removeItem(rootItemId, this._source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let searchIds = [];
|
||||
let folderIdMap = [];
|
||||
|
||||
for (let node of batch.nodes) {
|
||||
if (!node.children || node.children.length == 0)
|
||||
continue; // Nothing to restore for this root
|
||||
|
||||
if (node.root) {
|
||||
let container = PlacesUtils.placesRootId; // Default to places root
|
||||
switch (node.root) {
|
||||
case "bookmarksMenuFolder":
|
||||
container = PlacesUtils.bookmarksMenuFolderId;
|
||||
break;
|
||||
case "tagsFolder":
|
||||
container = PlacesUtils.tagsFolderId;
|
||||
break;
|
||||
case "unfiledBookmarksFolder":
|
||||
container = PlacesUtils.unfiledBookmarksFolderId;
|
||||
break;
|
||||
case "toolbarFolder":
|
||||
container = PlacesUtils.toolbarFolderId;
|
||||
break;
|
||||
case "mobileFolder":
|
||||
container = PlacesUtils.mobileFolderId;
|
||||
break;
|
||||
}
|
||||
|
||||
// Insert the data into the db
|
||||
for (let child of node.children) {
|
||||
let index = child.index;
|
||||
let [folders, searches] =
|
||||
this.importJSONNode(child, container, index, 0);
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
if (folders[i])
|
||||
folderIdMap[i] = folders[i];
|
||||
}
|
||||
searchIds = searchIds.concat(searches);
|
||||
}
|
||||
} else {
|
||||
let [folders, searches] = this.importJSONNode(
|
||||
node, PlacesUtils.placesRootId, node.index, 0);
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
if (folders[i])
|
||||
folderIdMap[i] = folders[i];
|
||||
}
|
||||
searchIds = searchIds.concat(searches);
|
||||
}
|
||||
}
|
||||
|
||||
// Fixup imported place: uris that contain folders
|
||||
for (let id of searchIds) {
|
||||
let oldURI = PlacesUtils.bookmarks.getBookmarkURI(id);
|
||||
let uri = fixupQuery(oldURI, folderIdMap);
|
||||
if (!uri.equals(oldURI)) {
|
||||
PlacesUtils.bookmarks.changeBookmarkURI(id, uri, this._source);
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
PlacesUtils.bookmarks.runInBatchMode(batch, null);
|
||||
}
|
||||
|
||||
// Change to nodes[0].children as we don't import the root, and also filter
|
||||
// out any obsolete "tagsFolder" sections.
|
||||
nodes = nodes[0].children.filter(node => !node.root || node.root != "tagsFolder");
|
||||
|
||||
// If we're replacing, then erase existing bookmarks first.
|
||||
if (this._replace) {
|
||||
await PlacesBackups.eraseEverythingIncludingUserRoots({ source: this._source });
|
||||
}
|
||||
|
||||
let folderIdToGuidMap = {};
|
||||
let searchGuids = [];
|
||||
|
||||
// Now do some cleanup on the imported nodes so that the various guids
|
||||
// match what we need for insertTree, and we also have mappings of folders
|
||||
// so we can repair any searches after inserting the bookmarks (see bug 824502).
|
||||
for (let node of nodes) {
|
||||
if (!node.children || node.children.length == 0)
|
||||
continue; // Nothing to restore for this root
|
||||
|
||||
// Ensure we set the source correctly.
|
||||
node.source = this._source;
|
||||
|
||||
// Translate the node for insertTree.
|
||||
let [folders, searches] = translateTreeTypes(node);
|
||||
|
||||
folderIdToGuidMap = Object.assign(folderIdToGuidMap, folders);
|
||||
searchGuids = searchGuids.concat(searches);
|
||||
}
|
||||
|
||||
// Now we can add the actual nodes to the database.
|
||||
for (let node of nodes) {
|
||||
// Drop any nodes without children, we can't insert them.
|
||||
if (!node.children || node.children.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Places is moving away from supporting user-defined folders at the top
|
||||
// of the tree, however, until we have a migration strategy we need to
|
||||
// ensure any non-built-in folders are created (xref bug 1310299).
|
||||
if (!PlacesUtils.bookmarks.userContentRoots.includes(node.guid)) {
|
||||
node.parentGuid = PlacesUtils.bookmarks.rootGuid;
|
||||
await PlacesUtils.bookmarks.insert(node);
|
||||
}
|
||||
|
||||
await PlacesUtils.bookmarks.insertTree(node);
|
||||
|
||||
// Now add any favicons.
|
||||
try {
|
||||
insertFaviconsForTree(node);
|
||||
} catch (ex) {
|
||||
Cu.reportError(`Failed to insert favicons: ${ex}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Now update any bookmarks with a place: search that contain an index to
|
||||
// a folder id.
|
||||
for (let guid of searchGuids) {
|
||||
let searchBookmark = await PlacesUtils.bookmarks.fetch(guid);
|
||||
let url = await fixupQuery(searchBookmark.url, folderIdToGuidMap);
|
||||
if (url != searchBookmark.url) {
|
||||
await PlacesUtils.bookmarks.update({ guid, url, source: this._source });
|
||||
}
|
||||
await deferred.promise;
|
||||
// TODO (bug 1095426) once converted to the new bookmarks API, methods will
|
||||
// yield, so this hack should not be needed anymore.
|
||||
try {
|
||||
await Promise.all(this._importPromises);
|
||||
} finally {
|
||||
delete this._importPromises;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a JSON-serialized node and inserts it into the db.
|
||||
*
|
||||
* @param aData
|
||||
* The unwrapped data blob of dropped or pasted data.
|
||||
* @param aContainer
|
||||
* The container the data was dropped or pasted into
|
||||
* @param aIndex
|
||||
* The index within the container the item was dropped or pasted at
|
||||
* @return 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 BI_importJSONNode(aData, aContainer, aIndex,
|
||||
aGrandParentId) {
|
||||
let folderIdMap = [];
|
||||
let searchIds = [];
|
||||
let id = -1;
|
||||
switch (aData.type) {
|
||||
case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
if (aContainer == PlacesUtils.tagsFolderId) {
|
||||
// Node is a tag
|
||||
if (aData.children) {
|
||||
for (let child of aData.children) {
|
||||
try {
|
||||
PlacesUtils.tagging.tagURI(
|
||||
NetUtil.newURI(child.uri), [aData.title], this._source);
|
||||
} catch (ex) {
|
||||
// Invalid tag child, skip it
|
||||
}
|
||||
}
|
||||
return [folderIdMap, searchIds];
|
||||
}
|
||||
} else if (aData.annos &&
|
||||
aData.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
|
||||
// Node is a livemark
|
||||
let feedURI = null;
|
||||
let siteURI = null;
|
||||
aData.annos = aData.annos.filter(function(aAnno) {
|
||||
switch (aAnno.name) {
|
||||
case PlacesUtils.LMANNO_FEEDURI:
|
||||
feedURI = NetUtil.newURI(aAnno.value);
|
||||
return false;
|
||||
case PlacesUtils.LMANNO_SITEURI:
|
||||
siteURI = NetUtil.newURI(aAnno.value);
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (feedURI) {
|
||||
let lmPromise = PlacesUtils.livemarks.addLivemark({
|
||||
title: aData.title,
|
||||
feedURI,
|
||||
parentId: aContainer,
|
||||
index: aIndex,
|
||||
lastModified: aData.lastModified,
|
||||
siteURI,
|
||||
guid: aData.guid,
|
||||
source: this._source
|
||||
}).then(aLivemark => {
|
||||
let id = aLivemark.id;
|
||||
if (aData.dateAdded)
|
||||
PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded,
|
||||
this._source);
|
||||
if (aData.annos && aData.annos.length)
|
||||
PlacesUtils.setAnnotationsForItem(id, aData.annos,
|
||||
this._source);
|
||||
});
|
||||
this._importPromises.push(lmPromise);
|
||||
}
|
||||
} else {
|
||||
let isMobileFolder = aData.annos &&
|
||||
aData.annos.some(anno => anno.name == PlacesUtils.MOBILE_ROOT_ANNO);
|
||||
if (isMobileFolder) {
|
||||
// Mobile bookmark folders are special: we move their children to
|
||||
// the mobile root instead of importing them. We also rewrite
|
||||
// queries to use the special folder ID, and ignore generic
|
||||
// properties like timestamps and annotations set on the folder.
|
||||
id = PlacesUtils.mobileFolderId;
|
||||
} else {
|
||||
// For other folders, set `id` so that we can import timestamps
|
||||
// and annotations at the end of this function.
|
||||
id = PlacesUtils.bookmarks.createFolder(
|
||||
aContainer, aData.title, aIndex, aData.guid, this._source);
|
||||
}
|
||||
folderIdMap[aData.id] = id;
|
||||
// Process children
|
||||
if (aData.children) {
|
||||
for (let i = 0; i < aData.children.length; i++) {
|
||||
let child = aData.children[i];
|
||||
let [folders, searches] =
|
||||
this.importJSONNode(child, id, i, aContainer);
|
||||
for (let j = 0; j < folders.length; j++) {
|
||||
if (folders[j])
|
||||
folderIdMap[j] = folders[j];
|
||||
}
|
||||
searchIds = searchIds.concat(searches);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PlacesUtils.TYPE_X_MOZ_PLACE:
|
||||
id = PlacesUtils.bookmarks.insertBookmark(
|
||||
aContainer, NetUtil.newURI(aData.uri), aIndex, aData.title, aData.guid, this._source);
|
||||
if (aData.keyword) {
|
||||
// POST data could be set in 2 ways:
|
||||
// 1. new backups have a postData property
|
||||
// 2. old backups have an item annotation
|
||||
let postDataAnno = aData.annos &&
|
||||
aData.annos.find(anno => anno.name == PlacesUtils.POST_DATA_ANNO);
|
||||
let postData = aData.postData || (postDataAnno && postDataAnno.value);
|
||||
let kwPromise = PlacesUtils.keywords.insert({ keyword: aData.keyword,
|
||||
url: aData.uri,
|
||||
postData,
|
||||
source: this._source });
|
||||
this._importPromises.push(kwPromise);
|
||||
}
|
||||
if (aData.tags) {
|
||||
let tags = aData.tags.split(",").filter(aTag =>
|
||||
aTag.length <= Ci.nsITaggingService.MAX_TAG_LENGTH);
|
||||
if (tags.length) {
|
||||
try {
|
||||
PlacesUtils.tagging.tagURI(NetUtil.newURI(aData.uri), tags, this._source);
|
||||
} catch (ex) {
|
||||
// Invalid tag child, skip it.
|
||||
Cu.reportError(`Unable to set tags "${tags.join(", ")}" for ${aData.uri}: ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aData.charset) {
|
||||
PlacesUtils.annotations.setPageAnnotation(
|
||||
NetUtil.newURI(aData.uri), PlacesUtils.CHARSET_ANNO, aData.charset,
|
||||
0, Ci.nsIAnnotationService.EXPIRE_NEVER);
|
||||
}
|
||||
if (aData.uri.substr(0, 6) == "place:")
|
||||
searchIds.push(id);
|
||||
if (aData.icon) {
|
||||
try {
|
||||
// Create a fake faviconURI to use (FIXME: bug 523932)
|
||||
let faviconURI = NetUtil.newURI("fake-favicon-uri:" + aData.uri);
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
faviconURI, aData.icon, 0,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
NetUtil.newURI(aData.uri), faviconURI, false,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Failed to import favicon data:" + ex);
|
||||
}
|
||||
}
|
||||
if (aData.iconUri) {
|
||||
try {
|
||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
NetUtil.newURI(aData.uri), NetUtil.newURI(aData.iconUri), false,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Failed to import favicon URI:" + ex);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
|
||||
id = PlacesUtils.bookmarks.insertSeparator(aContainer, aIndex, aData.guid, this._source);
|
||||
break;
|
||||
default:
|
||||
// Unknown node type
|
||||
}
|
||||
|
||||
// Set generic properties, valid for all nodes except tags and the mobile
|
||||
// root.
|
||||
if (id != -1 && id != PlacesUtils.mobileFolderId &&
|
||||
aContainer != PlacesUtils.tagsFolderId &&
|
||||
aGrandParentId != PlacesUtils.tagsFolderId) {
|
||||
if (aData.dateAdded)
|
||||
PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded,
|
||||
this._source);
|
||||
if (aData.lastModified)
|
||||
PlacesUtils.bookmarks.setItemLastModified(id, aData.lastModified,
|
||||
this._source);
|
||||
if (aData.annos && aData.annos.length)
|
||||
PlacesUtils.setAnnotationsForItem(id, aData.annos, this._source);
|
||||
}
|
||||
|
||||
return [folderIdMap, searchIds];
|
||||
}
|
||||
}
|
||||
|
||||
function notifyObservers(topic) {
|
||||
Services.obs.notifyObservers(null, topic, "json");
|
||||
|
@ -330,227 +570,19 @@ function notifyObservers(topic) {
|
|||
/**
|
||||
* Replaces imported folder ids with their local counterparts in a place: URI.
|
||||
*
|
||||
* @param {nsIURI} aQueryURI
|
||||
* @param aURI
|
||||
* A place: URI with folder ids.
|
||||
* @param {Object} aFolderIdMap
|
||||
* An array mapping of old folder IDs to new folder GUIDs.
|
||||
* @return {String} 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.
|
||||
* @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.
|
||||
*/
|
||||
async function fixupQuery(aQueryURI, aFolderIdMap) {
|
||||
const reGlobal = /folder=([0-9]+)/g;
|
||||
const re = /([0-9]+)/;
|
||||
|
||||
// Unfortunately .replace can't handle async functions. Therefore,
|
||||
// we find the folder guids we need to know the ids for first, then
|
||||
// do the async request, and finally replace everything in one go.
|
||||
let uri = aQueryURI.href;
|
||||
let found = uri.match(reGlobal);
|
||||
if (!found) {
|
||||
return uri;
|
||||
function fixupQuery(aQueryURI, aFolderIdMap) {
|
||||
let convert = function(str, p1, offset, s) {
|
||||
return "folder=" + aFolderIdMap[p1];
|
||||
}
|
||||
let stringURI = aQueryURI.spec.replace(/folder=([0-9]+)/g, convert);
|
||||
|
||||
let queryFolderGuids = [];
|
||||
for (let folderString of found) {
|
||||
let existingFolderId = folderString.match(re)[0];
|
||||
queryFolderGuids.push(aFolderIdMap[existingFolderId])
|
||||
}
|
||||
|
||||
let newFolderIds = await PlacesUtils.promiseManyItemIds(queryFolderGuids);
|
||||
let convert = function(str, p1) {
|
||||
return "folder=" + newFolderIds.get(aFolderIdMap[p1]);
|
||||
}
|
||||
return uri.replace(reGlobal, convert);
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of root folder names to Guids. To help fixupRootFolderGuid.
|
||||
*/
|
||||
const rootToFolderGuidMap = {
|
||||
"placesRoot": PlacesUtils.bookmarks.rootGuid,
|
||||
"bookmarksMenuFolder": PlacesUtils.bookmarks.menuGuid,
|
||||
"unfiledBookmarksFolder": PlacesUtils.bookmarks.unfiledGuid,
|
||||
"toolbarFolder": PlacesUtils.bookmarks.toolbarGuid,
|
||||
"mobileFolder": PlacesUtils.bookmarks.mobileGuid
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a bookmark node from the json version to the places GUID. This
|
||||
* will only change GUIDs for the built-in folders. Other folders will remain
|
||||
* unchanged.
|
||||
*
|
||||
* @param {Object} A bookmark node that is updated with the new GUID if necessary.
|
||||
*/
|
||||
function fixupRootFolderGuid(node) {
|
||||
if (!node.guid && node.root && node.root in rootToFolderGuidMap) {
|
||||
node.guid = rootToFolderGuidMap[node.root];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the JSON types for a node and its children into Places compatible
|
||||
* types. Also handles updating of other parameters e.g. dateAdded and lastModified.
|
||||
*
|
||||
* @param {Object} node A node to be updated. If it contains children, they will
|
||||
* be updated as well.
|
||||
* @return {Array} An array containing two items:
|
||||
* - {Object} A map of current folder ids to GUIDS
|
||||
* - {Array} An array of GUIDs for nodes that contain query URIs
|
||||
*/
|
||||
function translateTreeTypes(node) {
|
||||
let folderIdToGuidMap = {};
|
||||
let searchGuids = [];
|
||||
|
||||
// Do the uri fixup first, so we can be consistent in this function.
|
||||
if (node.uri) {
|
||||
node.url = node.uri;
|
||||
delete node.uri;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
|
||||
node.type = PlacesUtils.bookmarks.TYPE_FOLDER;
|
||||
|
||||
// Older type mobile folders have a random guid with an annotation. We need
|
||||
// to make sure those go into the proper mobile folder.
|
||||
let isMobileFolder = node.annos &&
|
||||
node.annos.some(anno => anno.name == PlacesUtils.MOBILE_ROOT_ANNO);
|
||||
if (isMobileFolder) {
|
||||
node.guid = PlacesUtils.bookmarks.mobileGuid;
|
||||
} else {
|
||||
// In case the Guid is broken, we need to fix it up.
|
||||
fixupRootFolderGuid(node);
|
||||
}
|
||||
|
||||
// Record the current id and the guid so that we can update any search
|
||||
// queries later.
|
||||
folderIdToGuidMap[node.id] = node.guid;
|
||||
break;
|
||||
case PlacesUtils.TYPE_X_MOZ_PLACE:
|
||||
node.type = PlacesUtils.bookmarks.TYPE_BOOKMARK;
|
||||
|
||||
if (node.url && node.url.substr(0, 6) == "place:") {
|
||||
searchGuids.push(node.guid);
|
||||
}
|
||||
|
||||
break;
|
||||
case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
|
||||
node.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
|
||||
if ("title" in node) {
|
||||
delete node.title;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// TODO We should handle this in a more robust fashion, see bug 1373610.
|
||||
Cu.reportError(`Unexpected bookmark type ${node.type}`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (node.dateAdded) {
|
||||
node.dateAdded = PlacesUtils.toDate(node.dateAdded);
|
||||
}
|
||||
|
||||
if (node.lastModified) {
|
||||
let lastModified = PlacesUtils.toDate(node.lastModified);
|
||||
// Ensure we get a last modified date that's later or equal to the dateAdded
|
||||
// so that we don't upset the Bookmarks API.
|
||||
if (lastModified >= node.dataAdded) {
|
||||
node.lastModified = lastModified;
|
||||
} else {
|
||||
delete node.lastModified;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.tags) {
|
||||
// Separate any tags into an array, and ignore any that are too long.
|
||||
node.tags = node.tags.split(",").filter(aTag =>
|
||||
aTag.length > 0 && aTag.length <= Ci.nsITaggingService.MAX_TAG_LENGTH);
|
||||
|
||||
// If we end up with none, then delete the property completely.
|
||||
if (!node.tags.length) {
|
||||
delete node.tags;
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes postData can be null, so delete it to make the validators happy.
|
||||
if (node.postData == null) {
|
||||
delete node.postData;
|
||||
}
|
||||
|
||||
// Now handle any children.
|
||||
if (!node.children) {
|
||||
return [folderIdToGuidMap, searchGuids];
|
||||
}
|
||||
|
||||
// First sort the children by index.
|
||||
node.children = node.children.sort((a, b) => {
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
||||
// Now do any adjustments required for the children.
|
||||
for (let child of node.children) {
|
||||
let [folders, searches] = translateTreeTypes(child);
|
||||
folderIdToGuidMap = Object.assign(folderIdToGuidMap, folders);
|
||||
searchGuids = searchGuids.concat(searches);
|
||||
}
|
||||
|
||||
return [folderIdToGuidMap, searchGuids];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles inserting favicons into the database for a bookmark node.
|
||||
* It is assumed the node has already been inserted into the bookmarks
|
||||
* database.
|
||||
*
|
||||
* @param {Object} node The bookmark node for icons to be inserted.
|
||||
*/
|
||||
function insertFaviconForNode(node) {
|
||||
if (node.icon) {
|
||||
try {
|
||||
// Create a fake faviconURI to use (FIXME: bug 523932)
|
||||
let faviconURI = Services.io.newURI("fake-favicon-uri:" + node.url);
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
faviconURI, node.icon, 0,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
Services.io.newURI(node.url), faviconURI, false,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Failed to import favicon data:" + ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.iconUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
Services.io.newURI(node.url), Services.io.newURI(node.iconUri), false,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal());
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Failed to import favicon URI:" + ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles inserting favicons into the database for a bookmark tree - a node
|
||||
* and its children.
|
||||
*
|
||||
* It is assumed the nodes have already been inserted into the bookmarks
|
||||
* database.
|
||||
*
|
||||
* @param {Object} nodeTree The bookmark node tree for icons to be inserted.
|
||||
*/
|
||||
function insertFaviconsForTree(nodeTree) {
|
||||
insertFaviconForNode(nodeTree);
|
||||
|
||||
if (nodeTree.children) {
|
||||
for (let child of nodeTree.children) {
|
||||
insertFaviconsForTree(child);
|
||||
}
|
||||
}
|
||||
return NetUtil.newURI(stringURI);
|
||||
}
|
||||
|
|
|
@ -150,12 +150,6 @@ var Bookmarks = Object.freeze({
|
|||
// be removed. Do not rely on this, rather use the tagging service API.
|
||||
tagsGuid: "tags________",
|
||||
|
||||
/**
|
||||
* The GUIDs of the user content root folders that we support, for easy access
|
||||
* as a set.
|
||||
*/
|
||||
userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
|
||||
|
||||
/**
|
||||
* Inserts a bookmark-item into the bookmarks tree.
|
||||
*
|
||||
|
@ -269,7 +263,7 @@ var Bookmarks = Object.freeze({
|
|||
* will be used for all the items inserted. Any indices or custom parentGuids
|
||||
* set on children will be ignored and overwritten.
|
||||
*
|
||||
* @param {Object} tree
|
||||
* @param tree
|
||||
* object representing a tree of bookmark items to insert.
|
||||
*
|
||||
* @return {Promise} resolved when the creation is complete.
|
||||
|
@ -287,7 +281,7 @@ var Bookmarks = Object.freeze({
|
|||
}
|
||||
|
||||
if (!PlacesUtils.isValidGuid(tree.guid)) {
|
||||
throw new Error(`The parent guid is not valid (${tree.guid} ${tree.title}).`);
|
||||
throw new Error("The parent guid is not valid.");
|
||||
}
|
||||
|
||||
if (tree.guid == this.rootGuid) {
|
||||
|
@ -305,7 +299,6 @@ var Bookmarks = Object.freeze({
|
|||
|
||||
// Serialize the tree into an array of items to insert into the db.
|
||||
let insertInfos = [];
|
||||
let insertLivemarkInfos = [];
|
||||
let urlsThatMightNeedPlaces = [];
|
||||
|
||||
// We want to use the same 'last added' time for all the entries
|
||||
|
@ -363,14 +356,8 @@ var Bookmarks = Object.freeze({
|
|||
(b.dateAdded && b.lastModified >= b.dateAdded) }
|
||||
, index: { replaceWith: indexToUse++ }
|
||||
, source: { replaceWith: source }
|
||||
, annos: {}
|
||||
, keyword: { validIf: b => b.type == TYPE_BOOKMARK }
|
||||
, charset: { validIf: b => b.type == TYPE_BOOKMARK }
|
||||
, postData: { validIf: b => b.type == TYPE_BOOKMARK }
|
||||
, tags: { validIf: b => b.type == TYPE_BOOKMARK }
|
||||
, children: { validIf: b => b.type == TYPE_FOLDER && Array.isArray(b.children) }
|
||||
});
|
||||
|
||||
if (shouldUseNullIndices) {
|
||||
insertInfo.index = null;
|
||||
}
|
||||
|
@ -379,21 +366,6 @@ var Bookmarks = Object.freeze({
|
|||
if (insertInfo.type == Bookmarks.TYPE_BOOKMARK) {
|
||||
urlsThatMightNeedPlaces.push(insertInfo.url);
|
||||
}
|
||||
|
||||
// As we don't track indexes for children of root folders, and we
|
||||
// insert livemarks separately, we create a temporary placeholder in
|
||||
// the bookmarks, and later we'll replace it by the real livemark.
|
||||
if (isLivemark(insertInfo)) {
|
||||
// Make the current insertInfo item a placeholder.
|
||||
let livemarkInfo = Object.assign({}, insertInfo);
|
||||
|
||||
// Delete the annotations that make it a livemark.
|
||||
delete insertInfo.annos;
|
||||
|
||||
// Now save the livemark info for later.
|
||||
insertLivemarkInfos.push(livemarkInfo);
|
||||
}
|
||||
|
||||
insertInfos.push(insertInfo);
|
||||
// Process any children. We have to use info.children here rather than
|
||||
// insertInfo.children because validateBookmarkObject doesn't copy over
|
||||
|
@ -417,7 +389,6 @@ var Bookmarks = Object.freeze({
|
|||
}
|
||||
return lastAddedForParent;
|
||||
}
|
||||
|
||||
// We want to validate synchronously, but we can't know the index at which
|
||||
// we're inserting into the parent. We just use NULL instead,
|
||||
// and the SQL query with which we insert will update it as necessary.
|
||||
|
@ -435,9 +406,6 @@ var Bookmarks = Object.freeze({
|
|||
|
||||
await insertBookmarkTree(insertInfos, source, parent,
|
||||
urlsThatMightNeedPlaces, lastAddedForParent);
|
||||
|
||||
await insertLivemarkData(insertLivemarkInfos);
|
||||
|
||||
// Now update the indices of root items in the objects we return.
|
||||
// These may be wrong if someone else modified the table between
|
||||
// when we fetched the parent and inserted our items, but the actual
|
||||
|
@ -458,32 +426,13 @@ var Bookmarks = Object.freeze({
|
|||
let item = insertInfos[i];
|
||||
let itemId = itemIdMap.get(item.guid);
|
||||
let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
|
||||
// For sub-folders, we need to make sure their children have the correct parent ids.
|
||||
let parentId;
|
||||
if (item.guid === parent.guid ||
|
||||
Bookmarks.userContentRoots.includes(item.parentGuid)) {
|
||||
// We're the item being inserted at the top-level, or we're a top-level
|
||||
// folder, so the parent id won't have changed.
|
||||
parentId = parent._id;
|
||||
} else {
|
||||
// This is a parent folder that's been updated, so we need to
|
||||
// use the new item id.
|
||||
parentId = itemIdMap.get(item.parentGuid);
|
||||
}
|
||||
|
||||
notify(observers, "onItemAdded", [ itemId, parentId, item.index,
|
||||
notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
|
||||
item.type, uri, item.title || null,
|
||||
PlacesUtils.toPRTime(item.dateAdded), item.guid,
|
||||
item.parentGuid, item.source ],
|
||||
{ isTagging: false });
|
||||
// Remove non-enumerable properties.
|
||||
delete item.source;
|
||||
|
||||
// Note, annotations for livemark data are deleted from insertInfo
|
||||
// within appendInsertionInfoForInfoArray, so we won't be duplicating
|
||||
// the insertions here.
|
||||
await handleBookmarkItemSpecialData(itemId, item);
|
||||
|
||||
insertInfos[i] = Object.assign({}, item);
|
||||
}
|
||||
return insertInfos;
|
||||
|
@ -749,16 +698,18 @@ var Bookmarks = Object.freeze({
|
|||
options.source = Bookmarks.SOURCES.DEFAULT;
|
||||
}
|
||||
|
||||
const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid,
|
||||
this.mobileGuid];
|
||||
return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: eraseEverything",
|
||||
async function(db) {
|
||||
let urls;
|
||||
|
||||
await db.executeTransaction(async function() {
|
||||
urls = await removeFoldersContents(db, Bookmarks.userContentRoots, options);
|
||||
urls = await removeFoldersContents(db, folderGuids, options);
|
||||
const time = PlacesUtils.toPRTime(new Date());
|
||||
const syncChangeDelta =
|
||||
PlacesSyncUtils.bookmarks.determineSyncChangeDelta(options.source);
|
||||
for (let folderGuid of Bookmarks.userContentRoots) {
|
||||
for (let folderGuid of folderGuids) {
|
||||
await db.executeCached(
|
||||
`UPDATE moz_bookmarks SET lastModified = :time,
|
||||
syncChangeCounter = syncChangeCounter + :syncChangeDelta
|
||||
|
@ -1366,16 +1317,6 @@ function insertBookmark(item, parent) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a bookmark is a Livemark depending on how it is annotated.
|
||||
*
|
||||
* @param {Object} node The bookmark node to check.
|
||||
* @returns {Boolean} True if the node is a Livemark, false otherwise.
|
||||
*/
|
||||
function isLivemark(node) {
|
||||
return node.annos && node.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI);
|
||||
}
|
||||
|
||||
function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {
|
||||
return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: insertBookmarkTree", async function(db) {
|
||||
await db.executeTransaction(async function transaction() {
|
||||
|
@ -1416,102 +1357,6 @@ function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles any Livemarks within the passed items.
|
||||
*
|
||||
* @param {Array} items Livemark items that need to be added.
|
||||
*/
|
||||
async function insertLivemarkData(items) {
|
||||
for (let item of items) {
|
||||
let feedURI = null;
|
||||
let siteURI = null;
|
||||
item.annos = item.annos.filter(function(aAnno) {
|
||||
switch (aAnno.name) {
|
||||
case PlacesUtils.LMANNO_FEEDURI:
|
||||
feedURI = NetUtil.newURI(aAnno.value);
|
||||
return false;
|
||||
case PlacesUtils.LMANNO_SITEURI:
|
||||
siteURI = NetUtil.newURI(aAnno.value);
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let index = null;
|
||||
|
||||
// Delete the placeholder but note the index of it, so that we
|
||||
// can insert the livemark item at the right place.
|
||||
let placeholder = await Bookmarks.fetch(item.guid);
|
||||
index = placeholder.index;
|
||||
|
||||
await Bookmarks.remove(item.guid, {source: item.source});
|
||||
|
||||
if (feedURI) {
|
||||
item.feedURI = feedURI;
|
||||
item.siteURI = siteURI;
|
||||
item.index = index;
|
||||
|
||||
if (item.dateAdded) {
|
||||
item.dateAdded = PlacesUtils.toPRTime(item.dateAdded);
|
||||
}
|
||||
if (item.lastModified) {
|
||||
item.lastModified = PlacesUtils.toPRTime(item.lastModified);
|
||||
}
|
||||
|
||||
let livemark = await PlacesUtils.livemarks.addLivemark(item);
|
||||
|
||||
let id = livemark.id;
|
||||
if (item.annos && item.annos.length) {
|
||||
PlacesUtils.setAnnotationsForItem(id, item.annos,
|
||||
item.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles special data on a bookmark, e.g. annotations, keywords, tags, charsets,
|
||||
* inserting the data into the appropriate place.
|
||||
*
|
||||
* @param {Integer} itemId The ID of the item within the bookmarks database.
|
||||
* @param {Object} item The bookmark item with possible special data to be inserted.
|
||||
*/
|
||||
async function handleBookmarkItemSpecialData(itemId, item) {
|
||||
if (item.annos && item.annos.length) {
|
||||
PlacesUtils.setAnnotationsForItem(itemId, item.annos, item.source)
|
||||
}
|
||||
if ("keyword" in item && item.keyword) {
|
||||
// POST data could be set in 2 ways:
|
||||
// 1. new backups have a postData property
|
||||
// 2. old backups have an item annotation
|
||||
let postDataAnno = item.annos &&
|
||||
item.annos.find(anno => anno.name == PlacesUtils.POST_DATA_ANNO);
|
||||
let postData = item.postData || (postDataAnno && postDataAnno.value);
|
||||
try {
|
||||
await PlacesUtils.keywords.insert({
|
||||
keyword: item.keyword,
|
||||
url: item.url,
|
||||
postData,
|
||||
source: item.source
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError(`Failed to insert keywords: ${ex}`);
|
||||
}
|
||||
}
|
||||
if ("tags" in item) {
|
||||
try {
|
||||
PlacesUtils.tagging.tagURI(NetUtil.newURI(item.url), item.tags, item._source);
|
||||
} catch (ex) {
|
||||
// Invalid tag child, skip it.
|
||||
Cu.reportError(`Unable to set tags "${item.tags.join(", ")}" for ${item.url}: ${ex}`);
|
||||
}
|
||||
}
|
||||
if ("charset" in item && item.charset) {
|
||||
await PlacesUtils.setCharsetForURI(NetUtil.newURI(item.url), item.charset);
|
||||
}
|
||||
}
|
||||
|
||||
// Query implementation.
|
||||
|
||||
async function queryBookmarks(info) {
|
||||
|
|
|
@ -81,30 +81,6 @@ function getBackupFileForSameDate(aFilename) {
|
|||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top-level bookmark folders ids and guids.
|
||||
*
|
||||
* @return {Promise} Resolve with an array of objects containing id and guid
|
||||
* when the query is complete.
|
||||
*/
|
||||
async function getTopLevelFolderIds() {
|
||||
let db = await PlacesUtils.promiseDBConnection();
|
||||
let rows = await db.execute(
|
||||
"SELECT id, guid FROM moz_bookmarks WHERE parent = :parentId",
|
||||
{ parentId: PlacesUtils.placesRootId }
|
||||
);
|
||||
|
||||
let guids = [];
|
||||
for (let row of rows) {
|
||||
guids.push({
|
||||
id: row.getResultByName("id"),
|
||||
guid: row.getResultByName("guid")
|
||||
});
|
||||
}
|
||||
return guids;
|
||||
}
|
||||
|
||||
|
||||
this.PlacesBackups = {
|
||||
/**
|
||||
* Matches the backup filename:
|
||||
|
@ -568,44 +544,6 @@ this.PlacesBackups = {
|
|||
Components.utils.reportError("Unable to report telemetry.");
|
||||
}
|
||||
return [root, root.itemsCount];
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for PlacesUtils.bookmarks.eraseEverything that removes non-default
|
||||
* roots.
|
||||
*
|
||||
* Note that default roots are preserved, only their children will be removed.
|
||||
*
|
||||
* TODO Ideally we wouldn't need to worry about non-default roots. However,
|
||||
* until bug 1310299 is fixed, we still need to manage them.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* Additional options. Currently supports the following properties:
|
||||
* - source: The change source, forwarded to all bookmark observers.
|
||||
* Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
|
||||
*
|
||||
* @return {Promise} resolved when the removal is complete.
|
||||
* @resolves once the removal is complete.
|
||||
*/
|
||||
async eraseEverythingIncludingUserRoots(options = {}) {
|
||||
if (!options.source) {
|
||||
options.source = PlacesUtils.bookmarks.SOURCES.DEFAULT;
|
||||
}
|
||||
|
||||
let excludeItems =
|
||||
PlacesUtils.annotations.getItemsWithAnnotation(PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
|
||||
|
||||
let rootFolderChildren = await getTopLevelFolderIds();
|
||||
|
||||
// We only need to do top-level roots here.
|
||||
for (let child of rootFolderChildren) {
|
||||
if (!PlacesUtils.bookmarks.userContentRoots.includes(child.guid) &&
|
||||
child.guid != PlacesUtils.bookmarks.tagsGuid &&
|
||||
!excludeItems.includes(child.id)) {
|
||||
await PlacesUtils.bookmarks.remove(child.guid, {source: options.source});
|
||||
}
|
||||
}
|
||||
|
||||
return PlacesUtils.bookmarks.eraseEverything(options);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -249,11 +249,6 @@ const BOOKMARK_VALIDATORS = Object.freeze({
|
|||
},
|
||||
source: simpleValidateFunc(v => Number.isInteger(v) &&
|
||||
Object.values(PlacesUtils.bookmarks.SOURCES).includes(v)),
|
||||
annos: simpleValidateFunc(v => Array.isArray(v) && v.length),
|
||||
keyword: simpleValidateFunc(v => (typeof(v) == "string") && v.length),
|
||||
charset: simpleValidateFunc(v => (typeof(v) == "string") && v.length),
|
||||
postData: simpleValidateFunc(v => (typeof(v) == "string") && v.length),
|
||||
tags: simpleValidateFunc(v => Array.isArray(v) && v.length),
|
||||
});
|
||||
|
||||
// Sync bookmark records can contain additional properties.
|
||||
|
@ -1579,8 +1574,8 @@ this.PlacesUtils = {
|
|||
/**
|
||||
* Sets the character-set for a URI.
|
||||
*
|
||||
* @param {nsIURI} aURI
|
||||
* @param {String} aCharset character-set value.
|
||||
* @param aURI nsIURI
|
||||
* @param aCharset character-set value.
|
||||
* @return {Promise}
|
||||
*/
|
||||
setCharsetForURI: function PU_setCharsetForURI(aURI, aCharset) {
|
||||
|
@ -1728,22 +1723,13 @@ this.PlacesUtils = {
|
|||
* @param aGuid
|
||||
* an item GUID
|
||||
* @return {Promise}
|
||||
* @resolves to the item id.
|
||||
* @resolves to the GUID.
|
||||
* @rejects if there's no item for the given GUID.
|
||||
*/
|
||||
promiseItemId(aGuid) {
|
||||
return GuidHelper.getItemId(aGuid)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the item ids for multiple items (a bookmark, a folder or a separator)
|
||||
* given the unique ids for each item.
|
||||
*
|
||||
* @param {Array} aGuids An array of item GUIDs.
|
||||
* @return {Promise}
|
||||
* @resolves to a Map of item ids.
|
||||
* @rejects if not all of the GUIDs could be found.
|
||||
*/
|
||||
promiseManyItemIds(aGuids) {
|
||||
return GuidHelper.getManyItemIds(aGuids);
|
||||
},
|
||||
|
|
|
@ -40,121 +40,65 @@ const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX;
|
|||
var test = {
|
||||
_testRootId: null,
|
||||
_testRootTitle: "test root",
|
||||
_folderGuids: [],
|
||||
_folderIds: [],
|
||||
_bookmarkURIs: [],
|
||||
_count: 3,
|
||||
_extraBookmarksCount: 10,
|
||||
|
||||
populate: async function populate() {
|
||||
populate: function populate() {
|
||||
// folder to hold this test
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
|
||||
let testFolderItems = [];
|
||||
// Set a date 60 seconds ago, so that we can set newer bookmarks later.
|
||||
let dateAdded = new Date(new Date() - 60000);
|
||||
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId);
|
||||
this._testRootId =
|
||||
PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
|
||||
this._testRootTitle, DEFAULT_INDEX);
|
||||
|
||||
// create test folders each with a bookmark
|
||||
for (let i = 0; i < this._count; i++) {
|
||||
this._folderGuids.push(PlacesUtils.history.makeGuid());
|
||||
testFolderItems.push({
|
||||
guid: this._folderGuids[i],
|
||||
title: `folder${i}`,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
dateAdded,
|
||||
children: [{
|
||||
dateAdded,
|
||||
url: `http://${i}`,
|
||||
title: `bookmark${i}`,
|
||||
}]
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
let bookmarksTree = {
|
||||
guid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
children: [{
|
||||
dateAdded,
|
||||
title: this._testRootTitle,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
children: testFolderItems
|
||||
}]
|
||||
};
|
||||
|
||||
let insertedBookmarks = await PlacesUtils.bookmarks.insertTree(bookmarksTree);
|
||||
|
||||
// create a query URI with 1 folder (ie: folder shortcut)
|
||||
let folderIdsMap = await PlacesUtils.promiseManyItemIds(this._folderGuids);
|
||||
let folderIds = [];
|
||||
for (let id of folderIdsMap.values()) {
|
||||
folderIds.push(id);
|
||||
}
|
||||
|
||||
this._queryURI1 = `place:folder=${folderIdsMap.get(this._folderGuids[0])}&queryType=1`;
|
||||
this._queryURI1 = uri("place:folder=" + this._folderIds[0] + "&queryType=1");
|
||||
this._queryTitle1 = "query1";
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: insertedBookmarks[0].guid,
|
||||
dateAdded,
|
||||
url: this._queryURI1,
|
||||
title: this._queryTitle1
|
||||
});
|
||||
PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI1,
|
||||
DEFAULT_INDEX, this._queryTitle1);
|
||||
|
||||
// create a query URI with _count folders
|
||||
this._queryURI2 = `place:folder=${folderIds.join("&folder=")}&queryType=1`;
|
||||
this._queryURI2 = uri("place:folder=" + this._folderIds.join("&folder=") + "&queryType=1");
|
||||
this._queryTitle2 = "query2";
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: insertedBookmarks[0].guid,
|
||||
dateAdded,
|
||||
url: this._queryURI2,
|
||||
title: this._queryTitle2
|
||||
});
|
||||
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 = folderIds.map(function(aFolderId) {
|
||||
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 =
|
||||
PlacesUtils.history.queriesToQueryString(queries, queries.length, options);
|
||||
uri(PlacesUtils.history.queriesToQueryString(queries, queries.length, options));
|
||||
this._queryTitle3 = "query3";
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: insertedBookmarks[0].guid,
|
||||
dateAdded,
|
||||
url: this._queryURI3,
|
||||
title: this._queryTitle3
|
||||
});
|
||||
|
||||
// Create a query URI for most recent bookmarks with NO folders specified.
|
||||
this._queryURI4 = "place:queryType=1&sort=12&excludeItemIfParentHasAnnotation=livemark%2FfeedURI&maxResults=10&excludeQueries=1";
|
||||
this._queryTitle4 = "query4";
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: insertedBookmarks[0].guid,
|
||||
dateAdded,
|
||||
url: this._queryURI4,
|
||||
title: this._queryTitle4
|
||||
});
|
||||
|
||||
dump_table("moz_bookmarks");
|
||||
dump_table("moz_places");
|
||||
PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI3,
|
||||
DEFAULT_INDEX, this._queryTitle3);
|
||||
},
|
||||
|
||||
clean() {},
|
||||
|
||||
validate: async function validate(addExtras) {
|
||||
if (addExtras) {
|
||||
// Throw a wrench in the works by inserting some new bookmarks,
|
||||
// ensuring folder ids won't be the same, when restoring.
|
||||
let date = new Date() - (this._extraBookmarksCount * 1000);
|
||||
for (let i = 0; i < this._extraBookmarksCount; i++) {
|
||||
await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
url: uri("http://aaaa" + i),
|
||||
dateAdded: new Date(date + ((this._extraBookmarksCount - i) * 1000)),
|
||||
});
|
||||
}
|
||||
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 (let i = 0; i < 10; i++) {
|
||||
PlacesUtils.bookmarks.
|
||||
insertBookmark(PlacesUtils.bookmarksMenuFolderId, uri("http://aaaa" + i), DEFAULT_INDEX, "");
|
||||
}
|
||||
|
||||
var toolbar =
|
||||
|
@ -168,8 +112,8 @@ var test = {
|
|||
folderNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
|
||||
folderNode.containerOpen = true;
|
||||
|
||||
// |_count| folders + the query nodes
|
||||
do_check_eq(folderNode.childCount, this._count + 4);
|
||||
// |_count| folders + the query node
|
||||
do_check_eq(folderNode.childCount, this._count + 3);
|
||||
|
||||
for (let i = 0; i < this._count; i++) {
|
||||
var subFolder = folderNode.getChild(i);
|
||||
|
@ -191,9 +135,6 @@ var test = {
|
|||
// validate multiple queries query
|
||||
this.validateQueryNode3(folderNode.getChild(this._count + 2));
|
||||
|
||||
// validate recent folders query
|
||||
this.validateQueryNode4(folderNode.getChild(this._count + 3));
|
||||
|
||||
// clean up
|
||||
folderNode.containerOpen = false;
|
||||
toolbar.containerOpen = false;
|
||||
|
@ -207,8 +148,8 @@ var test = {
|
|||
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");
|
||||
do_check_true(uri(child.uri).equals(uri("http://0")))
|
||||
do_check_eq(child.title, "bookmark0")
|
||||
aNode.containerOpen = false;
|
||||
},
|
||||
|
||||
|
@ -240,51 +181,40 @@ var test = {
|
|||
do_check_eq(child.title, "bookmark" + i)
|
||||
}
|
||||
aNode.containerOpen = false;
|
||||
},
|
||||
|
||||
validateQueryNode4(aNode) {
|
||||
do_check_eq(aNode.title, this._queryTitle4);
|
||||
do_check_true(PlacesUtils.nodeIsQuery(aNode));
|
||||
|
||||
aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
aNode.containerOpen = true;
|
||||
// The query will list the extra bookmarks added at the start of validate.
|
||||
do_check_eq(aNode.childCount, this._extraBookmarksCount);
|
||||
for (var i = 0; i < aNode.childCount; i++) {
|
||||
var child = aNode.getChild(i);
|
||||
do_check_eq(child.uri, `http://aaaa${i}/`);
|
||||
}
|
||||
aNode.containerOpen = false;
|
||||
},
|
||||
}
|
||||
}
|
||||
tests.push(test);
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
// make json file
|
||||
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
|
||||
|
||||
// populate db
|
||||
for (let singleTest of tests) {
|
||||
await singleTest.populate();
|
||||
tests.forEach(function(aTest) {
|
||||
aTest.populate();
|
||||
// sanity
|
||||
await singleTest.validate(true);
|
||||
}
|
||||
aTest.validate();
|
||||
});
|
||||
|
||||
// export json to file
|
||||
await BookmarkJSONUtils.exportToFile(jsonFile);
|
||||
|
||||
// clean
|
||||
for (let singleTest of tests) {
|
||||
singleTest.clean();
|
||||
}
|
||||
tests.forEach(function(aTest) {
|
||||
aTest.clean();
|
||||
});
|
||||
|
||||
// restore json file
|
||||
await BookmarkJSONUtils.importFromFile(jsonFile, true);
|
||||
|
||||
// validate
|
||||
for (let singleTest of tests) {
|
||||
await singleTest.validate(false);
|
||||
}
|
||||
tests.forEach(function(aTest) {
|
||||
aTest.validate();
|
||||
});
|
||||
|
||||
// clean up
|
||||
await OS.File.remove(jsonFile);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const EXCLUDE_FROM_BACKUP_ANNO = "places/excludeFromBackup";
|
||||
// Menu, Toolbar, Unsorted, Tags, Mobile
|
||||
const PLACES_ROOTS_COUNT = 5;
|
||||
var tests = [];
|
||||
|
@ -48,7 +49,7 @@ var test = {
|
|||
idx, "exclude uri");
|
||||
// Annotate the bookmark for exclusion.
|
||||
PlacesUtils.annotations.setItemAnnotation(exItemId,
|
||||
PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
|
||||
EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
|
||||
PlacesUtils.annotations.EXPIRE_NEVER);
|
||||
|
||||
// create a root to be exclude
|
||||
|
@ -58,7 +59,7 @@ var test = {
|
|||
this._excludeRootTitle, idx);
|
||||
// Annotate the root for exclusion.
|
||||
PlacesUtils.annotations.setItemAnnotation(this._excludeRootId,
|
||||
PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
|
||||
EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
|
||||
PlacesUtils.annotations.EXPIRE_NEVER);
|
||||
// add a test bookmark exclude by exclusion of its parent
|
||||
PlacesUtils.bookmarks.insertBookmark(this._excludeRootId,
|
||||
|
@ -104,30 +105,26 @@ var test = {
|
|||
}
|
||||
}
|
||||
|
||||
// make json file
|
||||
var jsonFile;
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
|
||||
});
|
||||
add_task(async function() {
|
||||
// make json file
|
||||
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
|
||||
|
||||
add_task(async function test_export_import_excluded_file() {
|
||||
// populate db
|
||||
test.populate();
|
||||
|
||||
await BookmarkJSONUtils.exportToFile(jsonFile);
|
||||
|
||||
// restore json file
|
||||
do_print("Restoring json file");
|
||||
await BookmarkJSONUtils.importFromFile(jsonFile, true);
|
||||
|
||||
// validate without removing all bookmarks
|
||||
// restore do not remove backup exclude entries
|
||||
do_print("Validating...");
|
||||
test.validate(false);
|
||||
});
|
||||
|
||||
add_task(async function test_clearing_then_importing() {
|
||||
// cleanup
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
// manually remove the excluded root
|
||||
|
|
|
@ -20,9 +20,6 @@ this.push(myTest);
|
|||
*/
|
||||
|
||||
tests.push({
|
||||
// Initialise something to avoid undefined property warnings in validate.
|
||||
_litterTitle: "",
|
||||
|
||||
populate: function populate() {
|
||||
// check initial size
|
||||
var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
|
||||
|
@ -122,6 +119,10 @@ tests.push({
|
|||
}
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
// make json file
|
||||
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
|
||||
|
|
|
@ -49,18 +49,6 @@
|
|||
"uri": "http://en-us.www.mozilla.com/en-US/firefox/customize/",
|
||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="
|
||||
},
|
||||
{
|
||||
"guid": "OCyeUO5uu9FJ",
|
||||
"index": 3,
|
||||
"title": "About Us",
|
||||
"id": 10,
|
||||
"parent": 6,
|
||||
"dateAdded": 1361551979376699,
|
||||
"lastModified": 1361551979379060,
|
||||
"type": "text/x-moz-place",
|
||||
"uri": "http://en-us.www.mozilla.com/en-US/about/",
|
||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="
|
||||
},
|
||||
{
|
||||
"guid": "OCyeUO5uu9FI",
|
||||
"index": 2,
|
||||
|
@ -74,24 +62,16 @@
|
|||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="
|
||||
},
|
||||
{
|
||||
"guid": "QFM-QnE2ZpMz",
|
||||
"title": "Test null postData",
|
||||
"index": 4,
|
||||
"dateAdded": 1481639510868000,
|
||||
"lastModified": 1489563704300000,
|
||||
"id": 17,
|
||||
"charset": "UTF-8",
|
||||
"annos": [
|
||||
{
|
||||
"name": "bookmarkProperties/description",
|
||||
"flags": 0,
|
||||
"expires": 4,
|
||||
"value": "The best"
|
||||
}
|
||||
],
|
||||
"guid": "OCyeUO5uu9FJ",
|
||||
"index": 3,
|
||||
"title": "About Us",
|
||||
"id": 10,
|
||||
"parent": 6,
|
||||
"dateAdded": 1361551979376699,
|
||||
"lastModified": 1361551979379060,
|
||||
"type": "text/x-moz-place",
|
||||
"uri": "http://example.com/search?q=%s&suggid=",
|
||||
"postData": null
|
||||
"uri": "http://en-us.www.mozilla.com/en-US/about/",
|
||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -11,6 +11,10 @@ var bookmarkData = [
|
|||
{ uri: uri("http://en.wikipedia.org/wiki/Diplodocus"), title: "dinosaur, dj, rad word" }
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/*
|
||||
HTML+FEATURES SUMMARY:
|
||||
- import legacy bookmarks
|
||||
|
@ -48,7 +52,7 @@ add_task(async function() {
|
|||
title });
|
||||
}
|
||||
|
||||
await validate("initial database");
|
||||
await validate();
|
||||
|
||||
// Test exporting a Places canonical json file.
|
||||
// 1. export to bookmarks.exported.json
|
||||
|
@ -61,18 +65,14 @@ add_task(async function() {
|
|||
do_print("imported json");
|
||||
|
||||
// 4. run the test-suite
|
||||
await validate("re-imported json");
|
||||
await validate();
|
||||
do_print("validated import");
|
||||
});
|
||||
|
||||
async function validate(infoMsg) {
|
||||
do_print(`Validating ${infoMsg}: testMenuBookmarks`);
|
||||
async function validate() {
|
||||
await testMenuBookmarks();
|
||||
do_print(`Validating ${infoMsg}: testToolbarBookmarks`);
|
||||
await testToolbarBookmarks();
|
||||
do_print(`Validating ${infoMsg}: testUnfiledBookmarks`);
|
||||
testUnfiledBookmarks();
|
||||
do_print(`Validating ${infoMsg}: testTags`);
|
||||
testTags();
|
||||
await PlacesTestUtils.promiseAsyncUpdates();
|
||||
}
|
||||
|
|
|
@ -36,10 +36,6 @@ var test_bookmarks = {
|
|||
title: "About Us",
|
||||
url: "http://en-us.www.mozilla.com/en-US/about/",
|
||||
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="
|
||||
},
|
||||
{ guid: "QFM-QnE2ZpMz",
|
||||
title: "Test null postData",
|
||||
url: "http://example.com/search?q=%s&suggid="
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -76,10 +72,7 @@ var test_bookmarks = {
|
|||
{ guid: "OCyeUO5uu9FR",
|
||||
title: "Latest Headlines",
|
||||
url: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
|
||||
feedUrl: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
|
||||
// Note: date gets truncated to milliseconds, whereas the value in bookmarks.json
|
||||
// has full microseconds.
|
||||
dateAdded: 1361551979451000,
|
||||
feedUrl: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml"
|
||||
}
|
||||
],
|
||||
unfiled: [
|
||||
|
@ -199,7 +192,7 @@ function checkItem(aExpected, aNode) {
|
|||
let data = await deferred.promise;
|
||||
let base64Icon = "data:image/png;base64," +
|
||||
base64EncodeString(String.fromCharCode.apply(String, data));
|
||||
do_check_eq(base64Icon, aExpected.icon);
|
||||
do_check_true(base64Icon == aExpected.icon);
|
||||
break;
|
||||
case "keyword": {
|
||||
let entry = await PlacesUtils.keywords.fetch({ url: aNode.uri });
|
||||
|
|
|
@ -77,8 +77,8 @@ add_task(async function test_import_mobile_bookmarks_root() {
|
|||
guid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 0,
|
||||
children: [
|
||||
{ guid: "X6lUyOspVYwi", index: 0 },
|
||||
{ guid: "Utodo9b0oVws", index: 1 },
|
||||
{ guid: "Utodo9b0oVws", index: 0 },
|
||||
{ guid: "X6lUyOspVYwi", index: 1 },
|
||||
],
|
||||
}, {
|
||||
guid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
|
@ -96,12 +96,10 @@ add_task(async function test_import_mobile_bookmarks_root() {
|
|||
value: 1,
|
||||
}],
|
||||
children: [
|
||||
// The first two are in ..._import.json, the second two are in
|
||||
// ..._merge.json
|
||||
{ guid: "_o8e1_zxTJFg", index: 0 },
|
||||
{ guid: "QCtSqkVYUbXB", index: 1 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 2 },
|
||||
{ guid: "xV10h9Wi3FBM", index: 3 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 0 },
|
||||
{ guid: "xV10h9Wi3FBM", index: 1 },
|
||||
{ guid: "_o8e1_zxTJFg", index: 2 },
|
||||
{ guid: "QCtSqkVYUbXB", index: 3 },
|
||||
],
|
||||
}],
|
||||
}, "Should merge bookmarks root contents");
|
||||
|
@ -171,9 +169,9 @@ add_task(async function test_import_mobile_bookmarks_folder() {
|
|||
guid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 0,
|
||||
children: [
|
||||
{ guid: "X6lUyOspVYwi", index: 0 },
|
||||
{ guid: "XF4yRP6bTuil", index: 1 },
|
||||
{ guid: "Utodo9b0oVws", index: 2 },
|
||||
{ guid: "Utodo9b0oVws", index: 0 },
|
||||
{ guid: "X6lUyOspVYwi", index: 1 },
|
||||
{ guid: "XF4yRP6bTuil", index: 2 },
|
||||
],
|
||||
}, {
|
||||
guid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
|
@ -193,10 +191,10 @@ add_task(async function test_import_mobile_bookmarks_folder() {
|
|||
value: 1,
|
||||
}],
|
||||
children: [
|
||||
{ guid: "_o8e1_zxTJFg", index: 0 },
|
||||
{ guid: "QCtSqkVYUbXB", index: 1 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 2 },
|
||||
{ guid: "xV10h9Wi3FBM", index: 3 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 0 },
|
||||
{ guid: "xV10h9Wi3FBM", index: 1 },
|
||||
{ guid: "_o8e1_zxTJFg", index: 2 },
|
||||
{ guid: "QCtSqkVYUbXB", index: 3 },
|
||||
],
|
||||
}],
|
||||
}, "Should merge bookmarks folder contents into mobile root");
|
||||
|
@ -237,8 +235,8 @@ add_task(async function test_restore_multiple_bookmarks_folders() {
|
|||
value: 1,
|
||||
}],
|
||||
children: [
|
||||
{ guid: "a17yW6-nTxEJ", index: 0 },
|
||||
{ guid: "sSZ86WT9WbN3", index: 1 },
|
||||
{ guid: "sSZ86WT9WbN3", index: 0 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 1 },
|
||||
],
|
||||
}],
|
||||
}, "Should restore multiple bookmarks folder contents into root");
|
||||
|
@ -259,10 +257,10 @@ add_task(async function test_import_multiple_bookmarks_folders() {
|
|||
guid: PlacesUtils.bookmarks.menuGuid,
|
||||
index: 0,
|
||||
children: [
|
||||
{ guid: "X6lUyOspVYwi", index: 0 },
|
||||
{ guid: "buy7711R3ZgE", index: 1 },
|
||||
{ guid: "F_LBgd1fS_uQ", index: 2 },
|
||||
{ guid: "oIpmQXMWsXvY", index: 3 },
|
||||
{ guid: "buy7711R3ZgE", index: 0 },
|
||||
{ guid: "F_LBgd1fS_uQ", index: 1 },
|
||||
{ guid: "oIpmQXMWsXvY", index: 2 },
|
||||
{ guid: "X6lUyOspVYwi", index: 3 },
|
||||
],
|
||||
}, {
|
||||
guid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
|
@ -282,10 +280,10 @@ add_task(async function test_import_multiple_bookmarks_folders() {
|
|||
value: 1,
|
||||
}],
|
||||
children: [
|
||||
{ guid: "_o8e1_zxTJFg", index: 0 },
|
||||
{ guid: "QCtSqkVYUbXB", index: 1 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 2 },
|
||||
{ guid: "sSZ86WT9WbN3", index: 3 },
|
||||
{ guid: "sSZ86WT9WbN3", index: 0 },
|
||||
{ guid: "a17yW6-nTxEJ", index: 1 },
|
||||
{ guid: "_o8e1_zxTJFg", index: 2 },
|
||||
{ guid: "QCtSqkVYUbXB", index: 3 },
|
||||
],
|
||||
}],
|
||||
}, "Should merge multiple mobile folders into root");
|
||||
|
|
|
@ -6,13 +6,17 @@ const {
|
|||
fetchGuidsWithAnno,
|
||||
} = Cu.import("resource://gre/modules/PlacesSyncUtils.jsm", {});
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.importGlobalProperties(["URLSearchParams"]);
|
||||
Cu.importGlobalProperties(["crypto", "URLSearchParams"]);
|
||||
|
||||
const DESCRIPTION_ANNO = "bookmarkProperties/description";
|
||||
const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
|
||||
const SYNC_PARENT_ANNO = "sync/parent";
|
||||
|
||||
var makeGuid = PlacesUtils.history.makeGuid;
|
||||
function makeGuid() {
|
||||
return ChromeUtils.base64URLEncode(crypto.getRandomValues(new Uint8Array(9)), {
|
||||
pad: false,
|
||||
});
|
||||
}
|
||||
|
||||
function makeLivemarkServer() {
|
||||
let server = new HttpServer();
|
||||
|
|
Загрузка…
Ссылка в новой задаче