summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/browser/creditCard
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/test/browser/creditCard')
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser.ini51
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_anti_clickjacking.js123
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_action.js170
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_display.js311
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_fields.js198
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_iframe.js103
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_logo.js238
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_sync.js117
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_dropdown_layout.js57
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_fill_cancel_login.js37
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics.js165
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics_cc_type.js77
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_autodetect_type.js104
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_normalized.js109
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_telemetry.js872
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_editCreditCardDialog.js422
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_insecure_form.js145
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/browser_manageCreditCardsDialog.js290
-rw-r--r--browser/extensions/formautofill/test/browser/creditCard/head_cc.js1
19 files changed, 3590 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser.ini b/browser/extensions/formautofill/test/browser/creditCard/browser.ini
new file mode 100644
index 0000000000..613b3cf126
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser.ini
@@ -0,0 +1,51 @@
+[DEFAULT]
+prefs =
+ extensions.formautofill.creditCards.enabled=true
+ extensions.formautofill.reauth.enabled=true
+ # lower the interval for event telemetry in the content process to update the parent process
+ toolkit.telemetry.ipcBatchTimeout=0
+support-files =
+ ../head.js
+ !/browser/extensions/formautofill/test/fixtures/autocomplete_basic.html
+ ../../fixtures/autocomplete_creditcard_basic.html
+ ../../fixtures/autocomplete_creditcard_iframe.html
+ ../../fixtures/autocomplete_creditcard_cc_exp_field.html
+ ../../fixtures/without_autocomplete_creditcard_basic.html
+ head_cc.js
+
+[browser_anti_clickjacking.js]
+skip-if = !debug && os == "mac" # perma-fail see Bug 1600059
+[browser_creditCard_doorhanger_action.js]
+skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
+[browser_creditCard_doorhanger_display.js]
+skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
+[browser_creditCard_doorhanger_fields.js]
+skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
+[browser_creditCard_doorhanger_iframe.js]
+skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
+[browser_creditCard_doorhanger_logo.js]
+skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
+[browser_creditCard_doorhanger_sync.js]
+skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
+[browser_creditCard_dropdown_layout.js]
+skip-if = ((os == "mac") || (os == 'linux') || (os == 'win'))
+[browser_creditCard_fill_cancel_login.js]
+skip-if = ((!debug && os == "mac") || (os == 'linux') || (os == 'win'))
+[browser_creditCard_heuristics.js]
+skip-if = apple_silicon && !debug # Bug 1714221
+[browser_creditCard_heuristics_cc_type.js]
+skip-if = apple_silicon && !debug # Bug 1714221
+[browser_creditCard_submission_autodetect_type.js]
+skip-if = apple_silicon && !debug
+[browser_creditCard_submission_normalized.js]
+skip-if = apple_silicon && !debug
+[browser_creditCard_telemetry.js]
+skip-if =
+ apple_silicon && !debug # Bug 1714221
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_editCreditCardDialog.js]
+skip-if = ((os == 'linux') || (os == "mac") || (os == 'win')) # perma-fail see Bug 1600059
+[browser_insecure_form.js]
+skip-if = ((os == 'mac') || (os == 'linux') || (os == 'win')) # bug 1456284
+[browser_manageCreditCardsDialog.js]
+skip-if = ((os == 'win') || (os == 'mac') || (os == 'linux'))
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_anti_clickjacking.js b/browser/extensions/formautofill/test/browser/creditCard/browser_anti_clickjacking.js
new file mode 100644
index 0000000000..ff5feb54df
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_anti_clickjacking.js
@@ -0,0 +1,123 @@
+"use strict";
+
+const ADDRESS_URL =
+ "http://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
+const CC_URL =
+ "https://example.org/browser/browser/extensions/formautofill/test/browser/creditCard/autocomplete_creditcard_basic.html";
+
+add_task(async function setup_storage() {
+ await setStorage(
+ TEST_ADDRESS_1,
+ TEST_ADDRESS_2,
+ TEST_ADDRESS_3,
+ TEST_CREDIT_CARD_1,
+ TEST_CREDIT_CARD_2,
+ TEST_CREDIT_CARD_3
+ );
+});
+
+add_task(async function test_active_delay() {
+ // This is a workaround for the fact that we don't have a way
+ // to know when the popup was opened exactly and this makes our test
+ // racy when ensuring that we first test for disabled items before
+ // the delayed enabling happens.
+ //
+ // In the future we should consider adding an event when a popup
+ // gets opened and listen for it in this test before we check if the item
+ // is disabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.notification_enable_delay", 1000],
+ ["extensions.formautofill.reauth.enabled", false],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CC_URL },
+ async function (browser) {
+ const focusInput = "#cc-number";
+
+ // Open the popup -- we don't use openPopupOn() because there
+ // are things we need to check between these steps.
+ await SimpleTest.promiseFocus(browser);
+ const start = performance.now();
+ await runAndWaitForAutocompletePopupOpen(browser, async () => {
+ await focusAndWaitForFieldsIdentified(browser, focusInput);
+ });
+ const firstItem = getDisplayedPopupItems(browser)[0];
+ ok(firstItem.disabled, "Popup should be disbled upon opening.");
+ is(
+ browser.autoCompletePopup.selectedIndex,
+ -1,
+ "No item selected at first"
+ );
+
+ // Check that clicking on menu doesn't do anything while
+ // it is disabled
+ firstItem.click();
+ is(
+ browser.autoCompletePopup.selectedIndex,
+ -1,
+ "No item selected after clicking on disabled item"
+ );
+
+ // Check that the delay before enabling is as long as expected
+ await waitForPopupEnabled(browser);
+ const delta = performance.now() - start;
+ info(`Popup was disabled for ${delta} ms`);
+ ok(delta >= 1000, "Popup was disabled for at least 1000 ms");
+
+ // Check the clicking on the menu works now
+ firstItem.click();
+ is(
+ browser.autoCompletePopup.selectedIndex,
+ 0,
+ "First item selected after clicking on enabled item"
+ );
+
+ // Clean up
+ await closePopup(browser);
+ }
+ );
+});
+
+add_task(async function test_no_delay() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.notification_enable_delay", 1000],
+ ["extensions.formautofill.reauth.enabled", false],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_URL },
+ async function (browser) {
+ const focusInput = "#organization";
+
+ // Open the popup -- we don't use openPopupOn() because there
+ // are things we need to check between these steps.
+ await SimpleTest.promiseFocus(browser);
+ await runAndWaitForAutocompletePopupOpen(browser, async () => {
+ await focusAndWaitForFieldsIdentified(browser, focusInput);
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ });
+ const firstItem = getDisplayedPopupItems(browser)[0];
+ ok(!firstItem.disabled, "Popup should be enabled upon opening.");
+ is(
+ browser.autoCompletePopup.selectedIndex,
+ -1,
+ "No item selected at first"
+ );
+
+ // Check that clicking on menu doesn't do anything while
+ // it is disabled
+ firstItem.click();
+ is(
+ browser.autoCompletePopup.selectedIndex,
+ 0,
+ "First item selected after clicking on enabled item"
+ );
+
+ // Clean up
+ await closePopup(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_action.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_action.js
new file mode 100644
index 0000000000..82122925d7
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_action.js
@@ -0,0 +1,170 @@
+"use strict";
+
+add_task(async function test_save_doorhanger_click_save() {
+ let onChanged = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": "5577000055770004",
+ "#cc-exp-month": "12",
+ "#cc-exp-year": "2017",
+ "#cc-type": "mastercard",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+ is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
+ is(creditCards[0]["cc-type"], "mastercard", "Verify the cc-type field");
+ await removeAllRecords();
+});
+
+add_task(async function test_save_doorhanger_click_never_save() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 0",
+ "#cc-number": "6387060366272981",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MENU_BUTTON, 0);
+ }
+ );
+
+ await sleep(1000);
+ let creditCards = await getCreditCards();
+ let creditCardPref = SpecialPowers.getBoolPref(
+ ENABLED_AUTOFILL_CREDITCARDS_PREF
+ );
+ is(creditCards.length, 0, "No credit card in storage");
+ is(creditCardPref, false, "Credit card is disabled");
+ SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+});
+
+add_task(async function test_save_doorhanger_click_cancel_save() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": "5038146897157463",
+ },
+ });
+
+ ok(
+ !SpecialPowers.Services.prefs.prefHasUserValue(SYNC_USERNAME_PREF),
+ "Sync account should not exist by default"
+ );
+ await onPopupShown;
+ let cb = getDoorhangerCheckbox();
+ ok(cb.hidden, "Sync checkbox should be hidden");
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+
+ await sleep(1000);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 0, "No credit card saved");
+});
+
+add_task(async function test_update_doorhanger_click_update() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onChanged = waitForStorageChangedEvents("update", "notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "Mark Smith",
+ "#cc-number": "4111111111111111",
+ "#cc-exp-month": "4",
+ "#cc-exp-year": new Date().getFullYear(),
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card in storage");
+ is(creditCards[0]["cc-name"], "Mark Smith", "name field got updated");
+ await removeAllRecords();
+});
+
+add_task(async function test_update_doorhanger_click_save() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+ let onChanged = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ let onPopupShown = waitForPopupShown();
+ await openPopupOn(browser, "form #cc-name");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(browser, "#cc-name", "John Doe");
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ await osKeyStoreLoginShown;
+ }
+ );
+ await onChanged;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 2, "2 credit cards in storage");
+ is(
+ creditCards[0]["cc-name"],
+ TEST_CREDIT_CARD_1["cc-name"],
+ "Original record's cc-name field is unchanged"
+ );
+ is(creditCards[1]["cc-name"], "User 1", "cc-name field in the new record");
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_display.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_display.js
new file mode 100644
index 0000000000..715eceb3eb
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_display.js
@@ -0,0 +1,311 @@
+"use strict";
+
+add_task(async function test_save_doorhanger_shown_no_profile() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": "5577000055770004",
+ "#cc-exp-month": "12",
+ "#cc-exp-year": "2017",
+ "#cc-type": "mastercard",
+ },
+ });
+
+ await onPopupShown;
+ }
+ );
+});
+
+add_task(async function test_save_doorhanger_shown_different_card_number() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-number": TEST_CREDIT_CARD_2["cc-number"],
+ },
+ });
+
+ await onPopupShown;
+ }
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_update_doorhanger_shown_different_card_name() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "Mark Smith",
+ "#cc-number": TEST_CREDIT_CARD_1["cc-number"],
+ "#cc-exp-month": TEST_CREDIT_CARD_1["cc-exp-month"],
+ "#cc-exp-year": TEST_CREDIT_CARD_1["cc-exp-year"],
+ },
+ });
+
+ await onPopupShown;
+ }
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_update_doorhanger_shown_different_card_expiry() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": TEST_CREDIT_CARD_1["cc-name"],
+ "#cc-number": TEST_CREDIT_CARD_1["cc-number"],
+ "#cc-exp-month": "12",
+ "#cc-exp-year": "2099",
+ },
+ });
+
+ await onPopupShown;
+ }
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_doorhanger_not_shown_when_autofill_untouched() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ await openPopupOn(browser, "form #cc-name");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await osKeyStoreLoginShown;
+ await waitForAutofill(browser, "#cc-name", "John Doe");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let form = content.document.getElementById("form");
+ form.querySelector("input[type=submit]").click();
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onUsed;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card");
+ is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
+ await removeAllRecords();
+});
+
+add_task(async function test_doorhanger_not_shown_when_fill_duplicate() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "John Doe",
+ "#cc-number": "4111111111111111",
+ "#cc-exp-month": "4",
+ "#cc-exp-year": new Date().getFullYear(),
+ "#cc-type": "visa",
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onUsed;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card in storage");
+ is(
+ creditCards[0]["cc-name"],
+ TEST_CREDIT_CARD_1["cc-name"],
+ "Verify the name field"
+ );
+ is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
+ await removeAllRecords();
+});
+
+add_task(
+ async function test_doorhanger_not_shown_when_autofill_then_fill_everything_duplicate() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 2, "2 credit card in storage");
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ await openPopupOn(browser, "form #cc-number");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(
+ browser,
+ "#cc-number",
+ TEST_CREDIT_CARD_1["cc-number"]
+ );
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ // Change number to the second credit card number
+ "#cc-number": TEST_CREDIT_CARD_2["cc-number"],
+ "#cc-name": TEST_CREDIT_CARD_2["cc-name"],
+ "#cc-exp-month": TEST_CREDIT_CARD_2["cc-exp-month"],
+ "#cc-exp-year": TEST_CREDIT_CARD_2["cc-exp-year"],
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ await osKeyStoreLoginShown;
+ }
+ );
+ await onUsed;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 2, "Still 2 credit card");
+ await removeAllRecords();
+ }
+);
+
+add_task(
+ async function test_doorhanger_not_shown_when_autofill_then_fill_number_duplicate() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1, {
+ ...TEST_CREDIT_CARD_1,
+ ...{ "cc-number": "5105105105105100" },
+ });
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 2, "2 credit card in storage");
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ await openPopupOn(browser, "form #cc-number");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(
+ browser,
+ "#cc-number",
+ TEST_CREDIT_CARD_1["cc-number"]
+ );
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ // Change number to the second credit card number
+ "#cc-number": "5105105105105100",
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ await osKeyStoreLoginShown;
+ }
+ );
+ await onUsed;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 2, "Still 2 credit card");
+ await removeAllRecords();
+ }
+);
+
+add_task(async function test_update_doorhanger_shown_when_fill_mergeable() {
+ await setStorage(TEST_CREDIT_CARD_3);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onChanged = waitForStorageChangedEvents("update", "notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 3",
+ "#cc-number": "5103059495477870",
+ "#cc-exp-month": "1",
+ "#cc-exp-year": "2000",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card in storage");
+ is(creditCards[0]["cc-name"], "User 3", "Verify the name field");
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_fields.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_fields.js
new file mode 100644
index 0000000000..c1ebef737e
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_fields.js
@@ -0,0 +1,198 @@
+"use strict";
+
+add_task(async function test_update_autofill_name_field() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onChanged = waitForStorageChangedEvents("update", "notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ let onPopupShown = waitForPopupShown();
+
+ await openPopupOn(browser, "form #cc-name");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await osKeyStoreLoginShown;
+ await waitForAutofill(browser, "#cc-name", "John Doe");
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card");
+ is(creditCards[0]["cc-name"], "User 1", "cc-name field is updated");
+ is(
+ creditCards[0]["cc-number"],
+ "************1111",
+ "Verify the card number field"
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_update_autofill_exp_date_field() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+ let onChanged = waitForStorageChangedEvents("update", "notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ let onPopupShown = waitForPopupShown();
+ await openPopupOn(browser, "form #cc-name");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await osKeyStoreLoginShown;
+ await waitForAutofill(browser, "#cc-name", "John Doe");
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-exp-year": "2019",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card");
+ is(creditCards[0]["cc-exp-year"], 2019, "cc-exp-year field is updated");
+ is(
+ creditCards[0]["cc-number"],
+ "************1111",
+ "Verify the card number field"
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_submit_unnormailzed_field() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "John Doe",
+ "#cc-number": "4111111111111111",
+ "#cc-exp-month": "4",
+ "#cc-exp-year": new Date().getFullYear().toString().substr(2, 2),
+ "#cc-type": "visa",
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card in storage");
+ is(
+ creditCards[0]["cc-exp-year"],
+ new Date().getFullYear(),
+ "Verify the expiry year field"
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_submit_invalid_network_field() {
+ let onChanged = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": "5038146897157463",
+ "#cc-exp-month": "12",
+ "#cc-exp-year": "2017",
+ "#cc-type": "gringotts",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+ is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
+ is(
+ creditCards[0]["cc-type"],
+ undefined,
+ "Invalid network/cc-type was not saved"
+ );
+
+ await removeAllRecords();
+});
+
+add_task(async function test_submit_combined_expiry_field() {
+ let onChanged = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_COMBINED_EXPIRY_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "John Doe",
+ "#cc-number": "374542158116607",
+ "#cc-exp": "05/28",
+ },
+ });
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Card should be added");
+ is(creditCards[0]["cc-exp"], "2028-05", "Verify cc-exp field");
+ is(creditCards[0]["cc-exp-month"], 5, "Verify cc-exp-month field");
+ is(creditCards[0]["cc-exp-year"], 2028, "Verify cc-exp-year field");
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_iframe.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_iframe.js
new file mode 100644
index 0000000000..2781e5acf6
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_iframe.js
@@ -0,0 +1,103 @@
+"use strict";
+
+add_task(async function test_iframe_submit_untouched_creditCard_form() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ // This test triggers two form submission events so cc 'timesUsed' count is 2.
+ // The first submission is triggered by standard form submission, and the
+ // second is triggered by page hiding.
+ const EXPECTED_ON_USED_COUNT = 2;
+ let notifyUsedCounter = EXPECTED_ON_USED_COUNT;
+ let onUsed = TestUtils.topicObserved(
+ "formautofill-storage-changed",
+ (subject, data) => {
+ if (data == "notifyUsed") {
+ notifyUsedCounter--;
+ }
+ return notifyUsedCounter == 0;
+ }
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_IFRAME_URL },
+ async function (browser) {
+ let osKeyStoreLoginShown =
+ OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ let iframeBC = browser.browsingContext.children[0];
+ await openPopupOnSubframe(browser, iframeBC, "form #cc-name");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ await osKeyStoreLoginShown;
+ await waitForAutofill(iframeBC, "#cc-name", "John Doe");
+
+ await SpecialPowers.spawn(iframeBC, [], async function () {
+ let form = content.document.getElementById("form");
+ form.querySelector("input[type=submit]").click();
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onUsed;
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 1, "Still 1 credit card");
+ is(
+ creditCards[0].timesUsed,
+ EXPECTED_ON_USED_COUNT,
+ "timesUsed field set to 2"
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_iframe_unload_save_card() {
+ let onChanged = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_IFRAME_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let iframeBC = browser.browsingContext.children[0];
+ await focusUpdateSubmitForm(
+ iframeBC,
+ {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": "4556194630960970",
+ "#cc-exp-month": "10",
+ "#cc-exp-year": "2024",
+ "#cc-type": "visa",
+ },
+ },
+ false
+ );
+
+ info("Removing iframe without submitting");
+ await SpecialPowers.spawn(browser, [], async function () {
+ let frame = content.document.querySelector("iframe");
+ frame.remove();
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+ is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
+ is(creditCards[0]["cc-type"], "visa", "Verify the cc-type field");
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_logo.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_logo.js
new file mode 100644
index 0000000000..9c4f4e4825
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_logo.js
@@ -0,0 +1,238 @@
+"use strict";
+
+/*
+ The next four tests look very similar because if we try to do multiple
+ credit card operations in one test, there's a good chance the test will timeout
+ and produce an invalid result.
+ We mitigate this issue by having each test only deal with one credit card in storage
+ and one credit card operation.
+*/
+add_task(async function test_submit_third_party_creditCard_logo() {
+ const amexCard = {
+ "cc-number": "374542158116607",
+ "cc-type": "amex",
+ "cc-name": "John Doe",
+ };
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": amexCard["cc-number"],
+ },
+ });
+
+ await onPopupShown;
+ let doorhanger = getNotification();
+ let creditCardLogo = doorhanger.querySelector(".desc-message-box image");
+ let creditCardLogoWithoutExtension = creditCardLogo.src.split(".", 1)[0];
+
+ is(
+ creditCardLogoWithoutExtension,
+ "chrome://formautofill/content/third-party/cc-logo-amex",
+ "CC logo should be amex"
+ );
+
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_update_third_party_creditCard_logo() {
+ const amexCard = {
+ "cc-number": "374542158116607",
+ "cc-name": "John Doe",
+ };
+
+ await setStorage(amexCard);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onChanged = waitForStorageChangedEvents("update");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "Mark Smith",
+ "#cc-number": amexCard["cc-number"],
+ "#cc-exp-month": "4",
+ "#cc-exp-year": new Date().getFullYear(),
+ },
+ });
+
+ await onPopupShown;
+
+ let doorhanger = getNotification();
+ let creditCardLogo = doorhanger.querySelector(".desc-message-box image");
+ let creditCardLogoWithoutExtension = creditCardLogo.src.split(".", 1)[0];
+ is(
+ creditCardLogoWithoutExtension,
+ `chrome://formautofill/content/third-party/cc-logo-amex`,
+ `CC Logo should be amex`
+ );
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+ await removeAllRecords();
+});
+
+add_task(async function test_submit_generic_creditCard_logo() {
+ const genericCard = {
+ "cc-number": "937899583135",
+ "cc-name": "John Doe",
+ };
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": genericCard["cc-number"],
+ },
+ });
+
+ await onPopupShown;
+ let doorhanger = getNotification();
+ let creditCardLogo = doorhanger.querySelector(".desc-message-box image");
+ let creditCardLogoWithoutExtension = creditCardLogo.src.split(".", 1)[0];
+
+ is(
+ creditCardLogoWithoutExtension,
+ "chrome://formautofill/content/icon-credit-card-generic",
+ "CC logo should be generic"
+ );
+
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+ await removeAllRecords();
+});
+
+add_task(async function test_update_generic_creditCard_logo() {
+ const genericCard = {
+ "cc-number": "937899583135",
+ "cc-name": "John Doe",
+ };
+
+ await setStorage(genericCard);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onChanged = waitForStorageChangedEvents("update");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "Mark Smith",
+ "#cc-number": genericCard["cc-number"],
+ "#cc-exp-month": "4",
+ "#cc-exp-year": new Date().getFullYear(),
+ },
+ });
+
+ await onPopupShown;
+
+ let doorhanger = getNotification();
+ let creditCardLogo = doorhanger.querySelector(".desc-message-box image");
+ let creditCardLogoWithoutExtension = creditCardLogo.src.split(".", 1)[0];
+ is(
+ creditCardLogoWithoutExtension,
+ `chrome://formautofill/content/icon-credit-card-generic`,
+ `CC Logo should be generic`
+ );
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+ await removeAllRecords();
+});
+
+add_task(async function test_save_panel_spaces_in_cc_number_logo() {
+ const amexCard = {
+ "cc-number": "37 4542 158116 607",
+ "cc-type": "amex",
+ "cc-name": "John Doe",
+ };
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-number": amexCard["cc-number"],
+ },
+ });
+
+ await onPopupShown;
+ let doorhanger = getNotification();
+ let creditCardLogo = doorhanger.querySelector(".desc-message-box image");
+ let creditCardLogoWithoutExtension = creditCardLogo.src.split(".", 1)[0];
+
+ is(
+ creditCardLogoWithoutExtension,
+ "chrome://formautofill/content/third-party/cc-logo-amex",
+ "CC logo should be amex"
+ );
+
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+});
+
+add_task(async function test_update_panel_with_spaces_in_cc_number_logo() {
+ const amexCard = {
+ "cc-number": "374 54215 8116607",
+ "cc-name": "John Doe",
+ };
+
+ await setStorage(amexCard);
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "1 credit card in storage");
+
+ let onChanged = waitForStorageChangedEvents("update", "notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "Mark Smith",
+ "#cc-number": amexCard["cc-number"],
+ "#cc-exp-month": "4",
+ "#cc-exp-year": new Date().getFullYear(),
+ },
+ });
+
+ await onPopupShown;
+
+ let doorhanger = getNotification();
+ let creditCardLogo = doorhanger.querySelector(".desc-message-box image");
+ let creditCardLogoWithoutExtension = creditCardLogo.src.split(".", 1)[0];
+ is(
+ creditCardLogoWithoutExtension,
+ `chrome://formautofill/content/third-party/cc-logo-amex`,
+ `CC Logo should be amex`
+ );
+ // We are not testing whether the cc-number is saved correctly,
+ // we only care that the logo in the update panel shows correctly.
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+ await onChanged;
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_sync.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_sync.js
new file mode 100644
index 0000000000..2064928820
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_doorhanger_sync.js
@@ -0,0 +1,117 @@
+"use strict";
+
+add_task(async function test_submit_creditCard_with_sync_account() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [SYNC_USERNAME_PREF, "foo@bar.com"],
+ [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
+ [ENABLED_AUTOFILL_CREDITCARDS_PREF, true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 2",
+ "#cc-number": "6387060366272981",
+ },
+ });
+
+ await onPopupShown;
+ let cb = getDoorhangerCheckbox();
+ ok(!cb.hidden, "Sync checkbox should be visible");
+ is(
+ SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF),
+ false,
+ "creditCards sync should be disabled by default"
+ );
+
+ // Verify if the checkbox and button state is changed.
+ let secondaryButton = getDoorhangerButton(SECONDARY_BUTTON);
+ let menuButton = getDoorhangerButton(MENU_BUTTON);
+ is(
+ cb.checked,
+ false,
+ "Checkbox state should match creditCards sync state"
+ );
+ is(
+ secondaryButton.disabled,
+ false,
+ "Not saving button should be enabled"
+ );
+ is(
+ menuButton.disabled,
+ false,
+ "Never saving menu button should be enabled"
+ );
+ // Click the checkbox to enable credit card sync.
+ cb.click();
+ is(
+ SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF),
+ true,
+ "creditCards sync should be enabled after checked"
+ );
+ is(
+ secondaryButton.disabled,
+ true,
+ "Not saving button should be disabled"
+ );
+ is(
+ menuButton.disabled,
+ true,
+ "Never saving menu button should be disabled"
+ );
+ // Click the checkbox again to disable credit card sync.
+ cb.click();
+ is(
+ SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF),
+ false,
+ "creditCards sync should be disabled after unchecked"
+ );
+ is(
+ secondaryButton.disabled,
+ false,
+ "Not saving button should be enabled again"
+ );
+ is(
+ menuButton.disabled,
+ false,
+ "Never saving menu button should be enabled again"
+ );
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+});
+
+add_task(async function test_submit_creditCard_with_synced_already() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [SYNC_CREDITCARDS_PREF, true],
+ [SYNC_USERNAME_PREF, "foo@bar.com"],
+ [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 2",
+ "#cc-number": "6387060366272981",
+ },
+ });
+
+ await onPopupShown;
+ let cb = getDoorhangerCheckbox();
+ ok(cb.hidden, "Sync checkbox should be hidden");
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_dropdown_layout.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_dropdown_layout.js
new file mode 100644
index 0000000000..d69f7129ee
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_dropdown_layout.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const CC_URL =
+ "https://example.org/browser/browser/extensions/formautofill/test/browser/creditCard/autocomplete_creditcard_basic.html";
+
+add_task(async function setup_storage() {
+ await setStorage(TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3);
+});
+
+async function reopenPopupWithResizedInput(browser, selector, newSize) {
+ await closePopup(browser);
+ /* eslint no-shadow: ["error", { "allow": ["selector", "newSize"] }] */
+ await SpecialPowers.spawn(
+ browser,
+ [{ selector, newSize }],
+ async function ({ selector, newSize }) {
+ const input = content.document.querySelector(selector);
+
+ input.style.boxSizing = "border-box";
+ input.style.width = newSize + "px";
+ }
+ );
+ await openPopupOn(browser, selector);
+}
+
+add_task(async function test_credit_card_dropdown() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CC_URL },
+ async function (browser) {
+ const focusInput = "#cc-number";
+ await openPopupOn(browser, focusInput);
+ const firstItem = getDisplayedPopupItems(browser)[0];
+
+ isnot(firstItem.getAttribute("ac-image"), "", "Should show icon");
+ ok(
+ firstItem.getAttribute("aria-label").startsWith("Visa "),
+ "aria-label should start with Visa"
+ );
+
+ // The breakpoint of two-lines layout is 185px
+ await reopenPopupWithResizedInput(browser, focusInput, 175);
+ is(
+ firstItem._itemBox.getAttribute("size"),
+ "small",
+ "Show two-lines layout"
+ );
+ await reopenPopupWithResizedInput(browser, focusInput, 195);
+ is(
+ firstItem._itemBox.hasAttribute("size"),
+ false,
+ "Show one-line layout"
+ );
+
+ await closePopup(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_fill_cancel_login.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_fill_cancel_login.js
new file mode 100644
index 0000000000..6304e4699f
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_fill_cancel_login.js
@@ -0,0 +1,37 @@
+"use strict";
+
+add_task(async function test_fill_creditCard_but_cancel_login() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_2);
+
+ let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(false); // cancel
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ await openPopupOn(browser, "#cc-name");
+ const ccItem = getDisplayedPopupItems(browser)[0];
+ let popupClosePromise = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "hidden"
+ );
+ await EventUtils.synthesizeMouseAtCenter(ccItem, {});
+ await Promise.all([osKeyStoreLoginShown, popupClosePromise]);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ is(content.document.querySelector("#cc-name").value, "", "Check name");
+ is(
+ content.document.querySelector("#cc-number").value,
+ "",
+ "Check number"
+ );
+ });
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics.js
new file mode 100644
index 0000000000..3801239234
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics.js
@@ -0,0 +1,165 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TESTCASES = [
+ {
+ description: "@autocomplete - all fields in the same form",
+ document: `<form>
+ <input id="cc-number" autocomplete="cc-number">
+ <input id="cc-name" autocomplete="cc-name">
+ <input id="cc-exp" autocomplete="cc-exp">
+ </form>`,
+ idsToShowPopup: ["cc-number", "cc-name", "cc-exp"],
+ },
+ {
+ description: "without @autocomplete - all fields in the same form",
+ document: `<form>
+ <input id="cc-number" placeholder="credit card number">
+ <input id="cc-name" placeholder="credit card holder name">
+ <input id="cc-exp" placeholder="expiration date">
+ </form>`,
+ idsToShowPopup: ["cc-number", "cc-name", "cc-exp"],
+ },
+ {
+ description: "@autocomplete - each field in its own form",
+ document: `<form><input id="cc-number" autocomplete="cc-number"></form>
+ <form><input id="cc-name" autocomplete="cc-name"></form>
+ <form><input id="cc-exp" autocomplete="cc-exp"></form>`,
+ idsToShowPopup: ["cc-number", "cc-name", "cc-exp"],
+ },
+ {
+ description:
+ "without @autocomplete - each field in its own form (high-confidence cc-number & cc-name)",
+ document: `<form><input id="cc-number" placeholder="credit card number"></form>
+ <form><input id="cc-name" placeholder="credit card holder name"></form>
+ <form><input id="cc-exp" placeholder="expiration date"></form>`,
+ prefs: [
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold",
+ "0.9",
+ ],
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.testConfidence",
+ "0.95",
+ ],
+ ],
+ idsToShowPopup: ["cc-number", "cc-name"],
+ idsWithNoPopup: ["cc-exp"],
+ },
+ {
+ description:
+ "without @autocomplete - each field in its own form (normal-confidence cc-number & cc-name)",
+ document: `<form><input id="cc-number" placeholder="credit card number"></form>
+ <form><input id="cc-name" placeholder="credit card holder name"></form>
+ <form><input id="cc-exp" placeholder="expiration date"></form>`,
+ prefs: [
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold",
+ "0.9",
+ ],
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.testConfidence",
+ "0.8",
+ ],
+ ],
+ idsWithNoPopup: ["cc-number", "cc-name", "cc-exp"],
+ },
+ {
+ description:
+ "with @autocomplete - cc-number/cc-name and another <input> in a form",
+ document: `<form>
+ <input id="cc-number" autocomplete="cc-number">
+ <input id="password" type="password">
+ </form>
+ <form>
+ <input id="cc-name" autocomplete="cc-name">
+ <input id="password" type="password">
+ </form>`,
+ idsToShowPopup: ["cc-number", "cc-name"],
+ },
+ {
+ description:
+ "without @autocomplete - high-confidence cc-number/cc-name and another <input> in a form",
+ document: `<form>
+ <input id="cc-number" placeholder="credit card number">
+ <input id="password" type="password">
+ </form>
+ <form>
+ <input id="cc-name" placeholder="credit card holder name">
+ <input id="password" type="password">
+ </form>`,
+ idsWithNoPopup: ["cc-number", "cc-name"],
+ },
+ {
+ description:
+ "without @autocomplete - high-confidence cc-number/cc-name and another hidden <input> in a form",
+ document: `<form>
+ <input id="cc-number" placeholder="credit card number">
+ <input id="token" type="hidden">
+ </form>
+ <form>
+ <input id="cc-name" placeholder="credit card holder name">
+ <input id="token" type="hidden">
+ </form>`,
+ prefs: [
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold",
+ "0.9",
+ ],
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.testConfidence",
+ "0.95",
+ ],
+ ],
+ idsToShowPopup: ["cc-number", "cc-name"],
+ },
+];
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.creditCards.supported", "on"],
+ ["extensions.formautofill.creditCards.enabled", true],
+ ],
+ });
+
+ await setStorage(TEST_CREDIT_CARD_1);
+});
+
+add_task(async function test_heuristics() {
+ for (const TEST of TESTCASES) {
+ info(`Test ${TEST.description}`);
+ if (TEST.prefs) {
+ await SpecialPowers.pushPrefEnv({ set: TEST.prefs });
+ }
+
+ await BrowserTestUtils.withNewTab(EMPTY_URL, async function (browser) {
+ await SpecialPowers.spawn(browser, [TEST.document], doc => {
+ // eslint-disable-next-line no-unsanitized/property
+ content.document.body.innerHTML = doc;
+ });
+
+ await SimpleTest.promiseFocus(browser);
+
+ let ids = TEST.idsToShowPopup ?? [];
+ for (const id of ids) {
+ await runAndWaitForAutocompletePopupOpen(browser, async () => {
+ await focusAndWaitForFieldsIdentified(browser, `#${id}`);
+ });
+ ok(true, `popup is opened on <input id=${id}>`);
+ }
+
+ ids = TEST.idsWithNoPopup ?? [];
+ for (const id of ids) {
+ await focusAndWaitForFieldsIdentified(browser, `#${id}`);
+ await ensureNoAutocompletePopup(browser);
+ }
+ });
+
+ if (TEST.prefs) {
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics_cc_type.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics_cc_type.js
new file mode 100644
index 0000000000..e3f12096c2
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_heuristics_cc_type.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PROFILE = {
+ "cc-name": "John Doe",
+ "cc-number": "4111111111111111",
+ // "cc-type" should be remove from proile after fixing Bug 1834768.
+ "cc-type": "visa",
+ "cc-exp-month": 4,
+ "cc-exp-year": new Date().getFullYear(),
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.creditCards.supported", "on"],
+ ["extensions.formautofill.creditCards.enabled", true],
+ ],
+ });
+});
+
+add_autofill_heuristic_tests([
+ {
+ description:
+ "cc-type select does not have any information in labels or attributes",
+ fixtureData: `
+ <form>
+ <input id="cc-number" autocomplete="cc-number">
+ <input id="cc-name" autocomplete="cc-name">
+ <select id="test">
+ <option value="" selected="">0</option>
+ <option value="VISA">1</option>
+ <option value="MasterCard">2</option>
+ <option value="DINERS">3</option>
+ <option value="Discover">4</option>
+ </select>
+ </form>
+ <form>
+ <input id="cc-number" autocomplete="cc-number">
+ <input id="cc-name" autocomplete="cc-name">
+ <select id="test">
+ <option value="0" selected="">Card Type</option>
+ <option value="1">Visa</option>
+ <option value="2">MasterCard</option>
+ <option value="3">Diners Club International</option>
+ <option value="4">Discover</option>
+ </select>
+ </form>`,
+ profile: TEST_PROFILE,
+ expectedResult: [
+ {
+ description: "cc-type option.value has the hint",
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
+ { fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
+ { fieldName: "cc-type", reason: "regex-heuristic", autofill: "VISA" },
+ ],
+ },
+ {
+ description: "cc-type option.text has the hint",
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
+ { fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
+ { fieldName: "cc-type", reason: "regex-heuristic", autofill: "1" },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_autodetect_type.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_autodetect_type.js
new file mode 100644
index 0000000000..825a2978de
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_autodetect_type.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_autodetect_credit_not_set() {
+ const testCard = {
+ "cc-name": "John Doe",
+ "cc-number": "4012888888881881",
+ "cc-exp-month": "06",
+ "cc-exp-year": "2044",
+ };
+ const expectedData = {
+ ...testCard,
+ ...{ "cc-type": "visa" },
+ };
+ let onChanged = waitForStorageChangedEvents("add");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let promiseShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": testCard["cc-name"],
+ "#cc-number": testCard["cc-number"],
+ "#cc-exp-month": testCard["cc-exp-month"],
+ "#cc-exp-year": testCard["cc-exp-year"],
+ "#cc-type": testCard["cc-type"],
+ },
+ });
+
+ await promiseShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ let savedCreditCard = creditCards[0];
+ let decryptedNumber = await OSKeyStore.decrypt(
+ savedCreditCard["cc-number-encrypted"]
+ );
+ savedCreditCard["cc-number"] = decryptedNumber;
+ for (let key in testCard) {
+ let expected = expectedData[key];
+ let actual = savedCreditCard[key];
+ Assert.equal(expected, actual, `${key} should match`);
+ }
+ await removeAllRecords();
+});
+
+add_task(async function test_autodetect_credit_overwrite() {
+ const testCard = {
+ "cc-name": "John Doe",
+ "cc-number": "4012888888881881",
+ "cc-exp-month": "06",
+ "cc-exp-year": "2044",
+ "cc-type": "master", // Wrong credit card type
+ };
+ const expectedData = {
+ ...testCard,
+ ...{ "cc-type": "visa" },
+ };
+ let onChanged = waitForStorageChangedEvents("add");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let promiseShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": testCard["cc-name"],
+ "#cc-number": testCard["cc-number"],
+ "#cc-exp-month": testCard["cc-exp-month"],
+ "#cc-exp-year": testCard["cc-exp-year"],
+ "#cc-type": testCard["cc-type"],
+ },
+ });
+
+ await promiseShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ let savedCreditCard = creditCards[0];
+ let decryptedNumber = await OSKeyStore.decrypt(
+ savedCreditCard["cc-number-encrypted"]
+ );
+ savedCreditCard["cc-number"] = decryptedNumber;
+ for (let key in testCard) {
+ let expected = expectedData[key];
+ let actual = savedCreditCard[key];
+ Assert.equal(expected, actual, `${key} should match`);
+ }
+
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_normalized.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_normalized.js
new file mode 100644
index 0000000000..76e125a196
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_submission_normalized.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// We want to ensure that non-normalized credit card data is normalized
+// correctly as part of the save credit card flow
+add_task(async function test_new_submitted_card_is_normalized() {
+ const testCard = {
+ "cc-name": "Test User",
+ "cc-number": "5038146897157463",
+ "cc-exp-month": "4",
+ "cc-exp-year": "25",
+ };
+ const expectedData = {
+ "cc-name": "Test User",
+ "cc-number": "5038146897157463",
+ "cc-exp-month": "4",
+ // cc-exp-year should be normalized to 2025
+ "cc-exp-year": "2025",
+ };
+ let onChanged = waitForStorageChangedEvents("add");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let promiseShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": testCard["cc-name"],
+ "#cc-number": testCard["cc-number"],
+ "#cc-exp-month": testCard["cc-exp-month"],
+ "#cc-exp-year": testCard["cc-exp-year"],
+ },
+ });
+
+ await promiseShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ let savedCreditCard = creditCards[0];
+ let decryptedNumber = await OSKeyStore.decrypt(
+ savedCreditCard["cc-number-encrypted"]
+ );
+ savedCreditCard["cc-number"] = decryptedNumber;
+ for (let key in testCard) {
+ let expected = expectedData[key];
+ let actual = savedCreditCard[key];
+ Assert.equal(expected, actual, `${key} should match`);
+ }
+ await removeAllRecords();
+});
+
+add_task(async function test_updated_card_is_normalized() {
+ const testCard = {
+ "cc-name": "Test User",
+ "cc-number": "5038146897157463",
+ "cc-exp-month": "11",
+ "cc-exp-year": "20",
+ };
+ await saveCreditCard(testCard);
+ const expectedData = {
+ "cc-name": "Test User",
+ "cc-number": "5038146897157463",
+ "cc-exp-month": "10",
+ // cc-exp-year should be normalized to 2027
+ "cc-exp-year": "2027",
+ };
+ let onChanged = waitForStorageChangedEvents("update");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let promiseShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": testCard["cc-name"],
+ "#cc-number": testCard["cc-number"],
+ "#cc-exp-month": "10",
+ "#cc-exp-year": "27",
+ },
+ });
+
+ await promiseShown;
+ await clickDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ await onChanged;
+
+ let creditCards = await getCreditCards();
+ let savedCreditCard = creditCards[0];
+ savedCreditCard["cc-number"] = await OSKeyStore.decrypt(
+ savedCreditCard["cc-number-encrypted"]
+ );
+
+ for (let key in testCard) {
+ let expected = expectedData[key];
+ let actual = savedCreditCard[key];
+ Assert.equal(expected, actual, `${key} should match`);
+ }
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_telemetry.js b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_telemetry.js
new file mode 100644
index 0000000000..e8790243ac
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_creditCard_telemetry.js
@@ -0,0 +1,872 @@
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const CC_NUM_USES_HISTOGRAM = "CREDITCARD_NUM_USES";
+
+function ccFormArgsv1(method, extra) {
+ return ["creditcard", method, "cc_form", undefined, extra];
+}
+
+function ccFormArgsv2(method, extra) {
+ return ["creditcard", method, "cc_form_v2", undefined, extra];
+}
+
+function buildccFormv2Extra(extra, defaultValue) {
+ let defaults = {};
+ for (const field of [
+ "cc_name",
+ "cc_number",
+ "cc_type",
+ "cc_exp",
+ "cc_exp_month",
+ "cc_exp_year",
+ ]) {
+ defaults[field] = defaultValue;
+ }
+
+ return { ...defaults, ...extra };
+}
+
+async function assertTelemetry(expected_content, expected_parent) {
+ let snapshots;
+
+ info(
+ `Waiting for ${expected_content?.length ?? 0} content events and ` +
+ `${expected_parent?.length ?? 0} parent events`
+ );
+
+ await TestUtils.waitForCondition(
+ () => {
+ snapshots = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ );
+
+ return (
+ (snapshots.parent?.length ?? 0) >= (expected_parent?.length ?? 0) &&
+ (snapshots.content?.length ?? 0) >= (expected_content?.length ?? 0)
+ );
+ },
+ "Wait for telemetry to be collected",
+ 100,
+ 100
+ );
+
+ info(JSON.stringify(snapshots, null, 2));
+
+ if (expected_content !== undefined) {
+ expected_content = expected_content.map(
+ ([category, method, object, value, extra]) => {
+ return { category, method, object, value, extra };
+ }
+ );
+
+ let clear = expected_parent === undefined;
+
+ TelemetryTestUtils.assertEvents(
+ expected_content,
+ {
+ category: "creditcard",
+ },
+ { clear, process: "content" }
+ );
+ }
+
+ if (expected_parent !== undefined) {
+ expected_parent = expected_parent.map(
+ ([category, method, object, value, extra]) => {
+ return { category, method, object, value, extra };
+ }
+ );
+ TelemetryTestUtils.assertEvents(
+ expected_parent,
+ {
+ category: "creditcard",
+ },
+ { process: "parent" }
+ );
+ }
+}
+
+async function assertHistogram(histogramId, expectedNonZeroRanges) {
+ let actualNonZeroRanges = {};
+ await TestUtils.waitForCondition(
+ () => {
+ const snapshot = Services.telemetry
+ .getHistogramById(histogramId)
+ .snapshot();
+ // Compute the actual ranges in the format { range1: value1, range2: value2 }.
+ for (let [range, value] of Object.entries(snapshot.values)) {
+ if (value > 0) {
+ actualNonZeroRanges[range] = value;
+ }
+ }
+
+ return (
+ JSON.stringify(actualNonZeroRanges) ==
+ JSON.stringify(expectedNonZeroRanges)
+ );
+ },
+ "Wait for telemetry to be collected",
+ 100,
+ 100
+ );
+
+ Assert.equal(
+ JSON.stringify(actualNonZeroRanges),
+ JSON.stringify(expectedNonZeroRanges)
+ );
+}
+
+async function openTabAndUseCreditCard(
+ idx,
+ creditCard,
+ { closeTab = true, submitForm = true } = {}
+) {
+ let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ CREDITCARD_FORM_URL
+ );
+ let browser = tab.linkedBrowser;
+
+ await openPopupOn(browser, "form #cc-name");
+ for (let i = 0; i <= idx; i++) {
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ }
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await osKeyStoreLoginShown;
+ await waitForAutofill(browser, "#cc-number", creditCard["cc-number"]);
+ await focusUpdateSubmitForm(
+ browser,
+ {
+ focusSelector: "#cc-number",
+ newValues: {},
+ },
+ submitForm
+ );
+
+ if (!closeTab) {
+ return tab;
+ }
+
+ await BrowserTestUtils.removeTab(tab);
+ return null;
+}
+
+add_setup(async function () {
+ Services.telemetry.setEventRecordingEnabled("creditcard", true);
+ registerCleanupFunction(async function () {
+ Services.telemetry.setEventRecordingEnabled("creditcard", false);
+ });
+});
+
+add_task(async function test_popup_opened() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [ENABLED_AUTOFILL_CREDITCARDS_PREF, true],
+ [AUTOFILL_CREDITCARDS_AVAILABLE_PREF, "on"],
+ ],
+ });
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+
+ await setStorage(TEST_CREDIT_CARD_1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ const focusInput = "#cc-number";
+
+ await openPopupOn(browser, focusInput);
+
+ // Clean up
+ await closePopup(browser);
+ }
+ );
+
+ await assertTelemetry([
+ ccFormArgsv2("detected", buildccFormv2Extra({ cc_exp: "false" }, "true")),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-number" }),
+ ccFormArgsv1("popup_shown"),
+ ]);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.detected_sections_count",
+ 1,
+ "There should be 1 section detected."
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.submitted_sections_count"
+ );
+
+ await removeAllRecords();
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_popup_opened_form_without_autocomplete() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [ENABLED_AUTOFILL_CREDITCARDS_PREF, true],
+ [AUTOFILL_CREDITCARDS_AVAILABLE_PREF, "on"],
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.testConfidence",
+ "1",
+ ],
+ ],
+ });
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+
+ await setStorage(TEST_CREDIT_CARD_1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_WITHOUT_AUTOCOMPLETE_URL },
+ async function (browser) {
+ const focusInput = "#cc-number";
+
+ await openPopupOn(browser, focusInput);
+
+ // Clean up
+ await closePopup(browser);
+ }
+ );
+
+ await assertTelemetry([
+ ccFormArgsv2(
+ "detected",
+ buildccFormv2Extra({ cc_number: "1", cc_name: "1", cc_exp: "false" }, "0")
+ ),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-number" }),
+ ccFormArgsv1("popup_shown"),
+ ]);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.detected_sections_count",
+ 1,
+ "There should be 1 section detected."
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.submitted_sections_count"
+ );
+
+ await removeAllRecords();
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(
+ async function test_popup_opened_form_without_autocomplete_separate_cc_number() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [ENABLED_AUTOFILL_CREDITCARDS_PREF, true],
+ [AUTOFILL_CREDITCARDS_AVAILABLE_PREF, "on"],
+ [
+ "extensions.formautofill.creditCards.heuristics.fathom.testConfidence",
+ "1",
+ ],
+ ],
+ });
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+
+ await setStorage(TEST_CREDIT_CARD_1);
+
+ // Click on the cc-number field of the form that only contains a cc-number field
+ // (detected by Fathom)
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_WITHOUT_AUTOCOMPLETE_URL },
+ async function (browser) {
+ await openPopupOn(browser, "#form2-cc-number #cc-number");
+ await closePopup(browser);
+ }
+ );
+
+ await assertTelemetry([
+ ccFormArgsv2("detected", buildccFormv2Extra({ cc_number: "1" }, "false")),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-number" }),
+ ccFormArgsv1("popup_shown"),
+ ]);
+
+ // Then click on the cc-name field of the form that doesn't have a cc-number field
+ // (detected by regexp-based heuristic)
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_WITHOUT_AUTOCOMPLETE_URL },
+ async function (browser) {
+ await openPopupOn(browser, "#form2-cc-other #cc-name");
+ await closePopup(browser);
+ }
+ );
+
+ await assertTelemetry([
+ ccFormArgsv2(
+ "detected",
+ buildccFormv2Extra(
+ { cc_name: "1", cc_type: "0", cc_exp_month: "0", cc_exp_year: "0" },
+ "false"
+ )
+ ),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-name" }),
+ ccFormArgsv1("popup_shown"),
+ ]);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.detected_sections_count",
+ 2,
+ "There should be 1 section detected."
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.submitted_sections_count"
+ );
+
+ await removeAllRecords();
+ await SpecialPowers.popPrefEnv();
+ }
+);
+
+add_task(async function test_submit_creditCard_new() {
+ async function test_per_command(
+ command,
+ idx,
+ useCount = {},
+ expectChanged = undefined
+ ) {
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let onChanged;
+ if (expectChanged !== undefined) {
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed");
+ }
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-name": "User 1",
+ "#cc-number": "5038146897157463",
+ "#cc-exp-month": "12",
+ "#cc-exp-year": "2017",
+ "#cc-type": "mastercard",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(command, idx);
+ if (expectChanged !== undefined) {
+ await onChanged;
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("parent"),
+ "formautofill.creditCards.autofill_profiles_count",
+ expectChanged,
+ "There should be ${expectChanged} profile(s) stored."
+ );
+ }
+ }
+ );
+
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, useCount);
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+ }
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+ Services.telemetry.getHistogramById(CC_NUM_USES_HISTOGRAM).clear();
+
+ let expected_content = [
+ ccFormArgsv2("detected", buildccFormv2Extra({ cc_exp: "false" }, "true")),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2(
+ "submitted",
+ buildccFormv2Extra({ cc_exp: "unavailable" }, "user_filled")
+ ),
+ ccFormArgsv1("submitted", {
+ // 5 fields plus submit button
+ fields_not_auto: "6",
+ fields_auto: "0",
+ fields_modified: "0",
+ }),
+ ];
+ await test_per_command(MAIN_BUTTON, undefined, { 1: 1 }, 1);
+ await assertTelemetry(expected_content, [
+ ["creditcard", "show", "capture_doorhanger"],
+ ["creditcard", "save", "capture_doorhanger"],
+ ]);
+
+ await test_per_command(SECONDARY_BUTTON);
+ await assertTelemetry(expected_content, [
+ ["creditcard", "show", "capture_doorhanger"],
+ ["creditcard", "cancel", "capture_doorhanger"],
+ ]);
+
+ await test_per_command(MENU_BUTTON, 0);
+ await assertTelemetry(expected_content, [
+ ["creditcard", "show", "capture_doorhanger"],
+ ["creditcard", "disable", "capture_doorhanger"],
+ ]);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.detected_sections_count",
+ 3,
+ "There should be 3 sections detected."
+ );
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ "formautofill.creditCards.submitted_sections_count",
+ 3,
+ "There should be 1 section submitted."
+ );
+});
+
+add_task(async function test_submit_creditCard_autofill() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.getHistogramById(CC_NUM_USES_HISTOGRAM).clear();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ Assert.equal(creditCards.length, 1, "1 credit card in storage");
+
+ await openTabAndUseCreditCard(0, TEST_CREDIT_CARD_1);
+
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 1: 1,
+ });
+
+ SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+
+ await assertTelemetry(
+ [
+ ccFormArgsv2("detected", buildccFormv2Extra({ cc_exp: "false" }, "true")),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-name" }),
+ ccFormArgsv1("popup_shown"),
+ ccFormArgsv2(
+ "filled",
+ buildccFormv2Extra({ cc_exp: "unavailable" }, "filled")
+ ),
+ ccFormArgsv1("filled"),
+ ccFormArgsv2(
+ "submitted",
+ buildccFormv2Extra({ cc_exp: "unavailable" }, "autofilled")
+ ),
+ ccFormArgsv1("submitted", {
+ fields_not_auto: "3",
+ fields_auto: "5",
+ fields_modified: "0",
+ }),
+ ],
+ []
+ );
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_submit_creditCard_update() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ async function test_per_command(
+ command,
+ idx,
+ useCount = {},
+ expectChanged = undefined
+ ) {
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ Assert.equal(creditCards.length, 1, "1 credit card in storage");
+
+ let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: CREDITCARD_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let onChanged;
+ if (expectChanged !== undefined) {
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed");
+ }
+
+ await openPopupOn(browser, "form #cc-name");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await osKeyStoreLoginShown;
+
+ await waitForAutofill(browser, "#cc-name", "John Doe");
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#cc-name",
+ newValues: {
+ "#cc-exp-year": "2019",
+ },
+ });
+ await onPopupShown;
+ await clickDoorhangerButton(command, idx);
+ if (expectChanged !== undefined) {
+ await onChanged;
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("parent"),
+ "formautofill.creditCards.autofill_profiles_count",
+ expectChanged,
+ "There should be ${expectChanged} profile(s) stored."
+ );
+ }
+ }
+ );
+
+ await assertHistogram("CREDITCARD_NUM_USES", useCount);
+
+ SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+
+ await removeAllRecords();
+ }
+ Services.telemetry.clearEvents();
+ Services.telemetry.getHistogramById(CC_NUM_USES_HISTOGRAM).clear();
+
+ let expected_content = [
+ ccFormArgsv2("detected", buildccFormv2Extra({ cc_exp: "false" }, "true")),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-name" }),
+ ccFormArgsv1("popup_shown"),
+ ccFormArgsv2(
+ "filled",
+ buildccFormv2Extra({ cc_exp: "unavailable" }, "filled")
+ ),
+ ccFormArgsv1("filled"),
+ ccFormArgsv2("filled_modified", { field_name: "cc-exp-year" }),
+ ccFormArgsv1("filled_modified", { field_name: "cc-exp-year" }),
+ ccFormArgsv2(
+ "submitted",
+ buildccFormv2Extra(
+ { cc_exp: "unavailable", cc_exp_year: "user_filled" },
+ "autofilled"
+ )
+ ),
+ ccFormArgsv1("submitted", {
+ fields_not_auto: "3",
+ fields_auto: "5",
+ fields_modified: "1",
+ }),
+ ];
+
+ await test_per_command(MAIN_BUTTON, undefined, { 1: 1 }, 1);
+ await assertTelemetry(expected_content, [
+ ["creditcard", "show", "update_doorhanger"],
+ ["creditcard", "update", "update_doorhanger"],
+ ]);
+
+ await test_per_command(SECONDARY_BUTTON, undefined, { 0: 1, 1: 1 }, 2);
+ await assertTelemetry(expected_content, [
+ ["creditcard", "show", "update_doorhanger"],
+ ["creditcard", "save", "update_doorhanger"],
+ ]);
+});
+
+const TEST_SELECTORS = {
+ selRecords: "#credit-cards",
+ btnRemove: "#remove",
+ btnAdd: "#add",
+ btnEdit: "#edit",
+};
+
+const DIALOG_SIZE = "width=600,height=400";
+
+add_task(async function test_removingCreditCardsViaKeyboardDelete() {
+ Services.telemetry.clearEvents();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let win = window.openDialog(
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ null,
+ DIALOG_SIZE
+ );
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+
+ Assert.equal(selRecords.length, 1, "One credit card");
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ Assert.equal(selRecords.length, 0, "No credit cards left");
+
+ win.close();
+
+ await assertTelemetry(undefined, [
+ ["creditcard", "show", "manage"],
+ ["creditcard", "delete", "manage"],
+ ]);
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_saveCreditCard() {
+ Services.telemetry.clearEvents();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-number"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ "0" + TEST_CREDIT_CARD_1["cc-exp-month"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ TEST_CREDIT_CARD_1["cc-exp-year"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ info("saving credit card");
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ });
+
+ await assertTelemetry(undefined, [["creditcard", "add", "manage"]]);
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_editCreditCard() {
+ Services.telemetry.clearEvents();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ await setStorage(TEST_CREDIT_CARD_1);
+
+ let creditCards = await getCreditCards();
+ Assert.equal(creditCards.length, 1, "only one credit card is in storage");
+ await testDialog(
+ EDIT_CREDIT_CARD_DIALOG_URL,
+ win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: creditCards[0],
+ }
+ );
+
+ await assertTelemetry(undefined, [
+ ["creditcard", "show_entry", "manage"],
+ ["creditcard", "edit", "manage"],
+ ]);
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_histogram() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ Services.telemetry.getHistogramById(CC_NUM_USES_HISTOGRAM).clear();
+
+ await setStorage(
+ TEST_CREDIT_CARD_1,
+ TEST_CREDIT_CARD_2,
+ TEST_CREDIT_CARD_3,
+ TEST_CREDIT_CARD_5
+ );
+ let creditCards = await getCreditCards();
+ Assert.equal(creditCards.length, 4, "3 credit cards in storage");
+
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 0: 4,
+ });
+
+ await openTabAndUseCreditCard(0, TEST_CREDIT_CARD_1);
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 0: 3,
+ 1: 1,
+ });
+
+ await openTabAndUseCreditCard(1, TEST_CREDIT_CARD_2);
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 0: 2,
+ 1: 2,
+ });
+
+ await openTabAndUseCreditCard(0, TEST_CREDIT_CARD_2);
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 0: 2,
+ 1: 1,
+ 2: 1,
+ });
+
+ await openTabAndUseCreditCard(1, TEST_CREDIT_CARD_1);
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 0: 2,
+ 2: 2,
+ });
+
+ await openTabAndUseCreditCard(2, TEST_CREDIT_CARD_5);
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {
+ 0: 1,
+ 1: 1,
+ 2: 2,
+ });
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+
+ await assertHistogram(CC_NUM_USES_HISTOGRAM, {});
+});
+
+add_task(async function test_clear_creditCard_autofill() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.getHistogramById(CC_NUM_USES_HISTOGRAM).clear();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, true]],
+ });
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ let creditCards = await getCreditCards();
+ Assert.equal(creditCards.length, 1, "1 credit card in storage");
+
+ let tab = await openTabAndUseCreditCard(0, TEST_CREDIT_CARD_1, {
+ closeTab: false,
+ submitForm: false,
+ });
+
+ let expected_content = [
+ ccFormArgsv2("detected", buildccFormv2Extra({ cc_exp: "false" }, "true")),
+ ccFormArgsv1("detected"),
+ ccFormArgsv2("popup_shown", { field_name: "cc-name" }),
+ ccFormArgsv1("popup_shown"),
+ ccFormArgsv2(
+ "filled",
+ buildccFormv2Extra({ cc_exp: "unavailable" }, "filled")
+ ),
+ ccFormArgsv1("filled"),
+ ];
+ await assertTelemetry(expected_content, []);
+ Services.telemetry.clearEvents();
+
+ let browser = tab.linkedBrowser;
+
+ let popupshown = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "shown"
+ );
+ // Already focus in "cc-number" field, press 'down' to bring to popup.
+ await BrowserTestUtils.synthesizeKey("KEY_ArrowDown", {}, browser);
+ await popupshown;
+
+ expected_content = [
+ ccFormArgsv2("popup_shown", { field_name: "cc-number" }),
+ ccFormArgsv1("popup_shown"),
+ ];
+ await assertTelemetry(expected_content, []);
+ Services.telemetry.clearEvents();
+
+ // kPress Clear Form.
+ await BrowserTestUtils.synthesizeKey("KEY_ArrowDown", {}, browser);
+ await BrowserTestUtils.synthesizeKey("KEY_Enter", {}, browser);
+
+ expected_content = [
+ ccFormArgsv2("filled_modified", { field_name: "cc-name" }),
+ ccFormArgsv1("filled_modified", { field_name: "cc-name" }),
+ ccFormArgsv2("filled_modified", { field_name: "cc-number" }),
+ ccFormArgsv1("filled_modified", { field_name: "cc-number" }),
+ ccFormArgsv2("filled_modified", { field_name: "cc-exp-month" }),
+ ccFormArgsv1("filled_modified", { field_name: "cc-exp-month" }),
+ ccFormArgsv2("filled_modified", { field_name: "cc-exp-year" }),
+ ccFormArgsv1("filled_modified", { field_name: "cc-exp-year" }),
+ ccFormArgsv2("filled_modified", { field_name: "cc-type" }),
+ ccFormArgsv1("filled_modified", { field_name: "cc-type" }),
+ ccFormArgsv2("cleared", { field_name: "cc-number" }),
+ // popup is shown again because when the field is cleared and is focused,
+ // we automatically triggers the popup.
+ ccFormArgsv2("popup_shown", { field_name: "cc-number" }),
+ ccFormArgsv1("popup_shown"),
+ ];
+
+ await assertTelemetry(expected_content, []);
+ Services.telemetry.clearEvents();
+
+ await BrowserTestUtils.removeTab(tab);
+
+ await removeAllRecords();
+ SpecialPowers.popPrefEnv();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_editCreditCardDialog.js b/browser/extensions/formautofill/test/browser/creditCard/browser_editCreditCardDialog.js
new file mode 100644
index 0000000000..f532ea5da9
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_editCreditCardDialog.js
@@ -0,0 +1,422 @@
+"use strict";
+
+add_setup(async function () {
+ let { formAutofillStorage } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofillStorage.sys.mjs"
+ );
+ await formAutofillStorage.initialize();
+});
+
+add_task(async function test_cancelEditCreditCardDialog() {
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, win => {
+ win.document.querySelector("#cancel").click();
+ });
+});
+
+add_task(async function test_cancelEditCreditCardDialogWithESC() {
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, win => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ });
+});
+
+add_task(async function test_saveCreditCard() {
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, win => {
+ ok(
+ win.document.documentElement
+ .querySelector("title")
+ .textContent.includes("Add"),
+ "Add card dialog title is correct"
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-number"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ "0" + TEST_CREDIT_CARD_1["cc-exp-month"].toString(),
+ {},
+ win
+ );
+ is(
+ win.document.activeElement.selectedOptions[0].text,
+ "04 - April",
+ "Displayed month should match number and name"
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ TEST_CREDIT_CARD_1["cc-exp-year"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ info("saving credit card");
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ });
+ let creditCards = await getCreditCards();
+
+ is(creditCards.length, 1, "only one credit card is in storage");
+ for (let [fieldName, fieldValue] of Object.entries(TEST_CREDIT_CARD_1)) {
+ if (fieldName === "cc-number") {
+ fieldValue = "*".repeat(fieldValue.length - 4) + fieldValue.substr(-4);
+ }
+ is(creditCards[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ is(creditCards[0].billingAddressGUID, undefined, "check billingAddressGUID");
+ ok(creditCards[0]["cc-number-encrypted"], "cc-number-encrypted exists");
+});
+
+add_task(async function test_saveCreditCardWithMaxYear() {
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-number"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ TEST_CREDIT_CARD_2["cc-exp-month"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ TEST_CREDIT_CARD_2["cc-exp-year"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ info("saving credit card");
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ });
+ let creditCards = await getCreditCards();
+
+ is(creditCards.length, 2, "Two credit cards are in storage");
+ for (let [fieldName, fieldValue] of Object.entries(TEST_CREDIT_CARD_2)) {
+ if (fieldName === "cc-number") {
+ fieldValue = "*".repeat(fieldValue.length - 4) + fieldValue.substr(-4);
+ }
+ is(creditCards[1][fieldName], fieldValue, "check " + fieldName);
+ }
+ ok(creditCards[1]["cc-number-encrypted"], "cc-number-encrypted exists");
+ await removeCreditCards([creditCards[1].guid]);
+});
+
+add_task(async function test_saveCreditCardWithBillingAddress() {
+ await setStorage(TEST_ADDRESS_4, TEST_ADDRESS_1);
+ let addresses = await getAddresses();
+ let billingAddress = addresses[0];
+
+ const TEST_CREDIT_CARD = Object.assign({}, TEST_CREDIT_CARD_2, {
+ billingAddressGUID: undefined,
+ });
+
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-number"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ TEST_CREDIT_CARD["cc-exp-month"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(
+ TEST_CREDIT_CARD["cc-exp-year"].toString(),
+ {},
+ win
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(billingAddress["given-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ info("saving credit card");
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ });
+ let creditCards = await getCreditCards();
+
+ is(creditCards.length, 2, "Two credit cards are in storage");
+ for (let [fieldName, fieldValue] of Object.entries(TEST_CREDIT_CARD)) {
+ if (fieldName === "cc-number") {
+ fieldValue = "*".repeat(fieldValue.length - 4) + fieldValue.substr(-4);
+ }
+ is(creditCards[1][fieldName], fieldValue, "check " + fieldName);
+ }
+ ok(creditCards[1]["cc-number-encrypted"], "cc-number-encrypted exists");
+ await removeCreditCards([creditCards[1].guid]);
+ await removeAddresses([addresses[0].guid, addresses[1].guid]);
+});
+
+add_task(async function test_editCreditCard() {
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "only one credit card is in storage");
+ await testDialog(
+ EDIT_CREDIT_CARD_DIALOG_URL,
+ win => {
+ ok(
+ win.document.documentElement
+ .querySelector("title")
+ .textContent.includes("Edit"),
+ "Edit card dialog title is correct"
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: creditCards[0],
+ }
+ );
+ ok(true, "Edit credit card dialog is closed");
+ creditCards = await getCreditCards();
+
+ is(creditCards.length, 1, "only one credit card is in storage");
+ is(
+ creditCards[0]["cc-name"],
+ TEST_CREDIT_CARD_1["cc-name"] + "test",
+ "cc name changed"
+ );
+ await removeCreditCards([creditCards[0].guid]);
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 0, "Credit card storage is empty");
+});
+
+add_task(async function test_editCreditCardWithMissingBillingAddress() {
+ const TEST_CREDIT_CARD = Object.assign({}, TEST_CREDIT_CARD_2, {
+ billingAddressGUID: "unknown-guid",
+ });
+ await setStorage(TEST_CREDIT_CARD);
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "one credit card in storage");
+ is(
+ creditCards[0].billingAddressGUID,
+ TEST_CREDIT_CARD.billingAddressGUID,
+ "Check saved billingAddressGUID"
+ );
+ await testDialog(
+ EDIT_CREDIT_CARD_DIALOG_URL,
+ win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: creditCards[0],
+ }
+ );
+ ok(true, "Edit credit card dialog is closed");
+ creditCards = await getCreditCards();
+
+ is(creditCards.length, 1, "only one credit card is in storage");
+ is(
+ creditCards[0]["cc-name"],
+ TEST_CREDIT_CARD["cc-name"] + "test",
+ "cc name changed"
+ );
+ is(
+ creditCards[0].billingAddressGUID,
+ undefined,
+ "unknown GUID removed upon manual save"
+ );
+ await removeCreditCards([creditCards[0].guid]);
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 0, "Credit card storage is empty");
+});
+
+add_task(async function test_addInvalidCreditCard() {
+ await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, async win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("test name", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeMouseAtCenter(
+ win.document.querySelector("#save"),
+ {},
+ win
+ );
+
+ is(
+ win.document.querySelector("form").checkValidity(),
+ false,
+ "cc-number is invalid"
+ );
+ await ensureCreditCardDialogNotClosed(win);
+ info("closing");
+ win.close();
+ });
+ info("closed");
+ let creditCards = await getCreditCards();
+
+ is(creditCards.length, 0, "Credit card storage is empty");
+});
+
+add_task(async function test_editInvalidCreditCardNumber() {
+ await setStorage(TEST_ADDRESS_4);
+ let addresses = await getAddresses();
+ let billingAddress = addresses[0];
+
+ const INVALID_CREDIT_CARD_NUMBER = "123456789";
+ const TEST_CREDIT_CARD = Object.assign({}, TEST_CREDIT_CARD_2, {
+ billingAddressGUID: billingAddress.guid,
+ guid: "invalid-number",
+ version: 2,
+ "cc-number": INVALID_CREDIT_CARD_NUMBER,
+ });
+
+ // Directly use FormAutofillStorage so we can set
+ // sourceSync: true, since saveCreditCard uses FormAutofillParent
+ // which doesn't expose this option.
+ let { formAutofillStorage } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofillStorage.sys.mjs"
+ );
+ await formAutofillStorage.initialize();
+ // Use `sourceSync: true` to bypass field normalization which will
+ // fail due to the invalid credit card number.
+ await formAutofillStorage.creditCards.add(TEST_CREDIT_CARD, {
+ sourceSync: true,
+ });
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "only one credit card is in storage");
+ is(
+ creditCards[0]["cc-number"],
+ "*********",
+ "invalid credit card number stored"
+ );
+ await testDialog(
+ EDIT_CREDIT_CARD_DIALOG_URL,
+ win => {
+ is(
+ win.document.querySelector("#cc-number").value,
+ INVALID_CREDIT_CARD_NUMBER,
+ "cc-number field should be showing invalid credit card number"
+ );
+ is(
+ win.document.querySelector("#cc-number").checkValidity(),
+ false,
+ "cc-number is invalid"
+ );
+ win.document.querySelector("#cancel").click();
+ },
+ {
+ record: creditCards[0],
+ skipDecryption: true,
+ }
+ );
+ ok(true, "Edit credit card dialog is closed");
+ creditCards = await getCreditCards();
+
+ is(creditCards.length, 1, "only one credit card is in storage");
+ is(
+ creditCards[0]["cc-number"],
+ "*********",
+ "invalid cc number still in record"
+ );
+ await removeCreditCards([creditCards[0].guid]);
+ await removeAddresses([addresses[0].guid]);
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 0, "Credit card storage is empty");
+ addresses = await getAddresses();
+ is(addresses.length, 0, "Address storage is empty");
+});
+
+add_task(async function test_editCreditCardWithInvalidNumber() {
+ const TEST_CREDIT_CARD = Object.assign({}, TEST_CREDIT_CARD_1);
+ await setStorage(TEST_CREDIT_CARD);
+
+ let creditCards = await getCreditCards();
+ is(creditCards.length, 1, "only one credit card is in storage");
+ await testDialog(
+ EDIT_CREDIT_CARD_DIALOG_URL,
+ win => {
+ ok(
+ win.document.documentElement
+ .querySelector("title")
+ .textContent.includes("Edit"),
+ "Edit card dialog title is correct"
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ is(
+ win.document.querySelector("#cc-number").validity.customError,
+ false,
+ "cc-number field should not have a custom error"
+ );
+ EventUtils.synthesizeKey("4111111111111112", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ is(
+ win.document.querySelector("#cc-number").validity.customError,
+ true,
+ "cc-number field should have a custom error"
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ win.document.querySelector("#cancel").click();
+ },
+ {
+ record: creditCards[0],
+ }
+ );
+ ok(true, "Edit credit card dialog is closed");
+ creditCards = await getCreditCards();
+
+ is(creditCards.length, 1, "only one credit card is in storage");
+ await removeCreditCards([creditCards[0].guid]);
+
+ creditCards = await getCreditCards();
+ is(creditCards.length, 0, "Credit card storage is empty");
+});
+
+add_task(async function test_noAutocompletePopupOnSystemTab() {
+ await setStorage(TEST_CREDIT_CARD_1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PRIVACY_PREF_URL },
+ async browser => {
+ // Open credit card manage dialog
+ await SpecialPowers.spawn(browser, [], async () => {
+ let button = content.document.querySelector(
+ "#creditCardAutofill button"
+ );
+ button.click();
+ });
+ let dialog = await waitForSubDialogLoad(
+ content,
+ MANAGE_CREDIT_CARDS_DIALOG_URL
+ );
+
+ // Open edit credit card dialog
+ await SpecialPowers.spawn(dialog, [], async () => {
+ let button = content.document.querySelector("#add");
+ button.click();
+ });
+ dialog = await waitForSubDialogLoad(content, EDIT_CREDIT_CARD_DIALOG_URL);
+
+ // Focus on credit card number field
+ await SpecialPowers.spawn(dialog, [], async () => {
+ let number = content.document.querySelector("#cc-number");
+ number.focus();
+ });
+
+ // autocomplete popup should not appear
+ await ensureNoAutocompletePopup(browser);
+ }
+ );
+
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_insecure_form.js b/browser/extensions/formautofill/test/browser/creditCard/browser_insecure_form.js
new file mode 100644
index 0000000000..5de499b942
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_insecure_form.js
@@ -0,0 +1,145 @@
+"use strict";
+
+// Remove the scheme from the URLs so we can switch between http: and https: later.
+const TEST_URL_PATH_CC =
+ "://example.org" +
+ HTTP_TEST_PATH +
+ "creditCard/autocomplete_creditcard_basic.html";
+const TEST_URL_PATH =
+ "://example.org" + HTTP_TEST_PATH + "autocomplete_basic.html";
+
+add_task(async function setup_storage() {
+ await setStorage(
+ TEST_ADDRESS_1,
+ TEST_ADDRESS_2,
+ TEST_ADDRESS_3,
+ TEST_CREDIT_CARD_1,
+ TEST_CREDIT_CARD_2,
+ TEST_CREDIT_CARD_3
+ );
+});
+
+add_task(async function test_insecure_form() {
+ async function runTest({
+ urlPath,
+ protocol,
+ focusInput,
+ expectedType,
+ expectedResultLength,
+ }) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: protocol + urlPath },
+ async function (browser) {
+ await openPopupOn(browser, focusInput);
+
+ const items = getDisplayedPopupItems(browser);
+ is(
+ items.length,
+ expectedResultLength,
+ `Should show correct amount of results in "${protocol}"`
+ );
+ const firstItem = items[0];
+ is(
+ firstItem.getAttribute("originaltype"),
+ expectedType,
+ `Item should attach with correct binding in "${protocol}"`
+ );
+
+ await closePopup(browser);
+ }
+ );
+ }
+
+ const testSets = [
+ {
+ urlPath: TEST_URL_PATH,
+ protocol: "https",
+ focusInput: "#organization",
+ expectedType: "autofill-profile",
+ expectedResultLength: 2,
+ },
+ {
+ urlPath: TEST_URL_PATH,
+ protocol: "http",
+ focusInput: "#organization",
+ expectedType: "autofill-profile",
+ expectedResultLength: 2,
+ },
+ {
+ urlPath: TEST_URL_PATH_CC,
+ protocol: "https",
+ focusInput: "#cc-name",
+ expectedType: "autofill-profile",
+ expectedResultLength: 3,
+ },
+ {
+ urlPath: TEST_URL_PATH_CC,
+ protocol: "http",
+ focusInput: "#cc-name",
+ expectedType: "autofill-insecureWarning", // insecure warning field
+ expectedResultLength: 1,
+ },
+ ];
+
+ for (const test of testSets) {
+ await runTest(test);
+ }
+});
+
+add_task(async function test_click_on_insecure_warning() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "http" + TEST_URL_PATH_CC },
+ async function (browser) {
+ await openPopupOn(browser, "#cc-name");
+ const insecureItem = getDisplayedPopupItems(browser)[0];
+ let popupClosePromise = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "hidden"
+ );
+ await EventUtils.synthesizeMouseAtCenter(insecureItem, {});
+ // Check input's value after popup closed to ensure the completion of autofilling.
+ await popupClosePromise;
+
+ const inputValue = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ return content.document.querySelector("#cc-name").value;
+ }
+ );
+ is(inputValue, "");
+
+ await closePopup(browser);
+ }
+ );
+});
+
+add_task(async function test_press_enter_on_insecure_warning() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "http" + TEST_URL_PATH_CC },
+ async function (browser) {
+ await openPopupOn(browser, "#cc-name");
+
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+
+ let popupClosePromise = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "hidden"
+ );
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ // Check input's value after popup closed to ensure the completion of autofilling.
+ await popupClosePromise;
+
+ const inputValue = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ return content.document.querySelector("#cc-name").value;
+ }
+ );
+ is(inputValue, "");
+
+ await closePopup(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/browser_manageCreditCardsDialog.js b/browser/extensions/formautofill/test/browser/creditCard/browser_manageCreditCardsDialog.js
new file mode 100644
index 0000000000..7f54140c78
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/browser_manageCreditCardsDialog.js
@@ -0,0 +1,290 @@
+"use strict";
+
+const TEST_SELECTORS = {
+ selRecords: "#credit-cards",
+ btnRemove: "#remove",
+ btnAdd: "#add",
+ btnEdit: "#edit",
+};
+
+const DIALOG_SIZE = "width=600,height=400";
+
+add_task(async function test_manageCreditCardsInitialState() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: MANAGE_CREDIT_CARDS_DIALOG_URL },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [TEST_SELECTORS], args => {
+ let selRecords = content.document.querySelector(args.selRecords);
+ let btnRemove = content.document.querySelector(args.btnRemove);
+ let btnAdd = content.document.querySelector(args.btnAdd);
+ let btnEdit = content.document.querySelector(args.btnEdit);
+
+ is(selRecords.length, 0, "No credit card");
+ is(btnRemove.disabled, true, "Remove button disabled");
+ is(btnAdd.disabled, false, "Add button enabled");
+ is(btnEdit.disabled, true, "Edit button disabled");
+ });
+ }
+ );
+});
+
+add_task(async function test_cancelManageCreditCardsDialogWithESC() {
+ let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL);
+ await waitForFocusAndFormReady(win);
+ let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ await unloadPromise;
+ ok(true, "Manage credit cards dialog is closed with ESC key");
+});
+
+add_task(async function test_removingSingleAndMultipleCreditCards() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.reduceTimerPrecision", false]],
+ });
+ await setStorage(
+ TEST_CREDIT_CARD_1,
+ TEST_CREDIT_CARD_2,
+ TEST_CREDIT_CARD_3,
+ TEST_CREDIT_CARD_4,
+ TEST_CREDIT_CARD_5
+ );
+
+ let win = window.openDialog(
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ null,
+ DIALOG_SIZE
+ );
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+ let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
+ let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
+
+ const expectedLabels = [
+ {
+ id: "credit-card-label-number-name-2",
+ args: { number: "**** 1881", name: "Chris P. Bacon", type: "Visa" },
+ },
+ {
+ id: "credit-card-label-number-2",
+ args: { number: "**** 5100", type: "MasterCard" },
+ },
+ {
+ id: "credit-card-label-number-expiration-2",
+ args: {
+ number: "**** 7870",
+ month: "1",
+ year: "2000",
+ type: "MasterCard",
+ },
+ },
+ {
+ id: "credit-card-label-number-name-expiration-2",
+ args: {
+ number: "**** 1045",
+ name: "Timothy Berners-Lee",
+ month: "12",
+ year: (new Date().getFullYear() + 10).toString(),
+ type: "Visa",
+ },
+ },
+ {
+ id: "credit-card-label-number-name-expiration-2",
+ args: {
+ number: "**** 1111",
+ name: "John Doe",
+ month: "4",
+ year: new Date().getFullYear().toString(),
+ type: "Visa",
+ },
+ },
+ ];
+
+ is(
+ selRecords.length,
+ expectedLabels.length,
+ "Correct number of credit cards"
+ );
+ expectedLabels.forEach((expected, i) => {
+ const l10nAttrs = document.l10n.getAttributes(selRecords[i]);
+ is(
+ l10nAttrs.id,
+ expected.id,
+ `l10n id set for credit card ${expectedLabels.length - i}`
+ );
+ Object.keys(expected.args).forEach(arg => {
+ is(
+ l10nAttrs.args[arg],
+ expected.args[arg],
+ `Set display ${arg} for credit card ${expectedLabels.length - i}`
+ );
+ });
+ });
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ is(btnRemove.disabled, false, "Remove button enabled");
+ is(btnEdit.disabled, false, "Edit button enabled");
+ EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 4, "Four credit cards left");
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeMouseAtCenter(
+ selRecords.children[3],
+ { shiftKey: true },
+ win
+ );
+ is(btnEdit.disabled, true, "Edit button disabled when multi-select");
+
+ EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 0, "All credit cards are removed");
+
+ win.close();
+});
+
+add_task(async function test_removingCreditCardsViaKeyboardDelete() {
+ await setStorage(TEST_CREDIT_CARD_1);
+ let win = window.openDialog(
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ null,
+ DIALOG_SIZE
+ );
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+
+ is(selRecords.length, 1, "One credit card");
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 0, "No credit cards left");
+
+ win.close();
+});
+
+add_task(async function test_creditCardsDialogWatchesStorageChanges() {
+ let win = window.openDialog(
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ null,
+ DIALOG_SIZE
+ );
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+
+ await setStorage(TEST_CREDIT_CARD_1);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 1, "One credit card is shown");
+
+ await removeCreditCards([selRecords.options[0].value]);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 0, "Credit card is removed");
+ win.close();
+});
+
+add_task(async function test_showCreditCardIcons() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.reduceTimerPrecision", false]],
+ });
+ await setStorage(TEST_CREDIT_CARD_1);
+ await setStorage(TEST_CREDIT_CARD_3);
+
+ let win = window.openDialog(
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ null,
+ DIALOG_SIZE
+ );
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+
+ is(
+ selRecords.classList.contains("branded"),
+ AppConstants.MOZILLA_OFFICIAL,
+ "record picker has 'branded' class in an MOZILLA_OFFICIAL build"
+ );
+
+ let option0 = selRecords.options[0];
+ let icon0Url = win.getComputedStyle(option0, "::before").backgroundImage;
+ let option1 = selRecords.options[1];
+ let icon1Url = win.getComputedStyle(option1, "::before").backgroundImage;
+
+ is(
+ option0.getAttribute("cc-type"),
+ "mastercard",
+ "Option has the expected cc-type"
+ );
+ is(
+ option1.getAttribute("cc-type"),
+ "visa",
+ "Option has the expected cc-type"
+ );
+
+ if (AppConstants.MOZILLA_OFFICIAL) {
+ ok(
+ icon0Url.includes("icon-credit-card-generic.svg"),
+ "unknown network option ::before element has the generic icon as backgroundImage: " +
+ icon0Url
+ );
+ ok(
+ icon1Url.includes("cc-logo-visa.svg"),
+ "visa option ::before element has the visa icon as backgroundImage " +
+ icon1Url
+ );
+ }
+
+ await removeCreditCards([option0.value, option1.value]);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 0, "Credit card is removed");
+ win.close();
+});
+
+add_task(async function test_hasEditLoginPrompt() {
+ if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
+ todo(
+ OSKeyStoreTestUtils.canTestOSKeyStoreLogin(),
+ "Cannot test OS key store login on official builds."
+ );
+ return;
+ }
+
+ await setStorage(TEST_CREDIT_CARD_1);
+
+ let win = window.openDialog(
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ null,
+ DIALOG_SIZE
+ );
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+ let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
+ let btnAdd = win.document.querySelector(TEST_SELECTORS.btnAdd);
+ let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+
+ let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(); // cancel
+ EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
+ await osKeyStoreLoginShown;
+ await new Promise(resolve => waitForFocus(resolve, win));
+ await new Promise(resolve => executeSoon(resolve));
+
+ // Login is not required for removing credit cards.
+ EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 0, "Credit card is removed");
+
+ // gSubDialog.open should be called when trying to add a credit card,
+ // no OS login dialog is required.
+ window.gSubDialog = {
+ open: url =>
+ is(url, EDIT_CREDIT_CARD_DIALOG_URL, "Edit credit card dialog is called"),
+ };
+ EventUtils.synthesizeMouseAtCenter(btnAdd, {}, win);
+ delete window.gSubDialog;
+
+ win.close();
+});
diff --git a/browser/extensions/formautofill/test/browser/creditCard/head_cc.js b/browser/extensions/formautofill/test/browser/creditCard/head_cc.js
new file mode 100644
index 0000000000..42196e8422
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/creditCard/head_cc.js
@@ -0,0 +1 @@
+/* import-globals-from ../head.js */