Bug 968177 - Apply the shared backups code to bookmarks.html export. r=mano

This commit is contained in:
Marco Bonardo 2014-03-03 17:51:42 +01:00
Родитель 12e3d71d6d
Коммит fbb61eabea
8 изменённых файлов: 259 добавлений и 273 удалений

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

@ -1046,13 +1046,13 @@ BrowserGlue.prototype = {
// forced migration (due to a major schema change).
// If the database is corrupt or has been newly created we should
// import bookmarks.
var dbStatus = PlacesUtils.history.databaseStatus;
var importBookmarks = !aInitialMigrationPerformed &&
let dbStatus = PlacesUtils.history.databaseStatus;
let importBookmarks = !aInitialMigrationPerformed &&
(dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE ||
dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT);
// Check if user or an extension has required to import bookmarks.html
var importBookmarksHTML = false;
let importBookmarksHTML = false;
try {
importBookmarksHTML =
Services.prefs.getBoolPref("browser.places.importBookmarksHTML");
@ -1063,7 +1063,7 @@ BrowserGlue.prototype = {
Task.spawn(function() {
// Check if Safe Mode or the user has required to restore bookmarks from
// default profile's bookmarks.html
var restoreDefaultBookmarks = false;
let restoreDefaultBookmarks = false;
try {
restoreDefaultBookmarks =
Services.prefs.getBoolPref("browser.bookmarks.restore_default_bookmarks");
@ -1091,10 +1091,7 @@ BrowserGlue.prototype = {
else {
// We have created a new database but we don't have any backup available
importBookmarks = true;
var dirService = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
var bookmarksHTMLFile = dirService.get("BMarks", Ci.nsILocalFile);
if (bookmarksHTMLFile.exists()) {
if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
// If bookmarks.html is available in current profile import it...
importBookmarksHTML = true;
}
@ -1120,36 +1117,30 @@ BrowserGlue.prototype = {
// An import operation is about to run.
// Don't try to recreate smart bookmarks if autoExportHTML is true or
// smart bookmarks are disabled.
var autoExportHTML = false;
let autoExportHTML = false;
try {
autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
} catch(ex) {}
var smartBookmarksVersion = 0;
let smartBookmarksVersion = 0;
try {
smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion");
} catch(ex) {}
if (!autoExportHTML && smartBookmarksVersion != -1)
Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
// Get bookmarks.html file location
var dirService = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
var bookmarksURI = null;
let bookmarksUrl = null;
if (restoreDefaultBookmarks) {
// User wants to restore bookmarks.html file from default profile folder
bookmarksURI = NetUtil.newURI("resource:///defaults/profile/bookmarks.html");
bookmarksUrl = "resource:///defaults/profile/bookmarks.html";
}
else {
var bookmarksFile = dirService.get("BMarks", Ci.nsILocalFile);
if (bookmarksFile.exists())
bookmarksURI = NetUtil.newURI(bookmarksFile);
else if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
bookmarksUrl = OS.Path.toFileURI(BookmarkHTMLUtils.defaultPath);
}
if (bookmarksURI) {
if (bookmarksUrl) {
// Import from bookmarks.html file.
try {
BookmarkHTMLUtils.importFromURL(bookmarksURI.spec, true).then(null,
BookmarkHTMLUtils.importFromURL(bookmarksUrl, true).then(null,
function onFailure() {
Cu.reportError("Bookmarks.html file could be corrupt.");
}
@ -1240,7 +1231,7 @@ BrowserGlue.prototype = {
// can safely add a profile-before-change blocker.
AsyncShutdown.profileBeforeChange.addBlocker(
"Places: bookmarks.html",
() => BookmarkHTMLUtils.exportToFile(Services.dirsvc.get("BMarks", Ci.nsIFile))
() => BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath)
.then(null, Cu.reportError)
);
}

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

@ -383,7 +383,7 @@ var PlacesOrganizer = {
let fpCallback = function fpCallback_done(aResult) {
if (aResult != Ci.nsIFilePicker.returnCancel) {
Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
BookmarkHTMLUtils.exportToFile(fp.file)
BookmarkHTMLUtils.exportToFile(fp.file.path)
.then(null, Components.utils.reportError);
}
};

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

@ -62,12 +62,19 @@ const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
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/FileUtils.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
"resource://gre/modules/PlacesBackups.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
const Container_Normal = 0;
const Container_Toolbar = 1;
@ -82,13 +89,8 @@ const MICROSEC_PER_SEC = 1000000;
const EXPORT_INDENT = " "; // four spaces
#ifdef XP_WIN
const EXPORT_NEWLINE = "\r\n";
#else
const EXPORT_NEWLINE = "\n";
#endif
let serialNumber = 0; // for favicons
// Counter used to build fake favicon urls.
let serialNumber = 0;
function base64EncodeString(aString) {
let stream = Cc["@mozilla.org/io/string-input-stream;1"]
@ -99,6 +101,18 @@ function base64EncodeString(aString) {
return encoder.encodeToString(stream, aString.length);
}
/**
* Provides HTML escaping for use in HTML attributes and body of the bookmarks
* file, compatible with the old bookmarks system.
*/
function escapeHtmlEntities(aText) {
return (aText || "").replace("&", "&", "g")
.replace("<", "&lt;", "g")
.replace(">", "&gt;", "g")
.replace("\"", "&quot;", "g")
.replace("'", "&#39;", "g");
}
this.BookmarkHTMLUtils = Object.freeze({
/**
* Loads the current bookmarks hierarchy from a "bookmarks.html" file.
@ -138,17 +152,47 @@ this.BookmarkHTMLUtils = Object.freeze({
/**
* Saves the current bookmarks hierarchy to a "bookmarks.html" file.
*
* @param aLocalFile
* nsIFile for the "bookmarks.html" file to be created.
* @param aFilePath
* OS.File path string for the "bookmarks.html" file to be created.
*
* @return {Promise}
* @resolves When the file has been created.
* @resolves To the exported bookmarks count when the file has been created.
* @rejects JavaScript exception.
* @deprecated passing an nsIFile is deprecated
*/
exportToFile: function BHU_exportToFile(aLocalFile) {
let exporter = new BookmarkExporter();
return exporter.exportToFile(aLocalFile);
exportToFile: function BHU_exportToFile(aFilePath) {
if (aFilePath instanceof Ci.nsIFile) {
Deprecated.warning("Passing an nsIFile to BookmarksHTMLUtils.exportToFile " +
"is deprecated. Please use an OS.File path string instead.",
"https://developer.mozilla.org/docs/JavaScript_OS.File");
aFilePath = aFilePath.path;
}
return Task.spawn(function* () {
let [bookmarks, count] = yield PlacesBackups.getBookmarksTree();
let startTime = Date.now();
// Report the time taken to convert the tree to HTML.
let exporter = new BookmarkExporter(bookmarks);
yield exporter.exportToFile(aFilePath);
try {
Services.telemetry
.getHistogramById("PLACES_EXPORT_TOHTML_MS")
.add(Date.now() - startTime);
} catch (ex) {
Components.utils.reportError("Unable to report telemetry.");
}
return count;
});
},
get defaultPath() {
try {
return Services.prefs.getCharPref("browser.bookmarks.file");
} catch (ex) {}
return OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.html")
}
});
function Frame(aFrameId) {
@ -877,299 +921,212 @@ BookmarkImporter.prototype = {
};
function BookmarkExporter() { }
function BookmarkExporter(aBookmarksTree) {
// Create a map of the roots.
let rootsMap = new Map();
for (let child of aBookmarksTree.children) {
if (child.root)
rootsMap.set(child.root, child);
}
// For backwards compatibility reasons the bookmarks menu is the root, while
// the bookmarks toolbar and unfiled bookmarks will be child items.
this._root = rootsMap.get("bookmarksMenuFolder");
for (let key of [ "toolbarFolder", "unfiledBookmarksFolder" ]) {
let root = rootsMap.get(key);
if (root.children && root.children.length > 0) {
if (!this._root.children)
this._root.children = [];
this._root.children.push(root);
}
}
}
BookmarkExporter.prototype = {
/**
* Provides HTML escaping for use in HTML attributes and body of the bookmarks
* file, compatible with the old bookmarks system.
*/
escapeHtml: function escapeHtml(aText) {
return (aText || "").replace("&", "&amp;", "g")
.replace("<", "&lt;", "g")
.replace(">", "&gt;", "g")
.replace("\"", "&quot;", "g")
.replace("'", "&#39;", "g");
},
/**
* Provides URL escaping for use in HTML attributes of the bookmarks file,
* compatible with the old bookmarks system.
*/
escapeUrl: function escapeUrl(aText) {
return (aText || "").replace("\"", "%22", "g");
},
exportToFile: function exportToFile(aLocalFile) {
return Task.spawn(this._doExportToFile(aLocalFile));
},
_doExportToFile: function doExportToFile(aLocalFile) {
// Create a file that can be accessed by the current user only.
let safeFileOut = Cc["@mozilla.org/network/safe-file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
safeFileOut.init(aLocalFile,
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE
| FileUtils.MODE_TRUNCATE,
parseInt("0600", 8), 0);
try {
// We need a buffered output stream for performance. See bug 202477.
let bufferedOut = Cc["@mozilla.org/network/buffered-output-stream;1"]
.createInstance(Ci.nsIBufferedOutputStream);
bufferedOut.init(safeFileOut, 4096);
exportToFile: function exportToFile(aFilePath) {
return Task.spawn(function* () {
// Create a file that can be accessed by the current user only.
let out = FileUtils.openAtomicFileOutputStream(new FileUtils.File(aFilePath));
try {
// Write bookmarks in UTF-8.
this._converterOut = Cc["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
this._converterOut.init(bufferedOut, "utf-8", 0, 0);
// We need a buffered output stream for performance. See bug 202477.
let bufferedOut = Cc["@mozilla.org/network/buffered-output-stream;1"]
.createInstance(Ci.nsIBufferedOutputStream);
bufferedOut.init(out, 4096);
try {
yield this._doExport();
// Flush the buffer and retain the target file on success only.
bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
// Write bookmarks in UTF-8.
this._converterOut = Cc["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
this._converterOut.init(bufferedOut, "utf-8", 0, 0);
try {
this._writeHeader();
yield this._writeContainer(this._root);
// Retain the target file on success only.
bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
} finally {
this._converterOut.close();
this._converterOut = null;
}
} finally {
this._converterOut.close();
this._converterOut = null;
bufferedOut.close();
}
} finally {
bufferedOut.close();
out.close();
}
} finally {
safeFileOut.close();
}
}.bind(this));
},
_converterOut: null,
_write: function write(aText) {
_write: function (aText) {
this._converterOut.writeString(aText || "");
},
_writeLine: function writeLine(aText) {
this._write(aText + EXPORT_NEWLINE);
_writeAttribute: function (aName, aValue) {
this._write(' ' + aName + '="' + aValue + '"');
},
_doExport: function doExport() {
_writeLine: function (aText) {
this._write(aText + "\n");
},
_writeHeader: function () {
this._writeLine("<!DOCTYPE NETSCAPE-Bookmark-file-1>");
this._writeLine("<!-- This is an automatically generated file.");
this._writeLine(" It will be read and overwritten.");
this._writeLine(" DO NOT EDIT! -->");
this._writeLine("<META HTTP-EQUIV=\"Content-Type\"" +
" CONTENT=\"text/html; charset=UTF-8\">");
this._writeLine('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; ' +
'charset=UTF-8">');
this._writeLine("<TITLE>Bookmarks</TITLE>");
},
// Write the Bookmarks Menu as the outer container.
let root = PlacesUtils.getFolderContents(
PlacesUtils.bookmarksMenuFolderId).root;
try {
this._writeLine("<H1>" + this.escapeHtml(root.title) + "</H1>");
_writeContainer: function (aItem, aIndent = "") {
if (aItem == this._root) {
this._writeLine("<H1>" + escapeHtmlEntities(this._root.title) + "</H1>");
this._writeLine("");
this._writeLine("<DL><p>");
yield this._writeContainerContents(root, "");
} finally {
root.containerOpen = false;
}
else {
this._write(aIndent + "<DT><H3");
this._writeDateAttributes(aItem);
if (aItem.root === "toolbarFolder")
this._writeAttribute("PERSONAL_TOOLBAR_FOLDER", "true");
else if (aItem.root === "unfiledBookmarksFolder")
this._writeAttribute("UNFILED_BOOKMARKS_FOLDER", "true");
this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</H3>");
}
// Write the Bookmarks Toolbar as a child item for backwards compatibility.
root = PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
try {
if (root.childCount > 0) {
yield this._writeContainer(root, EXPORT_INDENT);
}
} finally {
root.containerOpen = false;
}
this._writeDescription(aItem, aIndent);
// Write the Unfiled Bookmarks as a child item for backwards compatibility.
root = PlacesUtils.getFolderContents(
PlacesUtils.unfiledBookmarksFolderId).root;
try {
if (root.childCount > 0) {
yield this._writeContainer(root, EXPORT_INDENT);
}
} finally {
root.containerOpen = false;
}
this._writeLine("</DL><p>");
},
_writeContainer: function writeContainer(aItem, aIndent) {
this._write(aIndent + "<DT><H3");
yield this._writeDateAttributes(aItem);
if (aItem.itemId == PlacesUtils.placesRootId) {
this._write(" PLACES_ROOT=\"true\"");
} else if (aItem.itemId == PlacesUtils.bookmarksMenuFolderId) {
this._write(" BOOKMARKS_MENU=\"true\"");
} else if (aItem.itemId == PlacesUtils.unfiledBookmarksFolderId) {
this._write(" UNFILED_BOOKMARKS_FOLDER=\"true\"");
} else if (aItem.itemId == PlacesUtils.toolbarFolderId) {
this._write(" PERSONAL_TOOLBAR_FOLDER=\"true\"");
}
this._writeLine(">" + this.escapeHtml(aItem.title) + "</H3>");
yield this._writeDescription(aItem);
this._writeLine(aIndent + "<DL><p>");
yield this._writeContainerContents(aItem, aIndent);
this._writeLine(aIndent + "</DL><p>");
if (aItem.children)
yield this._writeContainerContents(aItem, aIndent);
if (aItem == this._root)
this._writeLine(aIndent + "</DL>");
else
this._writeLine(aIndent + "</DL><p>");
},
_writeContainerContents: function writeContainerContents(aItem, aIndent) {
_writeContainerContents: function (aItem, aIndent) {
let localIndent = aIndent + EXPORT_INDENT;
for (let i = 0; i < aItem.childCount; ++i) {
let child = aItem.getChild(i);
if (child.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER) {
// Since the livemarks service is asynchronous, use the annotations
// service to get the information for now.
if (PlacesUtils.annotations
.itemHasAnnotation(child.itemId,
PlacesUtils.LMANNO_FEEDURI)) {
yield this._writeLivemark(child, localIndent);
} else {
// This is a normal folder, open it.
PlacesUtils.asContainer(child).containerOpen = true;
try {
yield this._writeContainer(child, localIndent);
} finally {
child.containerOpen = false;
}
}
} else if (child.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
yield this._writeSeparator(child, localIndent);
} else {
for (let child of aItem.children) {
if (child.annos && child.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI))
this._writeLivemark(child, localIndent);
else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
yield this._writeContainer(child, localIndent);
else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR)
this._writeSeparator(child, localIndent);
else
yield this._writeItem(child, localIndent);
}
}
},
_writeSeparator: function writeSeparator(aItem, aIndent) {
_writeSeparator: function (aItem, aIndent) {
this._write(aIndent + "<HR");
// We keep exporting separator titles, but don't support them anymore.
let title = null;
try {
title = PlacesUtils.bookmarks.getItemTitle(aItem.itemId);
} catch (ex) { }
if (title) {
this._write(" NAME=\"" + this.escapeHtml(title) + "\"");
}
if (aItem.title)
this._writeAttribute("NAME", escapeHtmlEntities(aItem.title));
this._write(">");
},
_writeLivemark: function writeLivemark(aItem, aIndent) {
_writeLivemark: function (aItem, aIndent) {
this._write(aIndent + "<DT><A");
let feedSpec = PlacesUtils.annotations
.getItemAnnotation(aItem.itemId,
PlacesUtils.LMANNO_FEEDURI);
this._write(" FEEDURL=\"" + this.escapeUrl(feedSpec) + "\"");
// The site URI is optional.
try {
let siteSpec = PlacesUtils.annotations
.getItemAnnotation(aItem.itemId,
PlacesUtils.LMANNO_SITEURI);
if (siteSpec) {
this._write(" HREF=\"" + this.escapeUrl(siteSpec) + "\"");
}
} catch (ex) { }
this._writeLine(">" + this.escapeHtml(aItem.title) + "</A>");
yield this._writeDescription(aItem);
let feedSpec = aItem.annos.find(anno => anno.name == PlacesUtils.LMANNO_FEEDURI).value;
this._writeAttribute("FEEDURL", encodeURI(feedSpec));
let siteSpecAnno = aItem.annos.find(anno => anno.name == PlacesUtils.LMANNO_SITEURI);
if (siteSpecAnno)
this._writeAttribute("HREF", encodeURI(siteSpecAnno.value));
this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
this._writeDescription(aItem, aIndent);
},
_writeItem: function writeItem(aItem, aIndent) {
let itemUri = null;
_writeItem: function (aItem, aIndent) {
let uri = null;
try {
itemUri = NetUtil.newURI(aItem.uri);
uri = NetUtil.newURI(aItem.uri);
} catch (ex) {
// If the item URI is invalid, skip the item instead of failing later.
return;
}
this._write(aIndent + "<DT><A HREF=\"" + this.escapeUrl(aItem.uri) + "\"");
yield this._writeDateAttributes(aItem);
yield this._writeFaviconAttribute(itemUri);
this._write(aIndent + "<DT><A");
this._writeAttribute("HREF", encodeURI(aItem.uri));
this._writeDateAttributes(aItem);
yield this._writeFaviconAttribute(aItem);
let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(aItem.itemId);
if (keyword) {
this._write(" SHORTCUTURL=\"" + this.escapeHtml(keyword) + "\"");
}
let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(aItem.id);
if (aItem.keyword)
this._writeAttribute("SHORTCUTURL", escapeHtmlEntities(keyword));
if (PlacesUtils.annotations.itemHasAnnotation(aItem.itemId,
PlacesUtils.POST_DATA_ANNO)) {
let postData = PlacesUtils.annotations
.getItemAnnotation(aItem.itemId,
PlacesUtils.POST_DATA_ANNO);
this._write(" POST_DATA=\"" + this.escapeHtml(postData) + "\"");
}
let postDataAnno = aItem.annos &&
aItem.annos.find(anno => anno.name == PlacesUtils.POST_DATA_ANNO);
if (postDataAnno)
this._writeAttribute("POST_DATA", escapeHtmlEntities(postDataAnno.value));
if (PlacesUtils.annotations.itemHasAnnotation(aItem.itemId,
LOAD_IN_SIDEBAR_ANNO)) {
this._write(" WEB_PANEL=\"true\"");
}
if (aItem.annos && aItem.annos.some(anno => anno.name == LOAD_IN_SIDEBAR_ANNO))
this._writeAttribute("WEB_PANEL", "true");
if (aItem.charset)
this._writeAttribute("LAST_CHARSET", escapeHtmlEntities(aItem.charset));
if (aItem.tags)
this._writeAttribute("TAGS", aItem.tags);
this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
this._writeDescription(aItem, aIndent);
},
_writeDateAttributes: function (aItem) {
if (aItem.dateAdded)
this._writeAttribute("ADD_DATE",
Math.floor(aItem.dateAdded / MICROSEC_PER_SEC));
if (aItem.lastModified)
this._writeAttribute("LAST_MODIFIED",
Math.floor(aItem.lastModified / MICROSEC_PER_SEC));
},
_writeFaviconAttribute: function (aItem) {
if (!aItem.iconuri)
return;
let favicon;
try {
let lastCharset = yield PlacesUtils.getCharsetForURI(itemUri);
if (lastCharset) {
this._write(" LAST_CHARSET=\"" + this.escapeHtml(lastCharset) + "\"");
}
} catch(ex) { }
this._writeLine(">" + this.escapeHtml(aItem.title) + "</A>");
yield this._writeDescription(aItem);
},
_writeDateAttributes: function writeDateAttributes(aItem) {
if (aItem.dateAdded) {
this._write(" ADD_DATE=\"" +
Math.floor(aItem.dateAdded / MICROSEC_PER_SEC) + "\"");
}
if (aItem.lastModified) {
this._write(" LAST_MODIFIED=\"" +
Math.floor(aItem.lastModified / MICROSEC_PER_SEC) + "\"");
}
},
_writeFaviconAttribute: function writeFaviconAttribute(aItemUri) {
let [faviconURI, dataLen, data] = yield this._promiseFaviconData(aItemUri);
if (!faviconURI) {
// Skip in case of errors.
favicon = yield PlacesUtils.promiseFaviconData(aItem.uri);
} catch (ex) {
Components.utils.reportError("Unexpected Error trying to fetch icon data");
return;
}
this._write(" ICON_URI=\"" + this.escapeUrl(faviconURI.spec) + "\"");
this._writeAttribute("ICON_URI", encodeURI(favicon.uri.spec));
if (!faviconURI.schemeIs("chrome") && dataLen > 0) {
if (!favicon.uri.schemeIs("chrome") && favicon.dataLen > 0) {
let faviconContents = "data:image/png;base64," +
base64EncodeString(String.fromCharCode.apply(String, data));
this._write(" ICON=\"" + faviconContents + "\"");
}
},
_promiseFaviconData: function(aPageURI) {
var deferred = Promise.defer();
PlacesUtils.favicons.getFaviconDataForPage(aPageURI,
function (aURI, aDataLen, aData, aMimeType) {
deferred.resolve([aURI, aDataLen, aData, aMimeType]);
});
return deferred.promise;
},
_writeDescription: function writeDescription(aItem) {
if (PlacesUtils.annotations.itemHasAnnotation(aItem.itemId,
DESCRIPTION_ANNO)) {
let description = PlacesUtils.annotations
.getItemAnnotation(aItem.itemId,
DESCRIPTION_ANNO);
// The description is not indented.
this._writeLine("<DD>" + this.escapeHtml(description));
base64EncodeString(String.fromCharCode.apply(String, favicon.data));
this._writeAttribute("ICON", faviconContents);
}
},
_writeDescription: function (aItem, aIndent) {
let descriptionAnno = aItem.annos &&
aItem.annos.find(anno => anno.name == DESCRIPTION_ANNO);
if (descriptionAnno)
this._writeLine(aIndent + "<DD>" + escapeHtmlEntities(descriptionAnno.value));
}
};

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

@ -449,6 +449,7 @@ this.PlacesBackups = {
* The following properties exist only for a subset of bookmarks:
* * annos: array of annotations
* * uri: url
* * iconuri: favicon's url
* * keyword: associated keyword
* * charset: last known charset
* * tags: csv string of tags
@ -464,7 +465,8 @@ this.PlacesBackups = {
try {
rows = yield conn.execute(
"SELECT b.id, h.url, IFNULL(b.title, '') AS title, b.parent, " +
"b.position AS [index], b.type, b.dateAdded, b.lastModified, b.guid, " +
"b.position AS [index], b.type, b.dateAdded, b.lastModified, " +
"b.guid, f.url AS iconuri, " +
"( SELECT GROUP_CONCAT(t.title, ',') " +
"FROM moz_bookmarks b2 " +
"JOIN moz_bookmarks t ON t.id = +b2.parent AND t.parent = :tags_folder " +
@ -478,6 +480,7 @@ this.PlacesBackups = {
"FROM moz_bookmarks b " +
"LEFT JOIN moz_bookmarks p ON p.id = b.parent " +
"LEFT JOIN moz_places h ON h.id = b.fk " +
"LEFT JOIN moz_favicons f ON f.id = h.favicon_id " +
"WHERE b.id <> :tags_folder AND b.parent <> :tags_folder AND p.parent <> :tags_folder " +
"ORDER BY b.parent, b.position",
{ tags_folder: PlacesUtils.tagsFolderId,
@ -601,6 +604,9 @@ function sqliteRowToBookmarkObject(aRow) {
let tags = aRow.getResultByName("tags");
if (tags)
bookmark.tags = tags;
let iconuri = aRow.getResultByName("iconuri");
if (iconuri)
bookmark.iconuri = iconuri;
break;
case Ci.nsINavBookmarksService.TYPE_FOLDER:
bookmark.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;

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

@ -1776,6 +1776,30 @@ this.PlacesUtils = {
});
return deferred.promise;
},
/**
* Gets favicon data for a given page url.
*
* @param aPageUrl url of the page to look favicon for.
* @resolves to an object representing a favicon entry, having the following
* properties: { uri, dataLen, data, mimeType }
* @rejects JavaScript exception if the given url has no associated favicon.
*/
promiseFaviconData: function (aPageUrl) {
let deferred = Promise.defer();
PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(aPageUrl),
function (aURI, aDataLen, aData, aMimeType) {
if (aURI) {
deferred.resolve({ uri: aURI,
dataLen: aDataLen,
data: aData,
mimeType: aMimeType });
} else {
deferred.reject();
}
});
return deferred.promise;
}
};

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

@ -60,6 +60,7 @@ if CONFIG['MOZ_PLACES']:
]
EXTRA_JS_MODULES = [
'BookmarkHTMLUtils.jsm',
'BookmarkJSONUtils.jsm',
'ClusterLib.js',
'ColorAnalyzer_worker.js',
@ -69,7 +70,6 @@ if CONFIG['MOZ_PLACES']:
]
EXTRA_PP_JS_MODULES += [
'BookmarkHTMLUtils.jsm',
'PlacesUtils.jsm',
]

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

@ -7,7 +7,6 @@ const CURRENT_SCHEMA_VERSION = 23;
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
const NS_APP_BOOKMARKS_50_FILE = "BMarks";
// Shortcuts to transitions type.
const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;

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

@ -2836,6 +2836,15 @@
"extended_statistics_ok": true,
"description": "PLACES: Time to convert and write the backup"
},
"PLACES_EXPORT_TOHTML_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 50,
"high": 2000,
"n_buckets": 10,
"extended_statistics_ok": true,
"description": "PLACES: Time to convert and write bookmarks.html"
},
"FENNEC_FAVICONS_COUNT": {
"expires_in_version": "never",
"kind": "exponential",