mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1072 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1072 lines
		
	
	
	
		
			33 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, {
 | 
						|
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
 | 
						|
  PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
 | 
						|
  Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
 | 
						|
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
 | 
						|
  WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
 | 
						|
});
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  lazy,
 | 
						|
  "LoginHelper",
 | 
						|
  "resource://gre/modules/LoginHelper.jsm"
 | 
						|
);
 | 
						|
 | 
						|
var gMigrators = null;
 | 
						|
var gProfileStartup = null;
 | 
						|
var gL10n = null;
 | 
						|
var gPreviousDefaultBrowserKey = "";
 | 
						|
 | 
						|
let gForceExitSpinResolve = false;
 | 
						|
let gKeepUndoData = false;
 | 
						|
let gUndoData = null;
 | 
						|
 | 
						|
function getL10n() {
 | 
						|
  if (!gL10n) {
 | 
						|
    gL10n = new Localization(["browser/migration.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"],
 | 
						|
  },
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * 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
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  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,
 | 
						|
  });
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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;
 | 
						|
        } 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;
 | 
						|
  }
 | 
						|
 | 
						|
  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.jsm: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 null otherwise.
 | 
						|
   *
 | 
						|
   * If null is returned,  either no data can be imported for the given migrator,
 | 
						|
   * or aMigratorKey is invalid  (e.g. ie on mac, or mosaic everywhere).  This
 | 
						|
   * method should be used rather than direct getService for future compatibility
 | 
						|
   * (see bug 718280).
 | 
						|
   *
 | 
						|
   * @param {string} aKey
 | 
						|
   *   Internal name of the migration source. See `availableMigratorKeys`
 | 
						|
   *   for supported values by OS.
 | 
						|
   *
 | 
						|
   * @returns {MigratorBase}
 | 
						|
   *   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 {
 | 
						|
      return migrator && (await migrator.isSourceAvailable()) ? migrator : null;
 | 
						|
    } catch (ex) {
 | 
						|
      console.error(ex);
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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);
 | 
						|
    }
 | 
						|
 | 
						|
    // "firefox" is the least useful entry here, and might just be because we've set
 | 
						|
    // ourselves as the default (on Windows 7 and below). In that case, check if we
 | 
						|
    // have a registry key that tells us where to go:
 | 
						|
    if (
 | 
						|
      key == "firefox" &&
 | 
						|
      AppConstants.isPlatformAndVersionAtMost("win", "6.2")
 | 
						|
    ) {
 | 
						|
      // Because we remove the registry key, reading the registry key only works once.
 | 
						|
      // We save the value for subsequent calls to avoid hard-to-trace bugs when multiple
 | 
						|
      // consumers ask for this key.
 | 
						|
      if (gPreviousDefaultBrowserKey) {
 | 
						|
        key = gPreviousDefaultBrowserKey;
 | 
						|
      } else {
 | 
						|
        // We didn't have a saved value, so check the registry.
 | 
						|
        const kRegPath = "Software\\Mozilla\\Firefox";
 | 
						|
        let oldDefault = lazy.WindowsRegistry.readRegKey(
 | 
						|
          Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
 | 
						|
          kRegPath,
 | 
						|
          "OldDefaultBrowserCommand"
 | 
						|
        );
 | 
						|
        if (oldDefault) {
 | 
						|
          // Remove the key:
 | 
						|
          lazy.WindowsRegistry.removeRegKey(
 | 
						|
            Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
 | 
						|
            kRegPath,
 | 
						|
            "OldDefaultBrowserCommand"
 | 
						|
          );
 | 
						|
          try {
 | 
						|
            let file = Cc["@mozilla.org/file/local;1"].createInstance(
 | 
						|
              Ci.nsILocalFileWin
 | 
						|
            );
 | 
						|
            file.initWithCommandLine(oldDefault);
 | 
						|
            key =
 | 
						|
              APP_DESC_TO_KEY[file.getVersionInfoField("FileDescription")] ||
 | 
						|
              key;
 | 
						|
            // Save the value for future callers.
 | 
						|
            gPreviousDefaultBrowserKey = key;
 | 
						|
          } catch (ex) {
 | 
						|
            console.error(
 | 
						|
              "Could not convert old default browser value to description."
 | 
						|
            );
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    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.  On mac, this may just focus the wizard if it's
 | 
						|
   * already running, in which case aOpener and aOptions are ignored.
 | 
						|
   *
 | 
						|
   * 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 the new content-modal migration dialog is enabled and 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 dialog window.
 | 
						|
   */
 | 
						|
  showMigrationWizard(aOpener, aOptions) {
 | 
						|
    if (
 | 
						|
      Services.prefs.getBoolPref(
 | 
						|
        "browser.migrate.content-modal.enabled",
 | 
						|
        false
 | 
						|
      ) &&
 | 
						|
      !aOptions?.isStartupMigration
 | 
						|
    ) {
 | 
						|
      let entrypoint =
 | 
						|
        aOptions.entrypoint || this.MIGRATION_ENTRYPOINTS.UNKNOWN;
 | 
						|
      Services.telemetry
 | 
						|
        .getHistogramById("FX_MIGRATION_ENTRY_POINT_CATEGORICAL")
 | 
						|
        .add(entrypoint);
 | 
						|
 | 
						|
      let openStandaloneWindow = () => {
 | 
						|
        const FEATURES = "dialog,centerscreen,resizable=no";
 | 
						|
        const win = Services.ww.openWindow(
 | 
						|
          aOpener,
 | 
						|
          "chrome://browser/content/migration/migration-dialog-window.html",
 | 
						|
          "_blank",
 | 
						|
          FEATURES,
 | 
						|
          {
 | 
						|
            onResize: () => {
 | 
						|
              win.sizeToContent();
 | 
						|
            },
 | 
						|
            options: aOptions,
 | 
						|
          }
 | 
						|
        );
 | 
						|
        return Promise.resolve();
 | 
						|
      };
 | 
						|
 | 
						|
      if (aOptions.isStartupMigration) {
 | 
						|
        openStandaloneWindow();
 | 
						|
        return Promise.resolve();
 | 
						|
      }
 | 
						|
 | 
						|
      if (aOpener?.openPreferences) {
 | 
						|
        return aOpener.openPreferences("general-migrate");
 | 
						|
      }
 | 
						|
 | 
						|
      // If somehow we failed to open about:preferences, fall back to opening
 | 
						|
      // the top-level window.
 | 
						|
      openStandaloneWindow();
 | 
						|
      return Promise.resolve();
 | 
						|
    }
 | 
						|
    // Legacy migration dialog
 | 
						|
    const DIALOG_URL = "chrome://browser/content/migration/migration.xhtml";
 | 
						|
    let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
 | 
						|
    if (AppConstants.platform == "macosx" && !this.isStartupMigration) {
 | 
						|
      let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
 | 
						|
      if (win) {
 | 
						|
        win.focus();
 | 
						|
        return Promise.resolve();
 | 
						|
      }
 | 
						|
      // On mac, the migration wiazrd should only be modal in the case of
 | 
						|
      // startup-migration.
 | 
						|
      features = "centerscreen,chrome,resizable=no";
 | 
						|
    }
 | 
						|
    Services.ww.openWindow(aOpener, DIALOG_URL, "_blank", features, aOptions);
 | 
						|
    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
 | 
						|
   *   (The first option is hardcoded to be the most common browser for
 | 
						|
   *    the OS we run on.  See migration.xhtml).
 | 
						|
   * @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,
 | 
						|
  };
 | 
						|
 | 
						|
  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 */
 | 
						|
          );
 | 
						|
        }
 | 
						|
      },
 | 
						|
      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.
 | 
						|
   *
 | 
						|
   * @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
 | 
						|
   */
 | 
						|
  insertManyFavicons(favicons) {
 | 
						|
    let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance(
 | 
						|
      Ci.nsIContentSniffer
 | 
						|
    );
 | 
						|
    for (let faviconDataItem of favicons) {
 | 
						|
      let mimeType = sniffer.getMIMETypeFromContent(
 | 
						|
        null,
 | 
						|
        faviconDataItem.faviconData,
 | 
						|
        faviconDataItem.faviconData.length
 | 
						|
      );
 | 
						|
      let fakeFaviconURI = Services.io.newURI(
 | 
						|
        "fake-favicon-uri:" + faviconDataItem.uri.spec
 | 
						|
      );
 | 
						|
      lazy.PlacesUtils.favicons.replaceFaviconData(
 | 
						|
        fakeFaviconURI,
 | 
						|
        faviconDataItem.faviconData,
 | 
						|
        mimeType
 | 
						|
      );
 | 
						|
      lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
 | 
						|
        faviconDataItem.uri,
 | 
						|
        fakeFaviconURI,
 | 
						|
        true,
 | 
						|
        lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
 | 
						|
        null,
 | 
						|
        Services.scriptSecurityManager.getSystemPrincipal()
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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()];
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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
 | 
						|
   * histogram after a migration.
 | 
						|
   *
 | 
						|
   * @see MIGRATION_ENTRYPOINTS
 | 
						|
   * @readonly
 | 
						|
   * @enum {number}
 | 
						|
   */
 | 
						|
  #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",
 | 
						|
  });
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Translates an entrypoint string into the proper numeric value for the legacy
 | 
						|
   * FX_MIGRATION_ENTRY_POINT histogram.
 | 
						|
   *
 | 
						|
   * @param {string} entrypoint
 | 
						|
   *   The entrypoint to translate from MIGRATION_ENTRYPOINTS.
 | 
						|
   * @returns {number}
 | 
						|
   *   The numeric value for the legacy FX_MIGRATION_ENTRY_POINT histogram.
 | 
						|
   */
 | 
						|
  getLegacyMigrationEntrypoint(entrypoint) {
 | 
						|
    switch (entrypoint) {
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.FIRSTRUN: {
 | 
						|
        return 1;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.FXREFRESH: {
 | 
						|
        return 2;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.PLACES: {
 | 
						|
        return 3;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.PASSWORDS: {
 | 
						|
        return 4;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.NEWTAB: {
 | 
						|
        return 5;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.FILE_MENU: {
 | 
						|
        return 6;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.HELP_MENU: {
 | 
						|
        return 7;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.BOOKMARKS_TOOLBAR: {
 | 
						|
        return 8;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.PREFERENCES: {
 | 
						|
        return 9;
 | 
						|
      }
 | 
						|
      case this.MIGRATION_ENTRYPOINTS.UNKNOWN:
 | 
						|
      // Intentional fall-through
 | 
						|
      default: {
 | 
						|
        return 0; // Unknown
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Enum for the numeric value written to the FX_MIGRATION_SOURCE_BROWSER,
 | 
						|
   * and FX_STARTUP_MIGRATION_EXISTING_DEFAULT_BROWSER histograms.
 | 
						|
   *
 | 
						|
   * @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;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const MigrationUtilsSingleton = new MigrationUtils();
 | 
						|
 | 
						|
export { MigrationUtilsSingleton as MigrationUtils };
 |