tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MigrationWizardChild.sys.mjs (13163B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      8 
      9 const lazy = {};
     10 XPCOMUtils.defineLazyPreferenceGetter(
     11  lazy,
     12  "SHOW_IMPORT_ALL_PREF",
     13  "browser.migrate.content-modal.import-all.enabled",
     14  false
     15 );
     16 
     17 /**
     18 * This class is responsible for updating the state of a <migration-wizard>
     19 * component, and for listening for events from that component to perform
     20 * various migration functions.
     21 */
     22 export class MigrationWizardChild extends JSWindowActorChild {
     23  #wizardEl = null;
     24 
     25  /**
     26   * Retrieves the list of browsers and profiles from the parent process, and then
     27   * puts the migration wizard onto the selection page showing the list that they
     28   * can import from.
     29   *
     30   * @param {boolean} [allowOnlyFileMigrators=null]
     31   *   Set to true if showing the selection page is allowed if no browser migrators
     32   *   are found. If not true, and no browser migrators are found, then the wizard
     33   *   will be sent to the NO_BROWSERS_FOUND page.
     34   * @param {string} [migratorKey=null]
     35   *   If set, this will automatically select the first associated migrator with that
     36   *   migratorKey in the selector. If not set, the first item in the retrieved list
     37   *   of migrators will be selected.
     38   * @param {string} [fileImportErrorMessage=null]
     39   *   If set, this will display an error message below the browser / profile selector
     40   *   indicating that something had previously gone wrong with an import of type
     41   *   MIGRATOR_TYPES.FILE.
     42   */
     43  async #populateMigrators(
     44    allowOnlyFileMigrators,
     45    migratorKey,
     46    fileImportErrorMessage
     47  ) {
     48    let migrators = await this.sendQuery("GetAvailableMigrators");
     49    let hasBrowserMigrators = migrators.some(migrator => {
     50      return migrator.type == MigrationWizardConstants.MIGRATOR_TYPES.BROWSER;
     51    });
     52    let hasFileMigrators = migrators.some(migrator => {
     53      return migrator.type == MigrationWizardConstants.MIGRATOR_TYPES.FILE;
     54    });
     55    if (!hasBrowserMigrators && !allowOnlyFileMigrators) {
     56      this.setComponentState({
     57        page: MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND,
     58        hasFileMigrators,
     59      });
     60      this.#sendTelemetryEvent("noBrowsersFound");
     61    } else {
     62      this.setComponentState({
     63        migrators,
     64        page: MigrationWizardConstants.PAGES.SELECTION,
     65        showImportAll: lazy.SHOW_IMPORT_ALL_PREF,
     66        migratorKey,
     67        fileImportErrorMessage,
     68      });
     69    }
     70  }
     71 
     72  /**
     73   * General event handler function for events dispatched from the
     74   * <migration-wizard> component.
     75   *
     76   * @param {Event} event
     77   *   The DOM event being handled.
     78   * @returns {Promise}
     79   */
     80  async handleEvent(event) {
     81    this.#wizardEl = event.target;
     82 
     83    switch (event.type) {
     84      case "MigrationWizard:RequestState": {
     85        this.#sendTelemetryEvent("opened");
     86        await this.#requestState(event.detail?.allowOnlyFileMigrators);
     87        break;
     88      }
     89 
     90      case "MigrationWizard:BeginMigration": {
     91        let extraArgs = this.#recordBeginMigrationEvent(event.detail);
     92 
     93        let hasPermissions = await this.sendQuery("CheckPermissions", {
     94          key: event.detail.key,
     95          type: event.detail.type,
     96        });
     97 
     98        if (!hasPermissions) {
     99          if (event.detail.key == "safari") {
    100            this.#sendTelemetryEvent("safariPerms");
    101            this.setComponentState({
    102              page: MigrationWizardConstants.PAGES.SAFARI_PERMISSION,
    103            });
    104          } else {
    105            console.error(
    106              `A migrator with key ${event.detail.key} needs permissions, ` +
    107                "and no UI exists for that right now."
    108            );
    109          }
    110          return;
    111        }
    112 
    113        await this.beginMigration(event.detail, extraArgs);
    114        break;
    115      }
    116 
    117      case "MigrationWizard:RequestSafariPermissions": {
    118        let success = await this.sendQuery("RequestSafariPermissions");
    119        if (success) {
    120          let extraArgs = this.#constructExtraArgs(event.detail);
    121          await this.beginMigration(event.detail, extraArgs);
    122        }
    123        break;
    124      }
    125 
    126      case "MigrationWizard:SelectManualPasswordFile": {
    127        let path = await this.sendQuery("SelectManualPasswordFile");
    128        if (path) {
    129          event.detail.manualPasswordFilePath = path;
    130 
    131          let passwordResourceIndex = event.detail.resourceTypes.indexOf(
    132            MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
    133          );
    134          event.detail.resourceTypes.splice(passwordResourceIndex, 1);
    135 
    136          let extraArgs = this.#constructExtraArgs(event.detail);
    137          await this.beginMigration(event.detail, extraArgs);
    138        }
    139        break;
    140      }
    141 
    142      case "MigrationWizard:OpenAboutAddons": {
    143        this.sendAsyncMessage("OpenAboutAddons");
    144        break;
    145      }
    146 
    147      case "MigrationWizard:PermissionsNeeded": {
    148        // In theory, the migrator permissions might be requested on any
    149        // platform - but in practice, this only happens on Linux, so that's
    150        // why the event is named linux_perms.
    151        this.#sendTelemetryEvent("linuxPerms", {
    152          migrator_key: event.detail.key,
    153        });
    154        break;
    155      }
    156 
    157      case "MigrationWizard:GetPermissions": {
    158        let success = await this.sendQuery("GetPermissions", {
    159          key: event.detail.key,
    160        });
    161        if (success) {
    162          await this.#requestState(true /* allowOnlyFileMigrators */);
    163        }
    164        break;
    165      }
    166 
    167      case "MigrationWizard:OpenURL": {
    168        this.sendAsyncMessage("OpenURL", {
    169          url: event.detail.url,
    170          where: event.detail.where,
    171        });
    172        break;
    173      }
    174    }
    175  }
    176 
    177  async #requestState(allowOnlyFileMigrators) {
    178    this.setComponentState({
    179      page: MigrationWizardConstants.PAGES.LOADING,
    180    });
    181 
    182    await this.#populateMigrators(allowOnlyFileMigrators);
    183 
    184    this.#wizardEl.dispatchEvent(
    185      new this.contentWindow.CustomEvent("MigrationWizard:Ready", {
    186        bubbles: true,
    187      })
    188    );
    189  }
    190 
    191  /**
    192   * Sends a message to the parent actor to record Event Telemetry.
    193   *
    194   * @param {string} type
    195   *   The type of event being recorded.
    196   * @param {object} [args=null]
    197   *   Optional extra_args to supply for the event.
    198   */
    199  #sendTelemetryEvent(type, args) {
    200    this.sendAsyncMessage("RecordEvent", { type, args });
    201  }
    202 
    203  /**
    204   * Constructs extra arguments to pass to some Event Telemetry based
    205   * on the MigrationDetails passed up from the MigrationWizard.
    206   *
    207   * See migration-wizard.mjs for a definition of MigrationDetails.
    208   *
    209   * @param {object} migrationDetails
    210   *   A MigrationDetails object.
    211   * @returns {object}
    212   */
    213  #constructExtraArgs(migrationDetails) {
    214    let extraArgs = {
    215      migrator_key: migrationDetails.key,
    216      history: "0",
    217      formdata: "0",
    218      passwords: "0",
    219      bookmarks: "0",
    220      payment_methods: "0",
    221      extensions: "0",
    222      other: 0,
    223    };
    224 
    225    for (let type of migrationDetails.resourceTypes) {
    226      switch (type) {
    227        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY: {
    228          extraArgs.history = "1";
    229          break;
    230        }
    231 
    232        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA: {
    233          extraArgs.formdata = "1";
    234          break;
    235        }
    236 
    237        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS: {
    238          extraArgs.passwords = "1";
    239          break;
    240        }
    241 
    242        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS: {
    243          extraArgs.bookmarks = "1";
    244          break;
    245        }
    246 
    247        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS: {
    248          extraArgs.extensions = "1";
    249          break;
    250        }
    251 
    252        case MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES
    253          .PAYMENT_METHODS: {
    254          extraArgs.payment_methods = "1";
    255          break;
    256        }
    257 
    258        default: {
    259          extraArgs.other++;
    260        }
    261      }
    262    }
    263 
    264    return extraArgs;
    265  }
    266 
    267  /**
    268   * This migration wizard combines a lot of steps (selecting the browser, profile,
    269   * resources, and starting the migration) into a single page. This helper method
    270   * records Event Telemetry for each of those actions at the same time when a
    271   * migration begins.
    272   *
    273   * This method returns the extra_args object that was constructed for the
    274   * resources_selected and migration_started event so that a
    275   * "migration_finished" event can use the same extra_args without
    276   * regenerating it.
    277   *
    278   * See migration-wizard.mjs for a definition of MigrationDetails.
    279   *
    280   * @param {object} migrationDetails
    281   *   A MigrationDetails object.
    282   * @returns {object}
    283   */
    284  #recordBeginMigrationEvent(migrationDetails) {
    285    this.#sendTelemetryEvent("browserSelected", {
    286      migrator_key: migrationDetails.key,
    287    });
    288 
    289    if (migrationDetails.profile) {
    290      this.#sendTelemetryEvent("profileSelected", {
    291        migrator_key: migrationDetails.key,
    292      });
    293    }
    294 
    295    let extraArgs = this.#constructExtraArgs(migrationDetails);
    296 
    297    extraArgs.configured = String(Number(migrationDetails.expandedDetails));
    298    this.#sendTelemetryEvent("resourcesSelected", extraArgs);
    299    delete extraArgs.configured;
    300 
    301    this.#sendTelemetryEvent("migrationStarted", extraArgs);
    302    return extraArgs;
    303  }
    304 
    305  /**
    306   * Sends a message to the parent actor to attempt a migration.
    307   *
    308   * See migration-wizard.mjs for a definition of MigrationDetails.
    309   *
    310   * @param {object} migrationDetails
    311   *   A MigrationDetails object.
    312   * @param {object} extraArgs
    313   *   Extra argument object to pass to the Event Telemetry for finishing
    314   *   the migration.
    315   * @returns {Promise<undefined>}
    316   *   Returns a Promise that resolves after the parent responds to the migration
    317   *   message.
    318   */
    319  async beginMigration(migrationDetails, extraArgs) {
    320    // We redirect to manual password import for Safari and Chrome on Windows.
    321    if (
    322      migrationDetails.resourceTypes.includes(
    323        MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
    324      ) &&
    325      !migrationDetails.manualPasswordFilePath
    326    ) {
    327      if (migrationDetails.key == "safari") {
    328        this.#sendTelemetryEvent("safariPasswordFile");
    329        this.setComponentState({
    330          page: MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION,
    331        });
    332        return;
    333      } else if (
    334        migrationDetails.key == "chrome" &&
    335        AppConstants.platform == "win"
    336      ) {
    337        this.#sendTelemetryEvent("chromePasswordFile");
    338        this.setComponentState({
    339          page: MigrationWizardConstants.PAGES
    340            .CHROME_WINDOWS_PASSWORD_PERMISSION,
    341        });
    342        return;
    343      }
    344    }
    345 
    346    extraArgs = await this.sendQuery("Migrate", {
    347      migrationDetails,
    348      extraArgs,
    349    });
    350    this.#sendTelemetryEvent("migrationFinished", extraArgs);
    351 
    352    this.#wizardEl.dispatchEvent(
    353      new this.contentWindow.CustomEvent("MigrationWizard:DoneMigration", {
    354        bubbles: true,
    355      })
    356    );
    357  }
    358 
    359  /**
    360   * General message handler function for messages received from the
    361   * associated MigrationWizardParent JSWindowActor.
    362   *
    363   * @param {ReceiveMessageArgument} message
    364   *   The message received from the MigrationWizardParent.
    365   */
    366  receiveMessage(message) {
    367    switch (message.name) {
    368      case "UpdateProgress": {
    369        this.setComponentState({
    370          page: MigrationWizardConstants.PAGES.PROGRESS,
    371          progress: message.data.progress,
    372          key: message.data.key,
    373        });
    374        break;
    375      }
    376      case "UpdateFileImportProgress": {
    377        this.setComponentState({
    378          page: MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS,
    379          progress: message.data.progress,
    380          title: message.data.title,
    381        });
    382        break;
    383      }
    384      case "FileImportProgressError": {
    385        this.#populateMigrators(
    386          true,
    387          message.data.migratorKey,
    388          message.data.fileImportErrorMessage
    389        );
    390        break;
    391      }
    392    }
    393  }
    394 
    395  /**
    396   * Calls the `setState` method on the <migration-wizard> component. The
    397   * state is cloned into the execution scope of this.#wizardEl.
    398   *
    399   * @param {object} state The state object that a <migration-wizard>
    400   *   component expects. See the documentation for the element's setState
    401   *   method for more details.
    402   */
    403  setComponentState(state) {
    404    if (!this.#wizardEl) {
    405      return;
    406    }
    407    // We waive XrayWrappers in the event that the element is embedded in
    408    // a document without system privileges, like about:welcome.
    409    Cu.waiveXrays(this.#wizardEl).setState(
    410      Cu.cloneInto(
    411        state,
    412        // ownerGlobal doesn't exist in content windows.
    413        // eslint-disable-next-line mozilla/use-ownerGlobal
    414        this.#wizardEl.ownerDocument.defaultView
    415      )
    416    );
    417  }
    418 }