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
 | |
|       )
 | |
|     );
 | |
|   }
 | |
| }
 |