summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/ChromeProfileMigrator.jsm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /browser/components/migration/ChromeProfileMigrator.jsm
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--browser/components/migration/ChromeProfileMigrator.jsm757
1 files changed, 757 insertions, 0 deletions
diff --git a/browser/components/migration/ChromeProfileMigrator.jsm b/browser/components/migration/ChromeProfileMigrator.jsm
new file mode 100644
index 0000000000..66a1876467
--- /dev/null
+++ b/browser/components/migration/ChromeProfileMigrator.jsm
@@ -0,0 +1,757 @@
+/* -*- 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/. */
+
+"use strict";
+
+const AUTH_TYPE = {
+ SCHEME_HTML: 0,
+ SCHEME_BASIC: 1,
+ SCHEME_DIGEST: 2,
+};
+
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { ChromeMigrationUtils } = ChromeUtils.import(
+ "resource:///modules/ChromeMigrationUtils.jsm"
+);
+const { MigrationUtils, MigratorPrototype } = ChromeUtils.import(
+ "resource:///modules/MigrationUtils.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "PlacesUIUtils",
+ "resource:///modules/PlacesUIUtils.jsm"
+);
+
+/**
+ * Converts an array of chrome bookmark objects into one our own places code
+ * understands.
+ *
+ * @param items
+ * bookmark items to be inserted on this parent
+ * @param errorAccumulator
+ * function that gets called with any errors thrown so we don't drop them on the floor.
+ */
+function convertBookmarks(items, 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 });
+ } else if (item.type == "folder") {
+ let folderItem = {
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: item.name,
+ };
+ folderItem.children = convertBookmarks(item.children, errorAccumulator);
+ itemsToInsert.push(folderItem);
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ errorAccumulator(ex);
+ }
+ }
+ return itemsToInsert;
+}
+
+function ChromeProfileMigrator() {
+ this._chromeUserDataPathSuffix = "Chrome";
+}
+
+ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+ChromeProfileMigrator.prototype._keychainServiceName = "Chrome Safe Storage";
+ChromeProfileMigrator.prototype._keychainAccountName = "Chrome";
+
+ChromeProfileMigrator.prototype._getChromeUserDataPathIfExists = async function() {
+ if (this._chromeUserDataPath) {
+ return this._chromeUserDataPath;
+ }
+ let path = ChromeMigrationUtils.getDataPath(this._chromeUserDataPathSuffix);
+ let exists = await OS.File.exists(path);
+ if (exists) {
+ this._chromeUserDataPath = path;
+ } else {
+ this._chromeUserDataPath = null;
+ }
+ return this._chromeUserDataPath;
+};
+
+ChromeProfileMigrator.prototype.getResources = async function Chrome_getResources(
+ aProfile
+) {
+ let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+ if (chromeUserDataPath) {
+ let profileFolder = OS.Path.join(chromeUserDataPath, aProfile.id);
+ if (await OS.File.exists(profileFolder)) {
+ let localePropertySuffix = MigrationUtils._getLocalePropertyForBrowser(
+ this.getBrowserKey()
+ ).replace(/^source-name-/, "");
+ let possibleResourcePromises = [
+ GetBookmarksResource(
+ profileFolder,
+ localePropertySuffix,
+ this.getBrowserKey()
+ ),
+ GetHistoryResource(profileFolder),
+ GetCookiesResource(profileFolder),
+ ];
+ if (ChromeMigrationUtils.supportsLoginsForPlatform) {
+ possibleResourcePromises.push(
+ this._GetPasswordsResource(profileFolder)
+ );
+ }
+ let possibleResources = await Promise.all(possibleResourcePromises);
+ return possibleResources.filter(r => r != null);
+ }
+ }
+ return [];
+};
+
+ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastUsedDate() {
+ let sourceProfiles = await this.getSourceProfiles();
+ let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+ if (!chromeUserDataPath) {
+ return new Date(0);
+ }
+ let datePromises = sourceProfiles.map(async profile => {
+ let basePath = OS.Path.join(chromeUserDataPath, profile.id);
+ let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
+ async leafName => {
+ let path = OS.Path.join(basePath, leafName);
+ let info = await OS.File.stat(path).catch(() => null);
+ return info ? info.lastModificationDate : 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));
+};
+
+ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSourceProfiles() {
+ if ("__sourceProfiles" in this) {
+ return this.__sourceProfiles;
+ }
+
+ let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
+ if (!chromeUserDataPath) {
+ return [];
+ }
+
+ let localState;
+ let profiles = [];
+ try {
+ localState = await ChromeMigrationUtils.getLocalState(
+ this._chromeUserDataPathSuffix
+ );
+ 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") {
+ Cu.reportError("Error detecting Chrome profiles: " + e);
+ }
+ // 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;
+};
+
+Object.defineProperty(ChromeProfileMigrator.prototype, "sourceLocked", {
+ get: function Chrome_sourceLocked() {
+ // There is an exclusive lock on some SQLite databases. Assume they are locked for now.
+ return true;
+ },
+});
+
+async function GetBookmarksResource(
+ aProfileFolder,
+ aLocalePropertySuffix,
+ aBrowserKey
+) {
+ let bookmarksPath = OS.Path.join(aProfileFolder, "Bookmarks");
+ if (!(await OS.File.exists(bookmarksPath))) {
+ return null;
+ }
+
+ return {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ migrate(aCallback) {
+ return (async function() {
+ let gotErrors = false;
+ let errorGatherer = function() {
+ gotErrors = true;
+ };
+ // Parse Chrome bookmark file that is JSON format
+ let bookmarkJSON = await OS.File.read(bookmarksPath, {
+ encoding: "UTF-8",
+ });
+ let roots = JSON.parse(bookmarkJSON).roots;
+ let histogramBookmarkRoots = 0;
+
+ // Importing bookmark bar items
+ if (roots.bookmark_bar.children && roots.bookmark_bar.children.length) {
+ // Toolbar
+ histogramBookmarkRoots |=
+ MigrationUtils.SOURCE_BOOKMARK_ROOTS_BOOKMARKS_TOOLBAR;
+ let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
+ let bookmarks = convertBookmarks(
+ roots.bookmark_bar.children,
+ errorGatherer
+ );
+ if (
+ !Services.prefs.getBoolPref("browser.toolbars.bookmarks.2h2020") &&
+ !MigrationUtils.isStartupMigration &&
+ PlacesUtils.getChildCountForFolder(
+ PlacesUtils.bookmarks.toolbarGuid
+ ) > PlacesUIUtils.NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE
+ ) {
+ parentGuid = await MigrationUtils.createImportedBookmarksFolder(
+ aLocalePropertySuffix,
+ parentGuid
+ );
+ }
+ await MigrationUtils.insertManyBookmarksWrapper(
+ bookmarks,
+ parentGuid
+ );
+ PlacesUIUtils.maybeToggleBookmarkToolbarVisibilityAfterMigration();
+ }
+
+ // Importing bookmark menu items
+ if (roots.other.children && roots.other.children.length) {
+ // Bookmark menu
+ histogramBookmarkRoots |=
+ MigrationUtils.SOURCE_BOOKMARK_ROOTS_BOOKMARKS_MENU;
+ let parentGuid = PlacesUtils.bookmarks.menuGuid;
+ let bookmarks = convertBookmarks(roots.other.children, errorGatherer);
+ if (
+ !Services.prefs.getBoolPref("browser.toolbars.bookmarks.2h2020") &&
+ !MigrationUtils.isStartupMigration &&
+ PlacesUtils.getChildCountForFolder(PlacesUtils.bookmarks.menuGuid) >
+ PlacesUIUtils.NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE
+ ) {
+ parentGuid = await MigrationUtils.createImportedBookmarksFolder(
+ aLocalePropertySuffix,
+ parentGuid
+ );
+ }
+ await MigrationUtils.insertManyBookmarksWrapper(
+ bookmarks,
+ parentGuid
+ );
+ }
+ if (gotErrors) {
+ throw new Error("The migration included errors.");
+ }
+ Services.telemetry
+ .getKeyedHistogramById("FX_MIGRATION_BOOKMARKS_ROOTS")
+ .add(aBrowserKey, histogramBookmarkRoots);
+ })().then(
+ () => aCallback(true),
+ () => aCallback(false)
+ );
+ },
+ };
+}
+
+async function GetHistoryResource(aProfileFolder) {
+ let historyPath = OS.Path.join(aProfileFolder, "History");
+ if (!(await OS.File.exists(historyPath))) {
+ return null;
+ }
+
+ return {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ migrate(aCallback) {
+ (async function() {
+ const MAX_AGE_IN_DAYS = Services.prefs.getIntPref(
+ "browser.migrate.chrome.history.maxAgeInDays"
+ );
+ 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";
+ if (MAX_AGE_IN_DAYS) {
+ let maxAge = ChromeMigrationUtils.dateToChromeTime(
+ Date.now() - MAX_AGE_IN_DAYS * 24 * 60 * 60 * 1000
+ );
+ query += " AND last_visit_time > " + maxAge;
+ }
+ if (LIMIT) {
+ query += " ORDER BY last_visit_time DESC LIMIT " + LIMIT;
+ }
+
+ let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ historyPath,
+ "Chrome history",
+ query
+ );
+ let pageInfos = [];
+ let fallbackVisitDate = new Date();
+ for (let row of rows) {
+ try {
+ // if having typed_count, we changes transition type to typed.
+ let transition = PlacesUtils.history.TRANSITIONS.LINK;
+ if (row.getResultByName("typed_count") > 0) {
+ transition = PlacesUtils.history.TRANSITIONS.TYPED;
+ }
+
+ pageInfos.push({
+ title: row.getResultByName("title"),
+ url: new URL(row.getResultByName("url")),
+ visits: [
+ {
+ transition,
+ date: ChromeMigrationUtils.chromeTimeToDate(
+ row.getResultByName("last_visit_time"),
+ fallbackVisitDate
+ ),
+ },
+ ],
+ });
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ if (pageInfos.length) {
+ await MigrationUtils.insertVisitsWrapper(pageInfos);
+ }
+ })().then(
+ () => {
+ aCallback(true);
+ },
+ ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ }
+ );
+ },
+ };
+}
+
+async function GetCookiesResource(aProfileFolder) {
+ let cookiesPath = OS.Path.join(aProfileFolder, "Cookies");
+ if (!(await OS.File.exists(cookiesPath))) {
+ return null;
+ }
+
+ return {
+ type: MigrationUtils.resourceTypes.COOKIES,
+
+ async migrate(aCallback) {
+ // Get columns names and set is_sceure, is_httponly fields accordingly.
+ let columns = await MigrationUtils.getRowsFromDBWithoutLocks(
+ cookiesPath,
+ "Chrome cookies",
+ `PRAGMA table_info(cookies)`
+ ).catch(ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ });
+ // If the promise was rejected we will have already called aCallback,
+ // so we can just return here.
+ if (!columns) {
+ return;
+ }
+ columns = columns.map(c => c.getResultByName("name"));
+ let isHttponly = columns.includes("is_httponly")
+ ? "is_httponly"
+ : "httponly";
+ let isSecure = columns.includes("is_secure") ? "is_secure" : "secure";
+
+ let source_scheme = columns.includes("source_scheme")
+ ? "source_scheme"
+ : `"${Ci.nsICookie.SCHEME_UNSET}" as source_scheme`;
+
+ // We don't support decrypting cookies yet so only import plaintext ones.
+ let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
+ cookiesPath,
+ "Chrome cookies",
+ `SELECT host_key, name, value, path, expires_utc, ${isSecure}, ${isHttponly}, encrypted_value, ${source_scheme}
+ FROM cookies
+ WHERE length(encrypted_value) = 0`
+ ).catch(ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ });
+
+ // If the promise was rejected we will have already called aCallback,
+ // so we can just return here.
+ if (!rows) {
+ return;
+ }
+
+ let fallbackExpiryDate = 0;
+ for (let row of rows) {
+ let host_key = row.getResultByName("host_key");
+ if (host_key.match(/^\./)) {
+ // 1st character of host_key may be ".", so we have to remove it
+ host_key = host_key.substr(1);
+ }
+
+ let schemeType = Ci.nsICookie.SCHEME_UNSET;
+ switch (row.getResultByName("source_scheme")) {
+ case 1:
+ schemeType = Ci.nsICookie.SCHEME_HTTP;
+ break;
+ case 2:
+ schemeType = Ci.nsICookie.SCHEME_HTTPS;
+ break;
+ }
+
+ try {
+ let expiresUtc =
+ ChromeMigrationUtils.chromeTimeToDate(
+ row.getResultByName("expires_utc"),
+ fallbackExpiryDate
+ ) / 1000;
+ // No point adding cookies that don't have a valid expiry.
+ if (!expiresUtc) {
+ continue;
+ }
+
+ Services.cookies.add(
+ host_key,
+ row.getResultByName("path"),
+ row.getResultByName("name"),
+ row.getResultByName("value"),
+ row.getResultByName(isSecure),
+ row.getResultByName(isHttponly),
+ false,
+ parseInt(expiresUtc),
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ schemeType
+ );
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ aCallback(true);
+ },
+ };
+}
+
+ChromeProfileMigrator.prototype._GetPasswordsResource = async function(
+ aProfileFolder
+) {
+ let loginPath = OS.Path.join(aProfileFolder, "Login Data");
+ if (!(await OS.File.exists(loginPath))) {
+ return null;
+ }
+
+ let {
+ _chromeUserDataPathSuffix,
+ _keychainServiceName,
+ _keychainAccountName,
+ _keychainMockPassphrase = null,
+ } = this;
+
+ 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 => {
+ Cu.reportError(ex);
+ aCallback(false);
+ });
+ // 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.import(
+ "resource:///modules/ChromeWindowsLoginCrypto.jsm"
+ );
+ crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
+ } else if (AppConstants.platform == "macosx") {
+ let { ChromeMacOSLoginCrypto } = ChromeUtils.import(
+ "resource:///modules/ChromeMacOSLoginCrypto.jsm"
+ );
+ crypto = new ChromeMacOSLoginCrypto(
+ _keychainServiceName,
+ _keychainAccountName,
+ _keychainMockPassphrase
+ );
+ } else {
+ aCallback(false);
+ return;
+ }
+ } catch (ex) {
+ // Handle the user canceling Keychain access or other OSCrypto errors.
+ Cu.reportError(ex);
+ aCallback(false);
+ return;
+ }
+
+ let logins = [];
+ let fallbackCreationDate = new Date();
+ for (let row of rows) {
+ try {
+ let origin_url = 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: 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 = 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) {
+ Cu.reportError(e);
+ }
+ }
+ try {
+ if (logins.length) {
+ await MigrationUtils.insertLoginsWrapper(logins);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ if (crypto.finalize) {
+ crypto.finalize();
+ }
+ aCallback(true);
+ },
+ };
+};
+
+ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
+ChromeProfileMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
+ChromeProfileMigrator.prototype.classID = Components.ID(
+ "{4cec1de4-1671-4fc3-a53e-6c539dc77a26}"
+);
+
+/**
+ * Chromium migration
+ **/
+function ChromiumProfileMigrator() {
+ this._chromeUserDataPathSuffix = "Chromium";
+ this._keychainServiceName = "Chromium Safe Storage";
+ this._keychainAccountName = "Chromium";
+}
+
+ChromiumProfileMigrator.prototype = Object.create(
+ ChromeProfileMigrator.prototype
+);
+ChromiumProfileMigrator.prototype.classDescription =
+ "Chromium Profile Migrator";
+ChromiumProfileMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
+ChromiumProfileMigrator.prototype.classID = Components.ID(
+ "{8cece922-9720-42de-b7db-7cef88cb07ca}"
+);
+
+var EXPORTED_SYMBOLS = ["ChromeProfileMigrator", "ChromiumProfileMigrator"];
+
+/**
+ * Chrome Canary
+ * Not available on Linux
+ **/
+function CanaryProfileMigrator() {
+ this._chromeUserDataPathSuffix = "Canary";
+}
+CanaryProfileMigrator.prototype = Object.create(
+ ChromeProfileMigrator.prototype
+);
+CanaryProfileMigrator.prototype.classDescription =
+ "Chrome Canary Profile Migrator";
+CanaryProfileMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=canary";
+CanaryProfileMigrator.prototype.classID = Components.ID(
+ "{4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}"
+);
+
+if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
+ EXPORTED_SYMBOLS.push("CanaryProfileMigrator");
+}
+
+/**
+ * Chrome Dev - Linux only (not available in Mac and Windows)
+ */
+function ChromeDevMigrator() {
+ this._chromeUserDataPathSuffix = "Chrome Dev";
+}
+ChromeDevMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
+ChromeDevMigrator.prototype.classDescription = "Chrome Dev Profile Migrator";
+ChromeDevMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=chrome-dev";
+ChromeDevMigrator.prototype.classID = Components.ID(
+ "{7370a02a-4886-42c3-a4ec-d48c726ec30a}"
+);
+
+if (AppConstants.platform != "win" && AppConstants.platform != "macosx") {
+ EXPORTED_SYMBOLS.push("ChromeDevMigrator");
+}
+
+function ChromeBetaMigrator() {
+ this._chromeUserDataPathSuffix = "Chrome Beta";
+}
+ChromeBetaMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
+ChromeBetaMigrator.prototype.classDescription = "Chrome Beta Profile Migrator";
+ChromeBetaMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=chrome-beta";
+ChromeBetaMigrator.prototype.classID = Components.ID(
+ "{47f75963-840b-4950-a1f0-d9c1864f8b8e}"
+);
+
+if (AppConstants.platform != "macosx") {
+ EXPORTED_SYMBOLS.push("ChromeBetaMigrator");
+}
+
+function ChromiumEdgeMigrator() {
+ this._chromeUserDataPathSuffix = "Edge";
+ this._keychainServiceName = "Microsoft Edge Safe Storage";
+ this._keychainAccountName = "Microsoft Edge";
+}
+ChromiumEdgeMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
+ChromiumEdgeMigrator.prototype.classDescription =
+ "Chromium Edge Profile Migrator";
+ChromiumEdgeMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge";
+ChromiumEdgeMigrator.prototype.classID = Components.ID(
+ "{3c7f6b7c-baa9-4338-acfa-04bf79f1dcf1}"
+);
+
+function ChromiumEdgeBetaMigrator() {
+ this._chromeUserDataPathSuffix = "Edge Beta";
+ this._keychainServiceName = "Microsoft Edge Safe Storage";
+ this._keychainAccountName = "Microsoft Edge";
+}
+ChromiumEdgeBetaMigrator.prototype = Object.create(
+ ChromiumEdgeMigrator.prototype
+);
+ChromiumEdgeBetaMigrator.prototype.classDescription =
+ "Chromium Edge Beta Profile Migrator";
+ChromiumEdgeBetaMigrator.prototype.contractID =
+ "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge-beta";
+ChromiumEdgeBetaMigrator.prototype.classID = Components.ID(
+ "{0fc3d48a-c1c3-4871-b58f-a8b47d1555fb}"
+);
+
+if (AppConstants.platform == "macosx" || AppConstants.platform == "win") {
+ EXPORTED_SYMBOLS.push("ChromiumEdgeMigrator", "ChromiumEdgeBetaMigrator");
+}