Bug 1960560 - Part 2: Add steps for importing passwords manually from Chrome on Windows. r=migration-reviewers,fluent-reviewers,desktop-theme-reviewers,fxview-reviewers,omc-reviewers,Gijs,bolsson,pdahiya
Differential Revision: https://phabricator.services.mozilla.com/D246809
This commit is contained in:
committed by
mconley@mozilla.com
parent
c60ed6a900
commit
f62fb0b07c
@@ -23,6 +23,7 @@
|
|||||||
<link rel="localization" href="browser/newtab/onboarding.ftl" />
|
<link rel="localization" href="browser/newtab/onboarding.ftl" />
|
||||||
<link rel="localization" href="browser/spotlight.ftl" />
|
<link rel="localization" href="browser/spotlight.ftl" />
|
||||||
<link rel="localization" href="browser/migrationWizard.ftl" />
|
<link rel="localization" href="browser/migrationWizard.ftl" />
|
||||||
|
<link rel="localization" href="preview/migrationWizardChromeWindows.ftl" />
|
||||||
<link rel="localization" href="browser/preonboarding.ftl" />
|
<link rel="localization" href="browser/preonboarding.ftl" />
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
<link rel="localization" href="browser/newtab/onboarding.ftl" />
|
<link rel="localization" href="browser/newtab/onboarding.ftl" />
|
||||||
<link rel="localization" href="browser/spotlight.ftl" />
|
<link rel="localization" href="browser/spotlight.ftl" />
|
||||||
<link rel="localization" href="browser/migrationWizard.ftl" />
|
<link rel="localization" href="browser/migrationWizard.ftl" />
|
||||||
|
<link rel="localization" href="preview/migrationWizardChromeWindows.ftl" />
|
||||||
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<link rel="localization" href="toolkit/global/textActions.ftl" />
|
<link rel="localization" href="toolkit/global/textActions.ftl" />
|
||||||
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
||||||
<link rel="localization" href="browser/migrationWizard.ftl" />
|
<link rel="localization" href="browser/migrationWizard.ftl" />
|
||||||
|
<link rel="localization" href="preview/migrationWizardChromeWindows.ftl" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="chrome://browser/content/firefoxview/firefoxview.css"
|
href="chrome://browser/content/firefoxview/firefoxview.css"
|
||||||
|
|||||||
@@ -181,6 +181,22 @@ export class ChromeProfileMigrator extends MigratorBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Chrome on Windows, we show a specialized flow for importing passwords
|
||||||
|
* from a CSV file.
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get showsManualPasswordImport() {
|
||||||
|
// We show the CSV workflow on Windows, but _only_ in English locales, until
|
||||||
|
// bug 1965231 is closed.
|
||||||
|
return (
|
||||||
|
AppConstants.platform == "win" &&
|
||||||
|
this.constructor.key == "chrome" &&
|
||||||
|
Services.locale.appLocaleAsBCP47.startsWith("en")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_keychainServiceName = "Chrome Safe Storage";
|
_keychainServiceName = "Chrome Safe Storage";
|
||||||
|
|
||||||
_keychainAccountName = "Chrome";
|
_keychainAccountName = "Chrome";
|
||||||
|
|||||||
@@ -862,6 +862,18 @@ class MigrationUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by MigrationWizardParent during a migration to indicate that a
|
||||||
|
* manual migration of logins occurred via import from a CSV / TSV file, and
|
||||||
|
* should be counted towards the total number of imported logins.
|
||||||
|
*
|
||||||
|
* @param {number} totalLogins
|
||||||
|
* The number of logins imported manually from a CSV / TSV file.
|
||||||
|
*/
|
||||||
|
notifyLoginsManuallyImported(totalLogins) {
|
||||||
|
this._importQuantities.logins += totalLogins;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates through the favicons, sniffs for a mime type,
|
* Iterates through the favicons, sniffs for a mime type,
|
||||||
* and uses the mime type to properly import the favicon.
|
* and uses the mime type to properly import the favicon.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";
|
import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";
|
||||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||||
|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||||
|
|
||||||
const lazy = {};
|
const lazy = {};
|
||||||
XPCOMUtils.defineLazyPreferenceGetter(
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
@@ -316,18 +317,31 @@ export class MigrationWizardChild extends JSWindowActorChild {
|
|||||||
* message.
|
* message.
|
||||||
*/
|
*/
|
||||||
async beginMigration(migrationDetails, extraArgs) {
|
async beginMigration(migrationDetails, extraArgs) {
|
||||||
|
// We redirect to manual password import for Safari and Chrome on Windows.
|
||||||
if (
|
if (
|
||||||
migrationDetails.key == "safari" &&
|
|
||||||
migrationDetails.resourceTypes.includes(
|
migrationDetails.resourceTypes.includes(
|
||||||
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
||||||
) &&
|
) &&
|
||||||
!migrationDetails.manualPasswordFilePath
|
!migrationDetails.manualPasswordFilePath
|
||||||
) {
|
) {
|
||||||
this.#sendTelemetryEvent("safariPasswordFile");
|
if (migrationDetails.key == "safari") {
|
||||||
this.setComponentState({
|
this.#sendTelemetryEvent("safariPasswordFile");
|
||||||
page: MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION,
|
this.setComponentState({
|
||||||
});
|
page: MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION,
|
||||||
return;
|
});
|
||||||
|
return;
|
||||||
|
} else if (
|
||||||
|
migrationDetails.key == "chrome" &&
|
||||||
|
AppConstants.platform == "win" &&
|
||||||
|
Services.locale.appLocaleAsBCP47.startsWith("en")
|
||||||
|
) {
|
||||||
|
this.#sendTelemetryEvent("chromePasswordFile");
|
||||||
|
this.setComponentState({
|
||||||
|
page: MigrationWizardConstants.PAGES
|
||||||
|
.CHROME_WINDOWS_PASSWORD_PERMISSION,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extraArgs = await this.sendQuery("Migrate", {
|
extraArgs = await this.sendQuery("Migrate", {
|
||||||
|
|||||||
@@ -369,13 +369,10 @@ export class MigrationWizardParent extends JSWindowActorParent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (migrationDetails.manualPasswordFilePath) {
|
||||||
migrationDetails.key == lazy.SafariProfileMigrator?.key &&
|
// The caller supplied a password export file for another browser. We're
|
||||||
migrationDetails.manualPasswordFilePath
|
// going to pretend that there was a PASSWORDS resource to represent the
|
||||||
) {
|
// state of importing from that file.
|
||||||
// The caller supplied a password export file for Safari. We're going to
|
|
||||||
// pretend that there was a PASSWORDS resource for Safari to represent
|
|
||||||
// the state of importing from that file.
|
|
||||||
progress[
|
progress[
|
||||||
lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
||||||
] = {
|
] = {
|
||||||
@@ -394,6 +391,8 @@ export class MigrationWizardParent extends JSWindowActorParent {
|
|||||||
);
|
);
|
||||||
let quantity = summary.filter(entry => entry.result == "added").length;
|
let quantity = summary.filter(entry => entry.result == "added").length;
|
||||||
|
|
||||||
|
MigrationUtils.notifyLoginsManuallyImported(quantity);
|
||||||
|
|
||||||
progress[
|
progress[
|
||||||
lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
lazy.MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS
|
||||||
] = {
|
] = {
|
||||||
@@ -672,17 +671,18 @@ export class MigrationWizardParent extends JSWindowActorParent {
|
|||||||
) {
|
) {
|
||||||
for (let resourceType in MigrationUtils.resourceTypes) {
|
for (let resourceType in MigrationUtils.resourceTypes) {
|
||||||
// Normally, we check each possible resourceType to see if we have one or
|
// Normally, we check each possible resourceType to see if we have one or
|
||||||
// more corresponding resourceTypes in profileMigrationData. The exception
|
// more corresponding resourceTypes in profileMigrationData.
|
||||||
// is for Safari, where the migrator does not expose a PASSWORDS resource
|
//
|
||||||
// type, but we allow the user to express that they'd like to import
|
// The exception is for passwords for Safari, and for Chrome on Windows,
|
||||||
// passwords from it anyways. This is because the Safari migration flow is
|
// where we cannot import passwords automatically, but we allow the user
|
||||||
// special, and allows the user to import passwords from a file exported
|
// to express that they'd like to import passwords from it anyways. We
|
||||||
// from Safari.
|
// use this to determine whether or not to show guidance on how to
|
||||||
|
// manually import a passwords CSV file.
|
||||||
if (
|
if (
|
||||||
profileMigrationData & MigrationUtils.resourceTypes[resourceType] ||
|
profileMigrationData & MigrationUtils.resourceTypes[resourceType] ||
|
||||||
(migrator.constructor.key == lazy.SafariProfileMigrator?.key &&
|
(MigrationUtils.resourceTypes[resourceType] ==
|
||||||
MigrationUtils.resourceTypes[resourceType] ==
|
MigrationUtils.resourceTypes.PASSWORDS &&
|
||||||
MigrationUtils.resourceTypes.PASSWORDS)
|
migrator.showsManualPasswordImport)
|
||||||
) {
|
) {
|
||||||
availableResourceTypes.push(resourceType);
|
availableResourceTypes.push(resourceType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,16 @@ export class MigratorBase {
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses should override this and return true if the source browser
|
||||||
|
* cannot have its passwords imported directly, and if there is a specialized
|
||||||
|
* flow through the wizard to walk the user through importing from a CSV
|
||||||
|
* file manually.
|
||||||
|
*/
|
||||||
|
get showsManualPasswordImport() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns a number that is the bitwise OR of all resource
|
* This method returns a number that is the bitwise OR of all resource
|
||||||
* types that are available in aProfile. See MigrationUtils.resourceTypes
|
* types that are available in aProfile. See MigrationUtils.resourceTypes
|
||||||
|
|||||||
@@ -655,6 +655,18 @@ export class SafariProfileMigrator extends MigratorBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Safari on macOS, we show a specialized flow for importing passwords
|
||||||
|
* from a CSV file.
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get showsManualPasswordImport() {
|
||||||
|
// Since this migrator will only ever be used on macOS, all conditions are
|
||||||
|
// met and we can always return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
get mainPreferencesPropertyList() {
|
get mainPreferencesPropertyList() {
|
||||||
if (this._mainPreferencesPropertyList === undefined) {
|
if (this._mainPreferencesPropertyList === undefined) {
|
||||||
let file = FileUtils.getDir("UsrPrfs", []);
|
let file = FileUtils.getDir("UsrPrfs", []);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const MigrationWizardConstants = Object.freeze({
|
|||||||
FILE_IMPORT_PROGRESS: "file-import-progress",
|
FILE_IMPORT_PROGRESS: "file-import-progress",
|
||||||
SAFARI_PERMISSION: "safari-permission",
|
SAFARI_PERMISSION: "safari-permission",
|
||||||
SAFARI_PASSWORD_PERMISSION: "safari-password-permission",
|
SAFARI_PASSWORD_PERMISSION: "safari-password-permission",
|
||||||
|
CHROME_WINDOWS_PASSWORD_PERMISSION: "chrome-windows-password-permission",
|
||||||
NO_BROWSERS_FOUND: "no-browsers-found",
|
NO_BROWSERS_FOUND: "no-browsers-found",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,24 @@ export class MigrationWizard extends HTMLElement {
|
|||||||
</moz-button-group>
|
</moz-button-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div name="page-chrome-windows-password-permission">
|
||||||
|
<h1 data-l10n-id="migration-chrome-windows-password-import-header" part="header"></h1>
|
||||||
|
<span data-l10n-id="migration-chrome-windows-password-import-steps-header"></span>
|
||||||
|
<ol>
|
||||||
|
<li data-l10n-id="migration-chrome-windows-password-import-step1"><img class="chrome-icon-3dots" data-l10n-name="chrome-icon-3dots"/></li>
|
||||||
|
<li data-l10n-id="migration-chrome-windows-password-import-step2"></li>
|
||||||
|
<li data-l10n-id="migration-chrome-windows-password-import-step3"></li>
|
||||||
|
<li data-l10n-id="migration-chrome-windows-password-import-step4"></li>
|
||||||
|
</ol>
|
||||||
|
<p>
|
||||||
|
<span data-l10n-id="migration-chrome-windows-password-import-step4"></span>
|
||||||
|
</p>
|
||||||
|
<moz-button-group class="buttons" part="buttons">
|
||||||
|
<button class="manual-password-import-skip" data-l10n-id="migration-manual-password-import-skip-button"></button>
|
||||||
|
<button class="manual-password-import-select primary" data-l10n-id="migration-manual-password-import-select-button"></button>
|
||||||
|
</moz-button-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div name="page-safari-permission">
|
<div name="page-safari-permission">
|
||||||
<h1 data-l10n-id="migration-wizard-selection-header" part="header"></h1>
|
<h1 data-l10n-id="migration-wizard-selection-header" part="header"></h1>
|
||||||
<div data-l10n-id="migration-wizard-safari-permissions-sub-header"></div>
|
<div data-l10n-id="migration-wizard-safari-permissions-sub-header"></div>
|
||||||
@@ -298,6 +316,9 @@ export class MigrationWizard extends HTMLElement {
|
|||||||
if (window.MozXULElement) {
|
if (window.MozXULElement) {
|
||||||
window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
|
window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
|
||||||
window.MozXULElement.insertFTLIfNeeded("browser/migrationWizard.ftl");
|
window.MozXULElement.insertFTLIfNeeded("browser/migrationWizard.ftl");
|
||||||
|
window.MozXULElement.insertFTLIfNeeded(
|
||||||
|
"preview/migrationWizardChromeWindows.ftl"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
document.l10n.connectRoot(shadow);
|
document.l10n.connectRoot(shadow);
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,20 @@ browser.migration:
|
|||||||
expires: never
|
expires: never
|
||||||
telemetry_mirror: BrowserMigration_SafariPasswordFile_Wizard
|
telemetry_mirror: BrowserMigration_SafariPasswordFile_Wizard
|
||||||
|
|
||||||
|
chrome_password_file_wizard:
|
||||||
|
type: event
|
||||||
|
description: >
|
||||||
|
Recorded if the user is importing from Chrome, and was presented with
|
||||||
|
the page of the wizard requesting to import passwords from a file. This
|
||||||
|
currently only gets shown on Windows.
|
||||||
|
bugs:
|
||||||
|
- https://bugzil.la/1960560
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzil.la/1960560
|
||||||
|
notification_emails:
|
||||||
|
- mconley@mozilla.com
|
||||||
|
expires: never
|
||||||
|
|
||||||
migration_started_wizard:
|
migration_started_wizard:
|
||||||
type: event
|
type: event
|
||||||
description: >
|
description: >
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ tags = "os_integration"
|
|||||||
|
|
||||||
["browser_aboutwelcome_behavior.js"]
|
["browser_aboutwelcome_behavior.js"]
|
||||||
|
|
||||||
|
["browser_chrome_windows_passwords.js"]
|
||||||
|
run-if = ["os == 'win'"]
|
||||||
|
|
||||||
["browser_dialog_cancel_close.js"]
|
["browser_dialog_cancel_close.js"]
|
||||||
|
|
||||||
["browser_dialog_open.js"]
|
["browser_dialog_open.js"]
|
||||||
|
|||||||
@@ -0,0 +1,413 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { ChromeProfileMigrator } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/ChromeProfileMigrator.sys.mjs"
|
||||||
|
);
|
||||||
|
const { LoginCSVImport } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/LoginCSVImport.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
const TEST_FILE_PATH = getTestFilePath("dummy_file.csv");
|
||||||
|
|
||||||
|
// We use MockFilePicker to simulate a native file picker, and prepare it
|
||||||
|
// to return a dummy file pointed at TEST_FILE_PATH. The file at
|
||||||
|
// TEST_FILE_PATH is not required (nor expected) to exist.
|
||||||
|
const { MockFilePicker } = SpecialPowers;
|
||||||
|
|
||||||
|
add_setup(async function () {
|
||||||
|
MockFilePicker.init(window.browsingContext);
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
MockFilePicker.cleanup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function that does most of the heavy lifting for the tests in
|
||||||
|
* this file. Specfically, it takes care of:
|
||||||
|
*
|
||||||
|
* 1. Stubbing out the various hunks of the ChromeProfileMigrator in order
|
||||||
|
* to simulate a migration without actually performing one, since the
|
||||||
|
* migrator itself isn't being tested here.
|
||||||
|
* 2. Stubbing out parts of MigrationUtils and LoginCSVImport to have a
|
||||||
|
* consistent reporting on how many things are imported.
|
||||||
|
* 3. Setting up the MockFilePicker if expectsFilePicker is true to return
|
||||||
|
* the TEST_FILE_PATH.
|
||||||
|
* 4. Opens up the migration wizard, and chooses to import both BOOKMARKS
|
||||||
|
* and PASSWORDS, and then clicks "Import".
|
||||||
|
* 5. Waits for the migration wizard to show the Chrome password import
|
||||||
|
* instructions.
|
||||||
|
* 6. Runs taskFn
|
||||||
|
* 7. Closes the migration dialog.
|
||||||
|
*
|
||||||
|
* @param {boolean} expectsFilePicker
|
||||||
|
* True if the MockFilePicker should be set up to return TEST_FILE_PATH.
|
||||||
|
* @param {boolean} migrateBookmarks
|
||||||
|
* True if bookmarks should be migrated alongside passwords. If not, only
|
||||||
|
* passwords will be migrated.
|
||||||
|
* @param {boolean} shouldPasswordImportFail
|
||||||
|
* True if importing from the CSV file should fail.
|
||||||
|
* @param {Function} taskFn
|
||||||
|
* An asynchronous function that takes the following parameters in this
|
||||||
|
* order:
|
||||||
|
*
|
||||||
|
* {Element} wizard
|
||||||
|
* The opened migration wizard
|
||||||
|
* {Promise} filePickerShownPromise
|
||||||
|
* A Promise that resolves once the MockFilePicker has closed. This is
|
||||||
|
* undefined if expectsFilePicker was false.
|
||||||
|
* {object} importFromCSVStub
|
||||||
|
* The Sinon stub object for LoginCSVImport.importFromCSV. This can be
|
||||||
|
* used to check to see whether it was called.
|
||||||
|
* {Promise} didMigration
|
||||||
|
* A Promise that resolves to true once the migration completes.
|
||||||
|
* {Promise} wizardDone
|
||||||
|
* A Promise that resolves once the migration wizard reports that a
|
||||||
|
* migration has completed.
|
||||||
|
* @returns {Promise<undefined>}
|
||||||
|
*/
|
||||||
|
async function testChromePasswordHelper(
|
||||||
|
expectsFilePicker,
|
||||||
|
migrateBookmarks,
|
||||||
|
shouldPasswordImportFail,
|
||||||
|
taskFn
|
||||||
|
) {
|
||||||
|
let sandbox = sinon.createSandbox();
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.stub(MigrationUtils, "availableMigratorKeys").get(() => {
|
||||||
|
return ["chrome"];
|
||||||
|
});
|
||||||
|
|
||||||
|
let migrator = new ChromeProfileMigrator();
|
||||||
|
sandbox.stub(MigrationUtils, "getMigrator").resolves(migrator);
|
||||||
|
|
||||||
|
sandbox
|
||||||
|
.stub(ChromeProfileMigrator.prototype, "getSourceProfiles")
|
||||||
|
.resolves([{ id: "chrome-test-1", name: "Chrome test profile 1" }]);
|
||||||
|
|
||||||
|
// Have the migrator claim that only BOOKMARKS are available.
|
||||||
|
sandbox
|
||||||
|
.stub(ChromeProfileMigrator.prototype, "getMigrateData")
|
||||||
|
.resolves(MigrationUtils.resourceTypes.BOOKMARKS);
|
||||||
|
|
||||||
|
let migrateStub;
|
||||||
|
let didMigration = new Promise(resolve => {
|
||||||
|
migrateStub = sandbox
|
||||||
|
.stub(ChromeProfileMigrator.prototype, "migrate")
|
||||||
|
.callsFake((aResourceTypes, aStartup, aProfile, aProgressCallback) => {
|
||||||
|
if (!migrateBookmarks) {
|
||||||
|
Assert.ok(
|
||||||
|
false,
|
||||||
|
"Should not have called migrate when only migrating Chrome passwords."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.ok(
|
||||||
|
!aStartup,
|
||||||
|
"Migrator should not have been called as a startup migration."
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
aResourceTypes & MigrationUtils.resourceTypes.BOOKMARKS,
|
||||||
|
"Should have requested to migrate the BOOKMARKS resource."
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
!(aResourceTypes & MigrationUtils.resourceTypes.PASSWORDS),
|
||||||
|
"Should not have requested to migrate the PASSWORDS resource."
|
||||||
|
);
|
||||||
|
|
||||||
|
aProgressCallback(MigrationUtils.resourceTypes.BOOKMARKS, true);
|
||||||
|
Services.obs.notifyObservers(null, "Migration:Ended");
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// We'll pretend we added EXPECTED_QUANTITY passwords from the Chrome
|
||||||
|
// password file.
|
||||||
|
let results = [];
|
||||||
|
for (let i = 0; i < EXPECTED_QUANTITY; ++i) {
|
||||||
|
results.push({ result: "added" });
|
||||||
|
}
|
||||||
|
let importFromCSVStub = sandbox.stub(LoginCSVImport, "importFromCSV");
|
||||||
|
|
||||||
|
if (shouldPasswordImportFail) {
|
||||||
|
importFromCSVStub.rejects(new Error("Some error message"));
|
||||||
|
} else {
|
||||||
|
importFromCSVStub.resolves(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox.stub(MigrationUtils, "_importQuantities").value({
|
||||||
|
bookmarks: EXPECTED_QUANTITY,
|
||||||
|
});
|
||||||
|
|
||||||
|
let filePickerShownPromise;
|
||||||
|
|
||||||
|
if (expectsFilePicker) {
|
||||||
|
MockFilePicker.reset();
|
||||||
|
|
||||||
|
let dummyFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||||
|
dummyFile.initWithPath(TEST_FILE_PATH);
|
||||||
|
filePickerShownPromise = new Promise(resolve => {
|
||||||
|
MockFilePicker.showCallback = () => {
|
||||||
|
Assert.ok(true, "Filepicker shown.");
|
||||||
|
MockFilePicker.setFiles([dummyFile]);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
MockFilePicker.returnValue = MockFilePicker.returnOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
await withMigrationWizardDialog(async prefsWin => {
|
||||||
|
let dialogBody = prefsWin.document.body;
|
||||||
|
let wizard = dialogBody.querySelector("migration-wizard");
|
||||||
|
let wizardDone = BrowserTestUtils.waitForEvent(
|
||||||
|
wizard,
|
||||||
|
"MigrationWizard:DoneMigration"
|
||||||
|
);
|
||||||
|
|
||||||
|
let shadow = wizard.openOrClosedShadowRoot;
|
||||||
|
|
||||||
|
info("Choosing Chrome");
|
||||||
|
let panelItem = shadow.querySelector(
|
||||||
|
`panel-item[key="${ChromeProfileMigrator.key}"]`
|
||||||
|
);
|
||||||
|
panelItem.click();
|
||||||
|
|
||||||
|
let resourceTypeList = shadow.querySelector("#resource-type-list");
|
||||||
|
|
||||||
|
// Let's choose whether to import BOOKMARKS first.
|
||||||
|
let bookmarksNode = resourceTypeList.querySelector(
|
||||||
|
`label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS}"]`
|
||||||
|
);
|
||||||
|
bookmarksNode.control.checked = migrateBookmarks;
|
||||||
|
|
||||||
|
// Let's make sure that PASSWORDS is displayed despite the migrator only
|
||||||
|
// (currently) returning BOOKMARKS as an available resource to migrate.
|
||||||
|
let passwordsNode = resourceTypeList.querySelector(
|
||||||
|
`label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"]`
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
!passwordsNode.hidden,
|
||||||
|
"PASSWORDS should be available to import from."
|
||||||
|
);
|
||||||
|
passwordsNode.control.checked = true;
|
||||||
|
|
||||||
|
let deck = shadow.querySelector("#wizard-deck");
|
||||||
|
let switchedToChromePermissionPage =
|
||||||
|
BrowserTestUtils.waitForMutationCondition(
|
||||||
|
deck,
|
||||||
|
{ attributeFilter: ["selected-view"] },
|
||||||
|
() => {
|
||||||
|
return (
|
||||||
|
deck.getAttribute("selected-view") ==
|
||||||
|
"page-" +
|
||||||
|
MigrationWizardConstants.PAGES.CHROME_WINDOWS_PASSWORD_PERMISSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let importButton = shadow.querySelector("#import");
|
||||||
|
importButton.click();
|
||||||
|
await switchedToChromePermissionPage;
|
||||||
|
Assert.ok(true, "Went to Chrome permission page after attempting import.");
|
||||||
|
|
||||||
|
await taskFn(
|
||||||
|
wizard,
|
||||||
|
filePickerShownPromise,
|
||||||
|
importFromCSVStub,
|
||||||
|
didMigration,
|
||||||
|
migrateStub,
|
||||||
|
wizardDone
|
||||||
|
);
|
||||||
|
|
||||||
|
let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
|
||||||
|
let doneButton = shadow.querySelector(
|
||||||
|
"div[name='page-progress'] .done-button"
|
||||||
|
);
|
||||||
|
let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
|
||||||
|
|
||||||
|
doneButton.click();
|
||||||
|
await dialogClosed;
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.restore();
|
||||||
|
MockFilePicker.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the flow of importing passwords from Chrome via an
|
||||||
|
* exported CSV file.
|
||||||
|
*/
|
||||||
|
add_task(async function test_chrome_password_do_import() {
|
||||||
|
await testChromePasswordHelper(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
async (
|
||||||
|
wizard,
|
||||||
|
filePickerShownPromise,
|
||||||
|
importFromCSVStub,
|
||||||
|
didMigration,
|
||||||
|
migrateStub,
|
||||||
|
wizardDone
|
||||||
|
) => {
|
||||||
|
let shadow = wizard.openOrClosedShadowRoot;
|
||||||
|
let manualPasswordImportSelect = shadow.querySelector(
|
||||||
|
"div[name='page-chrome-windows-password-permission'] .manual-password-import-select"
|
||||||
|
);
|
||||||
|
|
||||||
|
manualPasswordImportSelect.click();
|
||||||
|
await filePickerShownPromise;
|
||||||
|
Assert.ok(true, "File picker was shown.");
|
||||||
|
|
||||||
|
await didMigration;
|
||||||
|
Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
|
||||||
|
|
||||||
|
await wizardDone;
|
||||||
|
|
||||||
|
assertQuantitiesShown(wizard, [
|
||||||
|
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
|
||||||
|
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that only passwords get imported if the user only opts
|
||||||
|
* to import passwords, and that nothing else gets imported.
|
||||||
|
*/
|
||||||
|
add_task(async function test_chrome_password_only_do_import() {
|
||||||
|
await testChromePasswordHelper(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
async (
|
||||||
|
wizard,
|
||||||
|
filePickerShownPromise,
|
||||||
|
importFromCSVStub,
|
||||||
|
didMigration,
|
||||||
|
migrateStub,
|
||||||
|
wizardDone
|
||||||
|
) => {
|
||||||
|
let shadow = wizard.openOrClosedShadowRoot;
|
||||||
|
let manualPasswordImportSelect = shadow.querySelector(
|
||||||
|
"div[name='page-chrome-windows-password-permission'] .manual-password-import-select"
|
||||||
|
);
|
||||||
|
manualPasswordImportSelect.click();
|
||||||
|
await filePickerShownPromise;
|
||||||
|
Assert.ok(true, "File picker was shown.");
|
||||||
|
|
||||||
|
await wizardDone;
|
||||||
|
|
||||||
|
assertQuantitiesShown(wizard, [
|
||||||
|
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
|
||||||
|
Assert.ok(
|
||||||
|
!migrateStub.called,
|
||||||
|
"ChromeProfileMigrator.migrate was not called."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the flow of importing passwords from Chrome when the file
|
||||||
|
* import fails.
|
||||||
|
*/
|
||||||
|
add_task(async function test_chrome_password_empty_csv_file() {
|
||||||
|
await testChromePasswordHelper(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
async (
|
||||||
|
wizard,
|
||||||
|
filePickerShownPromise,
|
||||||
|
importFromCSVStub,
|
||||||
|
didMigration,
|
||||||
|
migrateStub,
|
||||||
|
wizardDone
|
||||||
|
) => {
|
||||||
|
let shadow = wizard.openOrClosedShadowRoot;
|
||||||
|
let manualPasswordImportSelect = shadow.querySelector(
|
||||||
|
"div[name='page-chrome-windows-password-permission'] .manual-password-import-select"
|
||||||
|
);
|
||||||
|
manualPasswordImportSelect.click();
|
||||||
|
await filePickerShownPromise;
|
||||||
|
Assert.ok(true, "File picker was shown.");
|
||||||
|
|
||||||
|
await didMigration;
|
||||||
|
Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
|
||||||
|
|
||||||
|
await wizardDone;
|
||||||
|
|
||||||
|
let headerL10nID =
|
||||||
|
shadow.querySelector("#progress-header").dataset.l10nId;
|
||||||
|
Assert.equal(
|
||||||
|
headerL10nID,
|
||||||
|
"migration-wizard-progress-done-with-warnings-header"
|
||||||
|
);
|
||||||
|
|
||||||
|
let progressGroup = shadow.querySelector(
|
||||||
|
`.resource-progress-group[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"`
|
||||||
|
);
|
||||||
|
let progressIcon = progressGroup.querySelector(".progress-icon");
|
||||||
|
let messageText =
|
||||||
|
progressGroup.querySelector(".message-text").textContent;
|
||||||
|
|
||||||
|
Assert.equal(
|
||||||
|
progressIcon.getAttribute("state"),
|
||||||
|
"warning",
|
||||||
|
"Icon should be in the warning state."
|
||||||
|
);
|
||||||
|
Assert.stringMatches(
|
||||||
|
messageText,
|
||||||
|
/file doesn’t include any valid password data/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the user can skip importing passwords from Chrome.
|
||||||
|
*/
|
||||||
|
add_task(async function test_chrome_password_skip() {
|
||||||
|
await testChromePasswordHelper(
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
async (
|
||||||
|
wizard,
|
||||||
|
filePickerShownPromise,
|
||||||
|
importFromCSVStub,
|
||||||
|
didMigration,
|
||||||
|
migrateStub,
|
||||||
|
wizardDone
|
||||||
|
) => {
|
||||||
|
let shadow = wizard.openOrClosedShadowRoot;
|
||||||
|
let manualPasswordImportSkip = shadow.querySelector(
|
||||||
|
"div[name='page-chrome-windows-password-permission'] .manual-password-import-skip"
|
||||||
|
);
|
||||||
|
manualPasswordImportSkip.click();
|
||||||
|
|
||||||
|
await didMigration;
|
||||||
|
Assert.ok(!MockFilePicker.shown, "Never showed the file picker.");
|
||||||
|
Assert.ok(
|
||||||
|
!importFromCSVStub.called,
|
||||||
|
"Importing from CSV was never called."
|
||||||
|
);
|
||||||
|
|
||||||
|
await wizardDone;
|
||||||
|
|
||||||
|
assertQuantitiesShown(wizard, [
|
||||||
|
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -11,6 +11,10 @@ import { MigrationWizardConstants } from "chrome://browser/content/migration/mig
|
|||||||
// Imported for side-effects.
|
// Imported for side-effects.
|
||||||
import "toolkit-widgets/named-deck.js";
|
import "toolkit-widgets/named-deck.js";
|
||||||
|
|
||||||
|
window.MozXULElement.insertFTLIfNeeded(
|
||||||
|
"locales-preview/migrationWizardChromeWindows.ftl"
|
||||||
|
);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Domain-specific UI Widgets/Migration Wizard",
|
title: "Domain-specific UI Widgets/Migration Wizard",
|
||||||
component: "migration-wizard",
|
component: "migration-wizard",
|
||||||
@@ -592,6 +596,14 @@ SafariPasswordPermissions.args = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ChromeWindowsPasswordPermissions = Template.bind({});
|
||||||
|
ChromeWindowsPasswordPermissions.args = {
|
||||||
|
dialogMode: true,
|
||||||
|
state: {
|
||||||
|
page: MigrationWizardConstants.PAGES.CHROME_WINDOWS_PASSWORD_PERMISSION,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const NoBrowsersFound = Template.bind({});
|
export const NoBrowsersFound = Template.bind({});
|
||||||
NoBrowsersFound.args = {
|
NoBrowsersFound.args = {
|
||||||
dialogMode: true,
|
dialogMode: true,
|
||||||
|
|||||||
13
browser/locales-preview/migrationWizardChromeWindows.ftl
Normal file
13
browser/locales-preview/migrationWizardChromeWindows.ftl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
migration-chrome-windows-password-import-header = How to import passwords from Chrome
|
||||||
|
migration-chrome-windows-password-import-steps-header = In Chrome:
|
||||||
|
migration-chrome-windows-password-import-step1 = Open the main menu <img data-l10n-name="chrome-icon-3dots"/> and go to Passwords and Autofill > Google Password Manager.
|
||||||
|
migration-chrome-windows-password-import-step2 = Select “Settings” from the menu.
|
||||||
|
migration-chrome-windows-password-import-step3 = Choose “Download file” and save it to your device.
|
||||||
|
migration-chrome-windows-password-import-step4 = Return here and “Select File” to finish import.
|
||||||
|
|
||||||
|
migration-manual-password-import-skip-button = Skip
|
||||||
|
migration-manual-password-import-select-button = Select File
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
preview/linkPreview.ftl (../locales-preview/linkPreview.ftl)
|
preview/linkPreview.ftl (../locales-preview/linkPreview.ftl)
|
||||||
preview/smartTabGroups.ftl (../locales-preview/smartTabGroups.ftl)
|
preview/smartTabGroups.ftl (../locales-preview/smartTabGroups.ftl)
|
||||||
preview/taskbartabs.ftl (../locales-preview/taskbartabs.ftl)
|
preview/taskbartabs.ftl (../locales-preview/taskbartabs.ftl)
|
||||||
|
preview/migrationWizardChromeWindows.ftl (../locales-preview/migrationWizardChromeWindows.ftl)
|
||||||
|
|
||||||
@AB_CD@.jar:
|
@AB_CD@.jar:
|
||||||
% locale browser @AB_CD@ %locale/browser/
|
% locale browser @AB_CD@ %locale/browser/
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
skin/classic/browser/migration/migration-wizard.css (../shared/migration/migration-wizard.css)
|
skin/classic/browser/migration/migration-wizard.css (../shared/migration/migration-wizard.css)
|
||||||
skin/classic/browser/migration/success.svg (../shared/migration/success.svg)
|
skin/classic/browser/migration/success.svg (../shared/migration/success.svg)
|
||||||
skin/classic/browser/migration/progress-mask.svg (../shared/migration/progress-mask.svg)
|
skin/classic/browser/migration/progress-mask.svg (../shared/migration/progress-mask.svg)
|
||||||
|
skin/classic/browser/migration/chrome-icon-3dots.svg (../shared/migration/chrome-icon-3dots.svg)
|
||||||
skin/classic/browser/migration/safari-icon-3dots.svg (../shared/migration/safari-icon-3dots.svg)
|
skin/classic/browser/migration/safari-icon-3dots.svg (../shared/migration/safari-icon-3dots.svg)
|
||||||
skin/classic/browser/notification-icons/autoplay-media.svg (../shared/notification-icons/autoplay-media.svg)
|
skin/classic/browser/notification-icons/autoplay-media.svg (../shared/notification-icons/autoplay-media.svg)
|
||||||
skin/classic/browser/notification-icons/autoplay-media-blocked.svg (../shared/notification-icons/autoplay-media-blocked.svg)
|
skin/classic/browser/notification-icons/autoplay-media-blocked.svg (../shared/notification-icons/autoplay-media-blocked.svg)
|
||||||
|
|||||||
4
browser/themes/shared/migration/chrome-icon-3dots.svg
Normal file
4
browser/themes/shared/migration/chrome-icon-3dots.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- 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/. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g clip-path="url(#a)"><path fill="context-stroke" d="M8.354 5.886h-.705l-.235-.235v-.705l.235-.235h.705l.235.235v.705l-.235.235ZM8.354 8.587h-.705l-.235-.235v-.704l.235-.235h.705l.235.235v.704l-.235.235ZM8.589 11.054l-.235.235h-.705l-.235-.235v-.705l.235-.235h.705l.235.235v.705Z"/><path fill="context-fill" d="M15 8a7 7 0 1 0-7 7v1A8 8 0 1 1 8 0a8 8 0 0 1 0 16v-1a7 7 0 0 0 7-7Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 759 B |
@@ -340,16 +340,30 @@ summary {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.safari-icon-3dots {
|
.safari-icon-3dots,
|
||||||
|
.chrome-icon-3dots {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
vertical-align: middle;
|
|
||||||
content: url("chrome://browser/skin/migration/safari-icon-3dots.svg");
|
|
||||||
-moz-context-properties: fill, stroke;
|
-moz-context-properties: fill, stroke;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safari-icon-3dots {
|
||||||
|
content: url("chrome://browser/skin/migration/safari-icon-3dots.svg");
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
stroke: color-mix(in srgb, currentColor 10%, transparent 90%);
|
stroke: color-mix(in srgb, currentColor 10%, transparent 90%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chrome-icon-3dots {
|
||||||
|
content: url("chrome://browser/skin/migration/chrome-icon-3dots.svg");
|
||||||
|
/**
|
||||||
|
* Stroke and fill colours were sampled from built-in dark and light theme
|
||||||
|
* from Chrome on Windows
|
||||||
|
*/
|
||||||
|
fill: light-dark(#474747, #C7C7C7);
|
||||||
|
stroke: light-dark(#474747, #C7C7C7);
|
||||||
|
}
|
||||||
|
|
||||||
.no-browsers-found-message {
|
.no-browsers-found-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user