summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/mochitest/formautofill_common.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/test/mochitest/formautofill_common.js')
-rw-r--r--browser/extensions/formautofill/test/mochitest/formautofill_common.js478
1 files changed, 478 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/mochitest/formautofill_common.js b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
new file mode 100644
index 0000000000..d65f0beb49
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -0,0 +1,478 @@
+/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */
+/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/EventUtils.js */
+/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
+/* eslint-disable no-unused-vars */
+
+"use strict";
+
+let formFillChromeScript;
+let defaultTextColor;
+let defaultDisabledTextColor;
+let expectingPopup = null;
+
+const { FormAutofillUtils } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/shared/FormAutofillUtils.sys.mjs"
+);
+
+async function sleep(ms = 500, reason = "Intentionally wait for UI ready") {
+ SimpleTest.requestFlakyTimeout(reason);
+ await new Promise(resolve => setTimeout(resolve, ms));
+}
+
+async function focusAndWaitForFieldsIdentified(
+ input,
+ mustBeIdentified = false
+) {
+ info("expecting the target input being focused and indentified");
+ if (typeof input === "string") {
+ input = document.querySelector(input);
+ }
+ const rootElement = input.form || input.ownerDocument.documentElement;
+ const previouslyFocused = input != document.activeElement;
+
+ input.focus();
+
+ if (mustBeIdentified) {
+ rootElement.removeAttribute("test-formautofill-identified");
+ }
+ if (rootElement.hasAttribute("test-formautofill-identified")) {
+ return;
+ }
+ if (!previouslyFocused) {
+ await new Promise(resolve => {
+ formFillChromeScript.addMessageListener(
+ "FormAutofillTest:FieldsIdentified",
+ function onIdentified() {
+ formFillChromeScript.removeMessageListener(
+ "FormAutofillTest:FieldsIdentified",
+ onIdentified
+ );
+ resolve();
+ }
+ );
+ });
+ }
+ // In order to ensure that "markAsAutofillField" is fully executed, a short period
+ // of timeout is still required.
+ await sleep(300, "Guarantee asynchronous identifyAutofillFields is invoked");
+ rootElement.setAttribute("test-formautofill-identified", "true");
+}
+
+async function setInput(selector, value, userInput = false) {
+ const input = document.querySelector("input" + selector);
+ if (userInput) {
+ SpecialPowers.wrap(input).setUserInput(value);
+ } else {
+ input.value = value;
+ }
+ await focusAndWaitForFieldsIdentified(input);
+
+ return input;
+}
+
+function clickOnElement(selector) {
+ let element = document.querySelector(selector);
+
+ if (!element) {
+ throw new Error("Can not find the element");
+ }
+
+ SimpleTest.executeSoon(() => element.click());
+}
+
+// The equivalent helper function to getAdaptedProfiles in FormAutofillHandler.jsm that
+// transforms the given profile to expected filled profile.
+function _getAdaptedProfile(profile) {
+ const adaptedProfile = Object.assign({}, profile);
+
+ if (profile["street-address"]) {
+ adaptedProfile["street-address"] = FormAutofillUtils.toOneLineAddress(
+ profile["street-address"]
+ );
+ }
+
+ return adaptedProfile;
+}
+
+async function checkFieldHighlighted(elem, expectedValue) {
+ let isHighlightApplied;
+ await SimpleTest.promiseWaitForCondition(function checkHighlight() {
+ isHighlightApplied = elem.matches(":autofill");
+ return isHighlightApplied === expectedValue;
+ }, `Checking #${elem.id} highlight style`);
+
+ is(isHighlightApplied, expectedValue, `Checking #${elem.id} highlight style`);
+}
+
+async function checkFieldPreview(elem, expectedValue) {
+ is(
+ SpecialPowers.wrap(elem).previewValue,
+ expectedValue,
+ `Checking #${elem.id} previewValue`
+ );
+ let isTextColorApplied;
+ await SimpleTest.promiseWaitForCondition(function checkPreview() {
+ const computedStyle = window.getComputedStyle(elem);
+ const actualColor = computedStyle.getPropertyValue("color");
+ if (elem.disabled) {
+ isTextColorApplied = actualColor !== defaultDisabledTextColor;
+ } else {
+ isTextColorApplied = actualColor !== defaultTextColor;
+ }
+ return isTextColorApplied === !!expectedValue;
+ }, `Checking #${elem.id} preview style`);
+
+ is(isTextColorApplied, !!expectedValue, `Checking #${elem.id} preview style`);
+}
+
+async function checkFormFieldsStyle(profile, isPreviewing = true) {
+ const elems = document.querySelectorAll("input, select");
+
+ for (const elem of elems) {
+ let fillableValue;
+ let previewValue;
+ let isElementEligible =
+ FormAutofillUtils.isCreditCardOrAddressFieldType(elem) &&
+ FormAutofillUtils.isFieldAutofillable(elem);
+ if (!isElementEligible) {
+ fillableValue = "";
+ previewValue = "";
+ } else {
+ fillableValue = profile && profile[elem.id];
+ previewValue = (isPreviewing && fillableValue) || "";
+ }
+ await checkFieldHighlighted(elem, !!fillableValue);
+ await checkFieldPreview(elem, previewValue);
+ }
+}
+
+function checkFieldValue(elem, expectedValue) {
+ if (typeof elem === "string") {
+ elem = document.querySelector(elem);
+ }
+ is(elem.value, String(expectedValue), "Checking " + elem.id + " field");
+}
+
+async function triggerAutofillAndCheckProfile(profile) {
+ let adaptedProfile = _getAdaptedProfile(profile);
+ const promises = [];
+ for (const [fieldName, value] of Object.entries(adaptedProfile)) {
+ info(`triggerAutofillAndCheckProfile: ${fieldName}`);
+ const element = document.getElementById(fieldName);
+ const expectingEvent =
+ document.activeElement == element ? "input" : "change";
+ const checkFieldAutofilled = Promise.all([
+ new Promise(resolve => {
+ let beforeInputFired = false;
+ let hadEditor = SpecialPowers.wrap(element).hasEditor;
+ element.addEventListener(
+ "beforeinput",
+ event => {
+ beforeInputFired = true;
+ is(
+ event.inputType,
+ "insertReplacementText",
+ 'inputType value should be "insertReplacementText"'
+ );
+ is(
+ event.data,
+ String(value),
+ `data value of "beforeinput" should be "${value}"`
+ );
+ is(
+ event.dataTransfer,
+ null,
+ 'dataTransfer of "beforeinput" should be null'
+ );
+ is(
+ event.getTargetRanges().length,
+ 0,
+ 'getTargetRanges() of "beforeinput" should return empty array'
+ );
+ is(
+ event.cancelable,
+ SpecialPowers.getBoolPref(
+ "dom.input_event.allow_to_cancel_set_user_input"
+ ),
+ `"beforeinput" event should be cancelable on ${element.tagName} unless it's suppressed by the pref`
+ );
+ is(
+ event.bubbles,
+ true,
+ `"beforeinput" event should always bubble on ${element.tagName}`
+ );
+ resolve();
+ },
+ { once: true }
+ );
+ element.addEventListener(
+ "input",
+ event => {
+ if (element.tagName == "INPUT" && element.type == "text") {
+ if (hadEditor) {
+ ok(
+ beforeInputFired,
+ `"beforeinput" event should've been fired before "input" event on ${element.tagName}`
+ );
+ } else {
+ ok(
+ beforeInputFired,
+ `"beforeinput" event should've been fired before "input" event on ${element.tagName}`
+ );
+ }
+ ok(
+ event instanceof InputEvent,
+ `"input" event should be dispatched with InputEvent interface on ${element.tagName}`
+ );
+ is(
+ event.inputType,
+ "insertReplacementText",
+ 'inputType value should be "insertReplacementText"'
+ );
+ is(event.data, String(value), `data value should be "${value}"`);
+ is(event.dataTransfer, null, "dataTransfer should be null");
+ is(
+ event.getTargetRanges().length,
+ 0,
+ "getTargetRanges() should return empty array"
+ );
+ } else {
+ ok(
+ !beforeInputFired,
+ `"beforeinput" event shouldn't be fired on ${element.tagName}`
+ );
+ ok(
+ event instanceof Event && !(event instanceof UIEvent),
+ `"input" event should be dispatched with Event interface on ${element.tagName}`
+ );
+ }
+ is(
+ event.cancelable,
+ false,
+ `"input" event should be never cancelable on ${element.tagName}`
+ );
+ is(
+ event.bubbles,
+ true,
+ `"input" event should always bubble on ${element.tagName}`
+ );
+ resolve();
+ },
+ { once: true }
+ );
+ }),
+ new Promise(resolve =>
+ element.addEventListener(expectingEvent, resolve, { once: true })
+ ),
+ ]).then(() => checkFieldValue(element, value));
+
+ promises.push(checkFieldAutofilled);
+ }
+ // Press Enter key and trigger form autofill.
+ synthesizeKey("KEY_Enter");
+
+ return Promise.all(promises);
+}
+
+async function onStorageChanged(type) {
+ info(`expecting the storage changed: ${type}`);
+ return new Promise(resolve => {
+ formFillChromeScript.addMessageListener(
+ "formautofill-storage-changed",
+ function onChanged(data) {
+ formFillChromeScript.removeMessageListener(
+ "formautofill-storage-changed",
+ onChanged
+ );
+ is(data.data, type, `Receive ${type} storage changed event`);
+ resolve();
+ }
+ );
+ });
+}
+
+function checkMenuEntries(expectedValues, isFormAutofillResult = true) {
+ let actualValues = getMenuEntries();
+ // Expect one more item would appear at the bottom as the footer if the result is from form autofill.
+ let expectedLength = isFormAutofillResult
+ ? expectedValues.length + 1
+ : expectedValues.length;
+
+ is(actualValues.length, expectedLength, " Checking length of expected menu");
+ for (let i = 0; i < expectedValues.length; i++) {
+ is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
+ }
+}
+
+function invokeAsyncChromeTask(message, payload = {}) {
+ info(`expecting the chrome task finished: ${message}`);
+ return formFillChromeScript.sendQuery(message, payload);
+}
+
+async function addAddress(address) {
+ await invokeAsyncChromeTask("FormAutofillTest:AddAddress", { address });
+ await sleep();
+}
+
+async function removeAddress(guid) {
+ return invokeAsyncChromeTask("FormAutofillTest:RemoveAddress", { guid });
+}
+
+async function updateAddress(guid, address) {
+ return invokeAsyncChromeTask("FormAutofillTest:UpdateAddress", {
+ address,
+ guid,
+ });
+}
+
+async function checkAddresses(expectedAddresses) {
+ return invokeAsyncChromeTask("FormAutofillTest:CheckAddresses", {
+ expectedAddresses,
+ });
+}
+
+async function cleanUpAddresses() {
+ return invokeAsyncChromeTask("FormAutofillTest:CleanUpAddresses");
+}
+
+async function addCreditCard(creditcard) {
+ await invokeAsyncChromeTask("FormAutofillTest:AddCreditCard", { creditcard });
+ await sleep();
+}
+
+async function removeCreditCard(guid) {
+ return invokeAsyncChromeTask("FormAutofillTest:RemoveCreditCard", { guid });
+}
+
+async function checkCreditCards(expectedCreditCards) {
+ return invokeAsyncChromeTask("FormAutofillTest:CheckCreditCards", {
+ expectedCreditCards,
+ });
+}
+
+async function cleanUpCreditCards() {
+ return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards");
+}
+
+async function cleanUpStorage() {
+ await cleanUpAddresses();
+ await cleanUpCreditCards();
+}
+
+async function canTestOSKeyStoreLogin() {
+ let { canTest } = await invokeAsyncChromeTask(
+ "FormAutofillTest:CanTestOSKeyStoreLogin"
+ );
+ return canTest;
+}
+
+async function waitForOSKeyStoreLogin(login = false) {
+ await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", { login });
+}
+
+function patchRecordCCNumber(record) {
+ const number = record["cc-number"];
+ const ccNumberFmt = {
+ affix: "****",
+ label: number.substr(-4),
+ };
+
+ return Object.assign({}, record, { ccNumberFmt });
+}
+
+// Utils for registerPopupShownListener(in satchel_common.js) that handles dropdown popup
+// Please call "initPopupListener()" in your test and "await expectPopup()"
+// if you want to wait for dropdown menu displayed.
+function expectPopup() {
+ info("expecting a popup");
+ return new Promise(resolve => {
+ expectingPopup = resolve;
+ });
+}
+
+function notExpectPopup(ms = 500) {
+ info("not expecting a popup");
+ return new Promise((resolve, reject) => {
+ expectingPopup = reject.bind(this, "Unexpected Popup");
+ // TODO: We don't have an event to notify no popup showing, so wait for 500
+ // ms (in default) to predict any unexpected popup showing.
+ setTimeout(resolve, ms);
+ });
+}
+
+function popupShownListener() {
+ info("popup shown for test ");
+ if (expectingPopup) {
+ expectingPopup();
+ expectingPopup = null;
+ }
+}
+
+function initPopupListener() {
+ registerPopupShownListener(popupShownListener);
+}
+
+async function triggerPopupAndHoverItem(fieldSelector, selectIndex) {
+ await focusAndWaitForFieldsIdentified(fieldSelector);
+ synthesizeKey("KEY_ArrowDown");
+ await expectPopup();
+ for (let i = 0; i <= selectIndex; i++) {
+ synthesizeKey("KEY_ArrowDown");
+ }
+ await notifySelectedIndex(selectIndex);
+}
+
+function formAutoFillCommonSetup() {
+ // Remove the /creditCard path segement when referenced from the 'creditCard' subdirectory.
+ let chromeURL = SimpleTest.getTestFileURL(
+ "formautofill_parent_utils.js"
+ ).replace(/\/creditCard/, "");
+ formFillChromeScript = SpecialPowers.loadChromeScript(chromeURL);
+ formFillChromeScript.addMessageListener("onpopupshown", ({ results }) => {
+ gLastAutoCompleteResults = results;
+ if (gPopupShownListener) {
+ gPopupShownListener({ results });
+ }
+ });
+
+ add_setup(async () => {
+ info(`expecting the storage setup`);
+ await formFillChromeScript.sendQuery("setup");
+ });
+
+ SimpleTest.registerCleanupFunction(async () => {
+ info(`expecting the storage cleanup`);
+ await formFillChromeScript.sendQuery("cleanup");
+
+ formFillChromeScript.destroy();
+ expectingPopup = null;
+ });
+
+ document.addEventListener(
+ "DOMContentLoaded",
+ function () {
+ defaultTextColor = window
+ .getComputedStyle(document.querySelector("input"))
+ .getPropertyValue("color");
+
+ // This is needed for test_formautofill_preview_highlight.html to work properly
+ let disabledInput = document.querySelector(`input[disabled]`);
+ if (disabledInput) {
+ defaultDisabledTextColor = window
+ .getComputedStyle(disabledInput)
+ .getPropertyValue("color");
+ }
+ },
+ { once: true }
+ );
+}
+
+/*
+ * Extremely over-simplified detection of card type from card number just for
+ * our tests. This is needed to test the aria-label of credit card menu entries.
+ */
+function getCCTypeName(creditCard) {
+ return creditCard["cc-number"][0] == "4" ? "Visa" : "MasterCard";
+}
+
+formAutoFillCommonSetup();