summaryrefslogtreecommitdiffstats
path: root/application/basilisk/components/migration/EdgeProfileMigrator.js
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/components/migration/EdgeProfileMigrator.js')
-rw-r--r--application/basilisk/components/migration/EdgeProfileMigrator.js450
1 files changed, 450 insertions, 0 deletions
diff --git a/application/basilisk/components/migration/EdgeProfileMigrator.js b/application/basilisk/components/migration/EdgeProfileMigrator.js
new file mode 100644
index 000000000..afdcc2773
--- /dev/null
+++ b/application/basilisk/components/migration/EdgeProfileMigrator.js
@@ -0,0 +1,450 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+Cu.import("resource:///modules/MSMigrationUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "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(this, "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()) {
+ 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 {function} filterFn a function that is called for each row.
+ * Only rows for which it returns a truthy
+ * value are included in the result.
+ * @param {nsIFile} dbFile the database file to use. Defaults to
+ * the main Edge database.
+ * @returns {Array} An array of row objects.
+ */
+function readTableFromEdgeDB(tableName, columns, filterFn, dbFile = gEdgeDatabase) {
+ let database;
+ let rows = [];
+ try {
+ let logFile = dbFile.parent;
+ logFile.append("LogFiles");
+ database = 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(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) {
+ 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: function(aCallback) {
+ let typedURLs = this._typedURLs;
+ let places = [];
+ for (let [urlString, time] of typedURLs) {
+ let uri;
+ try {
+ uri = Services.io.newURI(urlString, null, null);
+ if (["http", "https", "ftp"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ continue;
+ }
+
+ // Note that the time will be in microseconds (PRTime),
+ // and Date.now() returns milliseconds. Places expects PRTime,
+ // so we multiply the Date.now return value to make up the difference.
+ let visitDate = time || (Date.now() * 1000);
+ places.push({
+ uri,
+ visits: [{ transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ visitDate}]
+ });
+ }
+
+ if (places.length == 0) {
+ aCallback(typedURLs.size == 0);
+ return;
+ }
+
+ MigrationUtils.insertVisitsWrapper(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ },
+};
+
+function EdgeReadingListMigrator() {
+}
+
+EdgeReadingListMigrator.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get exists() {
+ return !!gEdgeDatabase;
+ },
+
+ migrate(callback) {
+ this._migrateReadingList(PlacesUtils.bookmarks.menuGuid).then(
+ () => callback(true),
+ ex => {
+ Cu.reportError(ex);
+ callback(false);
+ }
+ );
+ },
+
+ _migrateReadingList: Task.async(function*(parentGuid) {
+ 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 == ESEDBReader.COLUMN_TYPES.JET_coltypBit) {
+ columns.push({name: "IsDeleted", type: "boolean"});
+ }
+ return columns;
+ };
+
+ let filterFn = row => {
+ return !row.IsDeleted;
+ };
+
+ let readingListItems = readTableFromEdgeDB("ReadingList", columnFn, filterFn);
+ if (!readingListItems.length) {
+ return;
+ }
+
+ let destFolderGuid = yield this._ensureReadingListFolder(parentGuid);
+ let exceptionThrown;
+ for (let item of readingListItems) {
+ let dateAdded = item.AddedDate || new Date();
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid: destFolderGuid, url: item.URL, title: item.Title, dateAdded
+ }).catch(ex => {
+ if (!exceptionThrown) {
+ exceptionThrown = ex;
+ }
+ Cu.reportError(ex);
+ });
+ }
+ if (exceptionThrown) {
+ throw exceptionThrown;
+ }
+ }),
+
+ _ensureReadingListFolder: Task.async(function*(parentGuid) {
+ if (!this.__readingListFolderGuid) {
+ let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
+ let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
+ this.__readingListFolderGuid = (yield MigrationUtils.insertBookmarkWrapper(folderSpec)).guid;
+ }
+ return this.__readingListFolderGuid;
+ }),
+};
+
+function EdgeBookmarksMigrator(dbOverride) {
+ this.dbOverride = dbOverride;
+}
+
+EdgeBookmarksMigrator.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get db() { return this.dbOverride || gEdgeDatabase },
+
+ get TABLE_NAME() { return "Favorites" },
+
+ get exists() {
+ if (!("_exists" in this)) {
+ this._exists = !!this.db;
+ }
+ return this._exists;
+ },
+
+ migrate(callback) {
+ this._migrateBookmarks(PlacesUtils.bookmarks.menuGuid).then(
+ () => callback(true),
+ ex => {
+ Cu.reportError(ex);
+ callback(false);
+ }
+ );
+ },
+
+ _migrateBookmarks: Task.async(function*(rootGuid) {
+ let {bookmarks, folderMap} = this._fetchBookmarksFromDB();
+ if (!bookmarks.length) {
+ return;
+ }
+ yield this._importBookmarks(bookmarks, folderMap, rootGuid);
+ }),
+
+ _importBookmarks: Task.async(function*(bookmarks, folderMap, rootGuid) {
+ if (!MigrationUtils.isStartupMigration) {
+ rootGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Edge", rootGuid);
+ }
+
+ let exceptionThrown;
+ for (let bookmark of bookmarks) {
+ // If this is a folder, we might have created it already to put other bookmarks in.
+ if (bookmark.IsFolder && bookmark._guid) {
+ continue;
+ }
+
+ // If this is a folder, just create folders up to and including that folder.
+ // Otherwise, create folders until we have a parent for this bookmark.
+ // This avoids duplicating logic for the bookmarks bar.
+ let folderId = bookmark.IsFolder ? bookmark.ItemId : bookmark.ParentId;
+ let parentGuid = yield this._getGuidForFolder(folderId, folderMap, rootGuid).catch(ex => {
+ if (!exceptionThrown) {
+ exceptionThrown = ex;
+ }
+ Cu.reportError(ex);
+ });
+
+ // If this was a folder, we're done with this item
+ if (bookmark.IsFolder) {
+ continue;
+ }
+
+ if (!parentGuid) {
+ // If we couldn't sort out a parent, fall back to importing on the root:
+ parentGuid = rootGuid;
+ }
+ let placesInfo = {
+ parentGuid,
+ url: bookmark.URL,
+ dateAdded: bookmark.DateUpdated || new Date(),
+ title: bookmark.Title,
+ };
+
+ yield MigrationUtils.insertBookmarkWrapper(placesInfo).catch(ex => {
+ if (!exceptionThrown) {
+ exceptionThrown = ex;
+ }
+ Cu.reportError(ex);
+ });
+ }
+
+ if (exceptionThrown) {
+ throw exceptionThrown;
+ }
+ }),
+
+ _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, filterFn, this.db);
+ return {bookmarks, folderMap};
+ },
+
+ _getGuidForFolder: Task.async(function*(folderId, folderMap, rootGuid) {
+ // If the folderId is not known as a folder in the folder map, we assume
+ // we just need the root
+ if (!folderMap.has(folderId)) {
+ return rootGuid;
+ }
+ let folder = folderMap.get(folderId);
+ // If the folder already has a places guid, just return that.
+ if (folder._guid) {
+ return folder._guid;
+ }
+
+ // Hacks! The bookmarks bar is special:
+ if (folder.Title == "_Favorites_Bar_") {
+ let toolbarGuid = PlacesUtils.bookmarks.toolbarGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ toolbarGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Edge", toolbarGuid);
+ }
+ folder._guid = toolbarGuid;
+ return folder._guid;
+ }
+ // Otherwise, get the right parent guid recursively:
+ let parentGuid = yield this._getGuidForFolder(folder.ParentId, folderMap, rootGuid);
+ let folderInfo = {
+ title: folder.Title,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ dateAdded: folder.DateUpdated || new Date(),
+ parentGuid,
+ };
+ // and add ourselves as a kid, and return the guid we got.
+ let parentBM = yield MigrationUtils.insertBookmarkWrapper(folderInfo);
+ folder._guid = parentBM.guid;
+ return folder._guid;
+ }),
+};
+
+function EdgeProfileMigrator() {
+ this.wrappedJSObject = this;
+}
+
+EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+EdgeProfileMigrator.prototype.getESEMigratorForTesting = function(dbOverride) {
+ return new EdgeBookmarksMigrator(dbOverride);
+};
+
+EdgeProfileMigrator.prototype.getResources = function() {
+ let resources = [
+ new EdgeBookmarksMigrator(),
+ MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+ new EdgeTypedURLMigrator(),
+ new EdgeReadingListMigrator(),
+ ];
+ let windowsVaultFormPasswordsMigrator =
+ MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+ windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
+ resources.push(windowsVaultFormPasswordsMigrator);
+ return resources.filter(r => r.exists);
+};
+
+EdgeProfileMigrator.prototype.getLastUsedDate = 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:
+ if (this.sourceProfiles !== null || !gEdgeDatabase) {
+ return Promise.resolve(new Date(0));
+ }
+ let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
+ let dbPath = 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()];
+ resolve(Math.max.apply(Math, times));
+ }));
+ 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.__defineGetter__("sourceProfiles", 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}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EdgeProfileMigrator]);