forked from mirrors/gecko-dev
518 lines
17 KiB
JavaScript
518 lines
17 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 { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
|
import { E10SUtils } from "resource://gre/modules/E10SUtils.sys.mjs";
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
XPCOMUtils.defineLazyGetter(lazy, "gFluentStrings", function() {
|
|
return new Localization([
|
|
"branding/brand.ftl",
|
|
"browser/migrationWizard.ftl",
|
|
]);
|
|
});
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
InternalTestingProfileMigrator:
|
|
"resource:///modules/InternalTestingProfileMigrator.sys.mjs",
|
|
MigrationWizardConstants:
|
|
"chrome://browser/content/migration/migration-wizard-constants.mjs",
|
|
});
|
|
|
|
/**
|
|
* This class is responsible for communicating with MigrationUtils to do the
|
|
* actual heavy-lifting of any kinds of migration work, based on messages from
|
|
* the associated MigrationWizardChild.
|
|
*/
|
|
export class MigrationWizardParent extends JSWindowActorParent {
|
|
constructor() {
|
|
super();
|
|
Services.telemetry.setEventRecordingEnabled("browser.migration", true);
|
|
}
|
|
|
|
didDestroy() {
|
|
Services.obs.notifyObservers(this, "MigrationWizard:Destroyed");
|
|
}
|
|
|
|
/**
|
|
* General message handler function for messages received from the
|
|
* associated MigrationWizardChild JSWindowActor.
|
|
*
|
|
* @param {ReceiveMessageArgument} message
|
|
* The message received from the MigrationWizardChild.
|
|
* @returns {Promise}
|
|
*/
|
|
async receiveMessage(message) {
|
|
// Some belt-and-suspenders here, mainly because the migration-wizard
|
|
// component can be embedded in less privileged content pages, so let's
|
|
// make sure that any messages from content are coming from the privileged
|
|
// about content process type.
|
|
if (
|
|
!this.browsingContext.currentWindowGlobal.isInProcess &&
|
|
this.browsingContext.currentRemoteType !=
|
|
E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE
|
|
) {
|
|
throw new Error(
|
|
"MigrationWizardParent: received message from the wrong content process type."
|
|
);
|
|
}
|
|
|
|
switch (message.name) {
|
|
case "GetAvailableMigrators": {
|
|
let availableMigrators = [];
|
|
for (const key of MigrationUtils.availableMigratorKeys) {
|
|
availableMigrators.push(this.#getMigratorAndProfiles(key));
|
|
}
|
|
|
|
// Wait for all getMigrator calls to resolve in parallel
|
|
let results = await Promise.all(availableMigrators);
|
|
|
|
for (const migrator of MigrationUtils.availableFileMigrators.values()) {
|
|
results.push(await this.#serializeFileMigrator(migrator));
|
|
}
|
|
|
|
// Each migrator might give us a single MigratorProfileInstance,
|
|
// or an Array of them, so we flatten them out and filter out
|
|
// any that ended up going wrong and returning null from the
|
|
// #getMigratorAndProfiles call.
|
|
let filteredResults = results
|
|
.flat()
|
|
.filter(result => result)
|
|
.sort((a, b) => {
|
|
return b.lastModifiedDate - a.lastModifiedDate;
|
|
});
|
|
|
|
for (let result of filteredResults) {
|
|
Services.telemetry.keyedScalarAdd(
|
|
"migration.discovered_migrators",
|
|
result.key,
|
|
1
|
|
);
|
|
}
|
|
return filteredResults;
|
|
}
|
|
|
|
case "Migrate": {
|
|
if (
|
|
message.data.type ==
|
|
lazy.MigrationWizardConstants.MIGRATOR_TYPES.BROWSER
|
|
) {
|
|
await this.#doBrowserMigration(
|
|
message.data.key,
|
|
message.data.resourceTypes,
|
|
message.data.profile
|
|
);
|
|
} else if (
|
|
message.data.type == lazy.MigrationWizardConstants.MIGRATOR_TYPES.FILE
|
|
) {
|
|
let window = this.browsingContext.topChromeWindow;
|
|
await this.#doFileMigration(window, message.data.key);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "CheckPermissions": {
|
|
if (
|
|
message.data.type ==
|
|
lazy.MigrationWizardConstants.MIGRATOR_TYPES.BROWSER
|
|
) {
|
|
let migrator = await MigrationUtils.getMigrator(message.data.key);
|
|
return migrator.hasPermissions();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case "RequestSafariPermissions": {
|
|
let safariMigrator = await MigrationUtils.getMigrator("safari");
|
|
return safariMigrator.getPermissions(
|
|
this.browsingContext.topChromeWindow
|
|
);
|
|
}
|
|
|
|
case "RecordEvent": {
|
|
this.#recordEvent(message.data.type, message.data.args);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Used for recording telemetry in the migration wizard.
|
|
*
|
|
* @param {string} type
|
|
* The type of event being recorded.
|
|
* @param {object} args
|
|
* The data to pass to telemetry when the event is recorded.
|
|
*/
|
|
#recordEvent(type, args = null) {
|
|
Services.telemetry.recordEvent(
|
|
"browser.migration",
|
|
type,
|
|
"wizard",
|
|
null,
|
|
args
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets the FileMigrator associated with the passed in key, and then opens
|
|
* a native file picker configured for that migrator. Once the user selects
|
|
* a file from the native file picker, this is then passed to the
|
|
* FileMigrator.migrate method.
|
|
*
|
|
* As the migration occurs, this will send UpdateProgress messages to the
|
|
* MigrationWizardChild to show the beginning and then the ending state of
|
|
* the migration.
|
|
*
|
|
* @param {DOMWindow} window
|
|
* The window that the native file picker should be associated with. This
|
|
* cannot be null. See nsIFilePicker.init for more details.
|
|
* @param {string} key
|
|
* The unique identification key for a file migrator.
|
|
* @returns {Promise<undefined>}
|
|
* Resolves once the file migrator's migrate method has resolved.
|
|
*/
|
|
async #doFileMigration(window, key) {
|
|
let fileMigrator = MigrationUtils.getFileMigrator(key);
|
|
let filePickerConfig = await fileMigrator.getFilePickerConfig();
|
|
|
|
let { result, path } = await new Promise(resolve => {
|
|
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
|
fp.init(window, filePickerConfig.title, Ci.nsIFilePicker.modeOpen);
|
|
|
|
for (let filter of filePickerConfig.filters) {
|
|
fp.appendFilter(filter.title, filter.extensionPattern);
|
|
}
|
|
fp.appendFilters(Ci.nsIFilePicker.filterAll);
|
|
fp.open(async fileOpenResult => {
|
|
resolve({ result: fileOpenResult, path: fp.file.path });
|
|
});
|
|
});
|
|
|
|
if (result == Ci.nsIFilePicker.returnCancel) {
|
|
// If the user cancels out of the file picker, the migration wizard should
|
|
// still be in the state that lets the user re-open the file picker if
|
|
// they closed it by accident, so we don't have to do anything else here.
|
|
return;
|
|
}
|
|
|
|
let progress = {};
|
|
for (let resourceType of fileMigrator.displayedResourceTypes) {
|
|
progress[resourceType] = {
|
|
inProgress: true,
|
|
message: "",
|
|
};
|
|
}
|
|
|
|
let [
|
|
progressHeaderString,
|
|
successHeaderString,
|
|
] = await lazy.gFluentStrings.formatValues([
|
|
fileMigrator.progressHeaderL10nID,
|
|
fileMigrator.successHeaderL10nID,
|
|
]);
|
|
|
|
this.sendAsyncMessage("UpdateFileImportProgress", {
|
|
title: progressHeaderString,
|
|
progress,
|
|
});
|
|
let migrationResult = await fileMigrator.migrate(path);
|
|
let successProgress = {};
|
|
for (let resourceType in migrationResult) {
|
|
successProgress[resourceType] = {
|
|
inProgress: false,
|
|
message: migrationResult[resourceType],
|
|
};
|
|
}
|
|
this.sendAsyncMessage("UpdateFileImportProgress", {
|
|
title: successHeaderString,
|
|
progress: successProgress,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Calls into MigrationUtils to perform a migration given the parameters
|
|
* sent via the wizard.
|
|
*
|
|
* @param {string} migratorKey
|
|
* The unique identification key for a migrator.
|
|
* @param {string[]} resourceTypeNames
|
|
* An array of strings, where each string represents a resource type
|
|
* that can be imported for this migrator and profile. The strings
|
|
* should be one of the key values of
|
|
* MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
|
|
* @param {object|null} profileObj
|
|
* A description of the user profile that the migrator can import.
|
|
* @param {string} profileObj.id
|
|
* A unique ID for the user profile.
|
|
* @param {string} profileObj.name
|
|
* The display name for the user profile.
|
|
* @returns {Promise<undefined>}
|
|
* Resolves once the Migration:Ended observer notification has fired.
|
|
*/
|
|
async #doBrowserMigration(migratorKey, resourceTypeNames, profileObj) {
|
|
let migrator = await MigrationUtils.getMigrator(migratorKey);
|
|
let availableResourceTypes = await migrator.getMigrateData(profileObj);
|
|
let resourceTypesToMigrate = 0;
|
|
let progress = {};
|
|
|
|
for (let resourceTypeName of resourceTypeNames) {
|
|
let resourceType = MigrationUtils.resourceTypes[resourceTypeName];
|
|
if (availableResourceTypes & resourceType) {
|
|
resourceTypesToMigrate |= resourceType;
|
|
progress[resourceTypeName] = {
|
|
inProgress: true,
|
|
message: "",
|
|
};
|
|
}
|
|
}
|
|
|
|
this.sendAsyncMessage("UpdateProgress", { key: migratorKey, progress });
|
|
|
|
try {
|
|
await migrator.migrate(
|
|
resourceTypesToMigrate,
|
|
false,
|
|
profileObj,
|
|
async resourceTypeNum => {
|
|
// Unfortunately, MigratorBase hands us the the numeric value of the
|
|
// MigrationUtils.resourceType for this callback. For now, we'll just
|
|
// do a look-up to map it to the right constant.
|
|
let foundResourceTypeName;
|
|
for (let resourceTypeName in MigrationUtils.resourceTypes) {
|
|
if (
|
|
MigrationUtils.resourceTypes[resourceTypeName] == resourceTypeNum
|
|
) {
|
|
foundResourceTypeName = resourceTypeName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundResourceTypeName) {
|
|
console.error(
|
|
"Could not find a resource type for value: ",
|
|
resourceTypeNum
|
|
);
|
|
} else {
|
|
// For now, we ignore errors in migration, and simply display
|
|
// the success state.
|
|
progress[foundResourceTypeName] = {
|
|
inProgress: false,
|
|
message: await this.#getStringForImportQuantity(
|
|
migratorKey,
|
|
foundResourceTypeName
|
|
),
|
|
};
|
|
this.sendAsyncMessage("UpdateProgress", {
|
|
key: migratorKey,
|
|
progress,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef {object} MigratorProfileInstance
|
|
* An object that describes a single user profile (or the default
|
|
* user profile) for a particular migrator.
|
|
* @property {string} key
|
|
* The unique identification key for a migrator.
|
|
* @property {string} displayName
|
|
* The display name for the migrator that will be shown to the user
|
|
* in the wizard.
|
|
* @property {string[]} resourceTypes
|
|
* An array of strings, where each string represents a resource type
|
|
* that can be imported for this migrator and profile. The strings
|
|
* should be one of the key values of
|
|
* MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
|
|
*
|
|
* Example: ["HISTORY", "FORMDATA", "PASSWORDS", "BOOKMARKS"]
|
|
* @property {object|null} profile
|
|
* A description of the user profile that the migrator can import.
|
|
* @property {string} profile.id
|
|
* A unique ID for the user profile.
|
|
* @property {string} profile.name
|
|
* The display name for the user profile.
|
|
*/
|
|
|
|
/**
|
|
* Asynchronously fetches a migrator for a particular key, and then
|
|
* also gets any user profiles that exist on for that migrator. Resolves
|
|
* to null if something goes wrong getting information about the migrator
|
|
* or any of the user profiles.
|
|
*
|
|
* @param {string} key
|
|
* The unique identification key for a migrator.
|
|
* @returns {Promise<MigratorProfileInstance[]|null>}
|
|
*/
|
|
async #getMigratorAndProfiles(key) {
|
|
try {
|
|
let migrator = await MigrationUtils.getMigrator(key);
|
|
if (!migrator?.enabled) {
|
|
return null;
|
|
}
|
|
|
|
let sourceProfiles = await migrator.getSourceProfiles();
|
|
if (Array.isArray(sourceProfiles)) {
|
|
if (!sourceProfiles.length) {
|
|
return null;
|
|
}
|
|
|
|
let result = [];
|
|
for (let profile of sourceProfiles) {
|
|
result.push(
|
|
await this.#serializeMigratorAndProfile(migrator, profile)
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
return this.#serializeMigratorAndProfile(migrator, sourceProfiles);
|
|
} catch (e) {
|
|
console.error(`Could not get migrator with key ${key}`, e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Asynchronously fetches information about what resource types can be
|
|
* migrated for a particular migrator and user profile, and then packages
|
|
* the migrator, user profile data, and resource type data into an object
|
|
* that can be sent down to the MigrationWizardChild.
|
|
*
|
|
* @param {MigratorBase} migrator
|
|
* A migrator subclass of MigratorBase.
|
|
* @param {object|null} profileObj
|
|
* The user profile object representing the profile to get information
|
|
* about. This object is usually gotten by calling getSourceProfiles on
|
|
* the migrator.
|
|
* @returns {Promise<MigratorProfileInstance>}
|
|
*/
|
|
async #serializeMigratorAndProfile(migrator, profileObj) {
|
|
let [profileMigrationData, lastModifiedDate] = await Promise.all([
|
|
migrator.getMigrateData(profileObj),
|
|
migrator.getLastUsedDate(),
|
|
]);
|
|
|
|
let availableResourceTypes = [];
|
|
|
|
for (let resourceType in MigrationUtils.resourceTypes) {
|
|
if (profileMigrationData & MigrationUtils.resourceTypes[resourceType]) {
|
|
availableResourceTypes.push(resourceType);
|
|
}
|
|
}
|
|
|
|
let displayName;
|
|
|
|
if (migrator.constructor.key == lazy.InternalTestingProfileMigrator.key) {
|
|
// In the case of the InternalTestingProfileMigrator, which is never seen
|
|
// by users outside of testing, we don't make our localization community
|
|
// localize it's display name, and just display the ID instead.
|
|
displayName = migrator.constructor.displayNameL10nID;
|
|
} else {
|
|
displayName = await lazy.gFluentStrings.formatValue(
|
|
migrator.constructor.displayNameL10nID
|
|
);
|
|
}
|
|
|
|
return {
|
|
type: lazy.MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
|
|
key: migrator.constructor.key,
|
|
displayName,
|
|
brandImage: migrator.constructor.brandImage,
|
|
resourceTypes: availableResourceTypes,
|
|
profile: profileObj,
|
|
lastModifiedDate,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns the "success" string for a particular resource type after
|
|
* migration has completed.
|
|
*
|
|
* @param {string} migratorKey
|
|
* The key for the migrator being used.
|
|
* @param {string} resourceTypeStr
|
|
* A string mapping to one of the key values of
|
|
* MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
|
|
* @returns {Promise<string>}
|
|
* The success string for the resource type after migration has completed.
|
|
*/
|
|
#getStringForImportQuantity(migratorKey, resourceTypeStr) {
|
|
switch (resourceTypeStr) {
|
|
case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS: {
|
|
let quantity = MigrationUtils.getImportedCount("bookmarks");
|
|
let stringID = "migration-wizard-progress-success-bookmarks";
|
|
|
|
if (
|
|
lazy.MigrationWizardConstants.USES_FAVORITES.includes(migratorKey)
|
|
) {
|
|
stringID = "migration-wizard-progress-success-favorites";
|
|
}
|
|
|
|
return lazy.gFluentStrings.formatValue(stringID, {
|
|
quantity,
|
|
});
|
|
}
|
|
case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY: {
|
|
return lazy.gFluentStrings.formatValue(
|
|
"migration-wizard-progress-success-history",
|
|
{
|
|
maxAgeInDays: MigrationUtils.HISTORY_MAX_AGE_IN_DAYS,
|
|
}
|
|
);
|
|
}
|
|
case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS: {
|
|
let quantity = MigrationUtils.getImportedCount("logins");
|
|
return lazy.gFluentStrings.formatValue(
|
|
"migration-wizard-progress-success-passwords",
|
|
{
|
|
quantity,
|
|
}
|
|
);
|
|
}
|
|
case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA: {
|
|
return lazy.gFluentStrings.formatValue(
|
|
"migration-wizard-progress-success-formdata"
|
|
);
|
|
}
|
|
default: {
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a Promise that resolves to a serializable representation of a
|
|
* FileMigrator for sending down to the MigrationWizard.
|
|
*
|
|
* @param {FileMigrator} fileMigrator
|
|
* The FileMigrator to serialize.
|
|
* @returns {Promise<object|null>}
|
|
* The serializable representation of the FileMigrator, or null if the
|
|
* migrator is disabled.
|
|
*/
|
|
async #serializeFileMigrator(fileMigrator) {
|
|
if (!fileMigrator.enabled) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
type: lazy.MigrationWizardConstants.MIGRATOR_TYPES.FILE,
|
|
key: fileMigrator.constructor.key,
|
|
displayName: await lazy.gFluentStrings.formatValue(
|
|
fileMigrator.constructor.displayNameL10nID
|
|
),
|
|
brandImage: fileMigrator.constructor.brandImage,
|
|
resourceTypes: [],
|
|
};
|
|
}
|
|
}
|