summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_generation_telemetry.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_generation_telemetry.html')
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_generation_telemetry.html309
1 files changed, 309 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_generation_telemetry.html b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_generation_telemetry.html
new file mode 100644
index 0000000000..4bbba09483
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_generation_telemetry.html
@@ -0,0 +1,309 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test telemetry on autofill and autocomplete on autocomplete=new-password fields</title>
+ <!-- This test assumes that telemetry events are not cleared after the `setup` task. -->
+ <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: telemetry events during 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(
+ "resource://testing-common/TestUtils.sys.mjs"
+ );
+
+ 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",
+ });
+
+ const TelemetryFilterPropsUsed = Object.freeze({
+ category: "pwmgr",
+ method: "autocomplete_field",
+ object: "generatedpassword",
+ });
+
+ const TelemetryFilterPropsShown = Object.freeze({
+ category: "pwmgr",
+ method: "autocomplete_shown",
+ object: "generatedpassword",
+ });
+
+ async function waitForTelemetryEventsCondition(cond, options = {},
+ errorMsg = "waitForTelemetryEventsCondition timed out", maxTries = 200) {
+ return TestUtils.waitForCondition(async () => {
+ let events = await getTelemetryEvents(options);
+ let result;
+ try {
+ result = cond(events);
+ info("waitForTelemetryEventsCondition, result: " + result);
+ } catch (e) {
+ info("waitForTelemetryEventsCondition caught exception, got events: " + JSON.stringify(events));
+ ok(false, `${e}\n${e.stack}`);
+ }
+ return result ? events : null;
+ }, errorMsg, maxTries);
+ }
+
+ async function showACPopup(formNumber, expectedACLabels) {
+ const autocompleteItems = await popupByArrowDown();
+ checkAutoCompleteResults(autocompleteItems, expectedACLabels,
+ window.location.host, "Check all rows are correct");
+ }
+
+ async function checkTelemetryEventsPWGenShown(expectedPWGenTelemetryEvents) {
+ info(`showed generated password option, check there are now ${expectedPWGenTelemetryEvents} generatedpassword telemetry events`);
+ await waitForTelemetryEventsCondition(events => {
+ return events.length == expectedPWGenTelemetryEvents;
+ }, { process: "parent", filterProps: TelemetryFilterPropsShown }, `Wait for there to be ${expectedPWGenTelemetryEvents} shown telemetry events`);
+ }
+
+ async function checkTelemetryEventsPWGenUsed(expectedPWGenTelemetryEvents) {
+ info("filled generated password again, ensure we don't record another generatedpassword autocomplete telemetry event");
+ let telemetryEvents;
+ try {
+ telemetryEvents = await waitForTelemetryEventsCondition(events => events.length == expectedPWGenTelemetryEvents + 1,
+ { process: "parent", filterProps: TelemetryFilterPropsUsed },
+ `Wait for there to be ${expectedPWGenTelemetryEvents + 1} used events`, 50);
+ } catch (ex) {}
+ ok(!telemetryEvents, `Expected to timeout waiting for there to be ${expectedPWGenTelemetryEvents + 1} events`);
+ }
+
+ function clearGeneratedPasswords() {
+ const { LoginManagerParent } = ChromeUtils.importESModule(
+ "resource://gre/modules/LoginManagerParent.sys.mjs"
+ );
+ if (LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin()) {
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+ }
+ }
+
+ add_setup(async () => {
+ let useEvents = await getTelemetryEvents({ process: "parent", filterProps: TelemetryFilterPropsUsed, clear: true });
+ is(useEvents.length, 0, "Expect 0 use events");
+ let showEvents = await getTelemetryEvents({ process: "parent", filterProps: TelemetryFilterPropsShown, clear: true });
+ is(showEvents.length, 0, "Expect 0 show events");
+ let acEvents = await getTelemetryEvents({ process: "parent", filterProps: TelemetryFilterPropsAC, clear: true });
+ is(acEvents.length, 0, "Expect 0 autocomplete events");
+ });
+
+ 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 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.uname.focus();
+
+ const [acEvent] = await waitForTelemetryEventsCondition(events => {
+ return events.length == 1;
+ }, { process: "parent", filterProps: TelemetryFilterPropsAC, clear: true }, `Wait for there to be 1 autocomplete telemetry event`);
+ checkACTelemetryEvent(acEvent, form.uname, {
+ "hadPrevious": "0",
+ "login": "1",
+ "loginsFooter": "1"
+ });
+ });
+
+ add_named_task("filling out two forms one after the other", async () => {
+ await setPreferencesForTask(
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", true],
+ );
+
+ const formsToTest = [
+ {
+ num: 2,
+ template: formTemplates.form2
+ },
+ {
+ num: 3,
+ template: formTemplates.form3
+ }
+ ];
+
+ // Bug 1616356 and Bug 1548878: Recorded once per origin
+ let expectedPWGenTelemetryEvents = 0;
+ // Bug 1619498: Recorded once every time the autocomplete popup is shown
+ let expectedACShownTelemetryEvents = 0;
+
+ for (const { num: formNumber, template } of formsToTest) {
+ runInParent(clearGeneratedPasswords);
+ await setStoredLoginsAsync([location.origin, "https://autofill", null, "user1", "pass1"]);
+
+ const form = setContentForTask(template);
+ await promiseFormsProcessedInSameProcess();
+ form.reset();
+
+ form.password.focus();
+
+ await showACPopup(formNumber, [
+ "user1",
+ "Use a Securely Generated Password",
+ ]);
+ expectedPWGenTelemetryEvents++;
+ expectedACShownTelemetryEvents++;
+
+ await checkTelemetryEventsPWGenShown(expectedPWGenTelemetryEvents);
+ let acEvents = await waitForTelemetryEventsCondition(events => {
+ return events.length == expectedACShownTelemetryEvents;
+ }, { process: "parent", filterProps: TelemetryFilterPropsAC }, `Wait for there to be ${expectedACShownTelemetryEvents} autocomplete telemetry event(s)`);
+ checkACTelemetryEvent(acEvents[expectedACShownTelemetryEvents - 1], form.password, {
+ "generatedPasswo": "1",
+ "hadPrevious": "0",
+ "login": "1",
+ "loginsFooter": "1"
+ });
+
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_Enter");
+ // Can't use promiseFormsProcessedInSameProcess() when autocomplete fills the field directly.
+ await SimpleTest.promiseWaitForCondition(() => form.password.value == "pass1", "Check pw 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");
+ }
+
+ info("This time select the generated password");
+ await showACPopup(formNumber, [
+ "Use a Securely Generated Password",
+ ]);
+ expectedACShownTelemetryEvents++;
+
+ await checkTelemetryEventsPWGenShown(expectedPWGenTelemetryEvents);
+ acEvents = await waitForTelemetryEventsCondition(events => {
+ return events.length == expectedACShownTelemetryEvents;
+ }, { process: "parent", filterProps: TelemetryFilterPropsAC }, `Wait for there to be ${expectedACShownTelemetryEvents} autocomplete telemetry event(s)`);
+ checkACTelemetryEvent(acEvents[expectedACShownTelemetryEvents - 1], form.password, {
+ "generatedPasswo": "1",
+ "hadPrevious": "0",
+ "loginsFooter": "1"
+ });
+
+ synthesizeKey("KEY_ArrowDown");
+ let storageAddPromise = promiseStorageChanged(["addLogin"]);
+ 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 storageAddPromise;
+
+ let logins = await LoginManager.getAllLogins();
+ let timePasswordChanged = logins[logins.length - 1].timePasswordChanged;
+ let time = dateAndTimeFormatter.format(new Date(timePasswordChanged));
+ const LABEL_NO_USERNAME = "No username (" + time + ")";
+
+ info("Check field is masked upon blurring");
+ synthesizeKey("KEY_Tab"); // blur
+ synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
+ // Remove selection for OS where the whole value is selected upon focus.
+ synthesizeKey("KEY_ArrowRight");
+
+ while (form.password.value) {
+ synthesizeKey("KEY_Backspace");
+ }
+
+ info("Blur the empty field to trigger a 'change' event");
+ synthesizeKey("KEY_Tab"); // blur
+ synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
+
+ info("Type a single character after blanking");
+ synthesizeKey("@");
+
+ info("Blur the single-character field to trigger a 'change' event");
+ synthesizeKey("KEY_Tab"); // blur
+ synthesizeKey("KEY_Tab", { shiftKey: true }); // focus again
+ synthesizeKey("KEY_Backspace"); // Blank the field again
+
+ await showACPopup(formNumber, [
+ LABEL_NO_USERNAME,
+ "Use a Securely Generated Password",
+ ]);
+ expectedACShownTelemetryEvents++;
+
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_Enter");
+ await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check generated pw filled");
+
+ await checkTelemetryEventsPWGenUsed(expectedPWGenTelemetryEvents);
+
+ info("filling the saved login to ensure the field is masked again");
+
+ while (form.password.value) {
+ synthesizeKey("KEY_Backspace");
+ }
+
+ info("Blur the field to trigger a 'change' event again");
+ synthesizeKey("KEY_Tab"); // blur
+ synthesizeKey("KEY_Tab", { shiftKey: true }); // focus 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",
+ ]);
+ expectedACShownTelemetryEvents++;
+
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_Enter");
+ await SimpleTest.promiseWaitForCondition(() => !!form.password.value, "Check saved generated pw filled");
+ synthesizeKey("KEY_Tab"); // blur
+ synthesizeKey("KEY_Tab", { shiftKey: true }); // focus
+ }
+ });
+</script>
+</pre>
+</body>
+</html>