diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js')
-rw-r--r-- | toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js | 1126 |
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(); + } +); |