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: [],
 | |
|     };
 | |
|   }
 | |
| }
 | 
