418 lines
13 KiB
JavaScript
418 lines
13 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";
|
|
import { AppConstants } from "resource://gre/modules/AppConstants.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("noBrowsersFound");
|
|
} 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("safariPerms");
|
|
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:SelectManualPasswordFile": {
|
|
let path = await this.sendQuery("SelectManualPasswordFile");
|
|
if (path) {
|
|
event.detail.manualPasswordFilePath = 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("linuxPerms", {
|
|
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;
|
|
}
|
|
|
|
case "MigrationWizard:OpenURL": {
|
|
this.sendAsyncMessage("OpenURL", {
|
|
url: event.detail.url,
|
|
where: event.detail.where,
|
|
});
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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("browserSelected", {
|
|
migrator_key: migrationDetails.key,
|
|
});
|
|
|
|
if (migrationDetails.profile) {
|
|
this.#sendTelemetryEvent("profileSelected", {
|
|
migrator_key: migrationDetails.key,
|
|
});
|
|
}
|
|
|
|
let extraArgs = this.#constructExtraArgs(migrationDetails);
|
|
|
|
extraArgs.configured = String(Number(migrationDetails.expandedDetails));
|
|
this.#sendTelemetryEvent("resourcesSelected", extraArgs);
|
|
delete extraArgs.configured;
|
|
|
|
this.#sendTelemetryEvent("migrationStarted", 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) {
|
|
// We redirect to manual password import for Safari and Chrome on Windows.
|
|
if (
|
|
migrationDetails.resourceTypes.includes(
|
|
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
|
) &&
|
|
!migrationDetails.manualPasswordFilePath
|
|
) {
|
|
if (migrationDetails.key == "safari") {
|
|
this.#sendTelemetryEvent("safariPasswordFile");
|
|
this.setComponentState({
|
|
page: MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION,
|
|
});
|
|
return;
|
|
} else if (
|
|
migrationDetails.key == "chrome" &&
|
|
AppConstants.platform == "win"
|
|
) {
|
|
this.#sendTelemetryEvent("chromePasswordFile");
|
|
this.setComponentState({
|
|
page: MigrationWizardConstants.PAGES
|
|
.CHROME_WINDOWS_PASSWORD_PERMISSION,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
extraArgs = await this.sendQuery("Migrate", {
|
|
migrationDetails,
|
|
extraArgs,
|
|
});
|
|
this.#sendTelemetryEvent("migrationFinished", 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
|
|
)
|
|
);
|
|
}
|
|
}
|