Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/**
* Test for LoginFormState._getFormFields.
*/
"use strict";
const { LoginFormFactory } = ChromeUtils.importESModule(
"resource://gre/modules/shared/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(
testcase.document
);
let input = document.querySelector("input");
MockDocument.mockOwnerDocumentProperty(
input,
document,
);
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;
}