forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1194 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1194 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | |
| import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   AMBrowserExtensionsImport: "resource://gre/modules/AddonManager.sys.mjs",
 | |
|   LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
 | |
|   PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
 | |
|   PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
 | |
|   Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
 | |
|   setTimeout: "resource://gre/modules/Timer.sys.mjs",
 | |
|   MigrationWizardConstants:
 | |
|     "chrome://browser/content/migration/migration-wizard-constants.mjs",
 | |
| });
 | |
| 
 | |
| ChromeUtils.defineLazyGetter(
 | |
|   lazy,
 | |
|   "gCanGetPermissionsOnPlatformPromise",
 | |
|   () => {
 | |
|     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
 | |
|     return fp.isModeSupported(Ci.nsIFilePicker.modeGetFolder);
 | |
|   }
 | |
| );
 | |
| 
 | |
| var gMigrators = null;
 | |
| var gFileMigrators = null;
 | |
| var gProfileStartup = null;
 | |
| var gL10n = null;
 | |
| 
 | |
| let gForceExitSpinResolve = false;
 | |
| let gKeepUndoData = false;
 | |
| let gUndoData = null;
 | |
| 
 | |
| function getL10n() {
 | |
|   if (!gL10n) {
 | |
|     gL10n = new Localization(["browser/migrationWizard.ftl"]);
 | |
|   }
 | |
|   return gL10n;
 | |
| }
 | |
| 
 | |
| const MIGRATOR_MODULES = Object.freeze({
 | |
|   EdgeProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/EdgeProfileMigrator.sys.mjs",
 | |
|     platforms: ["win"],
 | |
|   },
 | |
|   FirefoxProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/FirefoxProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
|   IEProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/IEProfileMigrator.sys.mjs",
 | |
|     platforms: ["win"],
 | |
|   },
 | |
|   SafariProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/SafariProfileMigrator.sys.mjs",
 | |
|     platforms: ["macosx"],
 | |
|   },
 | |
| 
 | |
|   // The following migrators are all variants of the ChromeProfileMigrator
 | |
| 
 | |
|   BraveProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
|   CanaryProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["macosx", "win"],
 | |
|   },
 | |
|   ChromeProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
|   ChromeBetaMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "win"],
 | |
|   },
 | |
|   ChromeDevMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux"],
 | |
|   },
 | |
|   ChromiumProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
|   Chromium360seMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["win"],
 | |
|   },
 | |
|   ChromiumEdgeMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["macosx", "win"],
 | |
|   },
 | |
|   ChromiumEdgeBetaMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["macosx", "win"],
 | |
|   },
 | |
|   OperaProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
|   VivaldiProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
|   OperaGXProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/ChromeProfileMigrator.sys.mjs",
 | |
|     platforms: ["macosx", "win"],
 | |
|   },
 | |
| 
 | |
|   InternalTestingProfileMigrator: {
 | |
|     moduleURI: "resource:///modules/InternalTestingProfileMigrator.sys.mjs",
 | |
|     platforms: ["linux", "macosx", "win"],
 | |
|   },
 | |
| });
 | |
| 
 | |
| const FILE_MIGRATOR_MODULES = Object.freeze({
 | |
|   PasswordFileMigrator: {
 | |
|     moduleURI: "resource:///modules/FileMigrators.sys.mjs",
 | |
|   },
 | |
|   BookmarksFileMigrator: {
 | |
|     moduleURI: "resource:///modules/FileMigrators.sys.mjs",
 | |
|   },
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * The singleton MigrationUtils service. This service is the primary mechanism
 | |
|  * by which migrations from other browsers to this browser occur. The singleton
 | |
|  * instance of this class is exported from this module as `MigrationUtils`.
 | |
|  */
 | |
| class MigrationUtils {
 | |
|   constructor() {
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(
 | |
|       this,
 | |
|       "HISTORY_MAX_AGE_IN_DAYS",
 | |
|       "browser.migrate.history.maxAgeInDays",
 | |
|       180
 | |
|     );
 | |
| 
 | |
|     ChromeUtils.registerWindowActor("MigrationWizard", {
 | |
|       parent: {
 | |
|         esModuleURI: "resource:///actors/MigrationWizardParent.sys.mjs",
 | |
|       },
 | |
| 
 | |
|       child: {
 | |
|         esModuleURI: "resource:///actors/MigrationWizardChild.sys.mjs",
 | |
|         events: {
 | |
|           "MigrationWizard:RequestState": { wantUntrusted: true },
 | |
|           "MigrationWizard:BeginMigration": { wantUntrusted: true },
 | |
|           "MigrationWizard:RequestSafariPermissions": { wantUntrusted: true },
 | |
|           "MigrationWizard:SelectSafariPasswordFile": { wantUntrusted: true },
 | |
|           "MigrationWizard:OpenAboutAddons": { wantUntrusted: true },
 | |
|           "MigrationWizard:PermissionsNeeded": { wantUntrusted: true },
 | |
|           "MigrationWizard:GetPermissions": { wantUntrusted: true },
 | |
|           "MigrationWizard:OpenURL": { wantUntrusted: true },
 | |
|         },
 | |
|       },
 | |
| 
 | |
|       includeChrome: true,
 | |
|       allFrames: true,
 | |
|       matches: [
 | |
|         "about:welcome",
 | |
|         "about:welcome?*",
 | |
|         "about:preferences",
 | |
|         "about:settings",
 | |
|         "chrome://browser/content/migration/migration-dialog-window.html",
 | |
|         "chrome://browser/content/spotlight.html",
 | |
|         "about:firefoxview",
 | |
|       ],
 | |
|     });
 | |
| 
 | |
|     ChromeUtils.defineLazyGetter(this, "IS_LINUX_SNAP_PACKAGE", () => {
 | |
|       if (
 | |
|         AppConstants.platform != "linux" ||
 | |
|         !Cc["@mozilla.org/gio-service;1"]
 | |
|       ) {
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       let gIOSvc = Cc["@mozilla.org/gio-service;1"].getService(
 | |
|         Ci.nsIGIOService
 | |
|       );
 | |
|       return gIOSvc.isRunningUnderSnap;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   resourceTypes = Object.freeze({
 | |
|     ALL: 0x0000,
 | |
|     /* 0x01 used to be used for settings, but was removed. */
 | |
|     COOKIES: 0x0002,
 | |
|     HISTORY: 0x0004,
 | |
|     FORMDATA: 0x0008,
 | |
|     PASSWORDS: 0x0010,
 | |
|     BOOKMARKS: 0x0020,
 | |
|     OTHERDATA: 0x0040,
 | |
|     SESSION: 0x0080,
 | |
|     PAYMENT_METHODS: 0x0100,
 | |
|     EXTENSIONS: 0x0200,
 | |
|   });
 | |
| 
 | |
|   /**
 | |
|    * Helper for implementing simple asynchronous cases of migration resources'
 | |
|    * |migrate(aCallback)| (see MigratorBase).  If your |migrate| method
 | |
|    * just waits for some file to be read, for example, and then migrates
 | |
|    * everything right away, you can wrap the async-function with this helper
 | |
|    * and not worry about notifying the callback.
 | |
|    *
 | |
|    * @example
 | |
|    * // For example, instead of writing:
 | |
|    * setTimeout(function() {
 | |
|    *   try {
 | |
|    *     ....
 | |
|    *     aCallback(true);
 | |
|    *   }
 | |
|    *   catch() {
 | |
|    *     aCallback(false);
 | |
|    *   }
 | |
|    * }, 0);
 | |
|    *
 | |
|    * // You may write:
 | |
|    * setTimeout(MigrationUtils.wrapMigrateFunction(function() {
 | |
|    *   if (importingFromMosaic)
 | |
|    *     throw Cr.NS_ERROR_UNEXPECTED;
 | |
|    * }, aCallback), 0);
 | |
|    *
 | |
|    * // ... and aCallback will be called with aSuccess=false when importing
 | |
|    * // from Mosaic, or with aSuccess=true otherwise.
 | |
|    *
 | |
|    * @param {Function} aFunction
 | |
|    *   the function that will be called sometime later.  If aFunction
 | |
|    *   throws when it's called, aCallback(false) is called, otherwise
 | |
|    *   aCallback(true) is called.
 | |
|    * @param {Function} aCallback
 | |
|    *   the callback function passed to |migrate|.
 | |
|    * @returns {Function}
 | |
|    *   the wrapped function.
 | |
|    */
 | |
|   wrapMigrateFunction(aFunction, aCallback) {
 | |
|     return function () {
 | |
|       let success = false;
 | |
|       try {
 | |
|         aFunction.apply(null, arguments);
 | |
|         success = true;
 | |
|       } catch (ex) {
 | |
|         console.error(ex);
 | |
|       }
 | |
|       // Do not change this to call aCallback directly in try try & catch
 | |
|       // blocks, because if aCallback throws, we may end up calling aCallback
 | |
|       // twice.
 | |
|       aCallback(success);
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Gets localized string corresponding to l10n-id
 | |
|    *
 | |
|    * @param {string} aKey
 | |
|    *   The key of the id of the localization to retrieve.
 | |
|    * @param {object} [aArgs=undefined]
 | |
|    *   An optional map of arguments to the id.
 | |
|    * @returns {Promise<string>}
 | |
|    *   A promise that resolves to the retrieved localization.
 | |
|    */
 | |
|   getLocalizedString(aKey, aArgs) {
 | |
|     let l10n = getL10n();
 | |
|     return l10n.formatValue(aKey, aArgs);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get all the rows corresponding to a select query from a database, without
 | |
|    * requiring a lock on the database. If fetching data fails (because someone
 | |
|    * else tried to write to the DB at the same time, for example), we will
 | |
|    * retry the fetch after a 100ms timeout, up to 10 times.
 | |
|    *
 | |
|    * @param {string} path
 | |
|    *   The file path to the database we want to open.
 | |
|    * @param {string} description
 | |
|    *   A developer-readable string identifying what kind of database we're
 | |
|    *   trying to open.
 | |
|    * @param {string} selectQuery
 | |
|    *   The SELECT query to use to fetch the rows.
 | |
|    * @param {Promise} [testDelayPromise]
 | |
|    *   An optional promise to await for after the first loop, used in tests.
 | |
|    *
 | |
|    * @returns {Promise<object[]|Error>}
 | |
|    *   A promise that resolves to an array of rows. The promise will be
 | |
|    *   rejected if the read/fetch failed even after retrying.
 | |
|    */
 | |
|   getRowsFromDBWithoutLocks(
 | |
|     path,
 | |
|     description,
 | |
|     selectQuery,
 | |
|     testDelayPromise = null
 | |
|   ) {
 | |
|     let dbOptions = {
 | |
|       readOnly: true,
 | |
|       ignoreLockingMode: true,
 | |
|       path,
 | |
|     };
 | |
| 
 | |
|     const RETRYLIMIT = 10;
 | |
|     const RETRYINTERVAL = 100;
 | |
|     return (async function innerGetRows() {
 | |
|       let rows = null;
 | |
|       for (let retryCount = RETRYLIMIT; retryCount; retryCount--) {
 | |
|         // Attempt to get the rows. If this succeeds, we will bail out of the loop,
 | |
|         // close the database in a failsafe way, and pass the rows back.
 | |
|         // If fetching the rows throws, we will wait RETRYINTERVAL ms
 | |
|         // and try again. This will repeat a maximum of RETRYLIMIT times.
 | |
|         let db;
 | |
|         let didOpen = false;
 | |
|         let previousExceptionMessage = null;
 | |
|         try {
 | |
|           db = await lazy.Sqlite.openConnection(dbOptions);
 | |
|           didOpen = true;
 | |
|           rows = await db.execute(selectQuery);
 | |
|           break;
 | |
|         } catch (ex) {
 | |
|           if (previousExceptionMessage != ex.message) {
 | |
|             console.error(ex);
 | |
|           }
 | |
|           previousExceptionMessage = ex.message;
 | |
|           if (ex.name == "NS_ERROR_FILE_CORRUPTED") {
 | |
|             break;
 | |
|           }
 | |
|         } finally {
 | |
|           try {
 | |
|             if (didOpen) {
 | |
|               await db.close();
 | |
|             }
 | |
|           } catch (ex) {}
 | |
|         }
 | |
|         await Promise.all([
 | |
|           new Promise(resolve => lazy.setTimeout(resolve, RETRYINTERVAL)),
 | |
|           testDelayPromise,
 | |
|         ]);
 | |
|       }
 | |
|       if (!rows) {
 | |
|         throw new Error(
 | |
|           "Couldn't get rows from the " + description + " database."
 | |
|         );
 | |
|       }
 | |
|       return rows;
 | |
|     })();
 | |
|   }
 | |
| 
 | |
|   get #migrators() {
 | |
|     if (!gMigrators) {
 | |
|       gMigrators = new Map();
 | |
|       for (let [symbol, { moduleURI, platforms }] of Object.entries(
 | |
|         MIGRATOR_MODULES
 | |
|       )) {
 | |
|         if (platforms.includes(AppConstants.platform)) {
 | |
|           let { [symbol]: migratorClass } =
 | |
|             ChromeUtils.importESModule(moduleURI);
 | |
|           if (gMigrators.has(migratorClass.key)) {
 | |
|             console.error(
 | |
|               "A pre-existing migrator exists with key " +
 | |
|                 `${migratorClass.key}. Not registering.`
 | |
|             );
 | |
|             continue;
 | |
|           }
 | |
|           gMigrators.set(migratorClass.key, new migratorClass());
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return gMigrators;
 | |
|   }
 | |
| 
 | |
|   get #fileMigrators() {
 | |
|     if (!gFileMigrators) {
 | |
|       gFileMigrators = new Map();
 | |
|       for (let [symbol, { moduleURI }] of Object.entries(
 | |
|         FILE_MIGRATOR_MODULES
 | |
|       )) {
 | |
|         let { [symbol]: migratorClass } = ChromeUtils.importESModule(moduleURI);
 | |
|         if (gFileMigrators.has(migratorClass.key)) {
 | |
|           console.error(
 | |
|             "A pre-existing file migrator exists with key " +
 | |
|               `${migratorClass.key}. Not registering.`
 | |
|           );
 | |
|           continue;
 | |
|         }
 | |
|         gFileMigrators.set(migratorClass.key, new migratorClass());
 | |
|       }
 | |
|     }
 | |
|     return gFileMigrators;
 | |
|   }
 | |
| 
 | |
|   forceExitSpinResolve() {
 | |
|     gForceExitSpinResolve = true;
 | |
|   }
 | |
| 
 | |
|   spinResolve(promise) {
 | |
|     if (!(promise instanceof Promise)) {
 | |
|       return promise;
 | |
|     }
 | |
|     let done = false;
 | |
|     let result = null;
 | |
|     let error = null;
 | |
|     gForceExitSpinResolve = false;
 | |
|     promise
 | |
|       .catch(e => {
 | |
|         error = e;
 | |
|       })
 | |
|       .then(r => {
 | |
|         result = r;
 | |
|         done = true;
 | |
|       });
 | |
| 
 | |
|     Services.tm.spinEventLoopUntil(
 | |
|       "MigrationUtils.sys.mjs:MU_spinResolve",
 | |
|       () => done || gForceExitSpinResolve
 | |
|     );
 | |
|     if (!done) {
 | |
|       throw new Error("Forcefully exited event loop.");
 | |
|     } else if (error) {
 | |
|       throw error;
 | |
|     } else {
 | |
|       return result;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the migrator for the given source, if any data is available
 | |
|    * for this source, or if permissions are required in order to read
 | |
|    * data from this source. Returns null otherwise.
 | |
|    *
 | |
|    * @param {string} aKey
 | |
|    *   Internal name of the migration source. See `availableMigratorKeys`
 | |
|    *   for supported values by OS.
 | |
|    * @returns {Promise<MigratorBase|null>}
 | |
|    *   A profile migrator implementing nsIBrowserProfileMigrator, if it can
 | |
|    *   import any data, null otherwise.
 | |
|    */
 | |
|   async getMigrator(aKey) {
 | |
|     let migrator = this.#migrators.get(aKey);
 | |
|     if (!migrator) {
 | |
|       console.error(`Could not find a migrator class for key ${aKey}`);
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       if (!migrator) {
 | |
|         return null;
 | |
|       }
 | |
| 
 | |
|       if (
 | |
|         (await migrator.isSourceAvailable()) ||
 | |
|         (!(await migrator.hasPermissions()) && migrator.canGetPermissions())
 | |
|       ) {
 | |
|         return migrator;
 | |
|       }
 | |
| 
 | |
|       return null;
 | |
|     } catch (ex) {
 | |
|       console.error(ex);
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   getFileMigrator(aKey) {
 | |
|     let migrator = this.#fileMigrators.get(aKey);
 | |
|     if (!migrator) {
 | |
|       console.error(`Could not find a file migrator class for key ${aKey}`);
 | |
|       return null;
 | |
|     }
 | |
|     return migrator;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns true if a migrator is registered with key aKey. No check is made
 | |
|    * to determine if a profile exists that the migrator can migrate from.
 | |
|    *
 | |
|    * @param {string} aKey
 | |
|    *   Internal name of the migration source. See `availableMigratorKeys`
 | |
|    *   for supported values by OS.
 | |
|    * @returns {boolean}
 | |
|    */
 | |
|   migratorExists(aKey) {
 | |
|     return this.#migrators.has(aKey);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Figure out what is the default browser, and if there is a migrator
 | |
|    * for it, return that migrator's internal name.
 | |
|    *
 | |
|    * For the time being, the "internal name" of a migrator is its contract-id
 | |
|    * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
 | |
|    * but it will soon be exposed properly.
 | |
|    *
 | |
|    * @returns {string}
 | |
|    */
 | |
|   getMigratorKeyForDefaultBrowser() {
 | |
|     // Canary uses the same description as Chrome so we can't distinguish them.
 | |
|     // Edge Beta on macOS uses "Microsoft Edge" with no "beta" indication.
 | |
|     const APP_DESC_TO_KEY = {
 | |
|       "Internet Explorer": "ie",
 | |
|       "Microsoft Edge": "edge",
 | |
|       Safari: "safari",
 | |
|       Firefox: "firefox",
 | |
|       Nightly: "firefox",
 | |
|       Opera: "opera",
 | |
|       Vivaldi: "vivaldi",
 | |
|       "Opera GX": "opera-gx",
 | |
|       "Brave Web Browser": "brave", // Windows, Linux
 | |
|       Brave: "brave", // OS X
 | |
|       "Google Chrome": "chrome", // Windows, Linux
 | |
|       Chrome: "chrome", // OS X
 | |
|       Chromium: "chromium", // Windows, OS X
 | |
|       "Chromium Web Browser": "chromium", // Linux
 | |
|       "360\u5b89\u5168\u6d4f\u89c8\u5668": "chromium-360se",
 | |
|     };
 | |
| 
 | |
|     let key = "";
 | |
|     try {
 | |
|       let browserDesc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
 | |
|         .getService(Ci.nsIExternalProtocolService)
 | |
|         .getApplicationDescription("http");
 | |
|       key = APP_DESC_TO_KEY[browserDesc] || "";
 | |
|       // Handle devedition, as well as "FirefoxNightly" on OS X.
 | |
|       if (!key && browserDesc.startsWith("Firefox")) {
 | |
|         key = "firefox";
 | |
|       }
 | |
|     } catch (ex) {
 | |
|       console.error("Could not detect default browser: ", ex);
 | |
|     }
 | |
| 
 | |
|     return key;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * True if we're in the process of a startup migration.
 | |
|    *
 | |
|    * @type {boolean}
 | |
|    */
 | |
|   get isStartupMigration() {
 | |
|     return gProfileStartup != null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * In the case of startup migration, this is set to the nsIProfileStartup
 | |
|    * instance passed to ProfileMigrator's migrate.
 | |
|    *
 | |
|    * @see showMigrationWizard
 | |
|    * @type {nsIProfileStartup|null}
 | |
|    */
 | |
|   get profileStartup() {
 | |
|     return gProfileStartup;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Show the migration wizard in about:preferences, or if there is not an existing
 | |
|    * browser window open, in a new top-level dialog window.
 | |
|    *
 | |
|    * NB: If you add new consumers, please add a migration entry point constant to
 | |
|    * MIGRATION_ENTRYPOINTS and supply that entrypoint with the entrypoint property
 | |
|    * in the aOptions argument.
 | |
|    *
 | |
|    * @param {Window} [aOpener=null]
 | |
|    *   optional; the window that asks to open the wizard.
 | |
|    * @param {object} [aOptions=null]
 | |
|    *   optional named arguments for the migration wizard.
 | |
|    * @param {string} [aOptions.entrypoint=undefined]
 | |
|    *   migration entry point constant. See MIGRATION_ENTRYPOINTS.
 | |
|    * @param {string} [aOptions.migratorKey=undefined]
 | |
|    *   The key for which migrator to use automatically. This is the key that is exposed
 | |
|    *   as a static getter on the migrator class.
 | |
|    * @param {MigratorBase} [aOptions.migrator=undefined]
 | |
|    *   A migrator instance to use automatically.
 | |
|    * @param {boolean} [aOptions.isStartupMigration=undefined]
 | |
|    *   True if this is a startup migration.
 | |
|    * @param {boolean} [aOptions.skipSourceSelection=undefined]
 | |
|    *   True if the source selection page of the wizard should be skipped.
 | |
|    * @param {string} [aOptions.profileId]
 | |
|    *   An identifier for the profile to use when migrating.
 | |
|    * @returns {Promise<undefined>}
 | |
|    *   If an about:preferences tab can be opened, this will resolve when
 | |
|    *   that tab has been switched to. Otherwise, this will resolve
 | |
|    *   just after opening the top-level dialog window.
 | |
|    */
 | |
|   showMigrationWizard(aOpener, aOptions) {
 | |
|     // When migration is kicked off from about:welcome, there are
 | |
|     // a few different behaviors that we want to test, controlled
 | |
|     // by a preference that is instrumented for Nimbus. The pref
 | |
|     // has the following possible states:
 | |
|     //
 | |
|     // "autoclose":
 | |
|     //   The user will be directed to the migration wizard in
 | |
|     //   about:preferences, but once the wizard is dismissed,
 | |
|     //   the tab will close.
 | |
|     //
 | |
|     // "standalone":
 | |
|     //   The migration wizard will open in a new top-level content
 | |
|     //   window.
 | |
|     //
 | |
|     // "default" / other
 | |
|     //   The user will be directed to the migration wizard in
 | |
|     //   about:preferences. The tab will not close once the
 | |
|     //   user closes the wizard.
 | |
|     let aboutWelcomeBehavior = Services.prefs.getCharPref(
 | |
|       "browser.migrate.content-modal.about-welcome-behavior",
 | |
|       "default"
 | |
|     );
 | |
| 
 | |
|     let entrypoint = aOptions.entrypoint || this.MIGRATION_ENTRYPOINTS.UNKNOWN;
 | |
|     Services.telemetry
 | |
|       .getHistogramById("FX_MIGRATION_ENTRY_POINT_CATEGORICAL")
 | |
|       .add(entrypoint);
 | |
| 
 | |
|     let openStandaloneWindow = blocking => {
 | |
|       let features = "dialog,centerscreen,resizable=no";
 | |
| 
 | |
|       if (blocking) {
 | |
|         features += ",modal";
 | |
|       }
 | |
| 
 | |
|       Services.ww.openWindow(
 | |
|         aOpener,
 | |
|         "chrome://browser/content/migration/migration-dialog-window.html",
 | |
|         "_blank",
 | |
|         features,
 | |
|         {
 | |
|           options: aOptions,
 | |
|         }
 | |
|       );
 | |
|       return Promise.resolve();
 | |
|     };
 | |
| 
 | |
|     if (aOptions.isStartupMigration) {
 | |
|       // Record that the uninstaller requested a profile refresh
 | |
|       if (Services.env.get("MOZ_UNINSTALLER_PROFILE_REFRESH")) {
 | |
|         Services.env.set("MOZ_UNINSTALLER_PROFILE_REFRESH", "");
 | |
|         Services.telemetry.scalarSet(
 | |
|           "migration.uninstaller_profile_refresh",
 | |
|           true
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       openStandaloneWindow(true /* blocking */);
 | |
|       return Promise.resolve();
 | |
|     }
 | |
| 
 | |
|     if (aOpener?.openPreferences) {
 | |
|       if (aOptions.entrypoint == this.MIGRATION_ENTRYPOINTS.NEWTAB) {
 | |
|         if (aboutWelcomeBehavior == "autoclose") {
 | |
|           return aOpener.openPreferences("general-migrate-autoclose");
 | |
|         } else if (aboutWelcomeBehavior == "standalone") {
 | |
|           openStandaloneWindow(false /* blocking */);
 | |
|           return Promise.resolve();
 | |
|         }
 | |
|       }
 | |
|       return aOpener.openPreferences("general-migrate");
 | |
|     }
 | |
| 
 | |
|     // If somehow we failed to open about:preferences, fall back to opening
 | |
|     // the top-level window.
 | |
|     openStandaloneWindow(false /* blocking */);
 | |
|     return Promise.resolve();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Show the migration wizard for startup-migration.  This should only be
 | |
|    * called by ProfileMigrator (see ProfileMigrator.js), which implements
 | |
|    * nsIProfileMigrator. This runs asynchronously if we are running an
 | |
|    * automigration.
 | |
|    *
 | |
|    * @param {nsIProfileStartup} aProfileStartup
 | |
|    *   the nsIProfileStartup instance provided to ProfileMigrator.migrate.
 | |
|    * @param {string|null} [aMigratorKey=null]
 | |
|    *   If set, the migration wizard will import from the corresponding
 | |
|    *   migrator, bypassing the source-selection page.  Otherwise, the
 | |
|    *   source-selection page will be displayed, either with the default
 | |
|    *   browser selected, if it could be detected and if there is a
 | |
|    *   migrator for it, or with the first option selected as a fallback
 | |
|    * @param {string|null} [aProfileToMigrate=null]
 | |
|    *   If set, the migration wizard will import from the profile indicated.
 | |
|    * @throws
 | |
|    *   if aMigratorKey is invalid or if it points to a non-existent
 | |
|    *   source.
 | |
|    */
 | |
|   startupMigration(aProfileStartup, aMigratorKey, aProfileToMigrate) {
 | |
|     this.spinResolve(
 | |
|       this.asyncStartupMigration(
 | |
|         aProfileStartup,
 | |
|         aMigratorKey,
 | |
|         aProfileToMigrate
 | |
|       )
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   async asyncStartupMigration(
 | |
|     aProfileStartup,
 | |
|     aMigratorKey,
 | |
|     aProfileToMigrate
 | |
|   ) {
 | |
|     if (!aProfileStartup) {
 | |
|       throw new Error(
 | |
|         "an profile-startup instance is required for startup-migration"
 | |
|       );
 | |
|     }
 | |
|     gProfileStartup = aProfileStartup;
 | |
| 
 | |
|     let skipSourceSelection = false,
 | |
|       migrator = null,
 | |
|       migratorKey = "";
 | |
|     if (aMigratorKey) {
 | |
|       migrator = await this.getMigrator(aMigratorKey);
 | |
|       if (!migrator) {
 | |
|         // aMigratorKey must point to a valid source, so, if it doesn't
 | |
|         // cleanup and throw.
 | |
|         this.finishMigration();
 | |
|         throw new Error(
 | |
|           "startMigration was asked to open auto-migrate from " +
 | |
|             "a non-existent source: " +
 | |
|             aMigratorKey
 | |
|         );
 | |
|       }
 | |
|       migratorKey = aMigratorKey;
 | |
|       skipSourceSelection = true;
 | |
|     } else {
 | |
|       let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser();
 | |
|       if (defaultBrowserKey) {
 | |
|         migrator = await this.getMigrator(defaultBrowserKey);
 | |
|         if (migrator) {
 | |
|           migratorKey = defaultBrowserKey;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!migrator) {
 | |
|       let migrators = await Promise.all(
 | |
|         this.availableMigratorKeys.map(key => this.getMigrator(key))
 | |
|       );
 | |
|       // If there's no migrator set so far, ensure that there is at least one
 | |
|       // migrator available before opening the wizard.
 | |
|       // Note that we don't need to check the default browser first, because
 | |
|       // if that one existed we would have used it in the block above this one.
 | |
|       if (!migrators.some(m => m)) {
 | |
|         // None of the keys produced a usable migrator, so finish up here:
 | |
|         this.finishMigration();
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let isRefresh =
 | |
|       migrator &&
 | |
|       skipSourceSelection &&
 | |
|       migratorKey == AppConstants.MOZ_APP_NAME;
 | |
| 
 | |
|     let entrypoint = this.MIGRATION_ENTRYPOINTS.FIRSTRUN;
 | |
|     if (isRefresh) {
 | |
|       entrypoint = this.MIGRATION_ENTRYPOINTS.FXREFRESH;
 | |
|     }
 | |
| 
 | |
|     this.showMigrationWizard(null, {
 | |
|       entrypoint,
 | |
|       migratorKey,
 | |
|       migrator,
 | |
|       isStartupMigration: !!aProfileStartup,
 | |
|       skipSourceSelection,
 | |
|       profileId: aProfileToMigrate,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * This is only pseudo-private because some tests and helper functions
 | |
|    * still expect to be able to directly access it.
 | |
|    */
 | |
|   _importQuantities = {
 | |
|     bookmarks: 0,
 | |
|     logins: 0,
 | |
|     history: 0,
 | |
|     cards: 0,
 | |
|     extensions: 0,
 | |
|   };
 | |
| 
 | |
|   getImportedCount(type) {
 | |
|     if (!this._importQuantities.hasOwnProperty(type)) {
 | |
|       throw new Error(
 | |
|         `Unknown import data type "${type}" passed to getImportedCount`
 | |
|       );
 | |
|     }
 | |
|     return this._importQuantities[type];
 | |
|   }
 | |
| 
 | |
|   insertBookmarkWrapper(bookmark) {
 | |
|     this._importQuantities.bookmarks++;
 | |
|     let insertionPromise = lazy.PlacesUtils.bookmarks.insert(bookmark);
 | |
|     if (!gKeepUndoData) {
 | |
|       return insertionPromise;
 | |
|     }
 | |
|     // If we keep undo data, add a promise handler that stores the undo data once
 | |
|     // the bookmark has been inserted in the DB, and then returns the bookmark.
 | |
|     let { parentGuid } = bookmark;
 | |
|     return insertionPromise.then(bm => {
 | |
|       let { guid, lastModified, type } = bm;
 | |
|       gUndoData.get("bookmarks").push({
 | |
|         parentGuid,
 | |
|         guid,
 | |
|         lastModified,
 | |
|         type,
 | |
|       });
 | |
|       return bm;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   insertManyBookmarksWrapper(bookmarks, parent) {
 | |
|     let insertionPromise = lazy.PlacesUtils.bookmarks.insertTree({
 | |
|       guid: parent,
 | |
|       children: bookmarks,
 | |
|     });
 | |
|     return insertionPromise.then(
 | |
|       insertedItems => {
 | |
|         this._importQuantities.bookmarks += insertedItems.length;
 | |
|         if (gKeepUndoData) {
 | |
|           let bmData = gUndoData.get("bookmarks");
 | |
|           for (let bm of insertedItems) {
 | |
|             let { parentGuid, guid, lastModified, type } = bm;
 | |
|             bmData.push({ parentGuid, guid, lastModified, type });
 | |
|           }
 | |
|         }
 | |
|         if (parent == lazy.PlacesUtils.bookmarks.toolbarGuid) {
 | |
|           lazy.PlacesUIUtils.maybeToggleBookmarkToolbarVisibility(
 | |
|             true /* aForceVisible */
 | |
|           ).catch(console.error);
 | |
|         }
 | |
|       },
 | |
|       ex => console.error(ex)
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   insertVisitsWrapper(pageInfos) {
 | |
|     let now = new Date();
 | |
|     // Ensure that none of the dates are in the future. If they are, rewrite
 | |
|     // them to be now. This means we don't loose history entries, but they will
 | |
|     // be valid for the history store.
 | |
|     for (let pageInfo of pageInfos) {
 | |
|       for (let visit of pageInfo.visits) {
 | |
|         if (visit.date && visit.date > now) {
 | |
|           visit.date = now;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     this._importQuantities.history += pageInfos.length;
 | |
|     if (gKeepUndoData) {
 | |
|       this.#updateHistoryUndo(pageInfos);
 | |
|     }
 | |
|     return lazy.PlacesUtils.history.insertMany(pageInfos);
 | |
|   }
 | |
| 
 | |
|   async insertLoginsWrapper(logins) {
 | |
|     this._importQuantities.logins += logins.length;
 | |
|     let inserted = await lazy.LoginHelper.maybeImportLogins(logins);
 | |
|     // Note that this means that if we import a login that has a newer password
 | |
|     // than we know about, we will update the login, and an undo of the import
 | |
|     // will not revert this. This seems preferable over removing the login
 | |
|     // outright or storing the old password in the undo file.
 | |
|     if (gKeepUndoData) {
 | |
|       for (let { guid, timePasswordChanged } of inserted) {
 | |
|         gUndoData.get("logins").push({ guid, timePasswordChanged });
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Iterates through the favicons, sniffs for a mime type,
 | |
|    * and uses the mime type to properly import the favicon.
 | |
|    *
 | |
|    * Note: You may not want to await on the returned promise, especially if by
 | |
|    *       doing so there's risk of interrupting the migration of more critical
 | |
|    *       data (e.g. bookmarks).
 | |
|    *
 | |
|    * @param {object[]} favicons
 | |
|    *   An array of Objects with these properties:
 | |
|    *     {Uint8Array} faviconData: The binary data of a favicon
 | |
|    *     {nsIURI} uri: The URI of the associated page
 | |
|    */
 | |
|   async insertManyFavicons(favicons) {
 | |
|     let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance(
 | |
|       Ci.nsIContentSniffer
 | |
|     );
 | |
| 
 | |
|     for (let faviconDataItem of favicons) {
 | |
|       let dataURL;
 | |
| 
 | |
|       try {
 | |
|         // getMIMETypeFromContent throws error if could not get the mime type
 | |
|         // from the data.
 | |
|         let mimeType = sniffer.getMIMETypeFromContent(
 | |
|           null,
 | |
|           faviconDataItem.faviconData,
 | |
|           faviconDataItem.faviconData.length
 | |
|         );
 | |
| 
 | |
|         dataURL = await new Promise((resolve, reject) => {
 | |
|           let buffer = new Uint8ClampedArray(faviconDataItem.faviconData);
 | |
|           let blob = new Blob([buffer], { type: mimeType });
 | |
|           let reader = new FileReader();
 | |
|           reader.addEventListener("load", () => resolve(reader.result));
 | |
|           reader.addEventListener("error", reject);
 | |
|           reader.readAsDataURL(blob);
 | |
|         });
 | |
| 
 | |
|         let fakeFaviconURI = Services.io.newURI(
 | |
|           "fake-favicon-uri:" + faviconDataItem.uri.spec
 | |
|         );
 | |
|         lazy.PlacesUtils.favicons.setFaviconForPage(
 | |
|           faviconDataItem.uri,
 | |
|           fakeFaviconURI,
 | |
|           Services.io.newURI(dataURL)
 | |
|         );
 | |
|       } catch (e) {
 | |
|         // Even if error happens for favicon, continue the process.
 | |
|         console.warn(e);
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async insertCreditCardsWrapper(cards) {
 | |
|     this._importQuantities.cards += cards.length;
 | |
|     let { formAutofillStorage } = ChromeUtils.importESModule(
 | |
|       "resource://autofill/FormAutofillStorage.sys.mjs"
 | |
|     );
 | |
| 
 | |
|     await formAutofillStorage.initialize();
 | |
|     for (let card of cards) {
 | |
|       try {
 | |
|         await formAutofillStorage.creditCards.add(card);
 | |
|       } catch (e) {
 | |
|         console.error("Failed to insert credit card due to error: ", e, card);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Responsible for calling the AddonManager API that ultimately installs the
 | |
|    * matched add-ons.
 | |
|    *
 | |
|    * @param {string} migratorKey a migrator key that we pass to
 | |
|    *                             `AMBrowserExtensionsImport` as the "browser
 | |
|    *                             identifier" used to match add-ons
 | |
|    * @param {string[]} extensionIDs a list of extension IDs from another browser
 | |
|    */
 | |
|   async installExtensionsWrapper(migratorKey, extensionIDs) {
 | |
|     const totalExtensions = extensionIDs.length;
 | |
| 
 | |
|     let importedAddonIDs = [];
 | |
|     try {
 | |
|       const result = await lazy.AMBrowserExtensionsImport.stageInstalls(
 | |
|         migratorKey,
 | |
|         extensionIDs
 | |
|       );
 | |
|       importedAddonIDs = result.importedAddonIDs;
 | |
|     } catch (e) {
 | |
|       console.error(`Failed to import extensions: ${e}`);
 | |
|     }
 | |
| 
 | |
|     this._importQuantities.extensions += importedAddonIDs.length;
 | |
| 
 | |
|     if (!importedAddonIDs.length) {
 | |
|       return [
 | |
|         lazy.MigrationWizardConstants.PROGRESS_VALUE.WARNING,
 | |
|         importedAddonIDs,
 | |
|       ];
 | |
|     }
 | |
|     if (totalExtensions == importedAddonIDs.length) {
 | |
|       return [
 | |
|         lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
 | |
|         importedAddonIDs,
 | |
|       ];
 | |
|     }
 | |
|     return [
 | |
|       lazy.MigrationWizardConstants.PROGRESS_VALUE.INFO,
 | |
|       importedAddonIDs,
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   initializeUndoData() {
 | |
|     gKeepUndoData = true;
 | |
|     gUndoData = new Map([
 | |
|       ["bookmarks", []],
 | |
|       ["visits", []],
 | |
|       ["logins", []],
 | |
|     ]);
 | |
|   }
 | |
| 
 | |
|   async #postProcessUndoData(state) {
 | |
|     if (!state) {
 | |
|       return state;
 | |
|     }
 | |
|     let bookmarkFolders = state
 | |
|       .get("bookmarks")
 | |
|       .filter(b => b.type == lazy.PlacesUtils.bookmarks.TYPE_FOLDER);
 | |
| 
 | |
|     let bookmarkFolderData = [];
 | |
|     let bmPromises = bookmarkFolders.map(({ guid }) => {
 | |
|       // Ignore bookmarks where the promise doesn't resolve (ie that are missing)
 | |
|       // Also check that the bookmark fetch returns isn't null before adding it.
 | |
|       return lazy.PlacesUtils.bookmarks.fetch(guid).then(
 | |
|         bm => bm && bookmarkFolderData.push(bm),
 | |
|         () => {}
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     await Promise.all(bmPromises);
 | |
|     let folderLMMap = new Map(
 | |
|       bookmarkFolderData.map(b => [b.guid, b.lastModified])
 | |
|     );
 | |
|     for (let bookmark of bookmarkFolders) {
 | |
|       let lastModified = folderLMMap.get(bookmark.guid);
 | |
|       // If the bookmark was deleted, the map will be returning null, so check:
 | |
|       if (lastModified) {
 | |
|         bookmark.lastModified = lastModified;
 | |
|       }
 | |
|     }
 | |
|     return state;
 | |
|   }
 | |
| 
 | |
|   stopAndRetrieveUndoData() {
 | |
|     let undoData = gUndoData;
 | |
|     gUndoData = null;
 | |
|     gKeepUndoData = false;
 | |
|     return this.#postProcessUndoData(undoData);
 | |
|   }
 | |
| 
 | |
|   #updateHistoryUndo(pageInfos) {
 | |
|     let visits = gUndoData.get("visits");
 | |
|     let visitMap = new Map(visits.map(v => [v.url, v]));
 | |
|     for (let pageInfo of pageInfos) {
 | |
|       let visitCount = pageInfo.visits.length;
 | |
|       let first, last;
 | |
|       if (visitCount > 1) {
 | |
|         let dates = pageInfo.visits.map(v => v.date);
 | |
|         first = Math.min.apply(Math, dates);
 | |
|         last = Math.max.apply(Math, dates);
 | |
|       } else {
 | |
|         first = last = pageInfo.visits[0].date;
 | |
|       }
 | |
|       let url = pageInfo.url;
 | |
|       if (url instanceof Ci.nsIURI) {
 | |
|         url = pageInfo.url.spec;
 | |
|       } else if (typeof url != "string") {
 | |
|         pageInfo.url.href;
 | |
|       }
 | |
| 
 | |
|       try {
 | |
|         new URL(url);
 | |
|       } catch (ex) {
 | |
|         // This won't save and we won't need to 'undo' it, so ignore this URL.
 | |
|         continue;
 | |
|       }
 | |
|       if (!visitMap.has(url)) {
 | |
|         visitMap.set(url, { url, visitCount, first, last });
 | |
|       } else {
 | |
|         let currentData = visitMap.get(url);
 | |
|         currentData.visitCount += visitCount;
 | |
|         currentData.first = Math.min(currentData.first, first);
 | |
|         currentData.last = Math.max(currentData.last, last);
 | |
|       }
 | |
|     }
 | |
|     gUndoData.set("visits", Array.from(visitMap.values()));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Cleans up references to migrators and nsIProfileInstance instances.
 | |
|    */
 | |
|   finishMigration() {
 | |
|     gMigrators = null;
 | |
|     gProfileStartup = null;
 | |
|     gL10n = null;
 | |
|   }
 | |
| 
 | |
|   get availableMigratorKeys() {
 | |
|     return [...this.#migrators.keys()];
 | |
|   }
 | |
| 
 | |
|   get availableFileMigrators() {
 | |
|     return [...this.#fileMigrators.values()];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Enum for the entrypoint that is being used to start migration.
 | |
|    * Callers can use the MIGRATION_ENTRYPOINTS getter to use these.
 | |
|    *
 | |
|    * These values are what's written into the
 | |
|    * FX_MIGRATION_ENTRY_POINT_CATEGORICAL histogram after a migration.
 | |
|    *
 | |
|    * @see MIGRATION_ENTRYPOINTS
 | |
|    * @readonly
 | |
|    * @enum {string}
 | |
|    */
 | |
|   #MIGRATION_ENTRYPOINTS_ENUM = Object.freeze({
 | |
|     /** The entrypoint was not supplied */
 | |
|     UNKNOWN: "unknown",
 | |
| 
 | |
|     /** Migration is occurring at startup */
 | |
|     FIRSTRUN: "firstrun",
 | |
| 
 | |
|     /** Migration is occurring at after a profile refresh */
 | |
|     FXREFRESH: "fxrefresh",
 | |
| 
 | |
|     /** Migration is being started from the Library window */
 | |
|     PLACES: "places",
 | |
| 
 | |
|     /** Migration is being started from our password management UI */
 | |
|     PASSWORDS: "passwords",
 | |
| 
 | |
|     /** Migration is being started from the default about:home/about:newtab */
 | |
|     NEWTAB: "newtab",
 | |
| 
 | |
|     /** Migration is being started from the File menu */
 | |
|     FILE_MENU: "file_menu",
 | |
| 
 | |
|     /** Migration is being started from the Help menu */
 | |
|     HELP_MENU: "help_menu",
 | |
| 
 | |
|     /** Migration is being started from the Bookmarks Toolbar */
 | |
|     BOOKMARKS_TOOLBAR: "bookmarks_toolbar",
 | |
| 
 | |
|     /** Migration is being started from about:preferences */
 | |
|     PREFERENCES: "preferences",
 | |
| 
 | |
|     /** Migration is being started from about:firefoxview */
 | |
|     FIREFOX_VIEW: "firefox_view",
 | |
|   });
 | |
| 
 | |
|   /**
 | |
|    * Returns an enum that should be used to record the entrypoint for
 | |
|    * starting a migration.
 | |
|    *
 | |
|    * @returns {number}
 | |
|    */
 | |
|   get MIGRATION_ENTRYPOINTS() {
 | |
|     return this.#MIGRATION_ENTRYPOINTS_ENUM;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Enum for the numeric value written to the FX_MIGRATION_SOURCE_BROWSER.
 | |
|    * histogram
 | |
|    *
 | |
|    * @see getSourceIdForTelemetry
 | |
|    * @readonly
 | |
|    * @enum {number}
 | |
|    */
 | |
|   #SOURCE_NAME_TO_ID_MAPPING_ENUM = Object.freeze({
 | |
|     nothing: 1,
 | |
|     firefox: 2,
 | |
|     edge: 3,
 | |
|     ie: 4,
 | |
|     chrome: 5,
 | |
|     "chrome-beta": 5,
 | |
|     "chrome-dev": 5,
 | |
|     chromium: 6,
 | |
|     canary: 7,
 | |
|     safari: 8,
 | |
|     "chromium-360se": 9,
 | |
|     "chromium-edge": 10,
 | |
|     "chromium-edge-beta": 10,
 | |
|     brave: 11,
 | |
|     opera: 12,
 | |
|     "opera-gx": 14,
 | |
|     vivaldi: 13,
 | |
|   });
 | |
| 
 | |
|   getSourceIdForTelemetry(sourceName) {
 | |
|     return this.#SOURCE_NAME_TO_ID_MAPPING_ENUM[sourceName] || 0;
 | |
|   }
 | |
| 
 | |
|   get HISTORY_MAX_AGE_IN_MILLISECONDS() {
 | |
|     return this.HISTORY_MAX_AGE_IN_DAYS * 24 * 60 * 60 * 1000;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Determines whether or not the underlying platform supports creating
 | |
|    * native file pickers that can do folder selection, which is a
 | |
|    * pre-requisite for getting read-access permissions for data from other
 | |
|    * browsers that we can import from.
 | |
|    *
 | |
|    * @returns {Promise<boolean>}
 | |
|    */
 | |
|   canGetPermissionsOnPlatform() {
 | |
|     return lazy.gCanGetPermissionsOnPlatformPromise;
 | |
|   }
 | |
| }
 | |
| 
 | |
| const MigrationUtilsSingleton = new MigrationUtils();
 | |
| 
 | |
| export { MigrationUtilsSingleton as MigrationUtils };
 | 
