diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/passwordmgr/test/unit/test_getFormFields.js | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/unit/test_getFormFields.js b/toolkit/components/passwordmgr/test/unit/test_getFormFields.js new file mode 100644 index 0000000000..a04b497181 --- /dev/null +++ b/toolkit/components/passwordmgr/test/unit/test_getFormFields.js @@ -0,0 +1,572 @@ +/** + * Test for LoginFormState._getFormFields. + */ + +"use strict"; + +const { LoginFormFactory } = ChromeUtils.importESModule( + "resource://gre/modules/LoginFormFactory.sys.mjs" +); + +const { LoginManagerChild } = ChromeUtils.importESModule( + "resource://gre/modules/LoginManagerChild.sys.mjs" +); + +const TESTENVIRONMENTS = { + filledPW1WithGeneratedPassword: { + generatedPWFieldSelectors: ["#pw1"], + }, +}; + +const TESTCASES = [ + { + description: "1 password field outside of a <form>", + document: `<input id="pw1" type=password>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "1 text field outside of a <form> without a password field", + document: `<input id="un1">`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: undefined, + // there is no password field to fill, so no sense testing with gen. passwords + extraTestEnvironments: [], + extraTestPreferences: [], + }, + { + description: "1 username & password field outside of a <form>", + document: `<input id="un1"> + <input id="pw1" type=password>`, + returnedFieldIDs: { + usernameField: "un1", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + beforeGetFunction(doc, formLike) { + // Access the formLike.elements lazy getter to have it cached. + Assert.equal( + formLike.elements.length, + 2, + "Check initial elements length" + ); + doc.getElementById("un1").remove(); + }, + description: "1 username & password field outside of a <form>, un1 removed", + document: `<input id="un1"> + <input id="pw1" type=password>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "1 username & password field in a <form>", + document: `<form> + <input id="un1"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un1", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "5 empty password fields outside of a <form>", + document: `<input id="pw1" type=password> + <input id="pw2" type=password> + <input id="pw3" type=password> + <input id="pw4" type=password> + <input id="pw5" type=password>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "6 empty password fields outside of a <form>", + document: `<input id="pw1" type=password> + <input id="pw2" type=password> + <input id="pw3" type=password> + <input id="pw4" type=password> + <input id="pw5" type=password> + <input id="pw6" type=password>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "4 password fields outside of a <form> (1 empty, 3 full) with skipEmpty", + document: `<input id="pw1" type=password> + <input id="pw2" type=password value="pass2"> + <input id="pw3" type=password value="pass3"> + <input id="pw4" type=password value="pass4">`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: true, + // This test assumes that pw1 has not been filled, so don't test prefilling it + extraTestEnvironments: [], + extraTestPreferences: [], + }, + { + description: "Form with 1 password field", + document: `<form><input id="pw1" type=password></form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "Form with 2 password fields", + document: `<form><input id="pw1" type=password><input id='pw2' type=password></form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "1 password field in a form, 1 outside (not processed)", + document: `<form><input id="pw1" type=password></form><input id="pw2" type=password>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "1 password field in a form, 1 text field outside (not processed)", + document: `<form><input id="pw1" type=password></form><input>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "1 text field in a form, 1 password field outside (not processed)", + document: `<form><input></form><input id="pw1" type=password>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "2 password fields outside of a <form> with 1 linked via @form", + document: `<input id="pw1" type=password><input id="pw2" type=password form='form1'> + <form id="form1"></form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "2 password fields outside of a <form> with 1 linked via @form + skipEmpty", + document: `<input id="pw1" type=password><input id="pw2" type=password form="form1"> + <form id="form1"></form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: true, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "2 password fields outside of a <form> with 1 linked via @form + skipEmpty with 1 empty", + document: `<input id="pw1" type=password value="pass1"><input id="pw2" type=password form="form1"> + <form id="form1"></form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: true, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "3 password fields, 2nd and 3rd are filled with generated passwords", + document: `<input id="pw1" type=password> + <input id="pw2" type=password value="pass2"> + <input id="pw3" type=password value="pass3">`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: "pw2", + confirmPasswordField: "pw3", + oldPasswordField: "pw1", + }, + skipEmptyFields: undefined, + generatedPWFieldSelectors: ["#pw2", "#pw3"], + // this test doesn't make sense to run with different filled generated password values + extraTestEnvironments: [], + extraTestPreferences: [], + }, + // begin of getusername heuristic tests + { + description: "multiple non-username like input fields in a <form>", + document: `<form> + <input id="un1"> + <input id="un2"> + <input id="un3"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un3", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "1 username input and multiple non-username like input in a <form>", + document: `<form> + <input id="un1"> + <input id="un2" autocomplete="username"> + <input id="un3"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un2", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "1 email input and multiple non-username like input in a <form>", + document: `<form> + <input id="un1"> + <input id="un2" autocomplete="email"> + <input id="un3"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un2", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "1 username & 1 email field, the email field is more close to the password", + document: `<form> + <input id="un1" autocomplete="username"> + <input id="un2" autocomplete="email"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un1", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: + "1 username and 1 email field, the username field is more close to the password", + document: `<form> + <input id="un1" autocomplete="email"> + <input id="un2" autocomplete="username"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un2", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "2 username fields in a <form>", + document: `<form> + <input id="un1" autocomplete="username"> + <input id="un2" autocomplete="username"> + <input id="un3"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un2", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "2 email fields in a <form>", + document: `<form> + <input id="un1" autocomplete="email"> + <input id="un2" autocomplete="email"> + <input id="un3"> + <input id="pw1" type=password> + </form>`, + returnedFieldIDs: { + usernameField: "un1", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + { + description: "the password field precedes the username field", + document: `<form> + <input id="un1"> + <input id="pw1" type=password> + <input id="un2" autocomplete="username"> + </form>`, + returnedFieldIDs: { + usernameField: "un1", + newPasswordField: "pw1", + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [TESTENVIRONMENTS.filledPW1WithGeneratedPassword], + extraTestPreferences: [], + }, + // end of getusername heuristic tests + { + description: "1 username field in a <form>", + document: `<form> + <input id="un1" autocomplete="username"> + </form>`, + returnedFieldIDs: { + usernameField: "un1", + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [], + extraTestPreferences: [], + }, + { + description: "1 input field in a <form>", + document: `<form> + <input id="un1""> + </form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [], + extraTestPreferences: [], + }, + { + description: "1 username field in a <form> with usernameOnlyForm pref off", + document: `<form> + <input id="un1" autocomplete="username"> + </form>`, + returnedFieldIDs: { + usernameField: null, + newPasswordField: null, + oldPasswordField: null, + }, + skipEmptyFields: undefined, + extraTestEnvironments: [], + extraTestPreferences: [["signon.usernameOnlyForm.enabled", false]], + }, +]; + +const TEST_ENVIRONMENT_CASES = TESTCASES.flatMap(tc => { + let arr = [tc]; + // also run this test case with this different state + for (let env of tc.extraTestEnvironments) { + arr.push({ + ...tc, + ...env, + }); + } + return arr; +}); + +function _setPrefs() { + Services.prefs.setBoolPref("signon.usernameOnlyForm.enabled", true); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("signon.usernameOnlyForm.enabled"); + }); +} + +_setPrefs(); + +for (let tc of TEST_ENVIRONMENT_CASES) { + info("Sanity checking the testcase: " + tc.description); + + (function () { + let testcase = tc; + add_task(async function () { + info("Starting testcase: " + testcase.description); + + for (let pref of testcase.extraTestPreferences) { + Services.prefs.setBoolPref(pref[0], pref[1]); + } + + info("Document string: " + testcase.document); + let document = MockDocument.createTestDocument( + "http://localhost:8080/test/", + testcase.document + ); + + let input = document.querySelector("input"); + MockDocument.mockOwnerDocumentProperty( + input, + document, + "http://localhost:8080/test/" + ); + + let formLike = LoginFormFactory.createFromField(input); + + if (testcase.beforeGetFunction) { + await testcase.beforeGetFunction(document, formLike); + } + + let lmc = new LoginManagerChild(); + let loginFormState = lmc.stateForDocument(formLike.ownerDocument); + loginFormState.generatedPasswordFields = _generateDocStateFromTestCase( + testcase, + document + ); + + let actual = loginFormState._getFormFields( + formLike, + testcase.skipEmptyFields, + new Set() + ); + + [ + "usernameField", + "newPasswordField", + "oldPasswordField", + "confirmPasswordField", + ].forEach(fieldName => { + Assert.ok( + fieldName in actual, + "_getFormFields return value includes " + fieldName + ); + }); + for (let key of Object.keys(testcase.returnedFieldIDs)) { + let expectedID = testcase.returnedFieldIDs[key]; + if (expectedID === null) { + Assert.strictEqual( + actual[key], + expectedID, + "Check returned field " + key + " is null" + ); + } else { + Assert.strictEqual( + actual[key].id, + expectedID, + "Check returned field " + key + " ID" + ); + } + } + + for (let pref of tc.extraTestPreferences) { + Services.prefs.clearUserPref(pref[0]); + } + }); + })(); +} + +function _generateDocStateFromTestCase(stateProperties, document) { + // prepopulate the document form state LMC holds with + // any generated password fields defined in this testcase + let generatedPasswordFields = new Set(); + info( + "stateProperties has generatedPWFieldSelectors: " + + stateProperties.generatedPWFieldSelectors?.join(", ") + ); + + if (stateProperties.generatedPWFieldSelectors?.length) { + stateProperties.generatedPWFieldSelectors.forEach(sel => { + let field = document.querySelector(sel); + if (field) { + generatedPasswordFields.add(field); + } else { + info(`No password field: ${sel} found in this document`); + } + }); + } + return generatedPasswordFields; +} |