Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Don't send onFormSubmit message on navigation if the user did not interact
with the login fields</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content">
<iframe id="loginFrame">
</iframe>
</div>
<pre id="test"></pre>
<script>
SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popup to show");
const EXAMPLE_COM = window.location.origin + "/tests/toolkit/components/passwordmgr/test/mochitest/";
const PREFILLED_FORM_URL = EXAMPLE_COM + "subtst_prefilled_form.html"
let iframe = document.getElementById("loginFrame");
function waitForLoad() {
return new Promise(resolve => {
function handleLoad() {
iframe.removeEventListener("load", handleLoad);
resolve();
}
iframe.addEventListener("load", handleLoad);
});
}
async function setupWithOneLogin(pageUrl) {
let origin = window.location.origin;
await setStoredLoginsAsync([origin, origin, null, "user1", "pass1"]);
let chromeScript = runInParent(async function testSetup() {
let logins = await Services.logins.getAllLogins();
for (let l of logins) {
info("Got login: " + l.username + ", " + l.password);
}
});
await setup(pageUrl);
return chromeScript;
}
function resetSavedLogins() {
let chromeScript = runInParent(function testTeardown() {
Services.logins.removeAllUserFacingLogins();
});
chromeScript.destroy();
}
async function setup(pageUrl) {
let loadPromise = waitForLoad();
let processedFormPromise = promiseFormsProcessed();
iframe.src = pageUrl;
await processedFormPromise;
info("initial form processed");
await loadPromise;
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
let doc = this.content.document;
let link = doc.createElement("a");
link.setAttribute("href", "http://mochi.test:8888");
doc.body.appendChild(link);
});
}
async function navigateWithoutUserInteraction() {
let loadPromise = waitForLoad();
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
let doc = this.content.document;
let hadInteracted = doc.userHasInteracted;
let target = doc.querySelector("a[href]");
if (target) {
target.click();
} else {
target = doc.querySelector("form");
target.submit();
}
is(doc.userHasInteracted, hadInteracted, "document.userHasInteracted shouldn't have changed");
});
await loadPromise;
}
async function userInput(selector, value) {
await SpecialPowers.spawn(getIframeBrowsingContext(window), [selector, value], async function(sel, val) {
// use "real" synthesized events rather than setUserInput to ensure
// document.userHasInteracted is flipped true
let EventUtils = ContentTaskUtils.getEventUtils(content);
let target = this.content.document.querySelector(sel);
target.focus();
target.select();
await EventUtils.synthesizeKey("KEY_Backspace", {}, this.content);
await EventUtils.sendString(val, this.content);
info(
`userInput: new target.value: ${target.value}`
);
target.blur();
return Promise.resolve();
});
}
function checkDocumentUserHasInteracted() {
return SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
return this.content.document.userHasInteracted;
});
}
add_task(async function test_init() {
// For this test, we'll be testing with & without user document interaction.
// So we'll reset the pref which dictates the behavior of
// LoginFormState._formHasModifiedFields in automation
// and ensure all interactions are properly emulated
ok(SpecialPowers.getBoolPref("signon.testOnlyUserHasInteractedByPrefValue"), "signon.testOnlyUserHasInteractedByPrefValue should default to true");
info("test_init, flipping the signon.testOnlyUserHasInteractedByPrefValue pref");
await SpecialPowers.pushPrefEnv({"set": [
["signon.testOnlyUserHasInteractedByPrefValue", false],
]});
SimpleTest.registerCleanupFunction(async function cleanup_pref() {
await SpecialPowers.popPrefEnv();
});
await SimpleTest.promiseWaitForCondition(() => LoginHelper.testOnlyUserHasInteractedWithDocument === null);
is(LoginHelper.testOnlyUserHasInteractedWithDocument, null,
"LoginHelper.testOnlyUserHasInteractedWithDocument should be null for this set of tests");
});
add_task(async function test_no_message_on_navigation() {
// If login field values were set by the website, we don't message to save the
// login values if the user did not interact with the fields before submiting.
await setup(PREFILLED_FORM_URL);
let submitMessageSent = false;
getSubmitMessage().then(_value => submitMessageSent = true);
await navigateWithoutUserInteraction();
// allow time to pass before concluding no onFormSubmit message was sent
await new Promise(res => setTimeout(res, 1000));
ok(!submitMessageSent, "onFormSubmit message is not sent on navigation since the login fields were not modified");
});
add_task(async function test_prefd_off_message_on_navigation() {
// Confirm the pref controls capture behavior with non-user-set field values.
await SpecialPowers.pushPrefEnv({"set": [
["signon.userInputRequiredToCapture.enabled", false],
]});
await setup(PREFILLED_FORM_URL);
let promiseSubmitMessage = getSubmitMessage();
await navigateWithoutUserInteraction();
await promiseSubmitMessage;
info("onFormSubmit message was sent as expected after navigation");
SpecialPowers.popPrefEnv();
});
add_task(async function test_message_with_user_interaction_on_navigation() {
await setup(PREFILLED_FORM_URL);
await userInput("#form-basic-username", "foo");
let promiseSubmitMessage = getSubmitMessage();
await navigateWithoutUserInteraction();
await promiseSubmitMessage;
info("onFormSubmit message was sent as expected after user interaction");
});
add_task(async function test_empty_form_with_input_handler() {
await setup(EXAMPLE_COM + "formless_basic.html");
await userInput("#form-basic-username", "user");
await userInput("#form-basic-password", "pass");
let promiseSubmitMessage = getSubmitMessage();
await navigateWithoutUserInteraction();
await promiseSubmitMessage;
info("onFormSubmit message was sent as expected after user interaction");
});
add_task(async function test_no_message_on_autofill_without_user_interaction() {
let chromeScript = await setupWithOneLogin(EXAMPLE_COM + "form_basic.html");
// Check for autofilled values.
await checkLoginFormInFrame(getIframeBrowsingContext(window, 0),
"form-basic-username", "user1",
"form-basic-password", "pass1");
info("LoginHelper.testOnlyUserHasInteractedWithDocument:" +
LoginHelper.testOnlyUserHasInteractedWithDocument
);
ok(!(await checkDocumentUserHasInteracted()), "document.userHasInteracted should be initially false");
let submitMessageSent = false;
getSubmitMessage().then(_value => submitMessageSent = true);
info("Navigating the page")
await navigateWithoutUserInteraction();
// allow time to pass before concluding no onFormSubmit message was sent
await new Promise(res => setTimeout(res, 1000));
chromeScript.destroy();
resetSavedLogins();
ok(!submitMessageSent, "onFormSubmit message is not sent on navigation since the document had no user interaction");
});
add_task(async function test_message_on_autofill_with_document_interaction() {
// We expect that as long as the form values !== their defaultValues,
// any document interaction allows the submit message to be sent
let chromeScript = await setupWithOneLogin(EXAMPLE_COM + "form_basic.html");
// Check for autofilled values.
await checkLoginFormInFrame(getIframeBrowsingContext(window, 0),
"form-basic-username", "user1",
"form-basic-password", "pass1");
let userInteracted = await checkDocumentUserHasInteracted();
ok(!userInteracted, "document.userHasInteracted should be initially false");
await SpecialPowers.spawn(getIframeBrowsingContext(window), ["#form-basic-username"], async function(sel) {
// Click somewhere in the document to ensure document.userHasInteracted is flipped to true
let EventUtils = ContentTaskUtils.getEventUtils(content);
let target = this.content.document.querySelector(sel);
await EventUtils.synthesizeMouseAtCenter(target, {}, this.content);
});
userInteracted = await checkDocumentUserHasInteracted();
ok(userInteracted, "After synthesizeMouseAtCenter, document.userHasInteracted should be true");
let promiseSubmitMessage = getSubmitMessage();
await navigateWithoutUserInteraction();
let { data } = await promiseSubmitMessage;
ok(data.autoFilledLoginGuid, "Message was sent with autoFilledLoginGuid");
info("Message was sent as expected after document user interaction");
chromeScript.destroy();
resetSavedLogins();
});
add_task(async function test_message_on_autofill_with_user_interaction() {
// Editing a field value causes the submit message to be sent as
// there is both document interaction and field modification
let chromeScript = await setupWithOneLogin(EXAMPLE_COM + "form_basic.html");
// Check for autofilled values.
await checkLoginFormInFrame(getIframeBrowsingContext(window, 0),
"form-basic-username", "user1",
"form-basic-password", "pass1");
userInput("#form-basic-username", "newuser");
let promiseSubmitMessage = getSubmitMessage();
await navigateWithoutUserInteraction();
let { data } = await promiseSubmitMessage;
ok(data.autoFilledLoginGuid, "Message was sent with autoFilledLoginGuid");
is(data.usernameField.value, "newuser", "Message was sent with correct usernameField.value");
info("Message was sent as expected after user form interaction");
chromeScript.destroy();
resetSavedLogins();
});
add_task(async function test_no_message_on_user_input_from_other_form() {
// ensure input into unrelated fields on the page don't change login form modified-ness
await setup(PREFILLED_FORM_URL);
// Add a form which will not be submitted and an input associated with that form
await SpecialPowers.spawn(getIframeBrowsingContext(window), [], function() {
let doc = this.content.document;
let loginForm = doc.querySelector("form");
let fragment = doc.createDocumentFragment();
let otherForm = doc.createElement("form");
otherForm.id ="otherForm";
fragment.appendChild(otherForm);
let alienField = doc.createElement("input");
alienField.id = "alienField";
alienField.type = "text"; // not a password field
alienField.setAttribute("form", "otherForm");
// new field is child of the login, but a member of different non-login form via its .form property
loginForm.appendChild(alienField);
doc.body.appendChild(fragment);
});
await userInput("#alienField", "something");
let submitMessageSent = false;
getSubmitMessage().then(data => {
info("submit mesage data: " + JSON.stringify(data));
submitMessageSent = true;
});
info("submitting the form");
await navigateWithoutUserInteraction();
// allow time to pass before concluding no onFormSubmit message was sent
await new Promise(res => setTimeout(res, 1000));
ok(!submitMessageSent, "onFormSubmit message is not sent on navigation since no login fields were modified");
});
</script>
</body>
</html>