Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test autofill and autocomplete on autocomplete=new-password fields</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="pwmgr_common.js"></script>
<script src="../../../satchel/test/satchel_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content"></div>
<pre id="test">
Login Manager test: autofill with password generation on `autocomplete=new-password` fields.
<template id="form1-template">
<form id="form1" action="https://autofill">
<input type="text" name="uname">
<input type="password" name="p">
<button type="submit" name="submit">Submit</button>
</form>
</template>
<template id="form2-template">
<form id="form2" action="https://autofill">
<input type="text" name="uname">
<input type="password" name="password" autocomplete="new-password">
<button type="submit" name="submit">Submit</button>
</form>
</template>
<template id="form3-template">
<form id="form3" action="https://autofill">
<input type="text" name="username">
<label>New password<input type="password" name="password"></label>
<button type="submit" name="submit">Submit</button>
</form>
</template>
<script class="testbody" type="text/javascript">
const { TestUtils } = SpecialPowers.ChromeUtils.importESModule(
);
const formTemplates = {
form1: document.getElementById("form1-template"),
form2: document.getElementById("form2-template"),
form3: document.getElementById("form3-template"),
};
const dateAndTimeFormatter = new SpecialPowers.Services.intl.DateTimeFormat(undefined, {
dateStyle: "medium",
});
async function showACPopup(formNumber, expectedACLabels) {
const autocompleteItems = await popupByArrowDown();
checkAutoCompleteResults(autocompleteItems, expectedACLabels,
window.location.host, "Check all rows are correct");
}
function clearGeneratedPasswords() {
const { LoginManagerParent } = ChromeUtils.importESModule(
);
if (LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin()) {
LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
}
}
add_named_task("autofill autocomplete username no generation", async () => {
await setPreferencesForTask(
["signon.generation.available", false],
["signon.generation.enabled", false],
);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form1 = setContentForTask(formTemplates.form1);
const autofillResult1 = await formAutofillResult(form1.id);
is(autofillResult1, "filled", "form has not been filled due to multiple logins");
// reference form was filled as expected?
is(form1.uname.value, "user1", "username is filled");
is(form1.p.value, "pass1", "password is filled");
const form2 = setContentForTask(formTemplates.form2);
const autofillResult2 = await formAutofillResult(form2.id);
is(autofillResult2, "password_autocomplete_new_password", "form has not been filled due to password_autocomplete_new_password");
// 2nd form should not be filled
is(form2.uname.value, "", "username is empty");
is(form2.password.value, "", "password is empty");
form2.uname.focus();
await showACPopup(2, ["user1"]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
const autofillResult3 = await formAutofillResult(form2.id);
is(autofillResult3, "filled", "form has been filled");
is(form2.uname.value, "user1", "username is filled");
is(form2.password.value, "pass1", "password is filled");
});
add_named_task("autofill autocomplete password no generation", async () => {
await setPreferencesForTask(
["signon.generation.available", false],
["signon.generation.enabled", false],
);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form = setContentForTask(formTemplates.form2);
const autofillResult = await formAutofillResult(form.id);
is(autofillResult, "password_autocomplete_new_password", "form has not been filled due to password_autocomplete_new_password");
// form should not be filled
is(form.uname.value, "", "username is empty");
is(form.password.value, "", "password is empty");
form.password.focus();
// No generation option on username fields.
await showACPopup(2, ["user1"]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => form.password.value == "pass1", "Check pw filled");
is(form.uname.value, "", "username is empty");
is(form.password.value, "pass1", "password is filled");
// No autocomplete results should appear for non-empty pw fields.
await noPopupByArrowDown();
});
add_named_task("autofill autocomplete username no generation #2", async () => {
await setPreferencesForTask(
["signon.generation.available", false],
["signon.generation.enabled", false],
);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form = setContentForTask(formTemplates.form2);
const autofillResult1 = await formAutofillResult(form.id);
is(autofillResult1, "password_autocomplete_new_password", "form has not been filled due to password_autocomplete_new_password");
// form should not be filled
is(form.uname.value, "", "username is empty");
is(form.password.value, "", "password is empty");
form.uname.focus();
// No generation option on username fields.
await showACPopup(2, ["user1"]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
const autofillResult2 = await formAutofillResult(form.id);
is(autofillResult2, "filled", "form has been filled");
is(form.uname.value, "user1", "username is filled");
is(form.password.value, "pass1", "password is filled");
});
add_named_task("autofill autocomplete passsword with generation", async () => {
await setPreferencesForTask(
["signon.generation.available", true],
["signon.generation.enabled", true],
);
runInParent(clearGeneratedPasswords);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form = setContentForTask(formTemplates.form2);
const formNumber = 2;
const autofillResult = await formAutofillResult(form.id);
is(autofillResult, "password_autocomplete_new_password", "form has not been filled due to password_autocomplete_new_password");
form.reset();
// form should not be filled
is(form.uname.value, "", "username is empty");
is(form.password.value, "", "password is empty");
form.password.focus();
await showACPopup(formNumber, [
"user1",
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => form.password.value == "pass1", "Check pw filled");
is(form.uname.value, "", "username is empty");
is(form.password.value, "pass1", "password is filled");
// No autocomplete results should appear for non-empty pw fields.
await noPopupByArrowDown();
info("Removing all logins to test auto-saving of generated passwords");
await LoginManager.removeAllUserFacingLogins();
while (form.password.value) {
synthesizeKey("KEY_Backspace");
}
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blanked field");
info("This time select the generated password");
await showACPopup(formNumber, [
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
const storageAddPromise = promiseStorageChanged(["addLogin"]);
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Before first fill of generated pw");
synthesizeKey("KEY_Enter");
info("waiting for the password field to be filled with the generated password");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "After first fill of generated pw");
info("Wait for generated password to be added to storage");
await storageAddPromise;
let logins = await LoginManager.getAllLogins();
const timePasswordChanged = logins[logins.length - 1].timePasswordChanged;
const time = dateAndTimeFormatter.format(new Date(timePasswordChanged));
const LABEL_NO_USERNAME = "No username (" + time + ")";
const generatedPW = form.password.value;
is(generatedPW.length, GENERATED_PASSWORD_LENGTH, "Check generated password length");
ok(generatedPW.match(GENERATED_PASSWORD_REGEX), "Check generated password format");
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "After fill");
info("Check field is masked upon blurring");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "After blur");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "After shift-tab to focus again");
// Remove selection for OS where the whole value is selected upon focus.
synthesizeKey("KEY_ArrowRight");
while (form.password.value) {
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, form.password.value);
synthesizeKey("KEY_Backspace");
}
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blanked field");
info("Blur the empty field to trigger a 'change' event");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blur after blanking");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Focus again after blanking");
info("Type a single character after blanking");
synthesizeKey("@");
info("Blur the single-character field to trigger a 'change' event");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blur after backspacing");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Focus again after backspacing");
synthesizeKey("KEY_Backspace"); // Blank the field again
await showACPopup(formNumber, [
LABEL_NO_USERNAME,
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
// Same generated password should be used, even despite the 'change' to @ earlier.
is(form.uname.value, "", "username is empty");
is(form.password.value, generatedPW, "password is filled with generated password");
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "Second fill of the generated pw");
logins = await LoginManager.getAllLogins();
is(logins.length, 1, "Still 1 login after filling the generated password a 2nd time");
is(logins[0].timePasswordChanged, timePasswordChanged, "Saved login wasn't changed");
is(logins[0].password, generatedPW, "Password is the same");
info("filling the saved login to ensure the field is masked again");
while (form.password.value) {
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, form.password.value);
synthesizeKey("KEY_Backspace");
}
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blanked field again");
info("Blur the field to trigger a 'change' event again");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blur after blanking again");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Focus again after blanking again");
// Remove selection for OS where the whole value is selected upon focus.
synthesizeKey("KEY_ArrowRight");
await showACPopup(formNumber, [
LABEL_NO_USERNAME,
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check saved generated pw filled");
// Same generated password should be used but from storage
is(form.uname.value, "", "username is empty");
is(form.password.value, generatedPW, "password is filled with generated password");
// Passwords from storage should always be masked.
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "after fill from storage");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "after blur");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "after shift-tab to focus again");
});
add_named_task("autofill autocomplete passsword with generation #2", async () => {
await setPreferencesForTask(
["signon.generation.available", true],
["signon.generation.enabled", true],
);
runInParent(clearGeneratedPasswords);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form = setContentForTask(formTemplates.form3);
const formNumber = 3;
const autofillResult = await formAutofillResult(form.id);
is(autofillResult, "filled", "form has been filled");
form.reset();
is(form.username.value, "", "username is empty");
is(form.password.value, "", "password is empty");
form.password.focus();
await showACPopup(formNumber, [
"user1",
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => form.password.value == "pass1", "Check pw filled");
is(form.username.value, "", "username is empty");
is(form.password.value, "pass1", "password is filled");
// No autocomplete results should appear for non-empty pw fields.
await noPopupByArrowDown();
info("Removing all logins to test auto-saving of generated passwords");
await LoginManager.removeAllUserFacingLogins();
while (form.password.value) {
synthesizeKey("KEY_Backspace");
}
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blanked field");
info("This time select the generated password");
await showACPopup(formNumber, [
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
const storageAddPromise = promiseStorageChanged(["addLogin"]);
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Before first fill of generated pw");
synthesizeKey("KEY_Enter");
info("waiting for the password field to be filled with the generated password");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "After first fill of generated pw");
info("Wait for generated password to be added to storage");
await storageAddPromise;
let logins = await LoginManager.getAllLogins();
const timePasswordChanged = logins[logins.length - 1].timePasswordChanged;
const time = dateAndTimeFormatter.format(new Date(timePasswordChanged));
const LABEL_NO_USERNAME = "No username (" + time + ")";
const generatedPW = form.password.value;
is(generatedPW.length, GENERATED_PASSWORD_LENGTH, "Check generated password length");
ok(generatedPW.match(GENERATED_PASSWORD_REGEX), "Check generated password format");
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "After fill");
info("Check field is masked upon blurring");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "After blur");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "After shift-tab to focus again");
// Remove selection for OS where the whole value is selected upon focus.
synthesizeKey("KEY_ArrowRight");
while (form.password.value) {
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, form.password.value);
synthesizeKey("KEY_Backspace");
}
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blanked field");
info("Blur the empty field to trigger a 'change' event");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blur after blanking");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Focus again after blanking");
info("Type a single character after blanking");
synthesizeKey("@");
info("Blur the single-character field to trigger a 'change' event");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blur after backspacing");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Focus again after backspacing");
synthesizeKey("KEY_Backspace"); // Blank the field again
await showACPopup(formNumber, [
LABEL_NO_USERNAME,
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
// Same generated password should be used, even despite the 'change' to @ earlier.
is(form.username.value, "", "username is empty");
is(form.password.value, generatedPW, "password is filled with generated password");
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, "Second fill of the generated pw");
logins = await LoginManager.getAllLogins();
is(logins.length, 1, "Still 1 login after filling the generated password a 2nd time");
is(logins[0].timePasswordChanged, timePasswordChanged, "Saved login wasn't changed");
is(logins[0].password, generatedPW, "Password is the same");
info("filling the saved login to ensure the field is masked again");
while (form.password.value) {
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, false, form.password.value);
synthesizeKey("KEY_Backspace");
}
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blanked field again");
info("Blur the field to trigger a 'change' event again");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Blur after blanking again");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "Focus again after blanking again");
// Remove selection for OS where the whole value is selected upon focus.
synthesizeKey("KEY_ArrowRight");
await showACPopup(formNumber, [
LABEL_NO_USERNAME,
"Use a Securely Generated Password",
]);
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_Enter");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check saved generated pw filled");
// Same generated password should be used but from storage
is(form.username.value, "", "username is empty");
is(form.password.value, generatedPW, "password is filled with generated password");
// Passwords from storage should always be masked.
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "after fill from storage");
synthesizeKey("KEY_Tab"); // blur
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "after blur");
synthesizeKey("KEY_Tab", { shiftKey: true }); // focus
LOGIN_FIELD_UTILS.checkPasswordMasked(form.password, true, "after shift-tab to focus again");
});
add_named_task("autofill autocomplete password save login disabled", async () => {
await setPreferencesForTask(
["signon.generation.available", true],
["signon.generation.enabled", true],
);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form = setContentForTask(formTemplates.form2);
const autofillResult = await formAutofillResult(form.id);
is(autofillResult, "password_autocomplete_new_password", "form has not been filled due to password_autocomplete_new_password");
// form should not be filled
is(form.uname.value, "", "username is empty");
is(form.password.value, "", "password is empty");
const formOrigin = new URL(document.documentURI).origin;
is(formOrigin, location.origin, "Expected form origin");
await LoginManager.setLoginSavingEnabled(location.origin, false);
form.password.focus();
// when login-saving is disabled for an origin, we expect no generated password row here
await showACPopup(2, ["user1"]);
// close any open menu
synthesizeKey("KEY_Escape");
await untilAutocompletePopupClosed();
await LoginManager.setLoginSavingEnabled(location.origin, true);
});
add_named_task("delete and reselect generated password", async () => {
await setPreferencesForTask(
["signon.generation.available", true],
["signon.generation.enabled", true],
);
await setStoredLoginsDuringTask([location.origin, "https://autofill", null, "user1", "pass1"]);
const form = setContentForTask(formTemplates.form2);
const autofillResult = await formAutofillResult(form.id);
is(autofillResult, "password_autocomplete_new_password", "form has not been filled due to password_autocomplete_new_password");
info("Removing all logins to test auto-saving of generated passwords");
await LoginManager.removeAllUserFacingLogins();
// form should not be filled
is(form.uname.value, "", "username is empty");
is(form.password.value, "", "password is empty");
async function showAndSelectACPopupItem(index) {
form.password.focus();
if (form.password.value) {
form.password.select();
synthesizeKey("KEY_Backspace");
}
const autocompleteItems = await popupByArrowDown();
if (index < 0) {
index = autocompleteItems.length + index;
}
for (let i = 0; i <= index; i++) {
synthesizeKey("KEY_ArrowDown");
}
await TestUtils.waitForTick();
return autocompleteItems[index];
}
let menuLabel, itemIndex, savedLogins;
// fill the password field with the generated password via auto-complete menu
const addLoginPromise = promiseStorageChanged(["addLogin"]);
// select last-but-2 item - the one before the footer
menuLabel = await showAndSelectACPopupItem(-2);
is(menuLabel, "Use a Securely Generated Password", "Check item label");
synthesizeKey("KEY_Enter");
info("waiting for the password field to be filled with the generated password");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
info("Wait for generated password to be added to storage");
await addLoginPromise;
savedLogins = await LoginManager.getAllLogins();
is(savedLogins.length, 1, "Check saved logins count");
form.uname.focus();
await TestUtils.waitForTick();
is(form.password.value.length, LoginTestUtils.generation.LENGTH, "Check password looks generated");
const GENERATED_PASSWORD = form.password.value;
info("clear the password field and delete the saved login using the AC menu")
const removeLoginPromise = promiseStorageChanged(["removeLogin"]);
itemIndex = 0;
menuLabel = await showAndSelectACPopupItem(itemIndex);
ok(menuLabel.includes("No username"), "Check first item is the auto-saved login");
// Send delete to remove the auto-saved login from storage
// On OS X, shift-backspace and shift-delete work, just delete does not.
// On Win/Linux, shift-backspace does not work, delete and shift-delete do.
synthesizeKey("KEY_Delete", {shiftKey: true});
await removeLoginPromise;
form.uname.focus();
await TestUtils.waitForTick();
savedLogins = await LoginManager.getAllLogins();
is(savedLogins.length, 0, "Check saved logins count");
info("Re-fill with the generated password");
// select last-but-2 item - the one before the footer
menuLabel = await showAndSelectACPopupItem(-2);
is(menuLabel, "Use a Securely Generated Password", "Check item label");
synthesizeKey("KEY_Enter");
info("waiting for the password field to be filled with the generated password");
await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
form.uname.focus();
await TestUtils.waitForTick();
is(form.password.value, GENERATED_PASSWORD, "Generated password has not changed");
});
</script>
</pre>
</body>
</html>