зеркало из https://github.com/mozilla/gecko-dev.git
581 строка
16 KiB
JavaScript
581 строка
16 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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/. */
|
|
|
|
"use strict";
|
|
|
|
const { AppConstants } = ChromeUtils.import(
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
const { MigrationUtils, MigratorPrototype } = ChromeUtils.import(
|
|
"resource:///modules/MigrationUtils.jsm"
|
|
);
|
|
const { MSMigrationUtils } = ChromeUtils.import(
|
|
"resource:///modules/MSMigrationUtils.jsm"
|
|
);
|
|
const lazy = {};
|
|
ChromeUtils.defineModuleGetter(
|
|
lazy,
|
|
"PlacesUtils",
|
|
"resource://gre/modules/PlacesUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
lazy,
|
|
"PlacesUIUtils",
|
|
"resource:///modules/PlacesUIUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
lazy,
|
|
"ESEDBReader",
|
|
"resource:///modules/ESEDBReader.jsm"
|
|
);
|
|
|
|
const kEdgeRegistryRoot =
|
|
"SOFTWARE\\Classes\\Local Settings\\Software\\" +
|
|
"Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\" +
|
|
"microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge";
|
|
const kEdgeDatabasePath = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
|
|
|
|
XPCOMUtils.defineLazyGetter(lazy, "gEdgeDatabase", function() {
|
|
let edgeDir = MSMigrationUtils.getEdgeLocalDataFolder();
|
|
if (!edgeDir) {
|
|
return null;
|
|
}
|
|
edgeDir.appendRelativePath(kEdgeDatabasePath);
|
|
if (!edgeDir.exists() || !edgeDir.isReadable() || !edgeDir.isDirectory()) {
|
|
return null;
|
|
}
|
|
let expectedLocation = edgeDir.clone();
|
|
expectedLocation.appendRelativePath(
|
|
"nouser1\\120712-0049\\DBStore\\spartan.edb"
|
|
);
|
|
if (
|
|
expectedLocation.exists() &&
|
|
expectedLocation.isReadable() &&
|
|
expectedLocation.isFile()
|
|
) {
|
|
expectedLocation.normalize();
|
|
return expectedLocation;
|
|
}
|
|
// We used to recurse into arbitrary subdirectories here, but that code
|
|
// went unused, so it likely isn't necessary, even if we don't understand
|
|
// where the magic folders above come from, they seem to be the same for
|
|
// everyone. Just return null if they're not there:
|
|
return null;
|
|
});
|
|
|
|
/**
|
|
* Get rows from a table in the Edge DB as an array of JS objects.
|
|
*
|
|
* @param {String} tableName the name of the table to read.
|
|
* @param {String[]|function} columns a list of column specifiers
|
|
* (see ESEDBReader.jsm) or a function that
|
|
* generates them based on the database
|
|
* reference once opened.
|
|
* @param {nsIFile} dbFile the database file to use. Defaults to
|
|
* the main Edge database.
|
|
* @param {function} filterFn Optional. A function that is called for each row.
|
|
* Only rows for which it returns a truthy
|
|
* value are included in the result.
|
|
* @returns {Array} An array of row objects.
|
|
*/
|
|
function readTableFromEdgeDB(
|
|
tableName,
|
|
columns,
|
|
dbFile = lazy.gEdgeDatabase,
|
|
filterFn = null
|
|
) {
|
|
let database;
|
|
let rows = [];
|
|
try {
|
|
let logFile = dbFile.parent;
|
|
logFile.append("LogFiles");
|
|
database = lazy.ESEDBReader.openDB(dbFile.parent, dbFile, logFile);
|
|
|
|
if (typeof columns == "function") {
|
|
columns = columns(database);
|
|
}
|
|
|
|
let tableReader = database.tableItems(tableName, columns);
|
|
for (let row of tableReader) {
|
|
if (!filterFn || filterFn(row)) {
|
|
rows.push(row);
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
Cu.reportError(
|
|
"Failed to extract items from table " +
|
|
tableName +
|
|
" in Edge database at " +
|
|
dbFile.path +
|
|
" due to the following error: " +
|
|
ex
|
|
);
|
|
// Deliberately make this fail so we expose failure in the UI:
|
|
throw ex;
|
|
} finally {
|
|
if (database) {
|
|
lazy.ESEDBReader.closeDB(database);
|
|
}
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
function EdgeTypedURLMigrator() {}
|
|
|
|
EdgeTypedURLMigrator.prototype = {
|
|
type: MigrationUtils.resourceTypes.HISTORY,
|
|
|
|
get _typedURLs() {
|
|
if (!this.__typedURLs) {
|
|
this.__typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
|
|
}
|
|
return this.__typedURLs;
|
|
},
|
|
|
|
get exists() {
|
|
return this._typedURLs.size > 0;
|
|
},
|
|
|
|
migrate(aCallback) {
|
|
let typedURLs = this._typedURLs;
|
|
let pageInfos = [];
|
|
for (let [urlString, time] of typedURLs) {
|
|
let url;
|
|
try {
|
|
url = new URL(urlString);
|
|
if (!["http:", "https:", "ftp:"].includes(url.protocol)) {
|
|
continue;
|
|
}
|
|
} catch (ex) {
|
|
Cu.reportError(ex);
|
|
continue;
|
|
}
|
|
|
|
pageInfos.push({
|
|
url,
|
|
visits: [
|
|
{
|
|
transition: lazy.PlacesUtils.history.TRANSITIONS.TYPED,
|
|
date: time ? lazy.PlacesUtils.toDate(time) : new Date(),
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
if (!pageInfos.length) {
|
|
aCallback(typedURLs.size == 0);
|
|
return;
|
|
}
|
|
|
|
MigrationUtils.insertVisitsWrapper(pageInfos).then(
|
|
() => aCallback(true),
|
|
() => aCallback(false)
|
|
);
|
|
},
|
|
};
|
|
|
|
function EdgeTypedURLDBMigrator() {}
|
|
|
|
EdgeTypedURLDBMigrator.prototype = {
|
|
type: MigrationUtils.resourceTypes.HISTORY,
|
|
|
|
get db() {
|
|
return lazy.gEdgeDatabase;
|
|
},
|
|
|
|
get exists() {
|
|
return !!this.db;
|
|
},
|
|
|
|
migrate(callback) {
|
|
this._migrateTypedURLsFromDB().then(
|
|
() => callback(true),
|
|
ex => {
|
|
Cu.reportError(ex);
|
|
callback(false);
|
|
}
|
|
);
|
|
},
|
|
|
|
async _migrateTypedURLsFromDB() {
|
|
if (await lazy.ESEDBReader.dbLocked(this.db)) {
|
|
throw new Error("Edge seems to be running - its database is locked.");
|
|
}
|
|
let columns = [
|
|
{ name: "URL", type: "string" },
|
|
{ name: "AccessDateTimeUTC", type: "date" },
|
|
];
|
|
|
|
let typedUrls = [];
|
|
try {
|
|
typedUrls = readTableFromEdgeDB("TypedUrls", columns, this.db);
|
|
} catch (ex) {
|
|
// Maybe the table doesn't exist (older versions of Win10).
|
|
// Just fall through and we'll return because there's no data.
|
|
// The `readTableFromEdgeDB` helper will report errors to the
|
|
// console anyway.
|
|
}
|
|
if (!typedUrls.length) {
|
|
return;
|
|
}
|
|
|
|
let pageInfos = [];
|
|
// Sometimes the values are bogus (e.g. 0 becomes some date in 1600),
|
|
// and places will throw *everything* away, not just the bogus ones,
|
|
// so deal with that by having a cutoff date. Also, there's not much
|
|
// point importing really old entries. The cut-off date is related to
|
|
// Edge's launch date.
|
|
const kDateCutOff = new Date("2016", 0, 1);
|
|
for (let typedUrlInfo of typedUrls) {
|
|
try {
|
|
let url = new URL(typedUrlInfo.URL);
|
|
if (!["http:", "https:", "ftp:"].includes(url.protocol)) {
|
|
continue;
|
|
}
|
|
|
|
let date = typedUrlInfo.AccessDateTimeUTC;
|
|
if (!date) {
|
|
date = kDateCutOff;
|
|
} else if (date < kDateCutOff) {
|
|
continue;
|
|
}
|
|
|
|
pageInfos.push({
|
|
url,
|
|
visits: [
|
|
{
|
|
transition: lazy.PlacesUtils.history.TRANSITIONS.TYPED,
|
|
date,
|
|
},
|
|
],
|
|
});
|
|
} catch (ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
}
|
|
await MigrationUtils.insertVisitsWrapper(pageInfos);
|
|
},
|
|
};
|
|
|
|
function EdgeReadingListMigrator(dbOverride) {
|
|
this.dbOverride = dbOverride;
|
|
}
|
|
|
|
EdgeReadingListMigrator.prototype = {
|
|
type: MigrationUtils.resourceTypes.BOOKMARKS,
|
|
|
|
get db() {
|
|
return this.dbOverride || lazy.gEdgeDatabase;
|
|
},
|
|
|
|
get exists() {
|
|
return !!this.db;
|
|
},
|
|
|
|
migrate(callback) {
|
|
this._migrateReadingList(lazy.PlacesUtils.bookmarks.menuGuid).then(
|
|
() => callback(true),
|
|
ex => {
|
|
Cu.reportError(ex);
|
|
callback(false);
|
|
}
|
|
);
|
|
},
|
|
|
|
async _migrateReadingList(parentGuid) {
|
|
if (await lazy.ESEDBReader.dbLocked(this.db)) {
|
|
throw new Error("Edge seems to be running - its database is locked.");
|
|
}
|
|
let columnFn = db => {
|
|
let columns = [
|
|
{ name: "URL", type: "string" },
|
|
{ name: "Title", type: "string" },
|
|
{ name: "AddedDate", type: "date" },
|
|
];
|
|
|
|
// Later versions have an IsDeleted column:
|
|
let isDeletedColumn = db.checkForColumn("ReadingList", "IsDeleted");
|
|
if (
|
|
isDeletedColumn &&
|
|
isDeletedColumn.dbType == lazy.ESEDBReader.COLUMN_TYPES.JET_coltypBit
|
|
) {
|
|
columns.push({ name: "IsDeleted", type: "boolean" });
|
|
}
|
|
return columns;
|
|
};
|
|
|
|
let filterFn = row => {
|
|
return !row.IsDeleted;
|
|
};
|
|
|
|
let readingListItems = readTableFromEdgeDB(
|
|
"ReadingList",
|
|
columnFn,
|
|
this.db,
|
|
filterFn
|
|
);
|
|
if (!readingListItems.length) {
|
|
return;
|
|
}
|
|
|
|
let destFolderGuid = await this._ensureReadingListFolder(parentGuid);
|
|
let bookmarks = [];
|
|
for (let item of readingListItems) {
|
|
let dateAdded = item.AddedDate || new Date();
|
|
// Avoid including broken URLs:
|
|
try {
|
|
new URL(item.URL);
|
|
} catch (ex) {
|
|
continue;
|
|
}
|
|
bookmarks.push({ url: item.URL, title: item.Title, dateAdded });
|
|
}
|
|
await MigrationUtils.insertManyBookmarksWrapper(bookmarks, destFolderGuid);
|
|
},
|
|
|
|
async _ensureReadingListFolder(parentGuid) {
|
|
if (!this.__readingListFolderGuid) {
|
|
let folderTitle = await MigrationUtils.getLocalizedString(
|
|
"imported-edge-reading-list"
|
|
);
|
|
let folderSpec = {
|
|
type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
parentGuid,
|
|
title: folderTitle,
|
|
};
|
|
this.__readingListFolderGuid = (
|
|
await MigrationUtils.insertBookmarkWrapper(folderSpec)
|
|
).guid;
|
|
}
|
|
return this.__readingListFolderGuid;
|
|
},
|
|
};
|
|
|
|
function EdgeBookmarksMigrator(dbOverride) {
|
|
this.dbOverride = dbOverride;
|
|
}
|
|
|
|
EdgeBookmarksMigrator.prototype = {
|
|
type: MigrationUtils.resourceTypes.BOOKMARKS,
|
|
|
|
get db() {
|
|
return this.dbOverride || lazy.gEdgeDatabase;
|
|
},
|
|
|
|
get TABLE_NAME() {
|
|
return "Favorites";
|
|
},
|
|
|
|
get exists() {
|
|
if (!("_exists" in this)) {
|
|
this._exists = !!this.db;
|
|
}
|
|
return this._exists;
|
|
},
|
|
|
|
migrate(callback) {
|
|
this._migrateBookmarks().then(
|
|
() => callback(true),
|
|
ex => {
|
|
Cu.reportError(ex);
|
|
callback(false);
|
|
}
|
|
);
|
|
},
|
|
|
|
async _migrateBookmarks() {
|
|
if (await lazy.ESEDBReader.dbLocked(this.db)) {
|
|
throw new Error("Edge seems to be running - its database is locked.");
|
|
}
|
|
let { toplevelBMs, toolbarBMs } = this._fetchBookmarksFromDB();
|
|
if (toplevelBMs.length) {
|
|
let parentGuid = lazy.PlacesUtils.bookmarks.menuGuid;
|
|
await MigrationUtils.insertManyBookmarksWrapper(toplevelBMs, parentGuid);
|
|
}
|
|
if (toolbarBMs.length) {
|
|
let parentGuid = lazy.PlacesUtils.bookmarks.toolbarGuid;
|
|
await MigrationUtils.insertManyBookmarksWrapper(toolbarBMs, parentGuid);
|
|
lazy.PlacesUIUtils.maybeToggleBookmarkToolbarVisibilityAfterMigration();
|
|
}
|
|
},
|
|
|
|
_fetchBookmarksFromDB() {
|
|
let folderMap = new Map();
|
|
let columns = [
|
|
{ name: "URL", type: "string" },
|
|
{ name: "Title", type: "string" },
|
|
{ name: "DateUpdated", type: "date" },
|
|
{ name: "IsFolder", type: "boolean" },
|
|
{ name: "IsDeleted", type: "boolean" },
|
|
{ name: "ParentId", type: "guid" },
|
|
{ name: "ItemId", type: "guid" },
|
|
];
|
|
let filterFn = row => {
|
|
if (row.IsDeleted) {
|
|
return false;
|
|
}
|
|
if (row.IsFolder) {
|
|
folderMap.set(row.ItemId, row);
|
|
}
|
|
return true;
|
|
};
|
|
let bookmarks = readTableFromEdgeDB(
|
|
this.TABLE_NAME,
|
|
columns,
|
|
this.db,
|
|
filterFn
|
|
);
|
|
let toplevelBMs = [],
|
|
toolbarBMs = [];
|
|
for (let bookmark of bookmarks) {
|
|
let bmToInsert;
|
|
// Ignore invalid URLs:
|
|
if (!bookmark.IsFolder) {
|
|
try {
|
|
new URL(bookmark.URL);
|
|
} catch (ex) {
|
|
Cu.reportError(
|
|
`Ignoring ${bookmark.URL} when importing from Edge because of exception: ${ex}`
|
|
);
|
|
continue;
|
|
}
|
|
bmToInsert = {
|
|
dateAdded: bookmark.DateUpdated || new Date(),
|
|
title: bookmark.Title,
|
|
url: bookmark.URL,
|
|
};
|
|
} /* bookmark.IsFolder */ else {
|
|
// Ignore the favorites bar bookmark itself.
|
|
if (bookmark.Title == "_Favorites_Bar_") {
|
|
continue;
|
|
}
|
|
if (!bookmark._childrenRef) {
|
|
bookmark._childrenRef = [];
|
|
}
|
|
bmToInsert = {
|
|
title: bookmark.Title,
|
|
type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
|
|
dateAdded: bookmark.DateUpdated || new Date(),
|
|
children: bookmark._childrenRef,
|
|
};
|
|
}
|
|
|
|
if (!folderMap.has(bookmark.ParentId)) {
|
|
toplevelBMs.push(bmToInsert);
|
|
} else {
|
|
let parent = folderMap.get(bookmark.ParentId);
|
|
if (parent.Title == "_Favorites_Bar_") {
|
|
toolbarBMs.push(bmToInsert);
|
|
continue;
|
|
}
|
|
if (!parent._childrenRef) {
|
|
parent._childrenRef = [];
|
|
}
|
|
parent._childrenRef.push(bmToInsert);
|
|
}
|
|
}
|
|
return { toplevelBMs, toolbarBMs };
|
|
},
|
|
};
|
|
|
|
function EdgeProfileMigrator() {
|
|
this.wrappedJSObject = this;
|
|
}
|
|
|
|
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
|
|
|
EdgeProfileMigrator.prototype.getBookmarksMigratorForTesting = function(
|
|
dbOverride
|
|
) {
|
|
return new EdgeBookmarksMigrator(dbOverride);
|
|
};
|
|
|
|
EdgeProfileMigrator.prototype.getReadingListMigratorForTesting = function(
|
|
dbOverride
|
|
) {
|
|
return new EdgeReadingListMigrator(dbOverride);
|
|
};
|
|
|
|
EdgeProfileMigrator.prototype.getResources = function() {
|
|
let resources = [
|
|
new EdgeBookmarksMigrator(),
|
|
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
|
|
new EdgeTypedURLMigrator(),
|
|
new EdgeTypedURLDBMigrator(),
|
|
new EdgeReadingListMigrator(),
|
|
];
|
|
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
|
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
|
|
resources.push(windowsVaultFormPasswordsMigrator);
|
|
return resources.filter(r => r.exists);
|
|
};
|
|
|
|
EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
|
|
// Don't do this if we don't have a single profile (see the comment for
|
|
// sourceProfiles) or if we can't find the database file:
|
|
let sourceProfiles = await this.getSourceProfiles();
|
|
if (sourceProfiles !== null || !lazy.gEdgeDatabase) {
|
|
return Promise.resolve(new Date(0));
|
|
}
|
|
let logFilePath = OS.Path.join(
|
|
lazy.gEdgeDatabase.parent.path,
|
|
"LogFiles",
|
|
"edb.log"
|
|
);
|
|
let dbPath = lazy.gEdgeDatabase.path;
|
|
let cookieMigrator = MSMigrationUtils.getCookiesMigrator(
|
|
MSMigrationUtils.MIGRATION_TYPE_EDGE
|
|
);
|
|
let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
|
|
let datePromises = [logFilePath, dbPath, ...cookiePaths].map(path => {
|
|
return OS.File.stat(path)
|
|
.catch(() => null)
|
|
.then(info => {
|
|
return info ? info.lastModificationDate : 0;
|
|
});
|
|
});
|
|
datePromises.push(
|
|
new Promise(resolve => {
|
|
let typedURLs = new Map();
|
|
try {
|
|
typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
|
|
} catch (ex) {}
|
|
let times = [0, ...typedURLs.values()];
|
|
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
|
resolve(Math.max.apply(Math, times) / 1000);
|
|
})
|
|
);
|
|
return Promise.all(datePromises).then(dates => {
|
|
return new Date(Math.max.apply(Math, dates));
|
|
});
|
|
};
|
|
|
|
/* Somewhat counterintuitively, this returns:
|
|
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
|
|
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
|
|
* See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
|
|
*/
|
|
EdgeProfileMigrator.prototype.getSourceProfiles = function() {
|
|
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
|
|
return isWin10OrHigher ? null : [];
|
|
};
|
|
|
|
EdgeProfileMigrator.prototype.__defineGetter__("sourceLocked", function() {
|
|
// There is an exclusive lock on some databases. Assume they are locked for now.
|
|
return true;
|
|
});
|
|
|
|
EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
|
|
EdgeProfileMigrator.prototype.contractID =
|
|
"@mozilla.org/profile/migrator;1?app=browser&type=edge";
|
|
EdgeProfileMigrator.prototype.classID = Components.ID(
|
|
"{62e8834b-2d17-49f5-96ff-56344903a2ae}"
|
|
);
|
|
|
|
var EXPORTED_SYMBOLS = ["EdgeProfileMigrator"];
|