Bug 1095427 - Convert HTML bookmarks import code to the new async Bookmarks.jsm API. r=mak

Original patch by Josh, completed by Standard8.

MozReview-Commit-ID: Baun3n69n1b

--HG--
extra : rebase_source : e83e6a3cb0cc448c407042eabf6f6acaf1aabe68
This commit is contained in:
Josh Aas 2017-10-02 16:53:00 +01:00
Родитель 589b90c591
Коммит 8d88b9d1a8
5 изменённых файлов: 343 добавлений и 290 удалений

Просмотреть файл

@ -138,20 +138,18 @@ this.BookmarkHTMLUtils = Object.freeze({
* @resolves When the new bookmarks have been created.
* @rejects JavaScript exception.
*/
importFromURL: function BHU_importFromURL(aSpec, aInitialImport) {
return (async function() {
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
try {
let importer = new BookmarkImporter(aInitialImport);
await importer.importFromURL(aSpec);
async importFromURL(aSpec, aInitialImport) {
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
try {
let importer = new BookmarkImporter(aInitialImport);
await importer.importFromURL(aSpec);
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
} catch (ex) {
Cu.reportError("Failed to import bookmarks from " + aSpec + ": " + ex);
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
throw ex;
}
})();
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
} catch (ex) {
Cu.reportError("Failed to import bookmarks from " + aSpec + ": " + ex);
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
throw ex;
}
},
/**
@ -167,7 +165,7 @@ this.BookmarkHTMLUtils = Object.freeze({
* @rejects JavaScript exception.
* @deprecated passing an nsIFile is deprecated
*/
importFromFile: function BHU_importFromFile(aFilePath, aInitialImport) {
async importFromFile(aFilePath, aInitialImport) {
if (aFilePath instanceof Ci.nsIFile) {
Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.importFromFile " +
"is deprecated. Please use an OS.File path string instead.",
@ -175,22 +173,20 @@ this.BookmarkHTMLUtils = Object.freeze({
aFilePath = aFilePath.path;
}
return (async function() {
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
try {
if (!(await OS.File.exists(aFilePath))) {
throw new Error("Cannot import from nonexisting html file: " + aFilePath);
}
let importer = new BookmarkImporter(aInitialImport);
await importer.importFromURL(OS.Path.toFileURI(aFilePath));
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
} catch (ex) {
Cu.reportError("Failed to import bookmarks from " + aFilePath + ": " + ex);
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
throw ex;
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
try {
if (!(await OS.File.exists(aFilePath))) {
throw new Error("Cannot import from nonexisting html file: " + aFilePath);
}
})();
let importer = new BookmarkImporter(aInitialImport);
await importer.importFromURL(OS.Path.toFileURI(aFilePath));
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
} catch (ex) {
Cu.reportError("Failed to import bookmarks from " + aFilePath + ": " + ex);
notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
throw ex;
}
},
/**
@ -239,8 +235,8 @@ this.BookmarkHTMLUtils = Object.freeze({
}
});
function Frame(aFrameId) {
this.containerId = aFrameId;
function Frame(aFolder) {
this.folder = aFolder;
/**
* How many <dl>s have been nested. Each frame/container should start
@ -290,25 +286,25 @@ function Frame(aFrameId) {
* This is cleared whenever we hit a <h3>, so that we know NOT to save this
* with a bookmark, but to keep it until
*/
this.previousLink = null; // nsIURI
this.previousLink = null;
/**
* contains the URL of the previous livemark, so that when the link ends,
* and the livemark title is known, we can create it.
*/
this.previousFeed = null; // nsIURI
this.previousFeed = null;
/**
* Contains the id of an imported, or newly created bookmark.
* Contains a reference to the last created bookmark or folder object.
*/
this.previousId = 0;
this.previousItem = null;
/**
* Contains the date-added and last-modified-date of an imported item.
* Used to override the values set by insertBookmark, createFolder, etc.
*/
this.previousDateAdded = 0;
this.previousLastModifiedDate = 0;
this.previousDateAdded = null;
this.previousLastModifiedDate = null;
}
function BookmarkImporter(aInitialImport) {
@ -317,12 +313,25 @@ function BookmarkImporter(aInitialImport) {
// counter.
this._source = aInitialImport ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
PlacesUtils.bookmarks.SOURCE_IMPORT;
// This root is where we construct the bookmarks tree into, following the format
// of the imported file.
// If we're doing an initial import, the non-menu roots will be created as
// children of this root, so in _getBookmarkTrees we'll split them out.
// If we're not doing an initial import, everything gets imported under the
// bookmark menu folder, so there won't be any need for _getBookmarkTrees to
// do separation.
this._bookmarkTree = {
type: PlacesUtils.bookmarks.TYPE_FOLDER,
guid: PlacesUtils.bookmarks.menuGuid,
children: []
};
this._frames = [];
this._frames.push(new Frame(PlacesUtils.bookmarksMenuFolderId));
this._frames.push(new Frame(this._bookmarkTree));
}
BookmarkImporter.prototype = {
_safeTrim: function safeTrim(aStr) {
return aStr ? aStr.trim() : aStr;
},
@ -340,56 +349,58 @@ BookmarkImporter.prototype = {
* name from the previous frame's heading.
*/
_newFrame: function newFrame() {
let containerId = -1;
let frame = this._curFrame;
let containerTitle = frame.previousText;
frame.previousText = "";
let containerType = frame.lastContainerType;
let folder = {
children: [],
type: PlacesUtils.bookmarks.TYPE_FOLDER
};
switch (containerType) {
case Container_Normal:
// append a new folder
containerId =
PlacesUtils.bookmarks.createFolder(frame.containerId,
containerTitle,
PlacesUtils.bookmarks.DEFAULT_INDEX,
/* aGuid */ null, this._source);
// This can only be a sub-folder so no need to set a guid here.
folder.title = containerTitle;
break;
case Container_Places:
containerId = PlacesUtils.placesRootId;
folder.guid = PlacesUtils.bookmarks.rootGuid;
break;
case Container_Menu:
containerId = PlacesUtils.bookmarksMenuFolderId;
folder.guid = PlacesUtils.bookmarks.menuGuid;
break;
case Container_Unfiled:
containerId = PlacesUtils.unfiledBookmarksFolderId;
folder.guid = PlacesUtils.bookmarks.unfiledGuid;
break;
case Container_Toolbar:
containerId = PlacesUtils.toolbarFolderId;
folder.guid = PlacesUtils.bookmarks.toolbarGuid;
break;
default:
// NOT REACHED
throw new Error("Unreached");
throw new Error("Unknown bookmark container type!");
}
if (frame.previousDateAdded > 0) {
try {
PlacesUtils.bookmarks.setItemDateAdded(containerId, frame.previousDateAdded, this._source);
} catch (e) {
}
frame.previousDateAdded = 0;
}
if (frame.previousLastModifiedDate > 0) {
try {
PlacesUtils.bookmarks.setItemLastModified(containerId, frame.previousLastModifiedDate, this._source);
} catch (e) {
}
// don't clear last-modified, in case there's a description
frame.folder.children.push(folder);
if (frame.previousDateAdded != null) {
folder.dateAdded = frame.previousDateAdded;
frame.previousDateAdded = null;
}
frame.previousId = containerId;
if (frame.previousLastModifiedDate != null) {
folder.lastModified = frame.previousLastModifiedDate;
frame.previousLastModifiedDate = null;
}
this._frames.push(new Frame(containerId));
if (!folder.hasOwnProperty("dateAdded") &&
folder.hasOwnProperty("lastModified")) {
folder.dateAdded = folder.lastModified;
}
frame.previousItem = folder;
this._frames.push(new Frame(folder));
},
/**
@ -402,27 +413,12 @@ BookmarkImporter.prototype = {
*/
_handleSeparator: function handleSeparator(aElt) {
let frame = this._curFrame;
try {
frame.previousId =
PlacesUtils.bookmarks.insertSeparator(frame.containerId,
PlacesUtils.bookmarks.DEFAULT_INDEX,
/* aGuid */ null,
this._source);
} catch (e) {}
},
/**
* Handles <H1>. We check for the attribute PLACES_ROOT and reset the
* container id if it's found. Otherwise, the default bookmark menu
* root is assumed and imported things will go into the bookmarks menu.
*/
_handleHead1Begin: function handleHead1Begin(aElt) {
if (this._frames.length > 1) {
return;
}
if (aElt.hasAttribute("places_root")) {
this._curFrame.containerId = PlacesUtils.placesRootId;
}
let separator = {
type: PlacesUtils.bookmarks.TYPE_SEPARATOR
};
frame.folder.children.push(separator);
frame.previousItem = separator;
},
/**
@ -503,12 +499,9 @@ BookmarkImporter.prototype = {
_handleLinkBegin: function handleLinkBegin(aElt) {
let frame = this._curFrame;
// Make sure that the feed URIs from previous frames are emptied.
frame.previousFeed = null;
// Make sure that the bookmark id from previous frames are emptied.
frame.previousId = 0;
// mPreviousText will hold link text, clear it.
frame.previousText = "";
frame.previousItem = null;
frame.previousText = ""; // Will hold link text, clear it.
// Get the attributes we care about.
let href = this._safeTrim(aElt.getAttribute("href"));
@ -526,7 +519,7 @@ BookmarkImporter.prototype = {
// For feeds, get the feed URL. If it is invalid, mPreviousFeed will be
// NULL and we'll create it as a normal bookmark.
if (feedUrl) {
frame.previousFeed = NetUtil.newURI(feedUrl);
frame.previousFeed = feedUrl;
}
// Ignore <a> tags that have no href.
@ -534,7 +527,7 @@ BookmarkImporter.prototype = {
// Save the address if it's valid. Note that we ignore errors if this is a
// feed since href is optional for them.
try {
frame.previousLink = NetUtil.newURI(href);
frame.previousLink = Services.io.newURI(href).spec;
} catch (e) {
if (!frame.previousFeed) {
frame.previousLink = null;
@ -550,91 +543,76 @@ BookmarkImporter.prototype = {
}
}
let bookmark = {};
// Only set the url for bookmarks, not for livemarks.
if (frame.previousLink && !frame.previousFeed) {
bookmark.url = frame.previousLink;
}
if (dateAdded) {
bookmark.dateAdded = this._convertImportedDateToInternalDate(dateAdded);
}
// Save bookmark's last modified date.
if (lastModified) {
frame.previousLastModifiedDate =
this._convertImportedDateToInternalDate(lastModified);
bookmark.lastModified = this._convertImportedDateToInternalDate(lastModified);
}
if (!dateAdded && lastModified) {
bookmark.dateAdded = bookmark.lastModified;
}
// If this is a live bookmark, we will handle it in HandleLinkEnd(), so we
// can skip bookmark creation.
if (frame.previousFeed) {
// This is a livemark, we've done all we need to do here, so finish early.
frame.folder.children.push(bookmark);
frame.previousItem = bookmark;
return;
}
// Create the bookmark. The title is unknown for now, we will set it later.
try {
frame.previousId =
PlacesUtils.bookmarks.insertBookmark(frame.containerId,
frame.previousLink,
PlacesUtils.bookmarks.DEFAULT_INDEX,
/* aTitle */ "",
/* aGuid */ null,
this._source);
} catch (e) {
return;
}
// Set the date added value, if we have it.
if (dateAdded) {
try {
PlacesUtils.bookmarks.setItemDateAdded(frame.previousId,
this._convertImportedDateToInternalDate(dateAdded), this._source);
} catch (e) {
}
}
// Adds tags to the URI, if there are any.
if (tags) {
try {
let tagsArray = tags.split(",");
PlacesUtils.tagging.tagURI(frame.previousLink, tagsArray, this._source);
} catch (e) {
bookmark.tags = 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 (!bookmark.tags.length) {
delete bookmark.tags;
}
}
// Save the favicon.
if (icon || iconUri) {
let iconUriObject;
try {
iconUriObject = NetUtil.newURI(iconUri);
} catch (e) {
}
if (icon || iconUriObject) {
try {
this._setFaviconForURI(frame.previousLink, iconUriObject, icon);
} catch (e) {
}
}
}
// Save the keyword.
if (keyword) {
let kwPromise = PlacesUtils.keywords.insert({ keyword,
url: frame.previousLink.spec,
postData,
source: this._source });
this._importPromises.push(kwPromise);
}
// Set load-in-sidebar annotation for the bookmark.
if (webPanel && webPanel.toLowerCase() == "true") {
try {
PlacesUtils.annotations.setItemAnnotation(frame.previousId,
LOAD_IN_SIDEBAR_ANNO,
1,
0,
PlacesUtils.annotations.EXPIRE_NEVER,
this._source);
} catch (e) {
if (!bookmark.hasOwnProperty("annos")) {
bookmark.annos = [];
}
bookmark.annos.push({ "name": LOAD_IN_SIDEBAR_ANNO,
"flags": 0,
"expires": 4,
"value": 1
});
}
// Import last charset.
if (lastCharset) {
let chPromise = PlacesUtils.setCharsetForURI(frame.previousLink, lastCharset, this._source);
this._importPromises.push(chPromise);
bookmark.charset = lastCharset;
}
if (keyword) {
bookmark.keyword = keyword;
}
if (postData) {
bookmark.postData = postData;
}
if (icon) {
bookmark.icon = icon;
}
if (iconUri) {
bookmark.iconUri = iconUri;
}
// Add bookmark to the tree.
frame.folder.children.push(bookmark);
frame.previousItem = bookmark;
},
_handleContainerBegin: function handleContainerBegin() {
@ -651,14 +629,6 @@ BookmarkImporter.prototype = {
if (frame.containerNesting > 0)
frame.containerNesting --;
if (this._frames.length > 1 && frame.containerNesting == 0) {
// we also need to re-set the imported last-modified date here. Otherwise
// the addition of items will override the imported field.
let prevFrame = this._previousFrame;
if (prevFrame.previousLastModifiedDate > 0) {
PlacesUtils.bookmarks.setItemLastModified(frame.containerId,
prevFrame.previousLastModifiedDate,
this._source);
}
this._frames.pop();
}
},
@ -679,43 +649,31 @@ BookmarkImporter.prototype = {
let frame = this._curFrame;
frame.previousText = frame.previousText.trim();
try {
if (frame.previousItem != null) {
if (frame.previousFeed) {
// The is a live bookmark. We create it here since in HandleLinkBegin we
// don't know the title.
let lmPromise = PlacesUtils.livemarks.addLivemark({
"title": frame.previousText,
"parentId": frame.containerId,
"index": PlacesUtils.bookmarks.DEFAULT_INDEX,
"feedURI": frame.previousFeed,
"siteURI": frame.previousLink,
"source": this._source,
if (!frame.previousItem.hasOwnProperty("annos")) {
frame.previousItem.annos = [];
}
frame.previousItem.type = PlacesUtils.bookmarks.TYPE_FOLDER;
frame.previousItem.annos.push({
"name": PlacesUtils.LMANNO_FEEDURI,
"flags": 0,
"expires": 4,
"value": frame.previousFeed
});
this._importPromises.push(lmPromise);
} else if (frame.previousLink) {
// This is a common bookmark.
PlacesUtils.bookmarks.setItemTitle(frame.previousId,
frame.previousText,
this._source);
if (frame.previousLink) {
frame.previousItem.annos.push({
"name": PlacesUtils.LMANNO_SITEURI,
"flags": 0,
"expires": 4,
"value": frame.previousLink
});
}
}
} catch (e) {
}
// Set last modified date as the last change.
if (frame.previousId > 0 && frame.previousLastModifiedDate > 0) {
try {
PlacesUtils.bookmarks.setItemLastModified(frame.previousId,
frame.previousLastModifiedDate,
this._source);
} catch (e) {
}
// Note: don't clear previousLastModifiedDate, because if this item has a
// description, we'll need to set it again.
frame.previousItem.title = frame.previousText;
}
frame.previousText = "";
},
_openContainer: function openContainer(aElt) {
@ -723,9 +681,6 @@ BookmarkImporter.prototype = {
return;
}
switch (aElt.localName) {
case "h1":
this._handleHead1Begin(aElt);
break;
case "h2":
case "h3":
case "h4":
@ -760,45 +715,17 @@ BookmarkImporter.prototype = {
// NOTE ES5 trim trims more than the previous C++ trim.
frame.previousText = frame.previousText.trim(); // important
if (frame.previousText) {
let itemId = !frame.previousLink ? frame.containerId
: frame.previousId;
try {
if (!PlacesUtils.annotations.itemHasAnnotation(itemId, DESCRIPTION_ANNO)) {
PlacesUtils.annotations.setItemAnnotation(itemId,
DESCRIPTION_ANNO,
frame.previousText,
0,
PlacesUtils.annotations.EXPIRE_NEVER,
this._source);
}
} catch (e) {
let item = frame.previousLink ? frame.previousItem : frame.folder;
if (!item.hasOwnProperty("annos")) {
item.annos = [];
}
item.annos.push({
"name": DESCRIPTION_ANNO,
"flags": 0,
"expires": 4,
"value": frame.previousText
});
frame.previousText = "";
// Set last-modified a 2nd time for all items with descriptions
// we need to set last-modified as the *last* step in processing
// any item type in the bookmarks.html file, so that we do
// not overwrite the imported value. for items without descriptions,
// setting this value after setting the item title is that
// last point at which we can save this value before it gets reset.
// for items with descriptions, it must set after that point.
// however, at the point at which we set the title, there's no way
// to determine if there will be a description following,
// so we need to set the last-modified-date at both places.
let lastModified;
if (!frame.previousLink) {
lastModified = this._previousFrame.previousLastModifiedDate;
} else {
lastModified = frame.previousLastModifiedDate;
}
if (itemId > 0 && lastModified > 0) {
PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified,
this._source);
}
}
frame.inDescription = false;
}
@ -893,26 +820,24 @@ BookmarkImporter.prototype = {
},
/**
* Converts a string date in seconds to an int date in microseconds
* Converts a string date in seconds to a date object
*/
_convertImportedDateToInternalDate: function convertImportedDateToInternalDate(aDate) {
if (aDate && !isNaN(aDate)) {
return parseInt(aDate) * 1000000; // in bookmarks.html this value is in seconds, not microseconds
try {
if (aDate && !isNaN(aDate)) {
return new Date(parseInt(aDate) * 1000); // in bookmarks.html this value is in seconds
}
} catch (ex) {
// Do nothing.
}
return Date.now();
return new Date();
},
runBatched: function runBatched(aDoc) {
_walkTreeForImport(aDoc) {
if (!aDoc) {
return;
}
if (this._isImportDefaults) {
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarksMenuFolderId, this._source);
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId, this._source);
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId, this._source);
}
let current = aDoc;
let next;
for (;;) {
@ -944,38 +869,71 @@ BookmarkImporter.prototype = {
}
},
_walkTreeForImport: function walkTreeForImport(aDoc) {
PlacesUtils.bookmarks.runInBatchMode(this, aDoc);
/**
* Returns the bookmark tree(s) from the importer. These are suitable for
* passing to PlacesUtils.bookmarks.insertTree().
*
* @returns {Array} An array of bookmark trees.
*/
_getBookmarkTrees() {
// If we're not importing defaults, then everything gets imported under the
// Bookmarks menu.
if (!this._isImportDefaults) {
return [this._bookmarkTree];
}
// If we are importing defaults, we need to separate out the top-level
// default folders into separate items, for the caller to pass into insertTree.
let bookmarkTrees = [this._bookmarkTree];
// The children of this "root" element will contain normal children of the
// bookmark menu as well as the places roots. Hence, we need to filter out
// the separate roots, but keep the children that are relevant to the
// bookmark menu.
this._bookmarkTree.children = this._bookmarkTree.children.filter(child => {
if (child.guid && PlacesUtils.bookmarks.userContentRoots.includes(child.guid)) {
bookmarkTrees.push(child);
return false;
}
return true;
});
return bookmarkTrees;
},
async importFromURL(href) {
this._importPromises = [];
await new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
xhr.onload = () => {
try {
this._walkTreeForImport(xhr.responseXML);
resolve();
} catch (e) {
reject(e);
}
};
xhr.onabort = xhr.onerror = xhr.ontimeout = () => {
reject(new Error("xmlhttprequest failed"));
};
xhr.open("GET", href);
xhr.responseType = "document";
xhr.overrideMimeType("text/html");
xhr.send();
});
// TODO (bug 1095427) 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;
/**
* Imports the bookmarks from the importer into the places database.
*
* @param {BookmarkImporter} importer The importer from which to get the
* bookmark information.
*/
async _importBookmarks() {
if (this._isImportDefaults) {
await PlacesUtils.bookmarks.eraseEverything();
}
let bookmarksTrees = this._getBookmarkTrees();
for (let tree of bookmarksTrees) {
if (!tree.children.length) {
continue;
}
// Give the tree the source.
tree.source = this._source;
await PlacesUtils.bookmarks.insertTree(tree);
insertFaviconsForTree(tree);
}
},
/**
* Imports data into the places database from the supplied url.
*
* @param {String} href The url to import data from.
*/
async importFromURL(href) {
let data = await fetchData(href);
this._walkTreeForImport(data);
await this._importBookmarks();
},
};
@ -1184,3 +1142,84 @@ BookmarkExporter.prototype = {
this._writeLine(aIndent + "<DD>" + escapeHtmlEntities(descriptionAnno.value));
}
};
/**
* 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);
}
}
}
/**
* Handles fetching data from a URL.
*
* @param {String} href The url to fetch data from.
* @return {Promise} Returns a promise that is resolved with the data once
* the fetch is complete, or is rejected if it fails.
*/
function fetchData(href) {
return new Promise((resolve, reject) => {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
xhr.onload = () => {
resolve(xhr.responseXML);
};
xhr.onabort = xhr.onerror = xhr.ontimeout = () => {
reject(new Error("xmlhttprequest failed"));
};
xhr.open("GET", href);
xhr.responseType = "document";
xhr.overrideMimeType("text/html");
xhr.send();
});
}

Просмотреть файл

@ -1407,7 +1407,8 @@ function insertBookmark(item, parent) {
* @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);
return node.type == Bookmarks.TYPE_FOLDER && node.annos &&
node.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI);
}
function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {

Просмотреть файл

@ -30,6 +30,7 @@
<DL><p>
<DT><A HREF="http://en-US.www.mozilla.com/en-US/firefox/central/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$GvPhC3">Getting Started</A>
<DT><A HREF="http://en-US.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/" LAST_MODIFIED="1177541035" FEEDURL="http://en-US.fxfeeds.mozilla.com/en-US/firefox/headlines.xml" ID="rdf:#$HvPhC3">Latest Headlines</A>
<DT><A LAST_MODIFIED="1177541035" FEEDURL="http://en-US.fxfeeds.mozilla.com/en-US/firefox/headlines.xml" ID="rdf:#$HvPhC3">Latest Headlines No Site</A>
<DD>Livemark test comment
</DL><p>
</DL><p>

Просмотреть файл

@ -126,8 +126,8 @@ async function testMenuBookmarks() {
async function testToolbarBookmarks() {
let root = PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
// child count (add 2 for pre-existing items)
Assert.equal(root.childCount, bookmarkData.length + 2);
// child count (add 3 for pre-existing items)
Assert.equal(root.childCount, bookmarkData.length + 3);
let livemarkNode = root.getChild(1);
Assert.equal("Latest Headlines", livemarkNode.title);
@ -138,11 +138,19 @@ async function testToolbarBookmarks() {
Assert.equal("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
livemark.feedURI.spec);
livemarkNode = root.getChild(2);
Assert.equal("Latest Headlines No Site", livemarkNode.title);
livemark = await PlacesUtils.livemarks.getLivemark({ id: livemarkNode.itemId });
Assert.equal(null, livemark.siteURI);
Assert.equal("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
livemark.feedURI.spec);
// test added bookmark data
let bookmarkNode = root.getChild(2);
let bookmarkNode = root.getChild(3);
Assert.equal(bookmarkNode.uri, bookmarkData[0].uri.spec);
Assert.equal(bookmarkNode.title, bookmarkData[0].title);
bookmarkNode = root.getChild(3);
bookmarkNode = root.getChild(4);
Assert.equal(bookmarkNode.uri, bookmarkData[1].uri.spec);
Assert.equal(bookmarkNode.title, bookmarkData[1].title);

Просмотреть файл

@ -59,6 +59,9 @@ var test_bookmarks = {
{ 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"
},
{ title: "Latest Headlines No Site",
feedUrl: "http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml"
}
],
unfiled: [
@ -78,13 +81,12 @@ add_task(async function setup() {
Services.prefs.setIntPref("browser.places.smartBookmarksVersion", -1);
// File pointer to legacy bookmarks file.
gBookmarksFileOld = do_get_file("bookmarks.preplaces.html");
gBookmarksFileOld = OS.Path.join(do_get_cwd().path, "bookmarks.preplaces.html");
// File pointer to a new Places-exported bookmarks file.
gBookmarksFileNew = Services.dirsvc.get("ProfD", Ci.nsIFile);
gBookmarksFileNew.append("bookmarks.exported.html");
if (gBookmarksFileNew.exists()) {
gBookmarksFileNew.remove(false);
gBookmarksFileNew = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.exported.html");
if (await OS.File.exists(gBookmarksFileNew)) {
await OS.File.remove(gBookmarksFileNew);
}
// This test must be the first one, since it setups the new bookmarks.html.
@ -347,7 +349,9 @@ function checkItem(aExpected, aNode) {
break;
case "feedUrl":
let livemark = await PlacesUtils.livemarks.getLivemark({ id });
do_check_eq(livemark.siteURI.spec, aExpected.url);
if (aExpected.url) {
do_check_eq(livemark.siteURI.spec, aExpected.url);
}
do_check_eq(livemark.feedURI.spec, aExpected.feedUrl);
break;
case "children":