summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html')
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html311
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>