зеркало из https://github.com/mozilla/gecko-dev.git
Bug 968177 - Apply the shared backups code to bookmarks.html export. r=mano
This commit is contained in:
Родитель
12e3d71d6d
Коммит
fbb61eabea
|
@ -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("<", "<", "g")
|
||||
.replace(">", ">", "g")
|
||||
.replace("\"", """, "g")
|
||||
.replace("'", "'", "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("&", "&", "g")
|
||||
.replace("<", "<", "g")
|
||||
.replace(">", ">", "g")
|
||||
.replace("\"", """, "g")
|
||||
.replace("'", "'", "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",
|
||||
|
|
Загрузка…
Ссылка в новой задаче