summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/ChromeProfileMigrator.sys.mjs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/components/migration/ChromeProfileMigrator.sys.mjs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/migration/ChromeProfileMigrator.sys.mjs')
-rw-r--r--browser/components/migration/ChromeProfileMigrator.sys.mjs1253
1 files changed, 1253 insertions, 0 deletions
diff --git a/browser/components/migration/ChromeProfileMigrator.sys.mjs b/browser/components/migration/ChromeProfileMigrator.sys.mjs
new file mode 100644
index 0000000000..342ac3f376
--- /dev/null
+++ b/browser/components/migration/ChromeProfileMigrator.sys.mjs
@@ -0,0 +1,1253 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et */
+/* 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/. */
+
+const AUTH_TYPE = {
+ SCHEME_HTML: 0,
+ SCHEME_BASIC: 1,
+ SCHEME_DIGEST: 2,
+};
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
+import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ ChromeMigrationUtils: "resource:///modules/ChromeMigrationUtils.sys.mjs",
+ FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
+ NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ Qihoo360seMigrationUtils: "resource:///modules/360seMigrationUtils.sys.mjs",
+ MigrationWizardConstants:
+ "chrome://browser/content/migration/migration-wizard-constants.mjs",
+});
+
+/**
+ * Converts an array of chrome bookmark objects into one our own places code
+ * understands.
+ *
+ * @param {object[]} items Chrome Bookmark items to be inserted on this parent
+ * @param {set} bookmarkURLAccumulator Accumulate all imported bookmark urls to be used for importing favicons
+ * @param {Function} errorAccumulator function that gets called with any errors
+ * thrown so we don't drop them on the floor.
+ * @returns {object[]}
+ */
+function convertBookmarks(items, bookmarkURLAccumulator, errorAccumulator) {
+ let itemsToInsert = [];
+ for (let item of items) {
+ try {
+ if (item.type == "url") {
+ if (item.url.trim().startsWith("chrome:")) {
+ // Skip invalid internal URIs. Creating an actual URI always reports
+ // messages to the console because Gecko has its own concept of how
+ // chrome:// URIs should be formed, so we avoid doing that.
+ continue;
+ }
+ if (item.url.trim().startsWith("edge:")) {
+ // Don't import internal Microsoft Edge URIs as they won't resolve within Firefox.
+ continue;
+ }
+ itemsToInsert.push({ url: item.url, title: item.name });
+ bookmarkURLAccumulator.add({ url: item.url });
+ } else if (item.type == "folder") {
+ let folderItem = {
+ type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: item.name,
+ };
+ folderItem.children = convertBookmarks(
+ item.children,
+ bookmarkURLAccumulator,
+ errorAccumulator
+ );
+ itemsToInsert.push(folderItem);
+ }
+ } catch (ex) {
+ console.error(ex);
+ errorAccumulator(ex);
+ }
+ }
+ return itemsToInsert;
+}
+
+/**
+ * Chrome profile migrator. This can also be used as a parent class for
+ * migrators for browsers that are variants of Chrome.
+ */
+export class ChromeProfileMigrator extends MigratorBase {
+ /**
+ * On Ubuntu Linux, when the browser is installed as a Snap package,
+ * we must request permission to read data from other browsers. We
+ * make that request by opening up a native file picker in folder
+ * selection mode and instructing the user to navigate to the folder
+ * that the other browser's user data resides in.
+ *
+ * For Snap packages, this gives the browser read access - but it does
+ * so through a temporary symlink that does not match the original user
+ * data path. Effectively, the user data directory is remapped to a
+ * temporary location on the file system. We record these remaps here,
+ * keyed on the original data directory.
+ *
+ * @type {Map<string, string>}
+ */
+ #dataPathRemappings = new Map();
+
+ static get key() {
+ return "chrome";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chrome";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/chrome.png";
+ }
+
+ get _chromeUserDataPathSuffix() {
+ return "Chrome";
+ }
+
+ async hasPermissions() {
+ let dataPath = await this._getChromeUserDataPathIfExists();
+ if (!dataPath) {
+ return true;
+ }
+
+ let localStatePath = PathUtils.join(dataPath, "Local State");
+ try {
+ // Read one byte since on snap we can check existence even without being able
+ // to read the file.
+ await IOUtils.read(localStatePath, { maxBytes: 1 });
+ return true;
+ } catch (ex) {
+ console.error("No permissions for local state folder.");
+ }
+ return false;
+ }
+
+ async getPermissions(win) {
+ // Get the original path to the user data and ignore any existing remapping.
+ // This allows us to set a new remapping if the user navigates the platforms
+ // filepicker to a different directory on a second permission request attempt.
+ let originalDataPath = await this._getChromeUserDataPathIfExists(
+ true /* noRemapping */
+ );
+ // Keep prompting the user until they pick something that grants us access
+ // to Chrome's local state directory.
+ while (!(await this.hasPermissions())) {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(win, "", Ci.nsIFilePicker.modeGetFolder);
+ fp.filterIndex = 1;
+ // Now wait for the filepicker to open and close. If the user picks
+ // the local state folder, the OS should grant us read access to everything
+ // inside, so we don't need to check or do anything else with what's
+ // returned by the filepicker.
+ let result = await new Promise(resolve => fp.open(resolve));
+ // Bail if the user cancels the dialog:
+ if (result == Ci.nsIFilePicker.returnCancel) {
+ return false;
+ }
+
+ let file = fp.file;
+ if (file && file.path != originalDataPath) {
+ this.#dataPathRemappings.set(originalDataPath, file.path);
+ }
+ }
+ return true;
+ }
+
+ async canGetPermissions() {
+ if (
+ !Services.prefs.getBoolPref(
+ "browser.migrate.chrome.get_permissions.enabled"
+ )
+ ) {
+ return false;
+ }
+
+ if (await MigrationUtils.canGetPermissionsOnPlatform()) {
+ let dataPath = await this._getChromeUserDataPathIfExists();
+ if (dataPath) {
+ let localStatePath = PathUtils.join(dataPath, "Local State");
+ if (await IOUtils.exists(localStatePath)) {
+ return dataPath;
+ }
+ }
+ }
+ return false;
+ }
+
+ _keychainServiceName = "Chrome Safe Storage";
+
+ _keychainAccountName = "Chrome";
+
+ /**
+ * Returns a Promise that resolves to the data path containing the
+ * Local State and profile directories for this browser.
+ *
+ * @param {boolean} [noRemapping=false]
+ * Set to true to bypass any remapping that might have occurred on
+ * platforms where the data path changes once permission has been
+ * granted.
+ * @returns {Promise<string>}
+ */
+ async _getChromeUserDataPathIfExists(noRemapping = false) {
+ if (this._chromeUserDataPath) {
+ // Skip looking up any remapping if `noRemapping` was passed. This is
+ // helpful if the caller needs create a new remapping and overwrite
+ // an old remapping, as "real" user data path is used as a key for
+ // the remapping.
+ if (noRemapping) {
+ return this._chromeUserDataPath;
+ }
+
+ let remappedPath = this.#dataPathRemappings.get(this._chromeUserDataPath);
+ return remappedPath || this._chromeUserDataPath;
+ }
+ let path = await lazy.ChromeMigrationUtils.getDataPath(
+ this._chromeUserDataPathSuffix
+ );
+ let exists = path && (await IOUtils.exists(path));
+ if (exists) {
+ this._chromeUserDataPath = path;
+ } else {
+ this._chromeUserDataPath = null;
+ }
+ return this._chromeUserDataPath;
+ }
+
+ async getResources(aProfile) {
+ if (!(await this.hasPermissions())) {
+ return [];
+ }
+
+ let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+ if (chromeUserDataPath) {
+ let profileFolder = chromeUserDataPath;
+ if (aProfile) {
+ profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
+ }
+ if (await IOUtils.exists(profileFolder)) {
+ let possibleResourcePromises = [
+ GetBookmarksResource(profileFolder, this.constructor.key),
+ GetHistoryResource(profileFolder),
+ GetFormdataResource(profileFolder),
+ GetExtensionsResource(aProfile.id, this.constructor.key),
+ ];
+ if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
+ possibleResourcePromises.push(
+ this._GetPasswordsResource(profileFolder),
+ this._GetPaymentMethodsResource(profileFolder)
+ );
+ }
+
+ // Some of these Promises might reject due to things like database
+ // corruptions. We absorb those rejections here and filter them
+ // out so that we only try to import the resources that don't appear
+ // corrupted.
+ let possibleResources = await Promise.allSettled(
+ possibleResourcePromises
+ );
+ return possibleResources
+ .filter(promise => {
+ return promise.status == "fulfilled" && promise.value !== null;
+ })
+ .map(promise => promise.value);
+ }
+ }
+ return [];
+ }
+
+ async getLastUsedDate() {
+ let sourceProfiles = await this.getSourceProfiles();
+ if (!sourceProfiles) {
+ return new Date(0);
+ }
+ let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+ if (!chromeUserDataPath) {
+ return new Date(0);
+ }
+ let datePromises = sourceProfiles.map(async profile => {
+ let basePath = PathUtils.join(chromeUserDataPath, profile.id);
+ let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
+ async leafName => {
+ let path = PathUtils.join(basePath, leafName);
+ let info = await IOUtils.stat(path).catch(() => null);
+ return info ? info.lastModified : 0;
+ }
+ );
+ let dates = await Promise.all(fileDatePromises);
+ return Math.max(...dates);
+ });
+ let datesOuter = await Promise.all(datePromises);
+ datesOuter.push(0);
+ return new Date(Math.max(...datesOuter));
+ }
+
+ async getSourceProfiles() {
+ if ("__sourceProfiles" in this) {
+ return this.__sourceProfiles;
+ }
+
+ let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+ if (!chromeUserDataPath) {
+ return [];
+ }
+
+ let localState;
+ let profiles = [];
+ try {
+ localState = await lazy.ChromeMigrationUtils.getLocalState(
+ this._chromeUserDataPathSuffix,
+ chromeUserDataPath
+ );
+ let info_cache = localState.profile.info_cache;
+ for (let profileFolderName in info_cache) {
+ profiles.push({
+ id: profileFolderName,
+ name: info_cache[profileFolderName].name || profileFolderName,
+ });
+ }
+ } catch (e) {
+ // Avoid reporting NotFoundErrors from trying to get local state.
+ if (localState || e.name != "NotFoundError") {
+ console.error("Error detecting Chrome profiles: ", e);
+ }
+
+ // If we didn't have permission to read the local state, return the
+ // empty array. The user might have the opportunity to request
+ // permission using `hasPermission` and `getPermission`.
+ if (e.name == "NotAllowedError") {
+ return [];
+ }
+
+ // If we weren't able to detect any profiles above, fallback to the Default profile.
+ let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
+ if (await IOUtils.exists(defaultProfilePath)) {
+ profiles = [
+ {
+ id: "Default",
+ name: "Default",
+ },
+ ];
+ }
+ }
+
+ let profileResources = await Promise.all(
+ profiles.map(async profile => ({
+ profile,
+ resources: await this.getResources(profile),
+ }))
+ );
+
+ // Only list profiles from which any data can be imported
+ this.__sourceProfiles = profileResources
+ .filter(({ resources }) => {
+ return resources && !!resources.length;
+ }, this)
+ .map(({ profile }) => profile);
+ return this.__sourceProfiles;
+ }
+
+ async _GetPasswordsResource(aProfileFolder) {
+ let loginPath = PathUtils.join(aProfileFolder, "Login Data");
+ if (!(await IOUtils.exists(loginPath))) {
+ return null;
+ }
+
+ let tempFilePath = null;
+ if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
+ tempFilePath = await IOUtils.createUniqueFile(
+ PathUtils.tempDir,
+ "Login Data"
+ );
+ await IOUtils.copy(loginPath, tempFilePath);
+ loginPath = tempFilePath;
+ }
+
+ let {
+ _chromeUserDataPathSuffix,
+ _keychainServiceName,
+ _keychainAccountName,
+ _keychainMockPassphrase = null,
+ } = this;
+
+ let countQuery = `SELECT COUNT(*) FROM logins WHERE blacklisted_by_user = 0`;
+
+ let countRows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ loginPath,
+ "Chrome passwords",
+ countQuery
+ );
+
+ if (!countRows[0].getResultByName("COUNT(*)")) {
+ return null;
+ }
+
+ return {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ async migrate(aCallback) {
+ let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ loginPath,
+ "Chrome passwords",
+ `SELECT origin_url, action_url, username_element, username_value,
+ password_element, password_value, signon_realm, scheme, date_created,
+ times_used FROM logins WHERE blacklisted_by_user = 0`
+ )
+ .catch(ex => {
+ console.error(ex);
+ aCallback(false);
+ })
+ .finally(() => {
+ return tempFilePath && IOUtils.remove(tempFilePath);
+ });
+
+ // If the promise was rejected we will have already called aCallback,
+ // so we can just return here.
+ if (!rows) {
+ return;
+ }
+
+ // If there are no relevant rows, return before initializing crypto and
+ // thus prompting for Keychain access on macOS.
+ if (!rows.length) {
+ aCallback(true);
+ return;
+ }
+
+ let crypto;
+ try {
+ if (AppConstants.platform == "win") {
+ let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
+ "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
+ );
+ crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
+ } else if (AppConstants.platform == "macosx") {
+ let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
+ "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
+ );
+ crypto = new ChromeMacOSLoginCrypto(
+ _keychainServiceName,
+ _keychainAccountName,
+ _keychainMockPassphrase
+ );
+ } else {
+ aCallback(false);
+ return;
+ }
+ } catch (ex) {
+ // Handle the user canceling Keychain access or other OSCrypto errors.
+ console.error(ex);
+ aCallback(false);
+ return;
+ }
+
+ let logins = [];
+ let fallbackCreationDate = new Date();
+ for (let row of rows) {
+ try {
+ let origin_url = lazy.NetUtil.newURI(
+ row.getResultByName("origin_url")
+ );
+ // Ignore entries for non-http(s)/ftp URLs because we likely can't
+ // use them anyway.
+ const kValidSchemes = new Set(["https", "http", "ftp"]);
+ if (!kValidSchemes.has(origin_url.scheme)) {
+ continue;
+ }
+ let loginInfo = {
+ username: row.getResultByName("username_value"),
+ password: await crypto.decryptData(
+ row.getResultByName("password_value"),
+ null
+ ),
+ origin: origin_url.prePath,
+ formActionOrigin: null,
+ httpRealm: null,
+ usernameElement: row.getResultByName("username_element"),
+ passwordElement: row.getResultByName("password_element"),
+ timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
+ row.getResultByName("date_created") + 0,
+ fallbackCreationDate
+ ).getTime(),
+ timesUsed: row.getResultByName("times_used") + 0,
+ };
+
+ switch (row.getResultByName("scheme")) {
+ case AUTH_TYPE.SCHEME_HTML:
+ let action_url = row.getResultByName("action_url");
+ if (!action_url) {
+ // If there is no action_url, store the wildcard "" value.
+ // See the `formActionOrigin` IDL comments.
+ loginInfo.formActionOrigin = "";
+ break;
+ }
+ let action_uri = lazy.NetUtil.newURI(action_url);
+ if (!kValidSchemes.has(action_uri.scheme)) {
+ continue; // This continues the outer for loop.
+ }
+ loginInfo.formActionOrigin = action_uri.prePath;
+ break;
+ case AUTH_TYPE.SCHEME_BASIC:
+ case AUTH_TYPE.SCHEME_DIGEST:
+ // signon_realm format is URIrealm, so we need remove URI
+ loginInfo.httpRealm = row
+ .getResultByName("signon_realm")
+ .substring(loginInfo.origin.length + 1);
+ break;
+ default:
+ throw new Error(
+ "Login data scheme type not supported: " +
+ row.getResultByName("scheme")
+ );
+ }
+ logins.push(loginInfo);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ try {
+ if (logins.length) {
+ await MigrationUtils.insertLoginsWrapper(logins);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ if (crypto.finalize) {
+ crypto.finalize();
+ }
+ aCallback(true);
+ },
+ };
+ }
+ async _GetPaymentMethodsResource(aProfileFolder) {
+ if (
+ !Services.prefs.getBoolPref(
+ "browser.migrate.chrome.payment_methods.enabled",
+ false
+ )
+ ) {
+ return null;
+ }
+
+ let paymentMethodsPath = PathUtils.join(aProfileFolder, "Web Data");
+
+ if (!(await IOUtils.exists(paymentMethodsPath))) {
+ return null;
+ }
+
+ let tempFilePath = null;
+ if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
+ tempFilePath = await IOUtils.createUniqueFile(
+ PathUtils.tempDir,
+ "Web Data"
+ );
+ await IOUtils.copy(paymentMethodsPath, tempFilePath);
+ paymentMethodsPath = tempFilePath;
+ }
+
+ let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ paymentMethodsPath,
+ "Chrome Credit Cards",
+ "SELECT name_on_card, card_number_encrypted, expiration_month, expiration_year FROM credit_cards"
+ )
+ .catch(ex => {
+ console.error(ex);
+ })
+ .finally(() => {
+ return tempFilePath && IOUtils.remove(tempFilePath);
+ });
+
+ if (!rows?.length) {
+ return null;
+ }
+
+ let {
+ _chromeUserDataPathSuffix,
+ _keychainServiceName,
+ _keychainAccountName,
+ _keychainMockPassphrase = null,
+ } = this;
+
+ return {
+ type: MigrationUtils.resourceTypes.PAYMENT_METHODS,
+
+ async migrate(aCallback) {
+ let crypto;
+ try {
+ if (AppConstants.platform == "win") {
+ let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
+ "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
+ );
+ crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
+ } else if (AppConstants.platform == "macosx") {
+ let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
+ "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
+ );
+ crypto = new ChromeMacOSLoginCrypto(
+ _keychainServiceName,
+ _keychainAccountName,
+ _keychainMockPassphrase
+ );
+ } else {
+ aCallback(false);
+ return;
+ }
+ } catch (ex) {
+ // Handle the user canceling Keychain access or other OSCrypto errors.
+ console.error(ex);
+ aCallback(false);
+ return;
+ }
+
+ let cards = [];
+ for (let row of rows) {
+ cards.push({
+ "cc-name": row.getResultByName("name_on_card"),
+ "cc-number": await crypto.decryptData(
+ row.getResultByName("card_number_encrypted"),
+ null
+ ),
+ "cc-exp-month": parseInt(
+ row.getResultByName("expiration_month"),
+ 10
+ ),
+ "cc-exp-year": parseInt(row.getResultByName("expiration_year"), 10),
+ });
+ }
+
+ await MigrationUtils.insertCreditCardsWrapper(cards);
+ aCallback(true);
+ },
+ };
+ }
+}
+
+async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
+ let bookmarksPath = PathUtils.join(aProfileFolder, "Bookmarks");
+ let faviconsPath = PathUtils.join(aProfileFolder, "Favicons");
+
+ if (aBrowserKey === "chromium-360se") {
+ let localState = {};
+ try {
+ localState = await lazy.ChromeMigrationUtils.getLocalState("360 SE");
+ } catch (ex) {
+ console.error(ex);
+ }
+
+ let alternativeBookmarks =
+ await lazy.Qihoo360seMigrationUtils.getAlternativeBookmarks({
+ bookmarksPath,
+ localState,
+ });
+ if (alternativeBookmarks.resource) {
+ return alternativeBookmarks.resource;
+ }
+
+ bookmarksPath = alternativeBookmarks.path;
+ }
+
+ if (!(await IOUtils.exists(bookmarksPath))) {
+ return null;
+ }
+
+ let tempFilePath = null;
+ if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
+ tempFilePath = await IOUtils.createUniqueFile(
+ PathUtils.tempDir,
+ "Favicons"
+ );
+ await IOUtils.copy(faviconsPath, tempFilePath);
+ faviconsPath = tempFilePath;
+ }
+
+ // check to read JSON bookmarks structure and see if any bookmarks exist else return null
+ // Parse Chrome bookmark file that is JSON format
+ let bookmarkJSON = await IOUtils.readJSON(bookmarksPath);
+ let other = bookmarkJSON.roots.other.children.length;
+ let bookmarkBar = bookmarkJSON.roots.bookmark_bar.children.length;
+ let synced = bookmarkJSON.roots.synced.children.length;
+
+ if (!other && !bookmarkBar && !synced) {
+ return null;
+ }
+ return {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ migrate(aCallback) {
+ return (async function () {
+ let gotErrors = false;
+ let errorGatherer = function () {
+ gotErrors = true;
+ };
+
+ let faviconRows = [];
+ try {
+ faviconRows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ faviconsPath,
+ "Chrome Bookmark Favicons",
+ `select fav.id, fav.url, map.page_url, bit.image_data FROM favicons as fav
+ INNER JOIN favicon_bitmaps bit ON (fav.id = bit.icon_id)
+ INNER JOIN icon_mapping map ON (map.icon_id = bit.icon_id)`
+ );
+ } catch (ex) {
+ console.error(ex);
+ } finally {
+ if (tempFilePath) {
+ await IOUtils.remove(tempFilePath);
+ }
+ }
+
+ // Create Hashmap for favicons
+ let faviconMap = new Map();
+ for (let faviconRow of faviconRows) {
+ // First, try to normalize the URI:
+ try {
+ let uri = lazy.NetUtil.newURI(
+ faviconRow.getResultByName("page_url")
+ );
+ faviconMap.set(uri.spec, {
+ faviconData: faviconRow.getResultByName("image_data"),
+ uri,
+ });
+ } catch (e) {
+ // Couldn't parse the URI, so just skip it.
+ continue;
+ }
+ }
+
+ let roots = bookmarkJSON.roots;
+ let bookmarkURLAccumulator = new Set();
+
+ // Importing bookmark bar items
+ if (roots.bookmark_bar.children && roots.bookmark_bar.children.length) {
+ // Toolbar
+ let parentGuid = lazy.PlacesUtils.bookmarks.toolbarGuid;
+ let bookmarks = convertBookmarks(
+ roots.bookmark_bar.children,
+ bookmarkURLAccumulator,
+ errorGatherer
+ );
+ await MigrationUtils.insertManyBookmarksWrapper(
+ bookmarks,
+ parentGuid
+ );
+ }
+
+ // Importing Other Bookmarks items
+ if (roots.other.children && roots.other.children.length) {
+ // Other Bookmarks
+ let parentGuid = lazy.PlacesUtils.bookmarks.unfiledGuid;
+ let bookmarks = convertBookmarks(
+ roots.other.children,
+ bookmarkURLAccumulator,
+ errorGatherer
+ );
+ await MigrationUtils.insertManyBookmarksWrapper(
+ bookmarks,
+ parentGuid
+ );
+ }
+
+ // Importing synced Bookmarks items
+ if (roots.synced.children && roots.synced.children.length) {
+ // Synced Bookmarks
+ let parentGuid = lazy.PlacesUtils.bookmarks.unfiledGuid;
+ let bookmarks = convertBookmarks(
+ roots.synced.children,
+ bookmarkURLAccumulator,
+ errorGatherer
+ );
+ await MigrationUtils.insertManyBookmarksWrapper(
+ bookmarks,
+ parentGuid
+ );
+ }
+
+ // Find all favicons with associated bookmarks
+ let favicons = [];
+ for (let bookmark of bookmarkURLAccumulator) {
+ try {
+ let uri = lazy.NetUtil.newURI(bookmark.url);
+ let favicon = faviconMap.get(uri.spec);
+ if (favicon) {
+ favicons.push(favicon);
+ }
+ } catch (e) {
+ // Couldn't parse the bookmark URI, so just skip
+ continue;
+ }
+ }
+
+ // Import Bookmark Favicons
+ MigrationUtils.insertManyFavicons(favicons);
+ if (gotErrors) {
+ throw new Error("The migration included errors.");
+ }
+ })().then(
+ () => aCallback(true),
+ () => aCallback(false)
+ );
+ },
+ };
+}
+
+async function GetHistoryResource(aProfileFolder) {
+ let historyPath = PathUtils.join(aProfileFolder, "History");
+ if (!(await IOUtils.exists(historyPath))) {
+ return null;
+ }
+
+ let tempFilePath = null;
+ if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
+ tempFilePath = await IOUtils.createUniqueFile(PathUtils.tempDir, "History");
+ await IOUtils.copy(historyPath, tempFilePath);
+ historyPath = tempFilePath;
+ }
+
+ let countQuery = "SELECT COUNT(*) FROM urls WHERE hidden = 0";
+
+ let countRows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ historyPath,
+ "Chrome history",
+ countQuery
+ );
+ if (!countRows[0].getResultByName("COUNT(*)")) {
+ return null;
+ }
+ return {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ migrate(aCallback) {
+ (async function () {
+ const LIMIT = Services.prefs.getIntPref(
+ "browser.migrate.chrome.history.limit"
+ );
+
+ let query =
+ "SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0";
+ let maxAge = lazy.ChromeMigrationUtils.dateToChromeTime(
+ Date.now() - MigrationUtils.HISTORY_MAX_AGE_IN_MILLISECONDS
+ );
+ query += " AND last_visit_time > " + maxAge;
+
+ if (LIMIT) {
+ query += " ORDER BY last_visit_time DESC LIMIT " + LIMIT;
+ }
+
+ let rows;
+ try {
+ rows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ historyPath,
+ "Chrome history",
+ query
+ );
+ } finally {
+ if (tempFilePath) {
+ await IOUtils.remove(tempFilePath);
+ }
+ }
+
+ let pageInfos = [];
+ let fallbackVisitDate = new Date();
+ for (let row of rows) {
+ try {
+ // if having typed_count, we changes transition type to typed.
+ let transition = lazy.PlacesUtils.history.TRANSITIONS.LINK;
+ if (row.getResultByName("typed_count") > 0) {
+ transition = lazy.PlacesUtils.history.TRANSITIONS.TYPED;
+ }
+
+ pageInfos.push({
+ title: row.getResultByName("title"),
+ url: new URL(row.getResultByName("url")),
+ visits: [
+ {
+ transition,
+ date: lazy.ChromeMigrationUtils.chromeTimeToDate(
+ row.getResultByName("last_visit_time"),
+ fallbackVisitDate
+ ),
+ },
+ ],
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ if (pageInfos.length) {
+ await MigrationUtils.insertVisitsWrapper(pageInfos);
+ }
+ })().then(
+ () => {
+ aCallback(true);
+ },
+ ex => {
+ console.error(ex);
+ aCallback(false);
+ }
+ );
+ },
+ };
+}
+
+async function GetFormdataResource(aProfileFolder) {
+ let formdataPath = PathUtils.join(aProfileFolder, "Web Data");
+ if (!(await IOUtils.exists(formdataPath))) {
+ return null;
+ }
+ let countQuery = "SELECT COUNT(*) FROM autofill";
+
+ let tempFilePath = null;
+ if (MigrationUtils.IS_LINUX_SNAP_PACKAGE) {
+ tempFilePath = await IOUtils.createUniqueFile(
+ PathUtils.tempDir,
+ "Web Data"
+ );
+ await IOUtils.copy(formdataPath, tempFilePath);
+ formdataPath = tempFilePath;
+ }
+
+ let countRows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ formdataPath,
+ "Chrome formdata",
+ countQuery
+ );
+ if (!countRows[0].getResultByName("COUNT(*)")) {
+ return null;
+ }
+ return {
+ type: MigrationUtils.resourceTypes.FORMDATA,
+
+ async migrate(aCallback) {
+ let query =
+ "SELECT name, value, count, date_created, date_last_used FROM autofill";
+ let rows;
+
+ try {
+ rows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ formdataPath,
+ "Chrome formdata",
+ query
+ );
+ } finally {
+ if (tempFilePath) {
+ await IOUtils.remove(tempFilePath);
+ }
+ }
+
+ let addOps = [];
+ for (let row of rows) {
+ try {
+ let fieldname = row.getResultByName("name");
+ let value = row.getResultByName("value");
+ if (fieldname && value) {
+ addOps.push({
+ op: "add",
+ fieldname,
+ value,
+ timesUsed: row.getResultByName("count"),
+ firstUsed: row.getResultByName("date_created") * 1000,
+ lastUsed: row.getResultByName("date_last_used") * 1000,
+ });
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ try {
+ await lazy.FormHistory.update(addOps);
+ } catch (e) {
+ console.error(e);
+ aCallback(false);
+ return;
+ }
+
+ aCallback(true);
+ },
+ };
+}
+
+async function GetExtensionsResource(aProfileId, aBrowserKey = "chrome") {
+ if (
+ !Services.prefs.getBoolPref(
+ "browser.migrate.chrome.extensions.enabled",
+ false
+ )
+ ) {
+ return null;
+ }
+ let extensions = await lazy.ChromeMigrationUtils.getExtensionList(aProfileId);
+ if (!extensions.length || aBrowserKey !== "chrome") {
+ return null;
+ }
+
+ return {
+ type: MigrationUtils.resourceTypes.EXTENSIONS,
+ async migrate(callback) {
+ let ids = extensions.map(extension => extension.id);
+ let [progressValue, importedExtensions] =
+ await MigrationUtils.installExtensionsWrapper(aBrowserKey, ids);
+ let details = {
+ progressValue,
+ totalExtensions: extensions,
+ importedExtensions,
+ };
+ if (
+ progressValue == lazy.MigrationWizardConstants.PROGRESS_VALUE.INFO ||
+ progressValue == lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS
+ ) {
+ callback(true, details);
+ } else {
+ callback(false);
+ }
+ },
+ };
+}
+
+/**
+ * Chromium migrator
+ */
+export class ChromiumProfileMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "chromium";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chromium";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/chromium.png";
+ }
+
+ _chromeUserDataPathSuffix = "Chromium";
+ _keychainServiceName = "Chromium Safe Storage";
+ _keychainAccountName = "Chromium";
+}
+
+/**
+ * Chrome Canary
+ * Not available on Linux
+ */
+export class CanaryProfileMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "canary";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-canary";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/canary.png";
+ }
+
+ get _chromeUserDataPathSuffix() {
+ return "Canary";
+ }
+
+ get _keychainServiceName() {
+ return "Chromium Safe Storage";
+ }
+
+ get _keychainAccountName() {
+ return "Chromium";
+ }
+}
+
+/**
+ * Chrome Dev - Linux only (not available in Mac and Windows)
+ */
+export class ChromeDevMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "chrome-dev";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chrome-dev";
+ }
+
+ _chromeUserDataPathSuffix = "Chrome Dev";
+ _keychainServiceName = "Chromium Safe Storage";
+ _keychainAccountName = "Chromium";
+}
+
+/**
+ * Chrome Beta migrator
+ */
+export class ChromeBetaMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "chrome-beta";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chrome-beta";
+ }
+
+ _chromeUserDataPathSuffix = "Chrome Beta";
+ _keychainServiceName = "Chromium Safe Storage";
+ _keychainAccountName = "Chromium";
+}
+
+/**
+ * Brave migrator
+ */
+export class BraveProfileMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "brave";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-brave";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/brave.png";
+ }
+
+ _chromeUserDataPathSuffix = "Brave";
+ _keychainServiceName = "Brave Browser Safe Storage";
+ _keychainAccountName = "Brave Browser";
+}
+
+/**
+ * Edge (Chromium-based) migrator
+ */
+export class ChromiumEdgeMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "chromium-edge";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chromium-edge";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/edge.png";
+ }
+
+ _chromeUserDataPathSuffix = "Edge";
+ _keychainServiceName = "Microsoft Edge Safe Storage";
+ _keychainAccountName = "Microsoft Edge";
+}
+
+/**
+ * Edge Beta (Chromium-based) migrator
+ */
+export class ChromiumEdgeBetaMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "chromium-edge-beta";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chromium-edge-beta";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/edgebeta.png";
+ }
+
+ _chromeUserDataPathSuffix = "Edge Beta";
+ _keychainServiceName = "Microsoft Edge Safe Storage";
+ _keychainAccountName = "Microsoft Edge";
+}
+
+/**
+ * Chromium 360 migrator
+ */
+export class Chromium360seMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "chromium-360se";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-chromium-360se";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/360.png";
+ }
+
+ _chromeUserDataPathSuffix = "360 SE";
+ _keychainServiceName = "Microsoft Edge Safe Storage";
+ _keychainAccountName = "Microsoft Edge";
+}
+
+/**
+ * Opera migrator
+ */
+export class OperaProfileMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "opera";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-opera";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/opera.png";
+ }
+
+ _chromeUserDataPathSuffix = "Opera";
+ _keychainServiceName = "Opera Safe Storage";
+ _keychainAccountName = "Opera";
+
+ getSourceProfiles() {
+ return null;
+ }
+}
+
+/**
+ * Opera GX migrator
+ */
+export class OperaGXProfileMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "opera-gx";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-opera-gx";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/operagx.png";
+ }
+
+ _chromeUserDataPathSuffix = "Opera GX";
+ _keychainServiceName = "Opera Safe Storage";
+ _keychainAccountName = "Opera";
+
+ getSourceProfiles() {
+ return null;
+ }
+}
+
+/**
+ * Vivaldi migrator
+ */
+export class VivaldiProfileMigrator extends ChromeProfileMigrator {
+ static get key() {
+ return "vivaldi";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-vivaldi";
+ }
+
+ static get brandImage() {
+ return "chrome://browser/content/migration/brands/vivaldi.png";
+ }
+
+ _chromeUserDataPathSuffix = "Vivaldi";
+ _keychainServiceName = "Vivaldi Safe Storage";
+ _keychainAccountName = "Vivaldi";
+}