MigrationWizardParent.sys.mjs (29904B)
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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 6 import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs"; 7 import { E10SUtils } from "resource://gre/modules/E10SUtils.sys.mjs"; 8 9 const lazy = {}; 10 11 ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () { 12 return new Localization([ 13 "branding/brand.ftl", 14 "browser/migrationWizard.ftl", 15 ]); 16 }); 17 18 ChromeUtils.defineESModuleGetters(lazy, { 19 FirefoxProfileMigrator: "resource:///modules/FirefoxProfileMigrator.sys.mjs", 20 InternalTestingProfileMigrator: 21 "resource:///modules/InternalTestingProfileMigrator.sys.mjs", 22 LoginCSVImport: "resource://gre/modules/LoginCSVImport.sys.mjs", 23 MigrationWizardConstants: 24 "chrome://browser/content/migration/migration-wizard-constants.mjs", 25 PasswordFileMigrator: "resource:///modules/FileMigrators.sys.mjs", 26 }); 27 28 if (AppConstants.platform == "macosx") { 29 ChromeUtils.defineESModuleGetters(lazy, { 30 SafariProfileMigrator: "resource:///modules/SafariProfileMigrator.sys.mjs", 31 }); 32 } 33 34 /** 35 * Set to true once the first instance of MigrationWizardParent has received 36 * a "GetAvailableMigrators" message. 37 */ 38 let gHasOpenedBefore = false; 39 40 /** 41 * This class is responsible for communicating with MigrationUtils to do the 42 * actual heavy-lifting of any kinds of migration work, based on messages from 43 * the associated MigrationWizardChild. 44 */ 45 export class MigrationWizardParent extends JSWindowActorParent { 46 didDestroy() { 47 Services.obs.notifyObservers(this, "MigrationWizard:Destroyed"); 48 MigrationUtils.finishMigration(); 49 } 50 51 /** 52 * General message handler function for messages received from the 53 * associated MigrationWizardChild JSWindowActor. 54 * 55 * @param {ReceiveMessageArgument} message 56 * The message received from the MigrationWizardChild. 57 * @returns {Promise} 58 */ 59 async receiveMessage(message) { 60 // Some belt-and-suspenders here, mainly because the migration-wizard 61 // component can be embedded in less privileged content pages, so let's 62 // make sure that any messages from content are coming from the privileged 63 // about content process type. 64 if ( 65 !this.browsingContext.currentWindowGlobal.isInProcess && 66 this.browsingContext.currentRemoteType != 67 E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE 68 ) { 69 throw new Error( 70 "MigrationWizardParent: received message from the wrong content process type." 71 ); 72 } 73 74 switch (message.name) { 75 case "GetAvailableMigrators": { 76 if (!gHasOpenedBefore) { 77 Glean.migration.timeToProduceMigratorList.start(); 78 } 79 80 let availableMigrators = []; 81 for (const key of MigrationUtils.availableMigratorKeys) { 82 availableMigrators.push(this.#getMigratorAndProfiles(key)); 83 } 84 85 // Wait for all getMigrator calls to resolve in parallel 86 let results = await Promise.all(availableMigrators); 87 88 for (const migrator of MigrationUtils.availableFileMigrators.values()) { 89 results.push(await this.#serializeFileMigrator(migrator)); 90 } 91 92 // Each migrator might give us a single MigratorProfileInstance, 93 // or an Array of them, so we flatten them out and filter out 94 // any that ended up going wrong and returning null from the 95 // #getMigratorAndProfiles call. 96 let filteredResults = results 97 .flat() 98 .filter(result => result) 99 .sort((a, b) => { 100 return b.lastModifiedDate - a.lastModifiedDate; 101 }); 102 103 if (!gHasOpenedBefore) { 104 gHasOpenedBefore = true; 105 Glean.migration.timeToProduceMigratorList.stop(); 106 } 107 108 return filteredResults; 109 } 110 111 case "Migrate": { 112 let { migrationDetails, extraArgs } = message.data; 113 if ( 114 migrationDetails.type == 115 lazy.MigrationWizardConstants.MIGRATOR_TYPES.BROWSER 116 ) { 117 return this.#doBrowserMigration(migrationDetails, extraArgs); 118 } else if ( 119 migrationDetails.type == 120 lazy.MigrationWizardConstants.MIGRATOR_TYPES.FILE 121 ) { 122 let window = this.browsingContext.topChromeWindow; 123 await this.#doFileMigration(window, migrationDetails.key); 124 return extraArgs; 125 } 126 break; 127 } 128 129 case "CheckPermissions": { 130 if ( 131 message.data.type == 132 lazy.MigrationWizardConstants.MIGRATOR_TYPES.BROWSER 133 ) { 134 let migrator = await MigrationUtils.getMigrator(message.data.key); 135 return migrator.hasPermissions(); 136 } 137 return true; 138 } 139 140 case "RequestSafariPermissions": { 141 let safariMigrator = await MigrationUtils.getMigrator("safari"); 142 return safariMigrator.getPermissions( 143 this.browsingContext.topChromeWindow 144 ); 145 } 146 147 case "SelectManualPasswordFile": { 148 return this.#selectManualPasswordFile( 149 this.browsingContext.topChromeWindow 150 ); 151 } 152 153 case "RecordEvent": { 154 this.#recordEvent(message.data.type, message.data.args); 155 break; 156 } 157 158 case "OpenAboutAddons": { 159 let browser = this.browsingContext.topChromeWindow; 160 this.#openAboutAddons(browser); 161 break; 162 } 163 164 case "GetPermissions": { 165 let migrator = await MigrationUtils.getMigrator(message.data.key); 166 return migrator.getPermissions(this.browsingContext.topChromeWindow); 167 } 168 169 case "OpenURL": { 170 let browser = this.browsingContext.topChromeWindow; 171 this.#openURL(browser, message.data.url, message.data.where); 172 break; 173 } 174 } 175 176 return null; 177 } 178 179 /** 180 * Used for recording telemetry in the migration wizard. 181 * 182 * @param {string} type 183 * The type of event being recorded. 184 * @param {object} args 185 * The data to pass to telemetry when the event is recorded. 186 */ 187 #recordEvent(type, args) { 188 Glean.browserMigration[type + "Wizard"].record(args); 189 } 190 191 /** 192 * Gets the FileMigrator associated with the passed in key, and then opens 193 * a native file picker configured for that migrator. Once the user selects 194 * a file from the native file picker, this is then passed to the 195 * FileMigrator.migrate method. 196 * 197 * As the migration occurs, this will send UpdateProgress messages to the 198 * MigrationWizardChild to show the beginning and then the ending state of 199 * the migration. 200 * 201 * @param {DOMWindow} window 202 * The window that the native file picker should be associated with. This 203 * cannot be null. See nsIFilePicker.init for more details. 204 * @param {string} key 205 * The unique identification key for a file migrator. 206 * @returns {Promise<undefined>} 207 * Resolves once the file migrator's migrate method has resolved. 208 */ 209 async #doFileMigration(window, key) { 210 let fileMigrator = MigrationUtils.getFileMigrator(key); 211 let filePickerConfig = await fileMigrator.getFilePickerConfig(); 212 213 let { result, path } = await new Promise(resolve => { 214 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); 215 fp.init( 216 window.browsingContext, 217 filePickerConfig.title, 218 Ci.nsIFilePicker.modeOpen 219 ); 220 221 for (let filter of filePickerConfig.filters) { 222 fp.appendFilter(filter.title, filter.extensionPattern); 223 } 224 fp.appendFilters(Ci.nsIFilePicker.filterAll); 225 fp.open(async fileOpenResult => { 226 resolve({ result: fileOpenResult, path: fp.file.path }); 227 }); 228 }); 229 230 if (result == Ci.nsIFilePicker.returnCancel) { 231 // If the user cancels out of the file picker, the migration wizard should 232 // still be in the state that lets the user re-open the file picker if 233 // they closed it by accident, so we don't have to do anything else here. 234 return; 235 } 236 237 let progress = {}; 238 for (let resourceType of fileMigrator.displayedResourceTypes) { 239 progress[resourceType] = { 240 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.LOADING, 241 message: "", 242 }; 243 } 244 245 let [progressHeaderString, successHeaderString] = 246 await lazy.gFluentStrings.formatValues([ 247 fileMigrator.progressHeaderL10nID, 248 fileMigrator.successHeaderL10nID, 249 ]); 250 251 this.sendAsyncMessage("UpdateFileImportProgress", { 252 title: progressHeaderString, 253 progress, 254 }); 255 256 let migrationResult; 257 try { 258 migrationResult = await fileMigrator.migrate(path); 259 } catch (e) { 260 this.sendAsyncMessage("FileImportProgressError", { 261 migratorKey: key, 262 fileImportErrorMessage: e.message, 263 }); 264 return; 265 } 266 267 let successProgress = {}; 268 for (let resourceType in migrationResult) { 269 successProgress[resourceType] = { 270 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS, 271 message: migrationResult[resourceType], 272 }; 273 } 274 this.sendAsyncMessage("UpdateFileImportProgress", { 275 title: successHeaderString, 276 progress: successProgress, 277 }); 278 } 279 280 /** 281 * Handles a request to open a native file picker to get the path to a 282 * CSV file that contains passwords exported from another browser. The 283 * returned path is in the form of a string, or `null` if the user cancelled 284 * the native picker. We use this for browsers or platforms that do not 285 * allow us to import passwords automatically. 286 * 287 * @param {DOMWindow} window 288 * The window that the native file picker should be associated with. This 289 * cannot be null. See nsIFilePicker.init for more details. 290 * @returns {Promise<string|null>} 291 */ 292 async #selectManualPasswordFile(window) { 293 let fileMigrator = MigrationUtils.getFileMigrator( 294 lazy.PasswordFileMigrator.key 295 ); 296 let filePickerConfig = await fileMigrator.getFilePickerConfig(); 297 298 let { result, path } = await new Promise(resolve => { 299 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); 300 fp.init( 301 window.browsingContext, 302 filePickerConfig.title, 303 Ci.nsIFilePicker.modeOpen 304 ); 305 306 for (let filter of filePickerConfig.filters) { 307 fp.appendFilter(filter.title, filter.extensionPattern); 308 } 309 fp.appendFilters(Ci.nsIFilePicker.filterAll); 310 fp.open(async fileOpenResult => { 311 resolve({ result: fileOpenResult, path: fp.file.path }); 312 }); 313 }); 314 315 if (result == Ci.nsIFilePicker.returnCancel) { 316 // If the user cancels out of the file picker, the migration wizard should 317 // still be in the state that lets the user re-open the file picker if 318 // they closed it by accident, so we don't have to do anything else here. 319 return null; 320 } 321 322 return path; 323 } 324 325 /** 326 * Calls into MigrationUtils to perform a migration given the parameters 327 * sent via the wizard. 328 * 329 * @param {MigrationDetails} migrationDetails 330 * See migration-wizard.mjs for a definition of MigrationDetails. 331 * @param {object} extraArgs 332 * Extra argument object that will be passed to the Event Telemetry for 333 * finishing the migration. This was initialized in the child actor, and 334 * will be sent back down to it to write to Telemetry once migration 335 * completes. 336 * 337 * @returns {Promise<object>} 338 * Resolves once the Migration:Ended observer notification has fired, 339 * passing the extraArgs for Telemetry back with any relevant properties 340 * updated. 341 */ 342 async #doBrowserMigration(migrationDetails, extraArgs) { 343 Glean.browserMigration.sourceBrowser.accumulateSingleSample( 344 MigrationUtils.getSourceIdForTelemetry(migrationDetails.key) 345 ); 346 347 let migrator = await MigrationUtils.getMigrator(migrationDetails.key); 348 let availableResourceTypes = await migrator.getMigrateData( 349 migrationDetails.profile 350 ); 351 let resourceTypesToMigrate = 0; 352 let progress = {}; 353 let gleanMigrationUsage = Glean.browserMigration.usage; 354 355 for (let resourceTypeName of migrationDetails.resourceTypes) { 356 let resourceType = MigrationUtils.resourceTypes[resourceTypeName]; 357 if (availableResourceTypes & resourceType) { 358 resourceTypesToMigrate |= resourceType; 359 progress[resourceTypeName] = { 360 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.LOADING, 361 message: "", 362 }; 363 364 if (!migrationDetails.autoMigration) { 365 gleanMigrationUsage[migrationDetails.key].accumulateSingleSample( 366 Math.log2(resourceType) 367 ); 368 } 369 } 370 } 371 372 if (migrationDetails.manualPasswordFilePath) { 373 // The caller supplied a password export file for another browser. We're 374 // going to pretend that there was a PASSWORDS resource to represent the 375 // state of importing from that file. 376 progress[ 377 lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS 378 ] = { 379 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.LOADING, 380 message: "", 381 }; 382 383 this.sendAsyncMessage("UpdateProgress", { 384 key: migrationDetails.key, 385 progress, 386 }); 387 388 try { 389 let summary = await lazy.LoginCSVImport.importFromCSV( 390 migrationDetails.manualPasswordFilePath 391 ); 392 let quantity = summary.filter(entry => entry.result == "added").length; 393 394 MigrationUtils.notifyLoginsManuallyImported(quantity); 395 396 progress[ 397 lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS 398 ] = { 399 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS, 400 message: await lazy.gFluentStrings.formatValue( 401 "migration-wizard-progress-success-passwords", 402 { 403 quantity, 404 } 405 ), 406 }; 407 } catch (e) { 408 progress[ 409 lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS 410 ] = { 411 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.WARNING, 412 message: await lazy.gFluentStrings.formatValue( 413 "migration-passwords-from-file-no-valid-data" 414 ), 415 }; 416 } 417 } 418 419 this.sendAsyncMessage("UpdateProgress", { 420 key: migrationDetails.key, 421 progress, 422 }); 423 424 // It's possible that only a Safari password file path was sent up, and 425 // there's nothing left to migrate, in which case we're done here. 426 if ( 427 migrationDetails.manualPasswordFilePath && 428 !migrationDetails.resourceTypes.length 429 ) { 430 return extraArgs; 431 } 432 433 try { 434 await migrator.migrate( 435 resourceTypesToMigrate, 436 false, 437 migrationDetails.profile, 438 async (resourceTypeNum, success, details) => { 439 // Unfortunately, MigratorBase hands us the the numeric value of the 440 // MigrationUtils.resourceType for this callback. For now, we'll just 441 // do a look-up to map it to the right constant. 442 let foundResourceTypeName; 443 for (let resourceTypeName in MigrationUtils.resourceTypes) { 444 if ( 445 MigrationUtils.resourceTypes[resourceTypeName] == resourceTypeNum 446 ) { 447 foundResourceTypeName = resourceTypeName; 448 break; 449 } 450 } 451 452 if (!foundResourceTypeName) { 453 console.error( 454 "Could not find a resource type for value: ", 455 resourceTypeNum 456 ); 457 } else { 458 if (!success) { 459 Glean.browserMigration.errors[ 460 migrationDetails.key 461 ].accumulateSingleSample(Math.log2(resourceTypeNum)); 462 } 463 if ( 464 foundResourceTypeName == 465 lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS 466 ) { 467 if (!success) { 468 // did not match any extensions 469 extraArgs.extensions = 470 lazy.MigrationWizardConstants.EXTENSIONS_IMPORT_RESULT.NONE_MATCHED; 471 progress[foundResourceTypeName] = { 472 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.WARNING, 473 message: await lazy.gFluentStrings.formatValue( 474 "migration-wizard-progress-no-matched-extensions" 475 ), 476 linkURL: Services.urlFormatter.formatURLPref( 477 "extensions.getAddons.link.url" 478 ), 479 linkText: await lazy.gFluentStrings.formatValue( 480 "migration-wizard-progress-extensions-addons-link" 481 ), 482 }; 483 } else if ( 484 details?.progressValue == 485 lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS 486 ) { 487 // did match all extensions 488 extraArgs.extensions = 489 lazy.MigrationWizardConstants.EXTENSIONS_IMPORT_RESULT.ALL_MATCHED; 490 progress[foundResourceTypeName] = { 491 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS, 492 message: await lazy.gFluentStrings.formatValue( 493 "migration-wizard-progress-success-extensions", 494 { 495 quantity: details.totalExtensions.length, 496 } 497 ), 498 }; 499 } else if ( 500 details?.progressValue == 501 lazy.MigrationWizardConstants.PROGRESS_VALUE.INFO 502 ) { 503 // did match some extensions 504 extraArgs.extensions = 505 lazy.MigrationWizardConstants.EXTENSIONS_IMPORT_RESULT.PARTIAL_MATCH; 506 progress[foundResourceTypeName] = { 507 value: lazy.MigrationWizardConstants.PROGRESS_VALUE.INFO, 508 message: await lazy.gFluentStrings.formatValue( 509 "migration-wizard-progress-partial-success-extensions", 510 { 511 matched: details.importedExtensions.length, 512 quantity: details.totalExtensions.length, 513 } 514 ), 515 linkURL: 516 Services.urlFormatter.formatURLPref("app.support.baseURL") + 517 "import-data-another-browser", 518 linkText: await lazy.gFluentStrings.formatValue( 519 "migration-wizard-progress-extensions-support-link" 520 ), 521 }; 522 } 523 } else { 524 progress[foundResourceTypeName] = { 525 value: success 526 ? lazy.MigrationWizardConstants.PROGRESS_VALUE.SUCCESS 527 : lazy.MigrationWizardConstants.PROGRESS_VALUE.WARNING, 528 message: await this.#getStringForImportQuantity( 529 migrationDetails.key, 530 foundResourceTypeName 531 ), 532 }; 533 } 534 this.sendAsyncMessage("UpdateProgress", { 535 key: migrationDetails.key, 536 progress, 537 }); 538 } 539 } 540 ); 541 } catch (e) { 542 console.error(e); 543 } 544 545 return extraArgs; 546 } 547 548 /** 549 * @typedef {object} MigratorProfileInstance 550 * An object that describes a single user profile (or the default 551 * user profile) for a particular migrator. 552 * @property {string} key 553 * The unique identification key for a migrator. 554 * @property {string} displayName 555 * The display name for the migrator that will be shown to the user 556 * in the wizard. 557 * @property {string[]} resourceTypes 558 * An array of strings, where each string represents a resource type 559 * that can be imported for this migrator and profile. The strings 560 * should be one of the key values of 561 * MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES. 562 * 563 * Example: ["HISTORY", "FORMDATA", "PASSWORDS", "BOOKMARKS"] 564 * @property {object|null} profile 565 * A description of the user profile that the migrator can import. 566 * @property {string} profile.id 567 * A unique ID for the user profile. 568 * @property {string} profile.name 569 * The display name for the user profile. 570 */ 571 572 /** 573 * Asynchronously fetches a migrator for a particular key, and then 574 * also gets any user profiles that exist on for that migrator. Resolves 575 * to null if something goes wrong getting information about the migrator 576 * or any of the user profiles. 577 * 578 * @param {string} key 579 * The unique identification key for a migrator. 580 * @returns {Promise<MigratorProfileInstance[]|null>} 581 */ 582 async #getMigratorAndProfiles(key) { 583 try { 584 let migrator = await MigrationUtils.getMigrator(key); 585 if (!migrator?.enabled) { 586 return null; 587 } 588 589 if (!(await migrator.hasPermissions())) { 590 // If we're unable to get permissions for this migrator, then we 591 // just don't bother showing it. 592 let permissionsPath = await migrator.canGetPermissions(); 593 if (!permissionsPath) { 594 return null; 595 } 596 return this.#serializeMigratorAndProfile( 597 migrator, 598 null, 599 false /* hasPermissions */, 600 permissionsPath 601 ); 602 } 603 604 let sourceProfiles = await migrator.getSourceProfiles(); 605 if (Array.isArray(sourceProfiles)) { 606 if (!sourceProfiles.length) { 607 return null; 608 } 609 610 Glean.migration.discoveredMigrators[key].add(sourceProfiles.length); 611 612 let result = []; 613 for (let profile of sourceProfiles) { 614 result.push( 615 await this.#serializeMigratorAndProfile(migrator, profile) 616 ); 617 } 618 return result; 619 } 620 621 Glean.migration.discoveredMigrators[key].add(1); 622 return this.#serializeMigratorAndProfile(migrator, sourceProfiles); 623 } catch (e) { 624 console.error(`Could not get migrator with key ${key}`, e); 625 } 626 return null; 627 } 628 629 /** 630 * Asynchronously fetches information about what resource types can be 631 * migrated for a particular migrator and user profile, and then packages 632 * the migrator, user profile data, and resource type data into an object 633 * that can be sent down to the MigrationWizardChild. 634 * 635 * @param {MigratorBase} migrator 636 * A migrator subclass of MigratorBase. 637 * @param {object|null} profileObj 638 * The user profile object representing the profile to get information 639 * about. This object is usually gotten by calling getSourceProfiles on 640 * the migrator. 641 * @param {boolean} [hasPermissions=true] 642 * Whether or not the migrator has permission to read the data for the 643 * other browser. It is expected that the caller will have already 644 * computed this by calling hasPermissions() on the migrator, and 645 * passing the result into this method. This is true by default. 646 * @param {string} [permissionsPath=undefined] 647 * The path that the selected migrator needs read access to in order to 648 * do a migration, in the event that hasPermissions is false. This is 649 * undefined if hasPermissions is true. 650 * @returns {Promise<MigratorProfileInstance>} 651 */ 652 async #serializeMigratorAndProfile( 653 migrator, 654 profileObj, 655 hasPermissions = true, 656 permissionsPath 657 ) { 658 let [profileMigrationData, lastModifiedDate] = await Promise.all([ 659 migrator.getMigrateData(profileObj), 660 migrator.getLastUsedDate(), 661 ]); 662 663 let availableResourceTypes = []; 664 665 // Even if we don't have permissions, we'll show the resources available 666 // for Safari. For Safari, the workflow is to request permissions only 667 // after the resources have been selected. 668 if ( 669 hasPermissions || 670 migrator.constructor.key == lazy.SafariProfileMigrator?.key 671 ) { 672 for (let resourceType in MigrationUtils.resourceTypes) { 673 // Normally, we check each possible resourceType to see if we have one or 674 // more corresponding resourceTypes in profileMigrationData. 675 // 676 // The exception is for passwords for Safari, and for Chrome on Windows, 677 // where we cannot import passwords automatically, but we allow the user 678 // to express that they'd like to import passwords from it anyways. We 679 // use this to determine whether or not to show guidance on how to 680 // manually import a passwords CSV file. 681 if ( 682 profileMigrationData & MigrationUtils.resourceTypes[resourceType] || 683 (MigrationUtils.resourceTypes[resourceType] == 684 MigrationUtils.resourceTypes.PASSWORDS && 685 migrator.showsManualPasswordImport) 686 ) { 687 availableResourceTypes.push(resourceType); 688 } 689 } 690 } 691 692 let displayName; 693 694 if (migrator.constructor.key == lazy.InternalTestingProfileMigrator.key) { 695 // In the case of the InternalTestingProfileMigrator, which is never seen 696 // by users outside of testing, we don't make our localization community 697 // localize it's display name, and just display the ID instead. 698 displayName = migrator.constructor.displayNameL10nID; 699 } else { 700 displayName = await lazy.gFluentStrings.formatValue( 701 migrator.constructor.displayNameL10nID 702 ); 703 } 704 705 return { 706 type: lazy.MigrationWizardConstants.MIGRATOR_TYPES.BROWSER, 707 key: migrator.constructor.key, 708 displayName, 709 brandImage: migrator.constructor.brandImage, 710 resourceTypes: availableResourceTypes, 711 profile: profileObj, 712 lastModifiedDate, 713 hasPermissions, 714 permissionsPath, 715 }; 716 } 717 718 /** 719 * Returns the "success" string for a particular resource type after 720 * migration has completed. 721 * 722 * @param {string} migratorKey 723 * The key for the migrator being used. 724 * @param {string} resourceTypeStr 725 * A string mapping to one of the key values of 726 * MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES. 727 * @returns {Promise<string>} 728 * The success string for the resource type after migration has completed. 729 */ 730 #getStringForImportQuantity(migratorKey, resourceTypeStr) { 731 if (migratorKey == lazy.FirefoxProfileMigrator.key) { 732 return ""; 733 } 734 735 switch (resourceTypeStr) { 736 case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS: { 737 let quantity = MigrationUtils.getImportedCount("bookmarks"); 738 let stringID = "migration-wizard-progress-success-bookmarks"; 739 740 if ( 741 lazy.MigrationWizardConstants.USES_FAVORITES.includes(migratorKey) 742 ) { 743 stringID = "migration-wizard-progress-success-favorites"; 744 } 745 746 return lazy.gFluentStrings.formatValue(stringID, { 747 quantity, 748 }); 749 } 750 case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY: { 751 return lazy.gFluentStrings.formatValue( 752 "migration-wizard-progress-success-history", 753 { 754 maxAgeInDays: MigrationUtils.HISTORY_MAX_AGE_IN_DAYS, 755 } 756 ); 757 } 758 case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS: { 759 let quantity = MigrationUtils.getImportedCount("logins"); 760 return lazy.gFluentStrings.formatValue( 761 "migration-wizard-progress-success-passwords", 762 { 763 quantity, 764 } 765 ); 766 } 767 case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA: { 768 return lazy.gFluentStrings.formatValue( 769 "migration-wizard-progress-success-formdata" 770 ); 771 } 772 case lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES 773 .PAYMENT_METHODS: { 774 let quantity = MigrationUtils.getImportedCount("cards"); 775 return lazy.gFluentStrings.formatValue( 776 "migration-wizard-progress-success-payment-methods", 777 { 778 quantity, 779 } 780 ); 781 } 782 default: { 783 return ""; 784 } 785 } 786 } 787 788 /** 789 * Returns a Promise that resolves to a serializable representation of a 790 * FileMigrator for sending down to the MigrationWizard. 791 * 792 * @param {FileMigrator} fileMigrator 793 * The FileMigrator to serialize. 794 * @returns {Promise<object|null>} 795 * The serializable representation of the FileMigrator, or null if the 796 * migrator is disabled. 797 */ 798 async #serializeFileMigrator(fileMigrator) { 799 if (!fileMigrator.enabled) { 800 return null; 801 } 802 803 return { 804 type: lazy.MigrationWizardConstants.MIGRATOR_TYPES.FILE, 805 key: fileMigrator.constructor.key, 806 displayName: await lazy.gFluentStrings.formatValue( 807 fileMigrator.constructor.displayNameL10nID 808 ), 809 brandImage: fileMigrator.constructor.brandImage, 810 resourceTypes: [], 811 }; 812 } 813 814 /** 815 * Opens the about:addons page in a new background tab in the same window 816 * as the passed browser. 817 * 818 * @param {Element} browser 819 * The browser element requesting that about:addons opens. 820 */ 821 #openAboutAddons(browser) { 822 let window = browser.ownerGlobal; 823 window.openTrustedLinkIn("about:addons", "tab", { inBackground: true }); 824 } 825 826 /** 827 * Opens a url in a new background tab in the same window 828 * as the passed browser. 829 * 830 * @param {Element} browser 831 * The browser element requesting that the URL opens in. 832 * @param {string} url 833 * The URL that will be opened. 834 * @param {string} where 835 * Where the URL will be opened. Defaults to current tab. 836 */ 837 #openURL(browser, url, where) { 838 let window = browser.ownerGlobal; 839 window.openLinkIn( 840 Services.urlFormatter.formatURL(url), 841 where || "current", 842 { 843 private: false, 844 triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal( 845 {} 846 ), 847 } 848 ); 849 } 850 }