"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(); });