forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			400 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
	
		
			12 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 { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";
 | 
						|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
XPCOMUtils.defineLazyPreferenceGetter(
 | 
						|
  lazy,
 | 
						|
  "SHOW_IMPORT_ALL_PREF",
 | 
						|
  "browser.migrate.content-modal.import-all.enabled",
 | 
						|
  false
 | 
						|
);
 | 
						|
 | 
						|
/**
 | 
						|
 * This class is responsible for updating the state of a <migration-wizard>
 | 
						|
 * component, and for listening for events from that component to perform
 | 
						|
 * various migration functions.
 | 
						|
 */
 | 
						|
export class MigrationWizardChild extends JSWindowActorChild {
 | 
						|
  #wizardEl = null;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Retrieves the list of browsers and profiles from the parent process, and then
 | 
						|
   * puts the migration wizard onto the selection page showing the list that they
 | 
						|
   * can import from.
 | 
						|
   *
 | 
						|
   * @param {boolean} [allowOnlyFileMigrators=null]
 | 
						|
   *   Set to true if showing the selection page is allowed if no browser migrators
 | 
						|
   *   are found. If not true, and no browser migrators are found, then the wizard
 | 
						|
   *   will be sent to the NO_BROWSERS_FOUND page.
 | 
						|
   * @param {string} [migratorKey=null]
 | 
						|
   *   If set, this will automatically select the first associated migrator with that
 | 
						|
   *   migratorKey in the selector. If not set, the first item in the retrieved list
 | 
						|
   *   of migrators will be selected.
 | 
						|
   * @param {string} [fileImportErrorMessage=null]
 | 
						|
   *   If set, this will display an error message below the browser / profile selector
 | 
						|
   *   indicating that something had previously gone wrong with an import of type
 | 
						|
   *   MIGRATOR_TYPES.FILE.
 | 
						|
   */
 | 
						|
  async #populateMigrators(
 | 
						|
    allowOnlyFileMigrators,
 | 
						|
    migratorKey,
 | 
						|
    fileImportErrorMessage
 | 
						|
  ) {
 | 
						|
    let migrators = await this.sendQuery("GetAvailableMigrators");
 | 
						|
    let hasBrowserMigrators = migrators.some(migrator => {
 | 
						|
      return migrator.type == MigrationWizardConstants.MIGRATOR_TYPES.BROWSER;
 | 
						|
    });
 | 
						|
    let hasFileMigrators = migrators.some(migrator => {
 | 
						|
      return migrator.type == MigrationWizardConstants.MIGRATOR_TYPES.FILE;
 | 
						|
    });
 | 
						|
    if (!hasBrowserMigrators && !allowOnlyFileMigrators) {
 | 
						|
      this.setComponentState({
 | 
						|
        page: MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND,
 | 
						|
        hasFileMigrators,
 | 
						|
      });
 | 
						|
      this.#sendTelemetryEvent("no_browsers_found");
 | 
						|
    } else {
 | 
						|
      this.setComponentState({
 | 
						|
        migrators,
 | 
						|
        page: MigrationWizardConstants.PAGES.SELECTION,
 | 
						|
        showImportAll: lazy.SHOW_IMPORT_ALL_PREF,
 | 
						|
        migratorKey,
 | 
						|
        fileImportErrorMessage,
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * General event handler function for events dispatched from the
 | 
						|
   * <migration-wizard> component.
 | 
						|
   *
 | 
						|
   * @param {Event} event
 | 
						|
   *   The DOM event being handled.
 | 
						|
   * @returns {Promise}
 | 
						|
   */
 | 
						|
  async handleEvent(event) {
 | 
						|
    this.#wizardEl = event.target;
 | 
						|
 | 
						|
    switch (event.type) {
 | 
						|
      case "MigrationWizard:RequestState": {
 | 
						|
        this.#sendTelemetryEvent("opened");
 | 
						|
        await this.#requestState(event.detail?.allowOnlyFileMigrators);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "MigrationWizard:BeginMigration": {
 | 
						|
        let extraArgs = this.#recordBeginMigrationEvent(event.detail);
 | 
						|
 | 
						|
        let hasPermissions = await this.sendQuery("CheckPermissions", {
 | 
						|
          key: event.detail.key,
 | 
						|
          type: event.detail.type,
 | 
						|
        });
 | 
						|
 | 
						|
        if (!hasPermissions) {
 | 
						|
          if (event.detail.key == "safari") {
 | 
						|
            this.#sendTelemetryEvent("safari_perms");
 | 
						|
            this.setComponentState({
 | 
						|
              page: MigrationWizardConstants.PAGES.SAFARI_PERMISSION,
 | 
						|
            });
 | 
						|
          } else {
 | 
						|
            console.error(
 | 
						|
              `A migrator with key ${event.detail.key} needs permissions, ` +
 | 
						|
                "and no UI exists for that right now."
 | 
						|
            );
 | 
						|
          }
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        await this.beginMigration(event.detail, extraArgs);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "MigrationWizard:RequestSafariPermissions": {
 | 
						|
        let success = await this.sendQuery("RequestSafariPermissions");
 | 
						|
        if (success) {
 | 
						|
          let extraArgs = this.#constructExtraArgs(event.detail);
 | 
						|
          await this.beginMigration(event.detail, extraArgs);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "MigrationWizard:SelectSafariPasswordFile": {
 | 
						|
        let path = await this.sendQuery("SelectSafariPasswordFile");
 | 
						|
        if (path) {
 | 
						|
          event.detail.safariPasswordFilePath = path;
 | 
						|
 | 
						|
          let passwordResourceIndex = event.detail.resourceTypes.indexOf(
 | 
						|
            MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
 | 
						|
          );
 | 
						|
          event.detail.resourceTypes.splice(passwordResourceIndex, 1);
 | 
						|
 | 
						|
          let extraArgs = this.#constructExtraArgs(event.detail);
 | 
						|
          await this.beginMigration(event.detail, extraArgs);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "MigrationWizard:OpenAboutAddons": {
 | 
						|
        this.sendAsyncMessage("OpenAboutAddons");
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "MigrationWizard:PermissionsNeeded": {
 | 
						|
        // In theory, the migrator permissions might be requested on any
 | 
						|
        // platform - but in practice, this only happens on Linux, so that's
 | 
						|
        // why the event is named linux_perms.
 | 
						|
        this.#sendTelemetryEvent("linux_perms", {
 | 
						|
          migrator_key: event.detail.key,
 | 
						|
        });
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "MigrationWizard:GetPermissions": {
 | 
						|
        let success = await this.sendQuery("GetPermissions", {
 | 
						|
          key: event.detail.key,
 | 
						|
        });
 | 
						|
        if (success) {
 | 
						|
          await this.#requestState(true /* allowOnlyFileMigrators */);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async #requestState(allowOnlyFileMigrators) {
 | 
						|
    this.setComponentState({
 | 
						|
      page: MigrationWizardConstants.PAGES.LOADING,
 | 
						|
    });
 | 
						|
 | 
						|
    await this.#populateMigrators(allowOnlyFileMigrators);
 | 
						|
 | 
						|
    this.#wizardEl.dispatchEvent(
 | 
						|
      new this.contentWindow.CustomEvent("MigrationWizard:Ready", {
 | 
						|
        bubbles: true,
 | 
						|
      })
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a message to the parent actor to record Event Telemetry.
 | 
						|
   *
 | 
						|
   * @param {string} type
 | 
						|
   *   The type of event being recorded.
 | 
						|
   * @param {object} [args=null]
 | 
						|
   *   Optional extra_args to supply for the event.
 | 
						|
   */
 | 
						|
  #sendTelemetryEvent(type, args) {
 | 
						|
    this.sendAsyncMessage("RecordEvent", { type, args });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Constructs extra arguments to pass to some Event Telemetry based
 | 
						|
   * on the MigrationDetails passed up from the MigrationWizard.
 | 
						|
   *
 | 
						|
   * See migration-wizard.mjs for a definition of MigrationDetails.
 | 
						|
   *
 | 
						|
   * @param {object} migrationDetails
 | 
						|
   *   A MigrationDetails object.
 | 
						|
   * @returns {object}
 | 
						|
   */
 | 
						|
  #constructExtraArgs(migrationDetails) {
 | 
						|
    let extraArgs = {
 | 
						|
      migrator_key: migrationDetails.key,
 | 
						|
      history: "0",
 | 
						|
      formdata: "0",
 | 
						|
      passwords: "0",
 | 
						|
      bookmarks: "0",
 | 
						|
      payment_methods: "0",
 | 
						|
      extensions: "0",
 | 
						|
      other: 0,
 | 
						|
    };
 | 
						|
 | 
						|
    for (let type of migrationDetails.resourceTypes) {
 | 
						|
      switch (type) {
 | 
						|
        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY: {
 | 
						|
          extraArgs.history = "1";
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA: {
 | 
						|
          extraArgs.formdata = "1";
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS: {
 | 
						|
          extraArgs.passwords = "1";
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS: {
 | 
						|
          extraArgs.bookmarks = "1";
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS: {
 | 
						|
          extraArgs.extensions = "1";
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES
 | 
						|
          .PAYMENT_METHODS: {
 | 
						|
          extraArgs.payment_methods = "1";
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        default: {
 | 
						|
          extraArgs.other++;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Event Telemetry extra arguments expect strings for every value, so
 | 
						|
    // now we coerce our "other" count into a string.
 | 
						|
    extraArgs.other = String(extraArgs.other);
 | 
						|
    return extraArgs;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * This migration wizard combines a lot of steps (selecting the browser, profile,
 | 
						|
   * resources, and starting the migration) into a single page. This helper method
 | 
						|
   * records Event Telemetry for each of those actions at the same time when a
 | 
						|
   * migration begins.
 | 
						|
   *
 | 
						|
   * This method returns the extra_args object that was constructed for the
 | 
						|
   * resources_selected and migration_started event so that a
 | 
						|
   * "migration_finished" event can use the same extra_args without
 | 
						|
   * regenerating it.
 | 
						|
   *
 | 
						|
   * See migration-wizard.mjs for a definition of MigrationDetails.
 | 
						|
   *
 | 
						|
   * @param {object} migrationDetails
 | 
						|
   *   A MigrationDetails object.
 | 
						|
   * @returns {object}
 | 
						|
   */
 | 
						|
  #recordBeginMigrationEvent(migrationDetails) {
 | 
						|
    this.#sendTelemetryEvent("browser_selected", {
 | 
						|
      migrator_key: migrationDetails.key,
 | 
						|
    });
 | 
						|
 | 
						|
    if (migrationDetails.profile) {
 | 
						|
      this.#sendTelemetryEvent("profile_selected", {
 | 
						|
        migrator_key: migrationDetails.key,
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    let extraArgs = this.#constructExtraArgs(migrationDetails);
 | 
						|
 | 
						|
    extraArgs.configured = String(Number(migrationDetails.expandedDetails));
 | 
						|
    this.#sendTelemetryEvent("resources_selected", extraArgs);
 | 
						|
    delete extraArgs.configured;
 | 
						|
 | 
						|
    this.#sendTelemetryEvent("migration_started", extraArgs);
 | 
						|
    return extraArgs;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a message to the parent actor to attempt a migration.
 | 
						|
   *
 | 
						|
   * See migration-wizard.mjs for a definition of MigrationDetails.
 | 
						|
   *
 | 
						|
   * @param {object} migrationDetails
 | 
						|
   *   A MigrationDetails object.
 | 
						|
   * @param {object} extraArgs
 | 
						|
   *   Extra argument object to pass to the Event Telemetry for finishing
 | 
						|
   *   the migration.
 | 
						|
   * @returns {Promise<undefined>}
 | 
						|
   *   Returns a Promise that resolves after the parent responds to the migration
 | 
						|
   *   message.
 | 
						|
   */
 | 
						|
  async beginMigration(migrationDetails, extraArgs) {
 | 
						|
    if (
 | 
						|
      migrationDetails.key == "safari" &&
 | 
						|
      migrationDetails.resourceTypes.includes(
 | 
						|
        MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
 | 
						|
      ) &&
 | 
						|
      !migrationDetails.safariPasswordFilePath
 | 
						|
    ) {
 | 
						|
      this.#sendTelemetryEvent("safari_password_file");
 | 
						|
      this.setComponentState({
 | 
						|
        page: MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION,
 | 
						|
      });
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    extraArgs = await this.sendQuery("Migrate", {
 | 
						|
      migrationDetails,
 | 
						|
      extraArgs,
 | 
						|
    });
 | 
						|
    this.#sendTelemetryEvent("migration_finished", extraArgs);
 | 
						|
 | 
						|
    this.#wizardEl.dispatchEvent(
 | 
						|
      new this.contentWindow.CustomEvent("MigrationWizard:DoneMigration", {
 | 
						|
        bubbles: true,
 | 
						|
      })
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * General message handler function for messages received from the
 | 
						|
   * associated MigrationWizardParent JSWindowActor.
 | 
						|
   *
 | 
						|
   * @param {ReceiveMessageArgument} message
 | 
						|
   *   The message received from the MigrationWizardParent.
 | 
						|
   */
 | 
						|
  receiveMessage(message) {
 | 
						|
    switch (message.name) {
 | 
						|
      case "UpdateProgress": {
 | 
						|
        this.setComponentState({
 | 
						|
          page: MigrationWizardConstants.PAGES.PROGRESS,
 | 
						|
          progress: message.data.progress,
 | 
						|
          key: message.data.key,
 | 
						|
        });
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case "UpdateFileImportProgress": {
 | 
						|
        this.setComponentState({
 | 
						|
          page: MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS,
 | 
						|
          progress: message.data.progress,
 | 
						|
          title: message.data.title,
 | 
						|
        });
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case "FileImportProgressError": {
 | 
						|
        this.#populateMigrators(
 | 
						|
          true,
 | 
						|
          message.data.migratorKey,
 | 
						|
          message.data.fileImportErrorMessage
 | 
						|
        );
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Calls the `setState` method on the <migration-wizard> component. The
 | 
						|
   * state is cloned into the execution scope of this.#wizardEl.
 | 
						|
   *
 | 
						|
   * @param {object} state The state object that a <migration-wizard>
 | 
						|
   *   component expects. See the documentation for the element's setState
 | 
						|
   *   method for more details.
 | 
						|
   */
 | 
						|
  setComponentState(state) {
 | 
						|
    if (!this.#wizardEl) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // We waive XrayWrappers in the event that the element is embedded in
 | 
						|
    // a document without system privileges, like about:welcome.
 | 
						|
    Cu.waiveXrays(this.#wizardEl).setState(
 | 
						|
      Cu.cloneInto(
 | 
						|
        state,
 | 
						|
        // ownerGlobal doesn't exist in content windows.
 | 
						|
        // eslint-disable-next-line mozilla/use-ownerGlobal
 | 
						|
        this.#wizardEl.ownerDocument.defaultView
 | 
						|
      )
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |