Bug 1665802 - Directly migrate importable passwords without wizard for single profile r=sfoster

Share importable handling from LoginManagerParent and support directly migrating.

Differential Revision: https://phabricator.services.mozilla.com/D90635
This commit is contained in:
Ed Lee 2020-09-24 20:00:08 +00:00
Родитель 8de1e9f262
Коммит ee5d495548
7 изменённых файлов: 146 добавлений и 44 удалений

Просмотреть файл

@ -527,7 +527,7 @@ var MigratorPrototype = {
},
};
var MigrationUtils = Object.freeze({
var MigrationUtils = Object.seal({
resourceTypes: {
COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,

Просмотреть файл

@ -796,16 +796,10 @@ let gAutoCompleteListener = {
);
break;
case "importableLogins":
loginManager.sendAsyncMessage(
"PasswordManager:OpenMigrationWizard",
selectedRowComment
);
Services.telemetry.recordEvent(
"exp_import",
"event",
"enter",
selectedRowComment
);
loginManager.sendAsyncMessage("PasswordManager:HandleImportable", {
browserId: selectedRowComment,
type: "enter",
});
break;
case "loginsFooter":
loginManager.sendAsyncMessage("PasswordManager:OpenPreferences", {

Просмотреть файл

@ -599,11 +599,12 @@ this.LoginManagerChild = class LoginManagerChild extends JSWindowActorChild {
}
this._fieldsWithPasswordGenerationForcedOn.add(inputElement);
// Clear the cache of previous autocomplete results so that the
// generation option appears.
gFormFillService.QueryInterface(Ci.nsIAutoCompleteInput);
gFormFillService.controller.resetInternalState();
gFormFillService.showPopup();
this.repopulateAutocompletePopup();
break;
}
case "PasswordManager:repopulateAutocompletePopup": {
this.repopulateAutocompletePopup();
break;
}
@ -620,6 +621,13 @@ this.LoginManagerChild = class LoginManagerChild extends JSWindowActorChild {
return undefined;
}
repopulateAutocompletePopup() {
// Clear the cache of previous autocomplete results to show new options.
gFormFillService.QueryInterface(Ci.nsIAutoCompleteInput);
gFormFillService.controller.resetInternalState();
gFormFillService.showPopup();
}
shouldIgnoreLoginManagerEvent(event) {
let nodePrincipal = event.target.nodePrincipal;
// If we have a system or null principal then prevent any more password manager code from running and

Просмотреть файл

@ -232,7 +232,7 @@ class LoginManagerParent extends JSWindowActorParent {
);
}
receiveMessage(msg) {
async receiveMessage(msg) {
let data = msg.data;
if (data.origin || data.formOrigin) {
throw new Error(
@ -313,13 +313,44 @@ class LoginManagerParent extends JSWindowActorParent {
break;
}
case "PasswordManager:OpenMigrationWizard": {
// Open the migration wizard pre-selecting the appropriate browser.
let window = this.getRootBrowser().ownerGlobal;
MigrationUtils.showMigrationWizard(window, [
MigrationUtils.MIGRATION_ENTRYPOINT_PASSWORDS,
data,
]);
case "PasswordManager:HandleImportable": {
const { browserId, type } = data;
// Directly migrate passwords for a single profile.
const migrator = await MigrationUtils.getMigrator(browserId);
const profiles = await migrator.getSourceProfiles();
// TODO: Bug 1666373 Use ExperimentAPI to enable only for treatment.
if (profiles.length == 1) {
const loginAdded = new Promise(resolve => {
const obs = (subject, topic, data) => {
if (data == "addLogin") {
Services.obs.removeObserver(obs, "passwordmgr-storage-changed");
resolve();
}
};
Services.obs.addObserver(obs, "passwordmgr-storage-changed");
});
await migrator.migrate(
MigrationUtils.resourceTypes.PASSWORDS,
null,
profiles[0]
);
await loginAdded;
// Reshow the popup with the imported password.
this.sendAsyncMessage("PasswordManager:repopulateAutocompletePopup");
} else {
// Open the migration wizard pre-selecting the appropriate browser.
MigrationUtils.showMigrationWizard(
this.getRootBrowser().ownerGlobal,
[MigrationUtils.MIGRATION_ENTRYPOINT_PASSWORDS, browserId]
);
}
Services.telemetry.recordEvent("exp_import", "event", type, browserId, {
profilesCount: profiles.length + "",
});
break;
}

Просмотреть файл

@ -6,6 +6,21 @@ const { MigrationUtils } = ChromeUtils.import(
);
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
// Dummy migrator to change and detect importable behavior.
const gTestMigrator = {
profiles: [],
getSourceProfiles() {
return this.profiles;
},
migrate: sinon
.stub()
.callsFake(() =>
LoginTestUtils.addLogin({ username: "import", password: "pass" })
),
};
add_task(async function check_fluent_ids() {
await document.l10n.ready;
MozXULElement.insertFTLIfNeeded("toolkit/main-window/autocomplete.ftl");
@ -28,13 +43,17 @@ add_task(async function test_initialize() {
const importable = sinon
.stub(ChromeMigrationUtils, "getImportableLogins")
.resolves(["chrome"]);
const migrator = sinon
.stub(MigrationUtils, "getMigrator")
.resolves(gTestMigrator);
// This makes the third autocomplete test *not* show import suggestions.
Services.prefs.setIntPref("signon.suggestImportCount", 2);
// This makes the last autocomplete test *not* show import suggestions.
Services.prefs.setIntPref("signon.suggestImportCount", 3);
registerCleanupFunction(() => {
debounce.restore();
importable.restore();
migrator.restore();
Services.prefs.clearUserPref("signon.suggestImportCount");
});
});
@ -60,6 +79,9 @@ add_task(async function import_suggestion_wizard() {
"Wait for importable suggestion to show"
);
// Pretend there's 2+ profiles to trigger the wizard.
gTestMigrator.profiles.length = 2;
info("Clicking on importable suggestion");
const wizardPromise = BrowserTestUtils.waitForCondition(
() => Services.wm.getMostRecentWindow("Browser:MigrationWizard"),
@ -71,6 +93,7 @@ add_task(async function import_suggestion_wizard() {
const wizard = await wizardPromise;
ok(wizard, "Wizard opened");
is(gTestMigrator.migrate.callCount, 0, "Direct migrate not used");
await closePopup(popup);
await BrowserTestUtils.closeWindow(wizard);
@ -113,6 +136,58 @@ add_task(async function import_suggestion_learn_more() {
);
});
add_task(async function import_suggestion_migrate() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "https://example.com" + DIRECTORY_PATH + "form_basic.html",
},
async function(browser) {
const popup = document.getElementById("PopupAutoComplete");
ok(popup, "Got popup");
await openACPopup(popup, browser, "#form-basic-username");
const importableItem = popup.querySelector(
`[originaltype="importableLogins"]`
);
ok(importableItem, "Got importable suggestion richlistitem");
await BrowserTestUtils.waitForCondition(
() => !importableItem.collapsed,
"Wait for importable suggestion to show"
);
// Pretend there's 1 profile to trigger migrate.
gTestMigrator.profiles.length = 1;
info("Clicking on importable suggestion");
const migratePromise = BrowserTestUtils.waitForCondition(
() => gTestMigrator.migrate.callCount,
"Wait for direct migration attempt"
);
EventUtils.synthesizeMouseAtCenter(importableItem, {});
const callCount = await migratePromise;
is(callCount, 1, "Direct migrate used once");
const importedItem = await BrowserTestUtils.waitForCondition(
() => popup.querySelector(`[originaltype="loginWithOrigin"]`),
"Wait for imported login to show"
);
EventUtils.synthesizeMouseAtCenter(importedItem, {});
const username = await SpecialPowers.spawn(
browser,
[],
() => content.document.getElementById("form-basic-username").value
);
is(username, "import", "username from import filled in");
LoginTestUtils.clearData();
}
);
});
add_task(async function import_suggestion_not_shown() {
await BrowserTestUtils.withNewTab(
{

Просмотреть файл

@ -298,6 +298,8 @@ exp_import:
description: >
Experimental import logins interface user interactions. Value is the browser suggested.
expiry_version: "87"
extra_keys:
profilesCount: Count of profiles found for the selected browser
notification_emails: ["edilee@mozilla.com"]
products: ["firefox"]
record_in_processes: ["main", "content"]

Просмотреть файл

@ -781,29 +781,21 @@
super();
MozXULElement.insertFTLIfNeeded("toolkit/main-window/autocomplete.ftl");
ChromeUtils.defineModuleGetter(
this,
"MigrationUtils",
"resource:///modules/MigrationUtils.jsm"
);
this.addEventListener("click", event => {
const browserId = this.getAttribute("ac-value");
if (event.button != 0) {
return;
}
// Open the migration wizard pre-selecting the appropriate browser.
this.MigrationUtils.showMigrationWizard(window, [
this.MigrationUtils.MIGRATION_ENTRYPOINT_PASSWORDS,
browserId,
]);
Services.telemetry.recordEvent(
"exp_import",
"event",
"click",
browserId
);
// Let the login manager parent handle this importable browser click.
gBrowser.selectedBrowser.browsingContext.currentWindowGlobal
.getActor("LoginManager")
.receiveMessage({
name: "PasswordManager:HandleImportable",
data: {
browserId: this.getAttribute("ac-value"),
type: "click",
},
});
});
}