diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html b/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html new file mode 100644 index 0000000000..c31efe6a98 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html @@ -0,0 +1,311 @@ +<!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> |