/** * Test for LoginFormState._getPasswordFields using LoginFormFactory. */ /* globals todo_check_eq */ "use strict"; const { LoginFormFactory } = ChromeUtils.importESModule( "resource://gre/modules/LoginFormFactory.sys.mjs" ); const { LoginFormState } = ChromeUtils.importESModule( "resource://gre/modules/LoginManagerChild.sys.mjs" ); const TESTCASES = [ { description: "Empty document", document: ``, returnedFieldIDsByFormLike: [], minPasswordLength: undefined, }, { description: "Non-password input with no
present", document: ``, // Only the IDs of password fields should be in this array returnedFieldIDsByFormLike: [[]], minPasswordLength: undefined, }, { description: "1 password field outside of a ", document: ``, returnedFieldIDsByFormLike: [["pw1"]], minPasswordLength: undefined, }, { description: "5 empty password fields outside of a ", document: ` `, returnedFieldIDsByFormLike: [["pw1", "pw2", "pw3", "pw4", "pw5"]], minPasswordLength: undefined, }, { description: "6 empty password fields outside of a ", document: ` `, returnedFieldIDsByFormLike: [[]], minPasswordLength: undefined, }, { description: "4 password fields outside of a (1 empty, 3 full) with minPasswordLength=2", document: ` `, returnedFieldIDsByFormLike: [["pw2", "pw3", "pw4"]], minPasswordLength: 2, }, { description: "Form with 1 password field", document: `
`, returnedFieldIDsByFormLike: [["pw1"]], minPasswordLength: undefined, }, { description: "Form with 2 password fields", document: `
`, returnedFieldIDsByFormLike: [["pw1", "pw2"]], minPasswordLength: undefined, }, { description: "1 password field in a form, 1 outside", document: `
`, returnedFieldIDsByFormLike: [["pw1"], ["pw2"]], minPasswordLength: undefined, }, { description: "2 password fields outside of a
with 1 linked via @form", document: `
`, returnedFieldIDsByFormLike: [["pw1"], ["pw2"]], minPasswordLength: undefined, }, { description: "2 password fields outside of a
with 1 linked via @form + minPasswordLength", document: `
`, returnedFieldIDsByFormLike: [[], []], minPasswordLength: 2, }, { description: "minPasswordLength should also skip white-space only fields", /* eslint-disable no-tabs */ document: `
`, /* eslint-enable no-tabs */ returnedFieldIDsByFormLike: [[], []], minPasswordLength: 2, }, { description: "minPasswordLength should skip too-short field values", document: `
`, returnedFieldIDsByFormLike: [["pw"]], minPasswordLength: 2, }, { description: "minPasswordLength should allow matching-length field values", document: `
`, returnedFieldIDsByFormLike: [["pw-matchlen", "pw"]], minPasswordLength: 2, }, { description: "2 password fields outside of a
with 1 linked via @form + minPasswordLength with 1 empty", document: `
`, returnedFieldIDsByFormLike: [["pw1"], []], minPasswordLength: 2, fieldOverrideRecipe: { // Ensure a recipe without `notPasswordSelector` doesn't cause a problem. hosts: ["localhost:8080"], }, }, { description: "3 password fields outside of a
with 1 linked via @form + minPasswordLength", document: `
`, returnedFieldIDsByFormLike: [["pw3"], ["pw2"]], minPasswordLength: 2, fieldOverrideRecipe: { hosts: ["localhost:8080"], notPasswordSelector: "#pw1", }, }, { beforeGetFunction(doc) { doc.getElementById("pw1").remove(); }, description: "1 password field outside of a
which gets removed/disconnected", document: ``, returnedFieldIDsByFormLike: [[]], minPasswordLength: undefined, }, ]; for (let tc of TESTCASES) { info("Sanity checking the testcase: " + tc.description); (function () { let testcase = tc; add_task(async function () { info("Starting testcase: " + testcase.description); let document = MockDocument.createTestDocument( "http://localhost:8080/test/", testcase.document ); let mapRootElementToFormLike = new Map(); for (let input of document.querySelectorAll("input")) { let formLike = LoginFormFactory.createFromField(input); let existingFormLike = mapRootElementToFormLike.get( formLike.rootElement ); if (!existingFormLike) { mapRootElementToFormLike.set(formLike.rootElement, formLike); continue; } // If the formLike is already present, ensure that the properties are the same. info( "Checking if the new FormLike for the same root has the same properties" ); formLikeEqual(formLike, existingFormLike); } if (testcase.beforeGetFunction) { await testcase.beforeGetFunction(document); } Assert.strictEqual( mapRootElementToFormLike.size, testcase.returnedFieldIDsByFormLike.length, "Check the correct number of different formLikes were returned" ); let formLikeIndex = -1; for (let formLikeFromInput of mapRootElementToFormLike.values()) { formLikeIndex++; let pwFields = LoginFormState._getPasswordFields(formLikeFromInput, { fieldOverrideRecipe: testcase.fieldOverrideRecipe, minPasswordLength: testcase.minPasswordLength, }); if ( ChromeUtils.getClassName(formLikeFromInput.rootElement) === "HTMLFormElement" ) { let formLikeFromForm = LoginFormFactory.createFromForm( formLikeFromInput.rootElement ); info( "Checking that the FormLike created for the matches" + " the one from a password field" ); formLikeEqual(formLikeFromInput, formLikeFromForm); } if (testcase.returnedFieldIDsByFormLike[formLikeIndex].length === 0) { Assert.strictEqual( pwFields, null, "If no password fields were found null should be returned" ); } else { Assert.strictEqual( pwFields.length, testcase.returnedFieldIDsByFormLike[formLikeIndex].length, "Check the # of password fields for formLike #" + formLikeIndex ); } for ( let i = 0; i < testcase.returnedFieldIDsByFormLike[formLikeIndex].length; i++ ) { let expectedID = testcase.returnedFieldIDsByFormLike[formLikeIndex][i]; Assert.strictEqual( pwFields[i].element.id, expectedID, "Check password field " + i + " ID" ); } } }); })(); } const EMOJI_TESTCASES = [ { description: "Single characters composed of 2 code units should ideally fail minPasswordLength of 2", document: `
`, returnedFieldIDsByFormLike: [["pw"]], minPasswordLength: 2, }, { description: "Single characters composed of multiple code units should ideally fail minPasswordLength of 2", document: `
`, minPasswordLength: 2, }, ]; // Note: Bug 780449 tracks our handling of emoji and multi-code-point characters in password fields // and the .length we should expect when a password value includes them for (let tc of EMOJI_TESTCASES) { info("Sanity checking the testcase: " + tc.description); (function () { let testcase = tc; add_task(async function () { info("Starting testcase: " + testcase.description); let document = MockDocument.createTestDocument( "http://localhost:8080/test/", testcase.document ); let input = document.querySelector("input[type='password']"); Assert.ok(input, "Found the password field"); let formLike = LoginFormFactory.createFromField(input); let pwFields = LoginFormState._getPasswordFields(formLike, { minPasswordLength: testcase.minPasswordLength, }); info("Got password fields: " + pwFields.length); todo_check_eq( pwFields.length, 0, "Check a single-character (emoji) password is excluded from the password fields collection" ); }); })(); }