summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/MigrationWizardChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/migration/MigrationWizardChild.sys.mjs')
-rw-r--r--browser/components/migration/MigrationWizardChild.sys.mjs328
1 files changed, 328 insertions, 0 deletions
diff --git a/browser/components/migration/MigrationWizardChild.sys.mjs b/browser/components/migration/MigrationWizardChild.sys.mjs
new file mode 100644
index 0000000000..a3894f7e92
--- /dev/null
+++ b/browser/components/migration/MigrationWizardChild.sys.mjs
@@ -0,0 +1,328 @@
+/* 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;
+
+ /**
+ * 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) {
+ switch (event.type) {
+ case "MigrationWizard:RequestState": {
+ this.#sendTelemetryEvent("opened");
+
+ this.#wizardEl = event.target;
+ this.setComponentState({
+ page: MigrationWizardConstants.PAGES.LOADING,
+ });
+
+ 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 && !event.detail?.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,
+ });
+ }
+
+ this.#wizardEl.dispatchEvent(
+ new this.contentWindow.CustomEvent("MigrationWizard:Ready", {
+ bubbles: true,
+ })
+ );
+ 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;
+ }
+ }
+ }
+
+ /**
+ * 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",
+ 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
+ .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;
+ }
+
+ await this.sendQuery("Migrate", migrationDetails);
+ 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;
+ }
+ }
+ }
+
+ /**
+ * 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
+ )
+ );
+ }
+}