summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js')
-rw-r--r--toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js1126
1 files changed, 1126 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js b/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js
new file mode 100644
index 0000000000..bf665faaed
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js
@@ -0,0 +1,1126 @@
+/**
+ * Test LoginManagerParent._onPasswordEditedOrGenerated()
+ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+const { LoginManagerParent } = ChromeUtils.importESModule(
+ "resource://gre/modules/LoginManagerParent.sys.mjs"
+);
+const { LoginManagerPrompter } = ChromeUtils.importESModule(
+ "resource://gre/modules/LoginManagerPrompter.sys.mjs"
+);
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+const loginTemplate = Object.freeze({
+ origin: "https://www.example.com",
+ formActionOrigin: "https://www.mozilla.org",
+});
+
+let LMP = new LoginManagerParent();
+
+function stubPrompter() {
+ let fakePromptToSavePassword = sinon.stub();
+ let fakePromptToChangePassword = sinon.stub();
+ sinon.stub(LMP, "_getPrompter").callsFake(() => {
+ return {
+ promptToSavePassword: fakePromptToSavePassword,
+ promptToChangePassword: fakePromptToChangePassword,
+ };
+ });
+ LMP._getPrompter().promptToSavePassword();
+ LMP._getPrompter().promptToChangePassword();
+ Assert.ok(LMP._getPrompter.calledTwice, "Checking _getPrompter stub");
+ Assert.ok(
+ fakePromptToSavePassword.calledOnce,
+ "Checking fakePromptToSavePassword stub"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking fakePromptToChangePassword stub"
+ );
+ function resetPrompterHistory() {
+ LMP._getPrompter.resetHistory();
+ fakePromptToSavePassword.resetHistory();
+ fakePromptToChangePassword.resetHistory();
+ }
+ function restorePrompter() {
+ LMP._getPrompter.restore();
+ }
+ resetPrompterHistory();
+ return {
+ fakePromptToSavePassword,
+ fakePromptToChangePassword,
+ resetPrompterHistory,
+ restorePrompter,
+ };
+}
+
+async function stubGeneratedPasswordForBrowsingContextId(id) {
+ Assert.ok(
+ LoginManagerParent._browsingContextGlobal,
+ "Check _browsingContextGlobal exists"
+ );
+ Assert.ok(
+ !LoginManagerParent._browsingContextGlobal.get(id),
+ `BrowsingContext ${id} shouldn't exist yet`
+ );
+ info(`Stubbing BrowsingContext.get(${id})`);
+ let stub = sinon
+ .stub(LoginManagerParent._browsingContextGlobal, "get")
+ .withArgs(id)
+ .callsFake(() => {
+ return {
+ currentWindowGlobal: {
+ documentPrincipal:
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://www.example.com^userContextId=6"
+ ),
+ documentURI: Services.io.newURI("https://www.example.com"),
+ },
+ get embedderElement() {
+ info("returning embedderElement");
+ let browser = MockDocument.createTestDocument(
+ "chrome://browser/content/browser.xhtml",
+ `<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <browser></browser>
+ </box>`,
+ "application/xml",
+ true
+ ).querySelector("browser");
+ MockDocument.mockBrowsingContextProperty(browser, this);
+ return browser;
+ },
+ get top() {
+ return this;
+ },
+ };
+ });
+ Assert.ok(
+ LoginManagerParent._browsingContextGlobal.get(id),
+ `Checking BrowsingContext.get(${id}) stub`
+ );
+
+ const generatedPassword = await LMP.getGeneratedPassword();
+ notEqual(generatedPassword, null, "Check password was returned");
+ equal(
+ generatedPassword.length,
+ LoginTestUtils.generation.LENGTH,
+ "Check password length"
+ );
+ equal(
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().size,
+ 1,
+ "1 added to cache"
+ );
+ equal(
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ ).value,
+ generatedPassword,
+ "Cache key and value"
+ );
+ LoginManagerParent._browsingContextGlobal.get.resetHistory();
+
+ return {
+ stub,
+ generatedPassword,
+ };
+}
+
+function checkEditTelemetryRecorded(expectedCount, msg) {
+ info("Check that expected telemetry event was recorded");
+ const snapshot = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ );
+ let resultsCount = 0;
+ if ("parent" in snapshot) {
+ const telemetryProps = Object.freeze({
+ category: "pwmgr",
+ method: "filled_field_edited",
+ object: "generatedpassword",
+ });
+ const results = snapshot.parent.filter(
+ ([time, category, method, object]) => {
+ return (
+ category === telemetryProps.category &&
+ method === telemetryProps.method &&
+ object === telemetryProps.object
+ );
+ }
+ );
+ resultsCount = results.length;
+ }
+ equal(
+ resultsCount,
+ expectedCount,
+ "Check count of pwmgr.filled_field_edited for generatedpassword: " + msg
+ );
+}
+
+async function startTestConditions(contextId) {
+ LMP.useBrowsingContext(contextId);
+
+ Assert.ok(
+ LMP._onPasswordEditedOrGenerated,
+ "LMP._onPasswordEditedOrGenerated exists"
+ );
+ equal(await LMP.getGeneratedPassword(), null, "Null with no BrowsingContext");
+ equal(
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().size,
+ 0,
+ "Empty cache to start"
+ );
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 0,
+ "Should have no saved logins at the start of the test"
+ );
+}
+
+/*
+ * Compare login details excluding usernameField and passwordField
+ */
+function assertLoginProperties(actualLogin, expected) {
+ equal(actualLogin.origin, expected.origin, "Compare origin");
+ equal(
+ actualLogin.formActionOrigin,
+ expected.formActionOrigin,
+ "Compare formActionOrigin"
+ );
+ equal(actualLogin.httpRealm, expected.httpRealm, "Compare httpRealm");
+ equal(actualLogin.username, expected.username, "Compare username");
+ equal(actualLogin.password, expected.password, "Compare password");
+}
+
+add_setup(async () => {
+ // Get a profile for storage.
+ do_get_profile();
+
+ // Force the feature to be enabled.
+ Services.prefs.setBoolPref("signon.generation.available", true);
+ Services.prefs.setBoolPref("signon.generation.enabled", true);
+ await LoginTestUtils.remoteSettings.setupImprovedPasswordRules();
+});
+
+add_task(async function test_onPasswordEditedOrGenerated_generatedPassword() {
+ await startTestConditions(99);
+ let { generatedPassword } = await stubGeneratedPasswordForBrowsingContextId(
+ 99
+ );
+ let { fakePromptToChangePassword, restorePrompter } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ let storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 0,
+ "Should have no saved logins at the start of the test"
+ );
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: generatedPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+
+ let [login] = await storageChangedPromised;
+ let expected = new LoginInfo(
+ "https://www.example.com",
+ "https://www.mozilla.org",
+ null,
+ "", // verify we don't include the username when auto-saving a login
+ generatedPassword
+ );
+
+ Assert.ok(login.equals(expected), "Check added login");
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[4],
+ "promptToChangePassword had a truthy 'notifySaved' argument"
+ );
+
+ info("Edit the password");
+ const newPassword = generatedPassword + "🔥";
+ storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ let generatedPW =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(generatedPW.edited, "Cached edited boolean should be true");
+ equal(generatedPW.value, newPassword, "Cached password should be updated");
+ // login metadata should be updated
+ let [dataArray] = await storageChangedPromised;
+ login = dataArray.queryElementAt(1, Ci.nsILoginInfo);
+ expected.password = newPassword;
+ Assert.ok(login.equals(expected), "Check updated login");
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 1,
+ "Should have 1 saved login still"
+ );
+
+ info(
+ "Simulate a second edit to check that the telemetry event for the first edit is not recorded twice"
+ );
+ const newerPassword = newPassword + "🦊";
+ storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newerPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ generatedPW = LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(generatedPW.edited, "Cached edited state should remain true");
+ equal(generatedPW.value, newerPassword, "Cached password should be updated");
+ [dataArray] = await storageChangedPromised;
+ login = dataArray.queryElementAt(1, Ci.nsILoginInfo);
+ expected.password = newerPassword;
+ Assert.ok(login.equals(expected), "Check updated login");
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 1,
+ "Should have 1 saved login still"
+ );
+
+ checkEditTelemetryRecorded(1, "with auto-save");
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ Services.telemetry.clearEvents();
+});
+
+add_task(
+ async function test_onPasswordEditedOrGenerated_editToEmpty_generatedPassword() {
+ await startTestConditions(99);
+ let { generatedPassword } = await stubGeneratedPasswordForBrowsingContextId(
+ 99
+ );
+ let { fakePromptToChangePassword, restorePrompter } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ let storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 0,
+ "Should have no saved logins at the start of the test"
+ );
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: generatedPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+
+ let [login] = await storageChangedPromised;
+ let expected = new LoginInfo(
+ "https://www.example.com",
+ "https://www.mozilla.org",
+ null,
+ "", // verify we don't include the username when auto-saving a login
+ generatedPassword
+ );
+
+ Assert.ok(login.equals(expected), "Check added login");
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[4],
+ "promptToChangePassword had a truthy 'notifySaved' argument"
+ );
+
+ info("Edit the password to be empty");
+ const newPassword = "";
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ let generatedPW =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(!generatedPW.edited, "Cached edited boolean should be false");
+ equal(
+ generatedPW.value,
+ generatedPassword,
+ "Cached password shouldn't be updated"
+ );
+
+ checkEditTelemetryRecorded(0, "Blanking doesn't count as an edit");
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ Services.telemetry.clearEvents();
+ }
+);
+
+add_task(async function test_addUsernameBeforeAutoSaveEdit() {
+ await startTestConditions(99);
+ let { generatedPassword } = await stubGeneratedPasswordForBrowsingContextId(
+ 99
+ );
+ let { fakePromptToChangePassword, restorePrompter, resetPrompterHistory } =
+ stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+ let fakePopupNotifications = {
+ getNotification: sinon.stub().returns({ dismissed: true }),
+ };
+ sinon.stub(LoginHelper, "getBrowserForPrompt").callsFake(() => {
+ return {
+ ownerGlobal: {
+ PopupNotifications: fakePopupNotifications,
+ },
+ };
+ });
+
+ let storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 0,
+ "Should have no saved logins at the start of the test"
+ );
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: generatedPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+
+ let [login] = await storageChangedPromised;
+ let expected = new LoginInfo(
+ "https://www.example.com",
+ "https://www.mozilla.org",
+ null,
+ "", // verify we don't include the username when auto-saving a login
+ generatedPassword
+ );
+
+ Assert.ok(login.equals(expected), "Check added login");
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[4],
+ "promptToChangePassword had a truthy 'notifySaved' argument"
+ );
+
+ info("Checking the getNotification stub");
+ Assert.ok(
+ !fakePopupNotifications.getNotification.called,
+ "getNotification didn't get called yet"
+ );
+ resetPrompterHistory();
+
+ info("Add a username to the auto-saved login in storage");
+ let loginWithUsername = login.clone();
+ loginWithUsername.username = "added_username";
+ LoginManagerPrompter._updateLogin(login, loginWithUsername);
+
+ info("Edit the password");
+ const newPassword = generatedPassword + "🔥";
+ storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ // will update the doorhanger with changed password
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ let generatedPW =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(generatedPW.edited, "Cached edited boolean should be true");
+ equal(generatedPW.value, newPassword, "Cached password should be updated");
+ let [dataArray] = await storageChangedPromised;
+ login = dataArray.queryElementAt(1, Ci.nsILoginInfo);
+ loginWithUsername.password = newPassword;
+ // the password should be updated in storage, but not the username (until the user confirms the doorhanger)
+ assertLoginProperties(login, loginWithUsername);
+ Assert.ok(login.matches(loginWithUsername, false), "Check updated login");
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 1,
+ "Should have 1 saved login still"
+ );
+
+ Assert.ok(
+ fakePopupNotifications.getNotification.calledOnce,
+ "getNotification was called"
+ );
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ // The generated password changed, so we expect notifySaved to be true
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[4],
+ "promptToChangePassword should have a falsey 'notifySaved' argument"
+ );
+ resetPrompterHistory();
+
+ info(
+ "Simulate a second edit to check that the telemetry event for the first edit is not recorded twice"
+ );
+ const newerPassword = newPassword + "🦊";
+ storageChangedPromised = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ info("Calling _onPasswordEditedOrGenerated again");
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newerPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ generatedPW = LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(generatedPW.edited, "Cached edited state should remain true");
+ equal(generatedPW.value, newerPassword, "Cached password should be updated");
+ [dataArray] = await storageChangedPromised;
+ login = dataArray.queryElementAt(1, Ci.nsILoginInfo);
+ loginWithUsername.password = newerPassword;
+ assertLoginProperties(login, loginWithUsername);
+ Assert.ok(login.matches(loginWithUsername, false), "Check updated login");
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 1,
+ "Should have 1 saved login still"
+ );
+
+ checkEditTelemetryRecorded(1, "with auto-save");
+
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ equal(
+ fakePromptToChangePassword.getCall(0).args[2].password,
+ newerPassword,
+ "promptToChangePassword had the updated password"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ LoginHelper.getBrowserForPrompt.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ Services.telemetry.clearEvents();
+});
+
+add_task(async function test_editUsernameOfFilledSavedLogin() {
+ await startTestConditions(99);
+ await stubGeneratedPasswordForBrowsingContextId(99);
+ let {
+ fakePromptToChangePassword,
+ fakePromptToSavePassword,
+ restorePrompter,
+ resetPrompterHistory,
+ } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+ let fakePopupNotifications = {
+ getNotification: sinon.stub().returns({ dismissed: true }),
+ };
+ sinon.stub(LoginHelper, "getBrowserForPrompt").callsFake(() => {
+ return {
+ ownerGlobal: {
+ PopupNotifications: fakePopupNotifications,
+ },
+ };
+ });
+
+ let login0Props = Object.assign({}, loginTemplate, {
+ username: "someusername",
+ password: "qweqweq",
+ });
+ info("Adding initial login: " + JSON.stringify(login0Props));
+ let savedLogin = await LoginTestUtils.addLogin(login0Props);
+
+ let logins = await Services.logins.getAllLogins();
+ info("Saved initial login: " + JSON.stringify(logins[0]));
+
+ equal(logins.length, 1, "Should have 1 saved login at the start of the test");
+
+ // first prompt to save a new login
+ let newUsername = "differentuser";
+ let newPassword = login0Props.password + "🔥";
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ autoFilledLoginGuid: savedLogin.guid,
+ newPasswordField: { value: newPassword },
+ usernameField: { value: newUsername },
+ triggeredByFillingGenerated: false,
+ }
+ );
+
+ let expected = new LoginInfo(
+ login0Props.origin,
+ login0Props.formActionOrigin,
+ null,
+ newUsername,
+ newPassword
+ );
+
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ info("Checking the getNotification stub");
+ Assert.ok(
+ !fakePopupNotifications.getNotification.called,
+ "getNotification was not called"
+ );
+ Assert.ok(
+ fakePromptToSavePassword.calledOnce,
+ "Checking promptToSavePassword was called"
+ );
+ Assert.ok(
+ fakePromptToSavePassword.getCall(0).args[2],
+ "promptToSavePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ !fakePromptToSavePassword.getCall(0).args[3],
+ "promptToSavePassword had a falsey 'notifySaved' argument"
+ );
+ assertLoginProperties(fakePromptToSavePassword.getCall(0).args[1], expected);
+ resetPrompterHistory();
+
+ // then prompt with matching username/password
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ autoFilledLoginGuid: savedLogin.guid,
+ newPasswordField: { value: login0Props.password },
+ usernameField: { value: login0Props.username },
+ triggeredByFillingGenerated: false,
+ }
+ );
+
+ expected = new LoginInfo(
+ login0Props.origin,
+ login0Props.formActionOrigin,
+ null,
+ login0Props.username,
+ login0Props.password
+ );
+
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ info("Checking the getNotification stub");
+ Assert.ok(
+ fakePopupNotifications.getNotification.called,
+ "getNotification was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ !fakePromptToChangePassword.getCall(0).args[4],
+ "promptToChangePassword had a falsey 'notifySaved' argument"
+ );
+ assertLoginProperties(
+ fakePromptToChangePassword.getCall(0).args[2],
+ expected
+ );
+ resetPrompterHistory();
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ LoginHelper.getBrowserForPrompt.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ Services.telemetry.clearEvents();
+});
+
+add_task(
+ async function test_onPasswordEditedOrGenerated_generatedPassword_withDisabledLogin() {
+ await startTestConditions(99);
+ let { generatedPassword } = await stubGeneratedPasswordForBrowsingContextId(
+ 99
+ );
+ let { restorePrompter } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ info("Disable login saving for the site");
+ Services.logins.setLoginSavingEnabled("https://www.example.com", false);
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: generatedPassword },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ equal(
+ (await Services.logins.getAllLogins()).length,
+ 0,
+ "Should have no saved logins since saving is disabled"
+ );
+ Assert.ok(
+ LMP._getPrompter.notCalled,
+ "Checking _getPrompter wasn't called"
+ );
+
+ // Clean up
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.setLoginSavingEnabled("https://www.example.com", true);
+ Services.logins.removeAllUserFacingLogins();
+ }
+);
+
+add_task(
+ async function test_onPasswordEditedOrGenerated_generatedPassword_withSavedEmptyUsername() {
+ await startTestConditions(99);
+ let login0Props = Object.assign({}, loginTemplate, {
+ username: "",
+ password: "qweqweq",
+ });
+ info("Adding initial login: " + JSON.stringify(login0Props));
+ let expected = await LoginTestUtils.addLogin(login0Props);
+
+ info(
+ "Saved initial login: " +
+ JSON.stringify(Services.logins.getAllLogins()[0])
+ );
+
+ let { generatedPassword: password1 } =
+ await stubGeneratedPasswordForBrowsingContextId(99);
+ let { restorePrompter, fakePromptToChangePassword } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: password1 },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ let logins = await Services.logins.getAllLogins();
+ equal(
+ logins.length,
+ 1,
+ "Should just have the previously-saved login with empty username"
+ );
+ assertLoginProperties(logins[0], login0Props);
+
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ !fakePromptToChangePassword.getCall(0).args[4],
+ "promptToChangePassword had a falsey 'notifySaved' argument"
+ );
+
+ info("Edit the password");
+ const newPassword = password1 + "🔥";
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newPassword },
+ usernameField: { value: "someusername" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ let generatedPW =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(generatedPW.edited, "Cached edited boolean should be true");
+ equal(generatedPW.storageGUID, null, "Should have no storageGUID");
+ equal(generatedPW.value, newPassword, "Cached password should be updated");
+ logins = await Services.logins.getAllLogins();
+ assertLoginProperties(logins[0], login0Props);
+ Assert.ok(logins[0].equals(expected), "Ensure no changes");
+ equal(logins.length, 1, "Should have 1 saved login still");
+
+ checkEditTelemetryRecorded(1, "Updating cache, not storage (no auto-save)");
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ Services.telemetry.clearEvents();
+ }
+);
+
+add_task(
+ async function test_onPasswordEditedOrGenerated_generatedPassword_withSavedEmptyUsernameAndUsernameValue() {
+ // Save as the above task but with a non-empty username field value.
+ await startTestConditions(99);
+ let login0Props = Object.assign({}, loginTemplate, {
+ username: "",
+ password: "qweqweq",
+ });
+ info("Adding initial login: " + JSON.stringify(login0Props));
+ let expected = await LoginTestUtils.addLogin(login0Props);
+
+ info(
+ "Saved initial login: " +
+ JSON.stringify(await Services.logins.getAllLogins()[0])
+ );
+
+ let { generatedPassword: password1 } =
+ await stubGeneratedPasswordForBrowsingContextId(99);
+ let {
+ restorePrompter,
+ fakePromptToChangePassword,
+ fakePromptToSavePassword,
+ } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: password1 },
+ usernameField: { value: "non-empty-username" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ let logins = await Services.logins.getAllLogins();
+ equal(
+ logins.length,
+ 1,
+ "Should just have the previously-saved login with empty username"
+ );
+ assertLoginProperties(logins[0], login0Props);
+
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.notCalled,
+ "Checking promptToChangePassword wasn't called"
+ );
+ Assert.ok(
+ fakePromptToSavePassword.calledOnce,
+ "Checking promptToSavePassword was called"
+ );
+ Assert.ok(
+ fakePromptToSavePassword.getCall(0).args[2],
+ "promptToSavePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ !fakePromptToSavePassword.getCall(0).args[3],
+ "promptToSavePassword had a falsey 'notifySaved' argument"
+ );
+
+ info("Edit the password");
+ const newPassword = password1 + "🔥";
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: newPassword },
+ usernameField: { value: "non-empty-username" },
+ triggeredByFillingGenerated: true,
+ }
+ );
+ Assert.ok(
+ fakePromptToChangePassword.notCalled,
+ "Checking promptToChangePassword wasn't called"
+ );
+ Assert.ok(
+ fakePromptToSavePassword.calledTwice,
+ "Checking promptToSavePassword was called again"
+ );
+ Assert.ok(
+ fakePromptToSavePassword.getCall(1).args[2],
+ "promptToSavePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ !fakePromptToSavePassword.getCall(1).args[3],
+ "promptToSavePassword had a falsey 'notifySaved' argument"
+ );
+
+ let generatedPW =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://www.example.com^userContextId=6"
+ );
+ Assert.ok(generatedPW.edited, "Cached edited boolean should be true");
+ equal(generatedPW.storageGUID, null, "Should have no storageGUID");
+ equal(generatedPW.value, newPassword, "Cached password should be updated");
+ logins = await Services.logins.getAllLogins();
+ assertLoginProperties(logins[0], login0Props);
+ Assert.ok(logins[0].equals(expected), "Ensure no changes");
+ equal(logins.length, 1, "Should have 1 saved login still");
+
+ checkEditTelemetryRecorded(
+ 1,
+ "Updating cache, not storage (no auto-save) with username in field"
+ );
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ Services.telemetry.clearEvents();
+ }
+);
+
+add_task(
+ async function test_onPasswordEditedOrGenerated_generatedPassword_withEmptyUsernameDifferentFormActionOrigin() {
+ await startTestConditions(99);
+ let login0Props = Object.assign({}, loginTemplate, {
+ username: "",
+ password: "qweqweq",
+ });
+ await LoginTestUtils.addLogin(login0Props);
+
+ let { generatedPassword: password1 } =
+ await stubGeneratedPasswordForBrowsingContextId(99);
+ let { restorePrompter, fakePromptToChangePassword } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.elsewhere.com",
+ newPasswordField: { value: password1 },
+ triggeredByFillingGenerated: true,
+ }
+ );
+
+ let savedLogins = await Services.logins.getAllLogins();
+ equal(
+ savedLogins.length,
+ 2,
+ "Should have saved the generated-password login"
+ );
+
+ assertLoginProperties(savedLogins[0], login0Props);
+ assertLoginProperties(
+ savedLogins[1],
+ Object.assign({}, loginTemplate, {
+ formActionOrigin: "https://www.elsewhere.com",
+ username: "",
+ password: password1,
+ })
+ );
+
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[2],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'notifySaved' argument"
+ );
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ }
+);
+
+add_task(
+ async function test_onPasswordEditedOrGenerated_generatedPassword_withSavedUsername() {
+ await startTestConditions(99);
+ let login0Props = Object.assign({}, loginTemplate, {
+ username: "previoususer",
+ password: "qweqweq",
+ });
+ await LoginTestUtils.addLogin(login0Props);
+
+ let { generatedPassword: password1 } =
+ await stubGeneratedPasswordForBrowsingContextId(99);
+ let { restorePrompter, fakePromptToChangePassword } = stubPrompter();
+ let rootBrowser = LMP.getRootBrowser();
+
+ await LMP._onPasswordEditedOrGenerated(
+ rootBrowser,
+ "https://www.example.com",
+ {
+ browsingContextId: 99,
+ formActionOrigin: "https://www.mozilla.org",
+ newPasswordField: { value: password1 },
+ triggeredByFillingGenerated: true,
+ }
+ );
+
+ let savedLogins = await Services.logins.getAllLogins();
+ equal(
+ savedLogins.length,
+ 2,
+ "Should have saved the generated-password login"
+ );
+ assertLoginProperties(savedLogins[0], login0Props);
+ assertLoginProperties(
+ savedLogins[1],
+ Object.assign({}, loginTemplate, {
+ username: "",
+ password: password1,
+ })
+ );
+
+ Assert.ok(LMP._getPrompter.calledOnce, "Checking _getPrompter was called");
+ Assert.ok(
+ fakePromptToChangePassword.calledOnce,
+ "Checking promptToChangePassword was called"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[2],
+ "promptToChangePassword had a truthy 'dismissed' argument"
+ );
+ Assert.ok(
+ fakePromptToChangePassword.getCall(0).args[3],
+ "promptToChangePassword had a truthy 'notifySaved' argument"
+ );
+
+ LoginManagerParent._browsingContextGlobal.get.restore();
+ restorePrompter();
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ Services.logins.removeAllUserFacingLogins();
+ }
+);