1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
10 ChromeUtils.defineESModuleGetters(lazy, {
11 AMBrowserExtensionsImport: "resource://gre/modules/AddonManager.sys.mjs",
12 LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
13 PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
14 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
15 Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
16 setTimeout: "resource://gre/modules/Timer.sys.mjs",
17 MigrationWizardConstants:
18 "chrome://browser/content/migration/migration-wizard-constants.mjs",
21 ChromeUtils.defineLazyGetter(
23 "gCanGetPermissionsOnPlatformPromise",
25 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
26 return fp.isModeSupported(Ci.nsIFilePicker.modeGetFolder);
30 var gMigrators = null;
31 var gFileMigrators = null;
32 var gProfileStartup = null;
35 let gForceExitSpinResolve = false;
36 let gKeepUndoData = false;
41 gL10n = new Localization(["browser/migrationWizard.ftl"]);
46 const MIGRATOR_MODULES = Object.freeze({
47 EdgeProfileMigrator: {
48 moduleURI: "resource:///modules/EdgeProfileMigrator.sys.mjs",
51 FirefoxProfileMigrator: {
52 moduleURI: "resource:///modules/FirefoxProfileMigrator.sys.mjs",
53 platforms: ["linux", "macosx", "win"],
56 moduleURI: "resource:///modules/IEProfileMigrator.sys.mjs",
59 SafariProfileMigrator: {
60 moduleURI: "resource:///modules/SafariProfileMigrator.sys.mjs",
61 platforms: ["macosx"],
64 // The following migrators are all variants of the ChromeProfileMigrator
66 BraveProfileMigrator: {
67 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
68 platforms: ["linux", "macosx", "win"],
70 CanaryProfileMigrator: {
71 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
72 platforms: ["macosx", "win"],
74 ChromeProfileMigrator: {
75 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
76 platforms: ["linux", "macosx", "win"],
79 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
80 platforms: ["linux", "win"],
83 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
86 ChromiumProfileMigrator: {
87 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
88 platforms: ["linux", "macosx", "win"],
90 Chromium360seMigrator: {
91 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
94 ChromiumEdgeMigrator: {
95 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
96 platforms: ["macosx", "win"],
98 ChromiumEdgeBetaMigrator: {
99 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
100 platforms: ["macosx", "win"],
102 OperaProfileMigrator: {
103 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
104 platforms: ["linux", "macosx", "win"],
106 VivaldiProfileMigrator: {
107 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
108 platforms: ["linux", "macosx", "win"],
110 OperaGXProfileMigrator: {
111 moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
112 platforms: ["macosx", "win"],
115 InternalTestingProfileMigrator: {
116 moduleURI: "resource:///modules/InternalTestingProfileMigrator.sys.mjs",
117 platforms: ["linux", "macosx", "win"],
121 const FILE_MIGRATOR_MODULES = Object.freeze({
122 PasswordFileMigrator: {
123 moduleURI: "resource:///modules/FileMigrators.sys.mjs",
125 BookmarksFileMigrator: {
126 moduleURI: "resource:///modules/FileMigrators.sys.mjs",
131 * The singleton MigrationUtils service. This service is the primary mechanism
132 * by which migrations from other browsers to this browser occur. The singleton
133 * instance of this class is exported from this module as `MigrationUtils`.
135 class MigrationUtils {
137 XPCOMUtils.defineLazyPreferenceGetter(
139 "HISTORY_MAX_AGE_IN_DAYS",
140 "browser.migrate.history.maxAgeInDays",
144 ChromeUtils.registerWindowActor("MigrationWizard", {
146 esModuleURI: "resource:///actors/MigrationWizardParent.sys.mjs",
150 esModuleURI: "resource:///actors/MigrationWizardChild.sys.mjs",
152 "MigrationWizard:RequestState": { wantUntrusted: true },
153 "MigrationWizard:BeginMigration": { wantUntrusted: true },
154 "MigrationWizard:RequestSafariPermissions": { wantUntrusted: true },
155 "MigrationWizard:SelectSafariPasswordFile": { wantUntrusted: true },
156 "MigrationWizard:OpenAboutAddons": { wantUntrusted: true },
157 "MigrationWizard:PermissionsNeeded": { wantUntrusted: true },
158 "MigrationWizard:GetPermissions": { wantUntrusted: true },
159 "MigrationWizard:OpenURL": { wantUntrusted: true },
170 "chrome://browser/content/migration/migration-dialog-window.html",
171 "chrome://browser/content/spotlight.html",
176 ChromeUtils.defineLazyGetter(this, "IS_LINUX_SNAP_PACKAGE", () => {
178 AppConstants.platform != "linux" ||
179 !Cc["@mozilla.org/gio-service;1"]
184 let gIOSvc = Cc["@mozilla.org/gio-service;1"].getService(
187 return gIOSvc.isRunningUnderSnap;
191 resourceTypes = Object.freeze({
193 /* 0x01 used to be used for settings, but was removed. */
201 PAYMENT_METHODS: 0x0100,
206 * Helper for implementing simple asynchronous cases of migration resources'
207 * ``migrate(aCallback)`` (see MigratorBase). If your ``migrate`` method
208 * just waits for some file to be read, for example, and then migrates
209 * everything right away, you can wrap the async-function with this helper
210 * and not worry about notifying the callback.
213 * // For example, instead of writing:
214 * setTimeout(function() {
225 * setTimeout(MigrationUtils.wrapMigrateFunction(function() {
226 * if (importingFromMosaic)
227 * throw Cr.NS_ERROR_UNEXPECTED;
230 * // ... and aCallback will be called with aSuccess=false when importing
231 * // from Mosaic, or with aSuccess=true otherwise.
233 * @param {Function} aFunction
234 * the function that will be called sometime later. If aFunction
235 * throws when it's called, aCallback(false) is called, otherwise
236 * aCallback(true) is called.
237 * @param {Function} aCallback
238 * the callback function passed to ``migrate``.
239 * @returns {Function}
240 * the wrapped function.
242 wrapMigrateFunction(aFunction, aCallback) {
246 aFunction.apply(null, arguments);
251 // Do not change this to call aCallback directly in try try & catch
252 // blocks, because if aCallback throws, we may end up calling aCallback
259 * Gets localized string corresponding to l10n-id
261 * @param {string} aKey
262 * The key of the id of the localization to retrieve.
263 * @param {object} [aArgs=undefined]
264 * An optional map of arguments to the id.
265 * @returns {Promise<string>}
266 * A promise that resolves to the retrieved localization.
268 getLocalizedString(aKey, aArgs) {
269 let l10n = getL10n();
270 return l10n.formatValue(aKey, aArgs);
274 * Get all the rows corresponding to a select query from a database, without
275 * requiring a lock on the database. If fetching data fails (because someone
276 * else tried to write to the DB at the same time, for example), we will
277 * retry the fetch after a 100ms timeout, up to 10 times.
279 * @param {string} path
280 * The file path to the database we want to open.
281 * @param {string} description
282 * A developer-readable string identifying what kind of database we're
284 * @param {string} selectQuery
285 * The SELECT query to use to fetch the rows.
286 * @param {Promise} [testDelayPromise]
287 * An optional promise to await for after the first loop, used in tests.
289 * @returns {Promise<object[]|Error>}
290 * A promise that resolves to an array of rows. The promise will be
291 * rejected if the read/fetch failed even after retrying.
293 getRowsFromDBWithoutLocks(
297 testDelayPromise = null
301 ignoreLockingMode: true,
305 const RETRYLIMIT = 10;
306 const RETRYINTERVAL = 100;
307 return (async function innerGetRows() {
309 for (let retryCount = RETRYLIMIT; retryCount; retryCount--) {
310 // Attempt to get the rows. If this succeeds, we will bail out of the loop,
311 // close the database in a failsafe way, and pass the rows back.
312 // If fetching the rows throws, we will wait RETRYINTERVAL ms
313 // and try again. This will repeat a maximum of RETRYLIMIT times.
316 let previousExceptionMessage = null;
318 db = await lazy.Sqlite.openConnection(dbOptions);
320 rows = await db.execute(selectQuery);
323 if (previousExceptionMessage != ex.message) {
326 previousExceptionMessage = ex.message;
327 if (ex.name == "NS_ERROR_FILE_CORRUPTED") {
338 new Promise(resolve => lazy.setTimeout(resolve, RETRYINTERVAL)),
344 "Couldn't get rows from the " + description + " database."
353 gMigrators = new Map();
354 for (let [symbol, { moduleURI, platforms }] of Object.entries(
357 if (platforms.includes(AppConstants.platform)) {
358 let { [symbol]: migratorClass } =
359 ChromeUtils.importESModule(moduleURI);
360 if (gMigrators.has(migratorClass.key)) {
362 "A pre-existing migrator exists with key " +
363 `${migratorClass.key}. Not registering.`
367 gMigrators.set(migratorClass.key, new migratorClass());
374 get #fileMigrators() {
375 if (!gFileMigrators) {
376 gFileMigrators = new Map();
377 for (let [symbol, { moduleURI }] of Object.entries(
378 FILE_MIGRATOR_MODULES
380 let { [symbol]: migratorClass } = ChromeUtils.importESModule(moduleURI);
381 if (gFileMigrators.has(migratorClass.key)) {
383 "A pre-existing file migrator exists with key " +
384 `${migratorClass.key}. Not registering.`
388 gFileMigrators.set(migratorClass.key, new migratorClass());
391 return gFileMigrators;
394 forceExitSpinResolve() {
395 gForceExitSpinResolve = true;
398 spinResolve(promise) {
399 if (!(promise instanceof Promise)) {
405 gForceExitSpinResolve = false;
415 Services.tm.spinEventLoopUntil(
416 "MigrationUtils.sys.mjs:MU_spinResolve",
417 () => done || gForceExitSpinResolve
420 throw new Error("Forcefully exited event loop.");
429 * Returns the migrator for the given source, if any data is available
430 * for this source, or if permissions are required in order to read
431 * data from this source. Returns null otherwise.
433 * @param {string} aKey
434 * Internal name of the migration source. See `availableMigratorKeys`
435 * for supported values by OS.
436 * @returns {Promise<MigratorBase|null>}
437 * A profile migrator implementing nsIBrowserProfileMigrator, if it can
438 * import any data, null otherwise.
440 async getMigrator(aKey) {
441 let migrator = this.#migrators.get(aKey);
443 console.error(`Could not find a migrator class for key ${aKey}`);
453 (await migrator.isSourceAvailable()) ||
454 (!(await migrator.hasPermissions()) && migrator.canGetPermissions())
466 getFileMigrator(aKey) {
467 let migrator = this.#fileMigrators.get(aKey);
469 console.error(`Could not find a file migrator class for key ${aKey}`);
476 * Returns true if a migrator is registered with key aKey. No check is made
477 * to determine if a profile exists that the migrator can migrate from.
479 * @param {string} aKey
480 * Internal name of the migration source. See `availableMigratorKeys`
481 * for supported values by OS.
484 migratorExists(aKey) {
485 return this.#migrators.has(aKey);
489 * Figure out what is the default browser, and if there is a migrator
490 * for it, return that migrator's internal name.
492 * For the time being, the "internal name" of a migrator is its contract-id
493 * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
494 * but it will soon be exposed properly.
498 getMigratorKeyForDefaultBrowser() {
499 // Canary uses the same description as Chrome so we can't distinguish them.
500 // Edge Beta on macOS uses "Microsoft Edge" with no "beta" indication.
501 const APP_DESC_TO_KEY = {
502 "Internet Explorer": "ie",
503 "Microsoft Edge": "edge",
509 "Opera GX": "opera-gx",
510 "Brave Web Browser": "brave", // Windows, Linux
511 Brave: "brave", // OS X
512 "Google Chrome": "chrome", // Windows, Linux
513 Chrome: "chrome", // OS X
514 Chromium: "chromium", // Windows, OS X
515 "Chromium Web Browser": "chromium", // Linux
516 "360\u5b89\u5168\u6d4f\u89c8\u5668": "chromium-360se",
521 let browserDesc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
522 .getService(Ci.nsIExternalProtocolService)
523 .getApplicationDescription("http");
524 key = APP_DESC_TO_KEY[browserDesc] || "";
525 // Handle devedition, as well as "FirefoxNightly" on OS X.
526 if (!key && browserDesc.startsWith("Firefox")) {
530 console.error("Could not detect default browser: ", ex);
537 * True if we're in the process of a startup migration.
541 get isStartupMigration() {
542 return gProfileStartup != null;
546 * In the case of startup migration, this is set to the nsIProfileStartup
547 * instance passed to ProfileMigrator's migrate.
549 * @see showMigrationWizard
550 * @type {nsIProfileStartup|null}
552 get profileStartup() {
553 return gProfileStartup;
557 * Show the migration wizard in about:preferences, or if there is not an existing
558 * browser window open, in a new top-level dialog window.
560 * NB: If you add new consumers, please add a migration entry point constant to
561 * MIGRATION_ENTRYPOINTS and supply that entrypoint with the entrypoint property
562 * in the aOptions argument.
564 * @param {Window} [aOpener=null]
565 * optional; the window that asks to open the wizard.
566 * @param {object} [aOptions=null]
567 * optional named arguments for the migration wizard.
568 * @param {string} [aOptions.entrypoint=undefined]
569 * migration entry point constant. See MIGRATION_ENTRYPOINTS.
570 * @param {string} [aOptions.migratorKey=undefined]
571 * The key for which migrator to use automatically. This is the key that is exposed
572 * as a static getter on the migrator class.
573 * @param {MigratorBase} [aOptions.migrator=undefined]
574 * A migrator instance to use automatically.
575 * @param {boolean} [aOptions.isStartupMigration=undefined]
576 * True if this is a startup migration.
577 * @param {boolean} [aOptions.skipSourceSelection=undefined]
578 * True if the source selection page of the wizard should be skipped.
579 * @param {string} [aOptions.profileId]
580 * An identifier for the profile to use when migrating.
581 * @returns {Promise<undefined>}
582 * If an about:preferences tab can be opened, this will resolve when
583 * that tab has been switched to. Otherwise, this will resolve
584 * just after opening the top-level dialog window.
586 showMigrationWizard(aOpener, aOptions) {
587 // When migration is kicked off from about:welcome, there are
588 // a few different behaviors that we want to test, controlled
589 // by a preference that is instrumented for Nimbus. The pref
590 // has the following possible states:
593 // The user will be directed to the migration wizard in
594 // about:preferences, but once the wizard is dismissed,
595 // the tab will close.
598 // The migration wizard will open in a new top-level content
602 // The user will be directed to the migration wizard in
603 // about:preferences. The tab will not close once the
604 // user closes the wizard.
605 let aboutWelcomeBehavior = Services.prefs.getCharPref(
606 "browser.migrate.content-modal.about-welcome-behavior",
610 let entrypoint = aOptions.entrypoint || this.MIGRATION_ENTRYPOINTS.UNKNOWN;
611 Glean.browserMigration.entryPointCategorical[entrypoint].add(1);
613 let openStandaloneWindow = blocking => {
614 let features = "dialog,centerscreen,resizable=no";
617 features += ",modal";
620 Services.ww.openWindow(
622 "chrome://browser/content/migration/migration-dialog-window.html",
629 return Promise.resolve();
632 if (aOptions.isStartupMigration) {
633 // Record that the uninstaller requested a profile refresh
634 if (Services.env.get("MOZ_UNINSTALLER_PROFILE_REFRESH")) {
635 Services.env.set("MOZ_UNINSTALLER_PROFILE_REFRESH", "");
636 Glean.migration.uninstallerProfileRefresh.set(true);
639 openStandaloneWindow(true /* blocking */);
640 return Promise.resolve();
643 if (aOpener?.openPreferences) {
644 if (aOptions.entrypoint == this.MIGRATION_ENTRYPOINTS.NEWTAB) {
645 if (aboutWelcomeBehavior == "autoclose") {
646 return aOpener.openPreferences("general-migrate-autoclose");
647 } else if (aboutWelcomeBehavior == "standalone") {
648 openStandaloneWindow(false /* blocking */);
649 return Promise.resolve();
652 return aOpener.openPreferences("general-migrate");
655 // If somehow we failed to open about:preferences, fall back to opening
656 // the top-level window.
657 openStandaloneWindow(false /* blocking */);
658 return Promise.resolve();
662 * Show the migration wizard for startup-migration. This should only be
663 * called by ProfileMigrator (see ProfileMigrator.js), which implements
664 * nsIProfileMigrator. This runs asynchronously if we are running an
667 * @param {nsIProfileStartup} aProfileStartup
668 * the nsIProfileStartup instance provided to ProfileMigrator.migrate.
669 * @param {string|null} [aMigratorKey=null]
670 * If set, the migration wizard will import from the corresponding
671 * migrator, bypassing the source-selection page. Otherwise, the
672 * source-selection page will be displayed, either with the default
673 * browser selected, if it could be detected and if there is a
674 * migrator for it, or with the first option selected as a fallback
675 * @param {string|null} [aProfileToMigrate=null]
676 * If set, the migration wizard will import from the profile indicated.
678 * if aMigratorKey is invalid or if it points to a non-existent
681 startupMigration(aProfileStartup, aMigratorKey, aProfileToMigrate) {
683 this.asyncStartupMigration(
691 async asyncStartupMigration(
696 if (!aProfileStartup) {
698 "an profile-startup instance is required for startup-migration"
701 gProfileStartup = aProfileStartup;
703 let skipSourceSelection = false,
707 migrator = await this.getMigrator(aMigratorKey);
709 // aMigratorKey must point to a valid source, so, if it doesn't
710 // cleanup and throw.
711 this.finishMigration();
713 "startMigration was asked to open auto-migrate from " +
714 "a non-existent source: " +
718 migratorKey = aMigratorKey;
719 skipSourceSelection = true;
721 let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser();
722 if (defaultBrowserKey) {
723 migrator = await this.getMigrator(defaultBrowserKey);
725 migratorKey = defaultBrowserKey;
731 let migrators = await Promise.all(
732 this.availableMigratorKeys.map(key => this.getMigrator(key))
734 // If there's no migrator set so far, ensure that there is at least one
735 // migrator available before opening the wizard.
736 // Note that we don't need to check the default browser first, because
737 // if that one existed we would have used it in the block above this one.
738 if (!migrators.some(m => m)) {
739 // None of the keys produced a usable migrator, so finish up here:
740 this.finishMigration();
747 skipSourceSelection &&
748 migratorKey == AppConstants.MOZ_APP_NAME;
750 let entrypoint = this.MIGRATION_ENTRYPOINTS.FIRSTRUN;
752 entrypoint = this.MIGRATION_ENTRYPOINTS.FXREFRESH;
755 this.showMigrationWizard(null, {
759 isStartupMigration: !!aProfileStartup,
761 profileId: aProfileToMigrate,
766 * This is only pseudo-private because some tests and helper functions
767 * still expect to be able to directly access it.
769 _importQuantities = {
777 getImportedCount(type) {
778 if (!this._importQuantities.hasOwnProperty(type)) {
780 `Unknown import data type "${type}" passed to getImportedCount`
783 return this._importQuantities[type];
786 insertBookmarkWrapper(bookmark) {
787 this._importQuantities.bookmarks++;
788 let insertionPromise = lazy.PlacesUtils.bookmarks.insert(bookmark);
789 if (!gKeepUndoData) {
790 return insertionPromise;
792 // If we keep undo data, add a promise handler that stores the undo data once
793 // the bookmark has been inserted in the DB, and then returns the bookmark.
794 let { parentGuid } = bookmark;
795 return insertionPromise.then(bm => {
796 let { guid, lastModified, type } = bm;
797 gUndoData.get("bookmarks").push({
807 insertManyBookmarksWrapper(bookmarks, parent) {
808 let insertionPromise = lazy.PlacesUtils.bookmarks.insertTree({
812 return insertionPromise.then(
814 this._importQuantities.bookmarks += insertedItems.length;
816 let bmData = gUndoData.get("bookmarks");
817 for (let bm of insertedItems) {
818 let { parentGuid, guid, lastModified, type } = bm;
819 bmData.push({ parentGuid, guid, lastModified, type });
822 if (parent == lazy.PlacesUtils.bookmarks.toolbarGuid) {
823 lazy.PlacesUIUtils.maybeToggleBookmarkToolbarVisibility(
824 true /* aForceVisible */
825 ).catch(console.error);
828 ex => console.error(ex)
832 insertVisitsWrapper(pageInfos) {
833 let now = new Date();
834 // Ensure that none of the dates are in the future. If they are, rewrite
835 // them to be now. This means we don't loose history entries, but they will
836 // be valid for the history store.
837 for (let pageInfo of pageInfos) {
838 for (let visit of pageInfo.visits) {
839 if (visit.date && visit.date > now) {
844 this._importQuantities.history += pageInfos.length;
846 this.#updateHistoryUndo(pageInfos);
848 return lazy.PlacesUtils.history.insertMany(pageInfos);
851 async insertLoginsWrapper(logins) {
852 this._importQuantities.logins += logins.length;
853 let inserted = await lazy.LoginHelper.maybeImportLogins(logins);
854 // Note that this means that if we import a login that has a newer password
855 // than we know about, we will update the login, and an undo of the import
856 // will not revert this. This seems preferable over removing the login
857 // outright or storing the old password in the undo file.
859 for (let { guid, timePasswordChanged } of inserted) {
860 gUndoData.get("logins").push({ guid, timePasswordChanged });
866 * Iterates through the favicons, sniffs for a mime type,
867 * and uses the mime type to properly import the favicon.
869 * Note: You may not want to await on the returned promise, especially if by
870 * doing so there's risk of interrupting the migration of more critical
871 * data (e.g. bookmarks).
873 * @param {object[]} favicons
874 * An array of Objects with these properties:
875 * {Uint8Array} faviconData: The binary data of a favicon
876 * {nsIURI} uri: The URI of the associated page
878 async insertManyFavicons(favicons) {
879 let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance(
883 for (let faviconDataItem of favicons) {
885 // getMIMETypeFromContent throws error if could not get the mime type
887 let mimeType = sniffer.getMIMETypeFromContent(
889 faviconDataItem.faviconData,
890 faviconDataItem.faviconData.length
893 let dataURL = await new Promise((resolve, reject) => {
894 let buffer = new Uint8ClampedArray(faviconDataItem.faviconData);
895 let blob = new Blob([buffer], { type: mimeType });
896 let reader = new FileReader();
897 reader.addEventListener("load", () => resolve(reader.result));
898 reader.addEventListener("error", reject);
899 reader.readAsDataURL(blob);
902 let fakeFaviconURI = Services.io.newURI(
903 "fake-favicon-uri:" + faviconDataItem.uri.spec
905 lazy.PlacesUtils.favicons
909 Services.io.newURI(dataURL)
911 .catch(console.warn);
913 // Even if error happens for favicon, continue the process.
919 async insertCreditCardsWrapper(cards) {
920 this._importQuantities.cards += cards.length;
921 let { formAutofillStorage } = ChromeUtils.importESModule(
922 "resource://autofill/FormAutofillStorage.sys.mjs"
925 await formAutofillStorage.initialize();
926 for (let card of cards) {
928 await formAutofillStorage.creditCards.add(card);
930 console.error("Failed to insert credit card due to error: ", e, card);
936 * Responsible for calling the AddonManager API that ultimately installs the
939 * @param {string} migratorKey a migrator key that we pass to
940 * `AMBrowserExtensionsImport` as the "browser
941 * identifier" used to match add-ons
942 * @param {string[]} extensionIDs a list of extension IDs from another browser
943 * @returns {(lazy.MigrationWizardConstants.PROGRESS_VALUE|string[])[]}
944 * An array whose first element is a `MigrationWizardConstants.PROGRESS_VALUE`
945 * and second element is an array of imported add-on ids.
947 async installExtensionsWrapper(migratorKey, extensionIDs) {
948 const totalExtensions = extensionIDs.length;
950 let importedAddonIDs = [];
952 const result = await lazy.AMBrowserExtensionsImport.stageInstalls(
956 importedAddonIDs = result.importedAddonIDs;
958 console.error(`Failed to import extensions: ${e}`);
961 this._importQuantities.extensions += importedAddonIDs.length;
963 if (!importedAddonIDs.length) {
965 lazy.MigrationWizardConstants.PROGRESS_VALUE.WARNING,
969 if (totalExtensions == importedAddonIDs.length) {
971 lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
976 lazy.MigrationWizardConstants.PROGRESS_VALUE.INFO,
981 initializeUndoData() {
982 gKeepUndoData = true;
983 gUndoData = new Map([
990 async #postProcessUndoData(state) {
994 let bookmarkFolders = state
996 .filter(b => b.type == lazy.PlacesUtils.bookmarks.TYPE_FOLDER);
998 let bookmarkFolderData = [];
999 let bmPromises = bookmarkFolders.map(({ guid }) => {
1000 // Ignore bookmarks where the promise doesn't resolve (ie that are missing)
1001 // Also check that the bookmark fetch returns isn't null before adding it.
1002 return lazy.PlacesUtils.bookmarks.fetch(guid).then(
1003 bm => bm && bookmarkFolderData.push(bm),
1008 await Promise.all(bmPromises);
1009 let folderLMMap = new Map(
1010 bookmarkFolderData.map(b => [b.guid, b.lastModified])
1012 for (let bookmark of bookmarkFolders) {
1013 let lastModified = folderLMMap.get(bookmark.guid);
1014 // If the bookmark was deleted, the map will be returning null, so check:
1016 bookmark.lastModified = lastModified;
1022 stopAndRetrieveUndoData() {
1023 let undoData = gUndoData;
1025 gKeepUndoData = false;
1026 return this.#postProcessUndoData(undoData);
1029 #updateHistoryUndo(pageInfos) {
1030 let visits = gUndoData.get("visits");
1031 let visitMap = new Map(visits.map(v => [v.url, v]));
1032 for (let pageInfo of pageInfos) {
1033 let visitCount = pageInfo.visits.length;
1035 if (visitCount > 1) {
1036 let dates = pageInfo.visits.map(v => v.date);
1037 first = Math.min.apply(Math, dates);
1038 last = Math.max.apply(Math, dates);
1040 first = last = pageInfo.visits[0].date;
1042 let url = pageInfo.url;
1043 if (url instanceof Ci.nsIURI) {
1044 url = pageInfo.url.spec;
1045 } else if (typeof url != "string") {
1052 // This won't save and we won't need to 'undo' it, so ignore this URL.
1055 if (!visitMap.has(url)) {
1056 visitMap.set(url, { url, visitCount, first, last });
1058 let currentData = visitMap.get(url);
1059 currentData.visitCount += visitCount;
1060 currentData.first = Math.min(currentData.first, first);
1061 currentData.last = Math.max(currentData.last, last);
1064 gUndoData.set("visits", Array.from(visitMap.values()));
1068 * Cleans up references to migrators and nsIProfileInstance instances.
1072 gProfileStartup = null;
1076 get availableMigratorKeys() {
1077 return [...this.#migrators.keys()];
1080 get availableFileMigrators() {
1081 return [...this.#fileMigrators.values()];
1085 * Enum for the entrypoint that is being used to start migration.
1086 * Callers can use the MIGRATION_ENTRYPOINTS getter to use these.
1088 * These values are what's written into the
1089 * FX_MIGRATION_ENTRY_POINT_CATEGORICAL histogram after a migration.
1091 * @see MIGRATION_ENTRYPOINTS
1095 #MIGRATION_ENTRYPOINTS_ENUM = Object.freeze({
1096 /** The entrypoint was not supplied */
1099 /** Migration is occurring at startup */
1100 FIRSTRUN: "firstrun",
1102 /** Migration is occurring at after a profile refresh */
1103 FXREFRESH: "fxrefresh",
1105 /** Migration is being started from the Library window */
1108 /** Migration is being started from our password management UI */
1109 PASSWORDS: "passwords",
1111 /** Migration is being started from the default about:home/about:newtab */
1114 /** Migration is being started from the File menu */
1115 FILE_MENU: "file_menu",
1117 /** Migration is being started from the Help menu */
1118 HELP_MENU: "help_menu",
1120 /** Migration is being started from the Bookmarks Toolbar */
1121 BOOKMARKS_TOOLBAR: "bookmarks_toolbar",
1123 /** Migration is being started from about:preferences */
1124 PREFERENCES: "preferences",
1126 /** Migration is being started from about:firefoxview */
1127 FIREFOX_VIEW: "firefox_view",
1131 * Returns an enum that should be used to record the entrypoint for
1132 * starting a migration.
1136 get MIGRATION_ENTRYPOINTS() {
1137 return this.#MIGRATION_ENTRYPOINTS_ENUM;
1141 * Enum for the numeric value written to the FX_MIGRATION_SOURCE_BROWSER.
1144 * @see getSourceIdForTelemetry
1148 #SOURCE_NAME_TO_ID_MAPPING_ENUM = Object.freeze({
1159 "chromium-360se": 9,
1160 "chromium-edge": 10,
1161 "chromium-edge-beta": 10,
1168 getSourceIdForTelemetry(sourceName) {
1169 return this.#SOURCE_NAME_TO_ID_MAPPING_ENUM[sourceName] || 0;
1172 get HISTORY_MAX_AGE_IN_MILLISECONDS() {
1173 return this.HISTORY_MAX_AGE_IN_DAYS * 24 * 60 * 60 * 1000;
1177 * Determines whether or not the underlying platform supports creating
1178 * native file pickers that can do folder selection, which is a
1179 * pre-requisite for getting read-access permissions for data from other
1180 * browsers that we can import from.
1182 * @returns {Promise<boolean>}
1184 canGetPermissionsOnPlatform() {
1185 return lazy.gCanGetPermissionsOnPlatformPromise;
1189 const MigrationUtilsSingleton = new MigrationUtils();
1191 export { MigrationUtilsSingleton as MigrationUtils };