summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/test/browser')
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser.ini13
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_display.js240
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_telemetry.js691
-rw-r--r--browser/extensions/formautofill/test/browser/address/head_address.js1
-rw-r--r--browser/extensions/formautofill/test/browser/browser.ini35
-rw-r--r--browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js137
-rw-r--r--browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js66
-rw-r--r--browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js58
-rw-r--r--browser/extensions/formautofill/test/browser/browser_autofill_address_select.js64
-rw-r--r--browser/extensions/formautofill/test/browser/browser_autofill_duplicate_fields.js95
-rw-r--r--browser/extensions/formautofill/test/browser/browser_check_installed.js12
-rw-r--r--browser/extensions/formautofill/test/browser/browser_dropdown_layout.js53
-rw-r--r--browser/extensions/formautofill/test/browser/browser_editAddressDialog.js951
-rw-r--r--browser/extensions/formautofill/test/browser/browser_fathom_cc.js204
-rw-r--r--browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js142
-rw-r--r--browser/extensions/formautofill/test/browser/browser_manageAddressesDialog.js105
-rw-r--r--browser/extensions/formautofill/test/browser/browser_privacyPreferences.js439
-rw-r--r--browser/extensions/formautofill/test/browser/browser_remoteiframe.js127
-rw-r--r--browser/extensions/formautofill/test/browser/browser_submission_in_private_mode.js37
-rw-r--r--browser/extensions/formautofill/test/browser/browser_update_doorhanger.js189
-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
-rw-r--r--browser/extensions/formautofill/test/browser/empty.html8
-rwxr-xr-xbrowser/extensions/formautofill/test/browser/fathom/test-setup.sh39
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/1.svg3
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/10.svg1
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/11.pngbin0 -> 4968 bytes
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/12.gifbin0 -> 37 bytes
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/13.svg16
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/14.svg14
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/15.svg1
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/16.svg11
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/17.binbin0 -> 9594 bytes
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/18.svg1
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/2.svg8
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/3.svg1
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/4.svg6
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/5.svg6
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/6.svg8
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/7.woff2bin0 -> 15480 bytes
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/8.woff2bin0 -> 15784 bytes
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/9.woff2bin0 -> 15908 bytes
-rw-r--r--browser/extensions/formautofill/test/browser/fathom/testing/sample.html20
-rw-r--r--browser/extensions/formautofill/test/browser/focus-leak/browser.ini12
-rw-r--r--browser/extensions/formautofill/test/browser/focus-leak/browser_iframe_typecontent_input_focus.js56
-rw-r--r--browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus.xhtml7
-rw-r--r--browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus_frame.html6
-rw-r--r--browser/extensions/formautofill/test/browser/head.js1094
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser.ini17
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_form.js74
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_inputs.js102
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_basic.js69
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_cc_exp.js56
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_de_fields.js32
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_fr_fields.js27
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_ignore_invisible_fields.js115
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_multiple_section.js118
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_parseAddressFields.js138
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_section_validation_address.js79
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/browser_sections_by_name.js318
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser.ini22
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_BestBuy.js82
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CDW.js71
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CostCo.js170
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_DirectAsda.js25
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Ebay.js25
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_GlobalDirectAsda.js24
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_HomeDepot.js77
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lufthansa.js28
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lush.js31
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Macys.js88
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_NewEgg.js109
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_OfficeDepot.js83
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_QVC.js96
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Sears.js81
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Staples.js78
-rw-r--r--browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Walmart.js93
94 files changed, 10895 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/browser/address/browser.ini b/browser/extensions/formautofill/test/browser/address/browser.ini
new file mode 100644
index 0000000000..4eb6cc8927
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+prefs =
+ extensions.formautofill.addresses.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
+ ../../fixtures/autocomplete_address_basic.html
+ ../../fixtures/without_autocomplete_address_basic.html
+ head_address.js
+
+[browser_address_doorhanger_display.js]
+[browser_address_telemetry.js]
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_display.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_display.js
new file mode 100644
index 0000000000..970051667a
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_display.js
@@ -0,0 +1,240 @@
+"use strict";
+
+async function expectSavedAddresses(expectedCount) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedCount,
+ `${addresses.length} address in the storage`
+ );
+ return addresses;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.addresses.capture.v2.enabled", true]],
+ });
+});
+
+add_task(async function test_save_doorhanger_shown_no_profile() {
+ await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "Test User",
+ "#organization": "Sesame Street",
+ "#street-address": "123 Sesame Street",
+ "#tel": "1-345-345-3456",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses(1);
+ await removeAllRecords();
+});
+
+add_task(async function test_save_doorhanger_shown_different_address() {
+ await setStorage(TEST_ADDRESS_1);
+ await expectSavedAddresses(1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": TEST_ADDRESS_2["give-name"],
+ "#street-address": TEST_ADDRESS_2["street-address"],
+ "#country": TEST_ADDRESS_2.country,
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses(2);
+ await removeAllRecords();
+});
+
+add_task(
+ async function test_update_doorhanger_shown_change_non_mergeable_given_name() {
+ await setStorage(TEST_ADDRESS_1);
+ await expectSavedAddresses(1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "Cena",
+ "#street-address": TEST_ADDRESS_1["street-address"],
+ "#country": TEST_ADDRESS_1.country,
+ "#email": TEST_ADDRESS_1.email,
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses(2);
+ await removeAllRecords();
+ }
+);
+
+add_task(async function test_update_doorhanger_shown_add_email_field() {
+ // TEST_ADDRESS_2 doesn't contain email field
+ await setStorage(TEST_ADDRESS_2);
+ await expectSavedAddresses(1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": TEST_ADDRESS_2["given-name"],
+ "#street-address": TEST_ADDRESS_2["street-address"],
+ "#country": TEST_ADDRESS_2.country,
+ "#email": "test@mozilla.org",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ const addresses = await expectSavedAddresses(1);
+ is(addresses[0].email, "test@mozilla.org", "Email field is saved");
+
+ await removeAllRecords();
+});
+
+add_task(async function test_doorhanger_not_shown_when_autofill_untouched() {
+ await setStorage(TEST_ADDRESS_2);
+ await expectSavedAddresses(1);
+
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await sleep(1000);
+ await openPopupOn(browser, "form #given-name");
+ await sleep(1000);
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await sleep(1000);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(
+ browser,
+ "#given-name",
+ TEST_ADDRESS_2["given-name"]
+ );
+
+ 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;
+
+ const addresses = await expectSavedAddresses(1);
+ is(addresses[0].timesUsed, 1, "timesUsed field set to 1");
+ await removeAllRecords();
+});
+
+add_task(async function test_doorhanger_not_shown_when_fill_duplicate() {
+ await setStorage(TEST_ADDRESS_4);
+ await expectSavedAddresses(1);
+
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": TEST_ADDRESS_4["given-name"],
+ "#family-name": TEST_ADDRESS_4["family-name"],
+ "#organization": TEST_ADDRESS_4.organization,
+ "#country": TEST_ADDRESS_4.country,
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onUsed;
+
+ const addresses = await expectSavedAddresses(1);
+ is(
+ addresses[0]["given-name"],
+ TEST_ADDRESS_4["given-name"],
+ "Verify the name field"
+ );
+ is(addresses[0].timesUsed, 1, "timesUsed field set to 1");
+ await removeAllRecords();
+});
+
+add_task(
+ async function test_doorhanger_not_shown_when_autofill_then_fill_everything_duplicate() {
+ await setStorage(TEST_ADDRESS_2, TEST_ADDRESS_3);
+ await expectSavedAddresses(2);
+
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await openPopupOn(browser, "form #given-name");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(
+ browser,
+ "#given-name",
+ TEST_ADDRESS_2["given-name"]
+ );
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ // Change number to the second credit card number
+ "#given-name": TEST_ADDRESS_3["given-name"],
+ "#street-address": TEST_ADDRESS_3["street-address"],
+ "#postal-code": TEST_ADDRESS_3["postal-code"],
+ "#country": "",
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onUsed;
+
+ await expectSavedAddresses(2);
+ await removeAllRecords();
+ }
+);
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_telemetry.js b/browser/extensions/formautofill/test/browser/address/browser_address_telemetry.js
new file mode 100644
index 0000000000..66dbc24951
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_telemetry.js
@@ -0,0 +1,691 @@
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const { AddressTelemetry } = ChromeUtils.import(
+ "resource://autofill/AutofillTelemetry.jsm"
+);
+
+// Preference definitions
+const ENABLED_PREF = ENABLED_AUTOFILL_ADDRESSES_PREF;
+const AVAILABLE_PREF = AUTOFILL_ADDRESSES_AVAILABLE_PREF;
+const CAPTURE_ENABLE_PREF = ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF;
+
+// Telemetry definitions
+const EVENT_CATEGORY = "address";
+
+const SCALAR_DETECTED_SECTION_COUNT =
+ "formautofill.addresses.detected_sections_count";
+const SCALAR_SUBMITTED_SECTION_COUNT =
+ "formautofill.addresses.submitted_sections_count";
+const SCALAR_AUTOFILL_PROFILE_COUNT =
+ "formautofill.addresses.autofill_profiles_count";
+
+const HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
+const HISTOGRAM_PROFILE_NUM_USES_KEY = "address";
+
+// Autofill UI
+const MANAGE_DIALOG_URL = MANAGE_ADDRESSES_DIALOG_URL;
+const EDIT_DIALOG_URL = EDIT_ADDRESS_DIALOG_URL;
+const DIALOG_SIZE = "width=600,height=400";
+const MANAGE_RECORD_SELECTOR = "#addresses";
+
+// Test specific definitions
+const TEST_PROFILE = TEST_ADDRESS_1;
+const TEST_PROFILE_1 = TEST_ADDRESS_1;
+const TEST_PROFILE_2 = TEST_ADDRESS_2;
+const TEST_PROFILE_3 = TEST_ADDRESS_3;
+
+const TEST_FOCUS_NAME_FIELD = "given-name";
+const TEST_FOCUS_NAME_FIELD_SELECTOR = "#" + TEST_FOCUS_NAME_FIELD;
+
+// Used for tests that update address fields after filling
+const TEST_NEW_VALUES = {
+ "#given-name": "Test User",
+ "#organization": "Sesame Street",
+ "#street-address": "123 Sesame Street",
+ "#tel": "1-345-345-3456",
+};
+
+const TEST_UPDATE_PROFILE_2_VALUES = {
+ "#email": "profile2@mozilla.org",
+};
+const TEST_BASIC_ADDRESS_FORM_URL = ADDRESS_FORM_URL;
+const TEST_BASIC_ADDRESS_FORM_WITHOUT_AC_URL =
+ ADDRESS_FORM_WITHOUT_AUTOCOMPLETE_URL;
+// This should be sync with the address fields that appear in TEST_BASIC_ADDRESS_FORM
+const TEST_BASIC_ADDRESS_FORM_FIELDS = [
+ "street_address",
+ "address_level1",
+ "address_level2",
+ "postal_code",
+ "country",
+ "given_name",
+ "family_name",
+ "organization",
+ "email",
+ "tel",
+];
+
+function buildFormExtra(list, fields, fieldValue, defaultValue, aExtra = {}) {
+ let extra = {};
+ for (const field of list) {
+ if (aExtra[field]) {
+ extra[field] = aExtra[field];
+ } else {
+ extra[field] = fields.includes(field) ? fieldValue : defaultValue;
+ }
+ }
+ return extra;
+}
+
+/**
+ * Utility function to generate expected value for `address_form` and `address_form_ext`
+ * telemetry event.
+ *
+ * @param {string} method see `methods` in `address_form` event telemetry
+ * @param {object} defaultExtra default extra object, this will not be overwritten
+ * @param {object} fields address fields that will be set to `value` param
+ * @param {string} value value to set for fields list in `fields` argument
+ * @param {string} defaultValue value to set for address fields that are not listed in `fields` argument`
+ */
+function formArgs(
+ method,
+ defaultExtra,
+ fields = [],
+ value = undefined,
+ defaultValue = null
+) {
+ if (["popup_shown", "filled_modified"].includes(method)) {
+ return [["address", method, "address_form", undefined, defaultExtra]];
+ }
+ let extra = buildFormExtra(
+ AddressTelemetry.SUPPORTED_FIELDS_IN_FORM,
+ fields,
+ value,
+ defaultValue,
+ defaultExtra
+ );
+
+ let extraExt = buildFormExtra(
+ AddressTelemetry.SUPPORTED_FIELDS_IN_FORM_EXT,
+ fields,
+ value,
+ defaultValue,
+ defaultExtra
+ );
+
+ // The order here should sync with AutofillTelemetry.
+ return [
+ ["address", method, "address_form", undefined, extra],
+ ["address", method, "address_form_ext", undefined, extraExt],
+ ];
+}
+
+function getProfiles() {
+ return getAddresses();
+}
+
+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: EVENT_CATEGORY,
+ },
+ { 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: EVENT_CATEGORY,
+ },
+ { process: "parent" }
+ );
+ }
+}
+
+function _assertHistogram(snapshot, expectedNonZeroRanges) {
+ // Compute the actual ranges in the format { range1: value1, range2: value2 }.
+ let actualNonZeroRanges = {};
+ for (let [range, value] of Object.entries(snapshot.values)) {
+ if (value > 0) {
+ actualNonZeroRanges[range] = value;
+ }
+ }
+
+ // These are stringified to visualize the differences between the values.
+ Assert.equal(
+ JSON.stringify(actualNonZeroRanges),
+ JSON.stringify(expectedNonZeroRanges)
+ );
+}
+
+function assertKeyedHistogram(histogramId, key, expected) {
+ let snapshot = Services.telemetry
+ .getKeyedHistogramById(histogramId)
+ .snapshot();
+
+ if (expected == undefined) {
+ Assert.deepEqual(snapshot, {});
+ } else {
+ _assertHistogram(snapshot[key], expected);
+ }
+}
+
+async function openTabAndUseAutofillProfile(
+ idx,
+ profile,
+ { closeTab = true, submitForm = true } = {}
+) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_BASIC_ADDRESS_FORM_URL
+ );
+ let browser = tab.linkedBrowser;
+
+ await openPopupOn(browser, "form " + TEST_FOCUS_NAME_FIELD_SELECTOR);
+
+ for (let i = 0; i <= idx; i++) {
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ }
+
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(
+ browser,
+ TEST_FOCUS_NAME_FIELD_SELECTOR,
+ profile[TEST_FOCUS_NAME_FIELD]
+ );
+ await focusUpdateSubmitForm(
+ browser,
+ {
+ focusSelector: TEST_FOCUS_NAME_FIELD_SELECTOR,
+ newValues: {},
+ },
+ submitForm
+ );
+
+ if (!closeTab) {
+ return tab;
+ }
+
+ await BrowserTestUtils.removeTab(tab);
+ return null;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [ENABLED_PREF, true],
+ [AVAILABLE_PREF, "on"],
+ [CAPTURE_ENABLE_PREF, true],
+ ],
+ });
+
+ Services.telemetry.setEventRecordingEnabled(EVENT_CATEGORY, true);
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+
+ registerCleanupFunction(() => {
+ Services.telemetry.setEventRecordingEnabled(EVENT_CATEGORY, false);
+ });
+});
+
+add_task(async function test_popup_opened() {
+ await setStorage(TEST_PROFILE);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_BASIC_ADDRESS_FORM_URL },
+ async function (browser) {
+ const focusInput = TEST_FOCUS_NAME_FIELD_SELECTOR;
+
+ await openPopupOn(browser, focusInput);
+
+ // Clean up
+ await closePopup(browser);
+ }
+ );
+
+ const fields = TEST_BASIC_ADDRESS_FORM_FIELDS;
+ await assertTelemetry([
+ ...formArgs("detected", {}, fields, "true", "false"),
+ ...formArgs("popup_shown", { field_name: TEST_FOCUS_NAME_FIELD }),
+ ]);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ SCALAR_DETECTED_SECTION_COUNT,
+ 1,
+ "There should be 1 section detected."
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ TelemetryTestUtils.getProcessScalars("content"),
+ SCALAR_SUBMITTED_SECTION_COUNT,
+ 1
+ );
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_popup_opened_form_without_autocomplete() {
+ await setStorage(TEST_PROFILE);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_BASIC_ADDRESS_FORM_WITHOUT_AC_URL },
+ async function (browser) {
+ const focusInput = TEST_FOCUS_NAME_FIELD_SELECTOR;
+ await openPopupOn(browser, focusInput);
+ await closePopup(browser);
+ }
+ );
+
+ const fields = TEST_BASIC_ADDRESS_FORM_FIELDS;
+ await assertTelemetry([
+ ...formArgs("detected", {}, fields, "0", "false"),
+ ...formArgs("popup_shown", { field_name: TEST_FOCUS_NAME_FIELD }),
+ ]);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ SCALAR_DETECTED_SECTION_COUNT,
+ 1,
+ "There should be 1 section detected."
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ TelemetryTestUtils.getProcessScalars("content"),
+ SCALAR_SUBMITTED_SECTION_COUNT
+ );
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_submit_autofill_profile_new() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.firstTimeUse", true]],
+ });
+ async function test_per_command(
+ command,
+ idx,
+ useCount = {},
+ expectChanged = undefined
+ ) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_BASIC_ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let onChanged;
+ if (expectChanged !== undefined) {
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed");
+ }
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: TEST_FOCUS_NAME_FIELD_SELECTOR,
+ newValues: TEST_NEW_VALUES,
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(command, idx);
+ if (expectChanged !== undefined) {
+ await onChanged;
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("parent"),
+ SCALAR_AUTOFILL_PROFILE_COUNT,
+ expectChanged,
+ "There should be ${expectChanged} profile(s) stored."
+ );
+ }
+ }
+ );
+
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ useCount
+ );
+
+ await removeAllRecords();
+ }
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+ Services.telemetry.getKeyedHistogramById(HISTOGRAM_PROFILE_NUM_USES).clear();
+
+ const fields = TEST_BASIC_ADDRESS_FORM_FIELDS;
+ let expected_content = [
+ ...formArgs("detected", {}, fields, "true", "false"),
+ ...formArgs("submitted", {}, fields, "user_filled", "unavailable"),
+ ];
+
+ // FTU
+ await test_per_command(MAIN_BUTTON, undefined, { 1: 1 }, 1);
+ await assertTelemetry(expected_content, [
+ [EVENT_CATEGORY, "show", "capture_doorhanger"],
+ [EVENT_CATEGORY, "pref", "capture_doorhanger"],
+ ]);
+
+ // Need to close preference tab
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ SCALAR_DETECTED_SECTION_COUNT,
+ 1,
+ "There should be 1 sections detected."
+ );
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("content"),
+ SCALAR_SUBMITTED_SECTION_COUNT,
+ 1,
+ "There should be 1 section submitted."
+ );
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_submit_autofill_profile_update() {
+ async function test_per_command(
+ command,
+ idx,
+ useCount = {},
+ expectChanged = undefined
+ ) {
+ await setStorage(TEST_PROFILE_2);
+ let profiles = await getProfiles();
+ Assert.equal(profiles.length, 1, "1 entry in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_BASIC_ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let onChanged;
+ if (expectChanged !== undefined) {
+ onChanged = TestUtils.topicObserved("formautofill-storage-changed");
+ }
+
+ await openPopupOn(browser, TEST_FOCUS_NAME_FIELD_SELECTOR);
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+
+ await waitForAutofill(
+ browser,
+ TEST_FOCUS_NAME_FIELD_SELECTOR,
+ TEST_PROFILE_2[TEST_FOCUS_NAME_FIELD]
+ );
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: TEST_FOCUS_NAME_FIELD_SELECTOR,
+ newValues: TEST_UPDATE_PROFILE_2_VALUES,
+ });
+ await onPopupShown;
+ await clickDoorhangerButton(command, idx);
+ if (expectChanged !== undefined) {
+ await onChanged;
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("parent"),
+ SCALAR_AUTOFILL_PROFILE_COUNT,
+ expectChanged,
+ "There should be ${expectChanged} profile(s) stored."
+ );
+ }
+ }
+ );
+
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ useCount
+ );
+
+ SpecialPowers.clearUserPref(ENABLED_PREF);
+
+ await removeAllRecords();
+ }
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+ Services.telemetry.getKeyedHistogramById(HISTOGRAM_PROFILE_NUM_USES).clear();
+
+ const fields = TEST_BASIC_ADDRESS_FORM_FIELDS;
+ let expected_content = [
+ ...formArgs("detected", {}, fields, "true", "false"),
+ ...formArgs("popup_shown", { field_name: TEST_FOCUS_NAME_FIELD }),
+ ...formArgs(
+ "filled",
+ {
+ given_name: "filled",
+ street_address: "filled",
+ country: "filled",
+ },
+ fields,
+ "not_filled",
+ "unavailable"
+ ),
+ ...formArgs(
+ "submitted",
+ {
+ given_name: "autofilled",
+ street_address: "autofilled",
+ country: "autofilled",
+ email: "user_filled",
+ },
+ fields,
+ "not_filled",
+ "unavailable"
+ ),
+ ];
+
+ await test_per_command(MAIN_BUTTON, undefined, { 1: 1 }, 1);
+ await assertTelemetry(expected_content, [
+ [EVENT_CATEGORY, "show", "update_doorhanger"],
+ [EVENT_CATEGORY, "update", "update_doorhanger"],
+ ]);
+
+ await test_per_command(SECONDARY_BUTTON, undefined, { 0: 1, 1: 1 }, 2);
+ await assertTelemetry(expected_content, [
+ [EVENT_CATEGORY, "show", "update_doorhanger"],
+ [EVENT_CATEGORY, "save", "update_doorhanger"],
+ ]);
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_removingAutofillProfilesViaKeyboardDelete() {
+ await setStorage(TEST_PROFILE);
+
+ let win = window.openDialog(MANAGE_DIALOG_URL, null, DIALOG_SIZE);
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(MANAGE_RECORD_SELECTOR);
+ Assert.equal(selRecords.length, 1, "One entry");
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ Assert.equal(selRecords.length, 0, "No entry left");
+
+ win.close();
+
+ await assertTelemetry(undefined, [
+ [EVENT_CATEGORY, "show", "manage"],
+ [EVENT_CATEGORY, "delete", "manage"],
+ ]);
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_saveAutofillProfile() {
+ Services.telemetry.clearEvents();
+
+ await testDialog(EDIT_DIALOG_URL, win => {
+ // TODP: Default to US because the layout will be different
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_PROFILE["given-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_PROFILE["family-name"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_PROFILE["street-address"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_PROFILE["address-level2"], {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey(TEST_PROFILE["postal-code"], {}, win);
+ info("saving one entry");
+ win.document.querySelector("#save").click();
+ });
+
+ await assertTelemetry(undefined, [[EVENT_CATEGORY, "add", "manage"]]);
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_editAutofillProfile() {
+ Services.telemetry.clearEvents();
+
+ await setStorage(TEST_PROFILE);
+
+ let profiles = await getProfiles();
+ Assert.equal(profiles.length, 1, "1 entry in storage");
+ await testDialog(
+ EDIT_DIALOG_URL,
+ win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: profiles[0],
+ }
+ );
+
+ await assertTelemetry(undefined, [
+ [EVENT_CATEGORY, "show_entry", "manage"],
+ [EVENT_CATEGORY, "edit", "manage"],
+ ]);
+
+ await removeAllRecords();
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_histogram() {
+ Services.telemetry.getKeyedHistogramById(HISTOGRAM_PROFILE_NUM_USES).clear();
+
+ await setStorage(TEST_PROFILE_1, TEST_PROFILE_2, TEST_PROFILE_3);
+ let profiles = await getProfiles();
+ Assert.equal(profiles.length, 3, "3 entry in storage");
+
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ { 0: 3 }
+ );
+
+ await openTabAndUseAutofillProfile(0, TEST_PROFILE_1);
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ { 0: 2, 1: 1 }
+ );
+
+ await openTabAndUseAutofillProfile(1, TEST_PROFILE_2);
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ { 0: 1, 1: 2 }
+ );
+
+ await openTabAndUseAutofillProfile(0, TEST_PROFILE_2);
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ { 0: 1, 1: 1, 2: 1 }
+ );
+
+ await openTabAndUseAutofillProfile(1, TEST_PROFILE_1);
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ { 0: 1, 2: 2 }
+ );
+
+ await openTabAndUseAutofillProfile(2, TEST_PROFILE_3);
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ { 1: 1, 2: 2 }
+ );
+
+ await removeAllRecords();
+
+ assertKeyedHistogram(
+ HISTOGRAM_PROFILE_NUM_USES,
+ HISTOGRAM_PROFILE_NUM_USES_KEY,
+ undefined
+ );
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+});
+
+add_task(async function test_clear_autofill_profile_autofill() {
+ // Address does not have clear pref. Keep the test so we know we should implement
+ // the test if we support clearing address via autocomplete.
+ Assert.ok(true);
+});
diff --git a/browser/extensions/formautofill/test/browser/address/head_address.js b/browser/extensions/formautofill/test/browser/address/head_address.js
new file mode 100644
index 0000000000..42196e8422
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/head_address.js
@@ -0,0 +1 @@
+/* import-globals-from ../head.js */
diff --git a/browser/extensions/formautofill/test/browser/browser.ini b/browser/extensions/formautofill/test/browser/browser.ini
new file mode 100644
index 0000000000..c9d100430b
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -0,0 +1,35 @@
+[DEFAULT]
+head = head.js
+support-files =
+ ./fathom/**
+ ../fixtures/autocomplete_basic.html
+ ../fixtures/autocomplete_iframe.html
+ ../fixtures/autocomplete_simple_basic.html
+ ./empty.html
+
+[browser_autocomplete_footer.js]
+skip-if = verify
+[browser_autocomplete_marked_back_forward.js]
+[browser_autocomplete_marked_detached_tab.js]
+skip-if =
+ verify && (os == "win")
+ os == "mac"
+[browser_autofill_address_select.js]
+[browser_autofill_duplicate_fields.js]
+[browser_check_installed.js]
+[browser_dropdown_layout.js]
+[browser_editAddressDialog.js]
+skip-if =
+ verify
+ win10_2004 # Bug 1723573
+ win11_2009 # Bug 1797751
+[browser_fathom_cc.js]
+[browser_first_time_use_doorhanger.js]
+skip-if = verify
+[browser_manageAddressesDialog.js]
+[browser_privacyPreferences.js]
+skip-if = (( os == "mac") || (os == 'linux') || (os == 'win')) # perma-fail see Bug 1600059
+[browser_remoteiframe.js]
+[browser_submission_in_private_mode.js]
+[browser_update_doorhanger.js]
+skip-if = true # bug 1426981 # Bug 1445538
diff --git a/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
new file mode 100644
index 0000000000..d2f5c72f16
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
@@ -0,0 +1,137 @@
+"use strict";
+
+const URL = BASE_URL + "autocomplete_basic.html";
+
+add_task(async function setup_storage() {
+ await setStorage(
+ TEST_ADDRESS_2,
+ TEST_ADDRESS_3,
+ TEST_ADDRESS_4,
+ TEST_ADDRESS_5
+ );
+});
+
+add_task(async function test_press_enter_on_footer() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ const {
+ autoCompletePopup: { richlistbox: itemsBox },
+ } = browser;
+
+ await openPopupOn(browser, "#organization");
+ // Navigate to the footer and press enter.
+ const listItemElems = itemsBox.querySelectorAll(
+ ".autocomplete-richlistitem"
+ );
+ const prefTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ PRIVACY_PREF_URL,
+ true
+ );
+ for (let i = 0; i < listItemElems.length; i++) {
+ if (!listItemElems[i].collapsed) {
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ }
+ }
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ info(`expecting tab: about:preferences#privacy opened`);
+ const prefTab = await prefTabPromise;
+ info(`expecting tab: about:preferences#privacy removed`);
+ BrowserTestUtils.removeTab(prefTab);
+ ok(
+ true,
+ "Tab: preferences#privacy was successfully opened by pressing enter on the footer"
+ );
+
+ await closePopup(browser);
+ }
+ );
+});
+
+add_task(async function test_click_on_footer() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ const {
+ autoCompletePopup: { richlistbox: itemsBox },
+ } = browser;
+
+ await openPopupOn(browser, "#organization");
+ // Click on the footer
+ let optionButton = itemsBox.querySelector(
+ ".autocomplete-richlistitem:last-child"
+ );
+ while (optionButton.collapsed) {
+ optionButton = optionButton.previousElementSibling;
+ }
+ optionButton = optionButton._optionButton;
+
+ const prefTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ PRIVACY_PREF_URL,
+ true
+ );
+ // Make sure dropdown is visible before continuing mouse synthesizing.
+ await BrowserTestUtils.waitForCondition(() =>
+ BrowserTestUtils.is_visible(optionButton)
+ );
+ await EventUtils.synthesizeMouseAtCenter(optionButton, {});
+ info(`expecting tab: about:preferences#privacy opened`);
+ const prefTab = await prefTabPromise;
+ info(`expecting tab: about:preferences#privacy removed`);
+ BrowserTestUtils.removeTab(prefTab);
+ ok(
+ true,
+ "Tab: preferences#privacy was successfully opened by clicking on the footer"
+ );
+
+ await closePopup(browser);
+ }
+ );
+});
+
+add_task(async function test_phishing_warning_single_category() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ const {
+ autoCompletePopup: { richlistbox: itemsBox },
+ } = browser;
+
+ await openPopupOn(browser, "#tel");
+ const warningBox = itemsBox.querySelector(
+ ".autocomplete-richlistitem:last-child"
+ )._warningTextBox;
+ ok(warningBox, "Got phishing warning box");
+ await expectWarningText(browser, "Autofills phone");
+ is(
+ warningBox.ownerGlobal.getComputedStyle(warningBox).backgroundColor,
+ "rgba(248, 232, 28, 0.2)",
+ "Check warning text background color"
+ );
+
+ await closePopup(browser);
+ }
+ );
+});
+
+add_task(async function test_phishing_warning_complex_categories() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ await openPopupOn(browser, "#street-address");
+
+ await expectWarningText(browser, "Also autofills organization, email");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await expectWarningText(browser, "Autofills address");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await expectWarningText(browser, "Also autofills organization, email");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await expectWarningText(browser, "Also autofills organization, email");
+
+ await closePopup(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
new file mode 100644
index 0000000000..ada61fa014
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_back_forward.js
@@ -0,0 +1,66 @@
+/**
+ * Test that autofill autocomplete works after back/forward navigation
+ */
+
+"use strict";
+
+const URL = BASE_URL + "autocomplete_basic.html";
+
+function checkPopup(autoCompletePopup) {
+ let first = autoCompletePopup.view.results[0];
+ const { primary, secondary } = JSON.parse(first.label);
+ ok(
+ primary.startsWith(TEST_ADDRESS_1["street-address"].split("\n")[0]),
+ "Check primary label is street address"
+ );
+ is(
+ secondary,
+ TEST_ADDRESS_1["address-level2"],
+ "Check secondary label is address-level2"
+ );
+}
+
+add_task(async function setup_storage() {
+ await setStorage(TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3);
+});
+
+add_task(async function test_back_forward() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ const { autoCompletePopup } = browser;
+
+ // Check the page after the initial load
+ await openPopupOn(browser, "#street-address");
+ checkPopup(autoCompletePopup);
+
+ // Now navigate forward and make sure autofill autocomplete results are still attached
+ let loadPromise = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, `${URL}?load=2`);
+ info("expecting browser loaded");
+ await loadPromise;
+
+ // Check the second page
+ await openPopupOn(browser, "#street-address");
+ checkPopup(autoCompletePopup);
+
+ // Check after hitting back to the first page
+ let stoppedPromise = BrowserTestUtils.browserStopped(browser);
+ browser.goBack();
+ info("expecting browser stopped");
+ await stoppedPromise;
+ await openPopupOn(browser, "#street-address");
+ checkPopup(autoCompletePopup);
+
+ // Check after hitting forward to the second page
+ stoppedPromise = BrowserTestUtils.browserStopped(browser);
+ browser.goForward();
+ info("expecting browser stopped");
+ await stoppedPromise;
+ await openPopupOn(browser, "#street-address");
+ checkPopup(autoCompletePopup);
+
+ await closePopup(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
new file mode 100644
index 0000000000..399e7e16e5
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_marked_detached_tab.js
@@ -0,0 +1,58 @@
+/**
+ * Test that autofill autocomplete works after detaching a tab
+ */
+
+"use strict";
+
+const URL = BASE_URL + "autocomplete_basic.html";
+
+function checkPopup(autoCompletePopup) {
+ let first = autoCompletePopup.view.results[0];
+ const { primary, secondary } = JSON.parse(first.label);
+ ok(
+ primary.startsWith(TEST_ADDRESS_1["street-address"].split("\n")[0]),
+ "Check primary label is street address"
+ );
+ is(
+ secondary,
+ TEST_ADDRESS_1["address-level2"],
+ "Check secondary label is address-level2"
+ );
+}
+
+add_task(async function setup_storage() {
+ await setStorage(TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3);
+});
+
+add_task(async function test_detach_tab_marked() {
+ let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: URL });
+ let browser = tab.linkedBrowser;
+ const { autoCompletePopup } = browser;
+
+ // Check the page after the initial load
+ await openPopupOn(browser, "#street-address");
+ checkPopup(autoCompletePopup);
+ await closePopup(browser);
+
+ // Detach the tab to a new window
+ info("expecting tab replaced with new window");
+ let windowLoadedPromise = BrowserTestUtils.waitForNewWindow();
+ let newWin = gBrowser.replaceTabWithWindow(
+ gBrowser.getTabForBrowser(browser)
+ );
+ await windowLoadedPromise;
+
+ info("tab was detached");
+ let newBrowser = newWin.gBrowser.selectedBrowser;
+ ok(newBrowser, "Found new <browser>");
+ let newAutoCompletePopup = newBrowser.autoCompletePopup;
+ ok(newAutoCompletePopup, "Found new autocomplete popup");
+
+ await openPopupOn(newBrowser, "#street-address");
+ checkPopup(newAutoCompletePopup);
+
+ await closePopup(newBrowser);
+ let windowRefocusedPromise = BrowserTestUtils.waitForEvent(window, "focus");
+ await BrowserTestUtils.closeWindow(newWin);
+ await windowRefocusedPromise;
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_autofill_address_select.js b/browser/extensions/formautofill/test/browser/browser_autofill_address_select.js
new file mode 100644
index 0000000000..68e38ad10d
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_autofill_address_select.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PROFILE_US = {
+ email: "address_us@mozilla.org",
+ organization: "Mozilla",
+ country: "US",
+};
+
+const TEST_PROFILE_CA = {
+ email: "address_ca@mozilla.org",
+ organization: "Mozilla",
+ country: "CA",
+};
+
+const MARKUP_SELECT_COUNTRY = `
+ <html><body>
+ <input id="email" autocomplete="email">
+ <input id="organization" autocomplete="organization">
+ <select id="country""" autocomplete="country">
+ <option value="Germany">Germany</option>
+ <option value="Canada">Canada</option>
+ <option value="United States">United States</option>
+ <option value="France">France</option>
+ </select>
+ </body></html>
+`;
+
+add_autofill_heuristic_tests([
+ {
+ fixtureData: MARKUP_SELECT_COUNTRY,
+ profile: TEST_PROFILE_US,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "email", autofill: TEST_PROFILE_US.email },
+ { fieldName: "organization", autofill: TEST_PROFILE_US.organization },
+ { fieldName: "country", autofill: "United States" },
+ ],
+ },
+ ],
+ },
+ {
+ fixtureData: MARKUP_SELECT_COUNTRY,
+ profile: TEST_PROFILE_CA,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "email", autofill: TEST_PROFILE_CA.email },
+ { fieldName: "organization", autofill: TEST_PROFILE_CA.organization },
+ { fieldName: "country", autofill: "Canada" },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/browser_autofill_duplicate_fields.js b/browser/extensions/formautofill/test/browser/browser_autofill_duplicate_fields.js
new file mode 100644
index 0000000000..d7f80c2749
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_autofill_duplicate_fields.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PROFILE = {
+ "given-name": "Timothy",
+ "additional-name": "John",
+ "family-name": "Berners-Lee",
+ organization: "Mozilla",
+ "street-address": "331 E Evelyn Ave",
+ "address-level2": "Mountain View",
+ "address-level1": "CA",
+ "postal-code": "94041",
+ country: "US",
+ tel: "+16509030800",
+ email: "address@mozilla.org",
+};
+
+add_autofill_heuristic_tests([
+ {
+ fixtureData: `
+ <html><body>
+ <input id="email" autocomplete="email">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ </body></html>
+ `,
+ profile: TEST_PROFILE,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "email", autofill: TEST_PROFILE.email },
+ { fieldName: "postal-code", autofill: TEST_PROFILE["postal-code"] },
+ { fieldName: "country", autofill: TEST_PROFILE.country },
+ ],
+ },
+ ],
+ },
+ {
+ description: "autofill multiple email fields(2)",
+ fixtureData: `
+ <html><body>
+ <input id="email" autocomplete="email">
+ <input id="email" autocomplete="email">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ </body></html>
+ `,
+ profile: TEST_PROFILE,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "email", autofill: TEST_PROFILE.email },
+ { fieldName: "email", autofill: TEST_PROFILE.email },
+ { fieldName: "postal-code", autofill: TEST_PROFILE["postal-code"] },
+ { fieldName: "country", autofill: TEST_PROFILE.country },
+ ],
+ },
+ ],
+ },
+ {
+ description: "autofill multiple email fields(3)",
+ fixtureData: `
+ <html><body>
+ <input id="email" autocomplete="email">
+ <input id="email" autocomplete="email">
+ <input id="email" autocomplete="email">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ </body></html>
+ `,
+ profile: TEST_PROFILE,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "email", autofill: TEST_PROFILE.email },
+ { fieldName: "email", autofill: TEST_PROFILE.email },
+ { fieldName: "email", autofill: TEST_PROFILE.email },
+ { fieldName: "postal-code", autofill: TEST_PROFILE["postal-code"] },
+ { fieldName: "country", autofill: TEST_PROFILE.country },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/browser_check_installed.js b/browser/extensions/formautofill/test/browser/browser_check_installed.js
new file mode 100644
index 0000000000..a93e64c209
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_check_installed.js
@@ -0,0 +1,12 @@
+"use strict";
+
+add_task(async function test_enabled() {
+ let addon = await AddonManager.getAddonByID("formautofill@mozilla.org");
+ isnot(addon, null, "Check addon exists");
+ is(addon.version, "1.0.1", "Check version");
+ is(addon.name, "Form Autofill", "Check name");
+ ok(addon.isCompatible, "Check application compatibility");
+ ok(!addon.appDisabled, "Check not app disabled");
+ ok(addon.isActive, "Check addon is active");
+ is(addon.type, "extension", "Check type is 'extension'");
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_dropdown_layout.js b/browser/extensions/formautofill/test/browser/browser_dropdown_layout.js
new file mode 100644
index 0000000000..bc1d2fccab
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_dropdown_layout.js
@@ -0,0 +1,53 @@
+"use strict";
+
+const URL =
+ "http://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
+
+add_task(async function setup_storage() {
+ await setStorage(TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_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_address_dropdown() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ const focusInput = "#organization";
+ await openPopupOn(browser, focusInput);
+ const firstItem = getDisplayedPopupItems(browser)[0];
+
+ is(firstItem.getAttribute("ac-image"), "", "Should not show icon");
+
+ // The breakpoint of two-lines layout is 150px
+ await reopenPopupWithResizedInput(browser, focusInput, 140);
+ is(
+ firstItem._itemBox.getAttribute("size"),
+ "small",
+ "Show two-lines layout"
+ );
+ await reopenPopupWithResizedInput(browser, focusInput, 160);
+ is(
+ firstItem._itemBox.hasAttribute("size"),
+ false,
+ "Show one-line layout"
+ );
+
+ await closePopup(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
new file mode 100644
index 0000000000..0088f916bd
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -0,0 +1,951 @@
+"use strict";
+
+const { FormAutofillUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/shared/FormAutofillUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ Region: "resource://gre/modules/Region.sys.mjs",
+});
+
+requestLongerTimeout(6);
+
+add_task(async function setup_supportedCountries() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUPPORTED_COUNTRIES_PREF, "US,CA,DE"]],
+ });
+});
+
+add_task(async function test_cancelEditAddressDialog() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ win.document.querySelector("#cancel").click();
+ });
+});
+
+add_task(async function test_cancelEditAddressDialogWithESC() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ });
+});
+
+add_task(async function test_defaultCountry() {
+ Region._setHomeRegion("CA", false);
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ let doc = win.document;
+ is(
+ doc.querySelector("#country").value,
+ "CA",
+ "Default country set to Canada"
+ );
+ doc.querySelector("#cancel").click();
+ });
+ Region._setHomeRegion("DE", false);
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ let doc = win.document;
+ is(
+ doc.querySelector("#country").value,
+ "DE",
+ "Default country set to Germany"
+ );
+ doc.querySelector("#cancel").click();
+ });
+ // Test unsupported country
+ Region._setHomeRegion("XX", false);
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ let doc = win.document;
+ is(doc.querySelector("#country").value, "", "Default country set to empty");
+ doc.querySelector("#cancel").click();
+ });
+ Region._setHomeRegion("US", false);
+});
+
+add_task(async function test_saveAddress() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ let doc = win.document;
+ // Verify labels
+ is(
+ doc.querySelector("#address-level1-container > .label-text").textContent,
+ "State",
+ "US address-level1 label should be 'State'"
+ );
+ is(
+ doc.querySelector("#postal-code-container > .label-text").textContent,
+ "ZIP Code",
+ "US postal-code label should be 'ZIP Code'"
+ );
+ // Input address info and verify move through form with tab keys
+ let keypresses = [
+ "VK_TAB",
+ TEST_ADDRESS_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_1["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS_1["address-level1"],
+ "VK_TAB",
+ TEST_ADDRESS_1["postal-code"],
+ "VK_TAB",
+ TEST_ADDRESS_1.organization,
+ "VK_TAB",
+ // TEST_ADDRESS_1.country, // Country is already US
+ "VK_TAB",
+ TEST_ADDRESS_1.tel,
+ "VK_TAB",
+ TEST_ADDRESS_1.email,
+ "VK_TAB",
+ ];
+ if (AppConstants.platform != "win") {
+ keypresses.push("VK_TAB", "VK_RETURN");
+ } else {
+ keypresses.push("VK_RETURN");
+ }
+ keypresses.forEach(keypress => {
+ if (
+ doc.activeElement.localName == "select" &&
+ !keypress.startsWith("VK_")
+ ) {
+ let field = doc.activeElement;
+ while (field.value != keypress) {
+ EventUtils.synthesizeKey(keypress[0], {}, win);
+ }
+ } else {
+ EventUtils.synthesizeKey(keypress, {}, win);
+ }
+ });
+ });
+ let addresses = await getAddresses();
+
+ is(addresses.length, 1, "only one address is in storage");
+ is(
+ Object.keys(TEST_ADDRESS_1).length,
+ 11,
+ "Sanity check number of properties"
+ );
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_1)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+});
+
+add_task(async function test_editAddress() {
+ let addresses = await getAddresses();
+ await testDialog(
+ EDIT_ADDRESS_DIALOG_URL,
+ win => {
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+
+ let stateSelect = win.document.querySelector("#address-level1");
+ is(
+ stateSelect.selectedOptions[0].value,
+ TEST_ADDRESS_1["address-level1"],
+ "address-level1 should be selected in the dropdown"
+ );
+
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: addresses[0],
+ }
+ );
+ addresses = await getAddresses();
+
+ is(addresses.length, 1, "only one address is in storage");
+ is(
+ addresses[0]["given-name"],
+ TEST_ADDRESS_1["given-name"] + "test",
+ "given-name changed"
+ );
+ await removeAddresses([addresses[0].guid]);
+
+ addresses = await getAddresses();
+ is(addresses.length, 0, "Address storage is empty");
+});
+
+add_task(
+ async function test_editAddressFrenchCanadianChangedToEnglishRepresentation() {
+ let addressClone = Object.assign({}, TEST_ADDRESS_CA_1);
+ addressClone["address-level1"] = "Colombie-Britannique";
+ await setStorage(addressClone);
+
+ let addresses = await getAddresses();
+ await testDialog(
+ EDIT_ADDRESS_DIALOG_URL,
+ win => {
+ let stateSelect = win.document.querySelector("#address-level1");
+ is(
+ stateSelect.selectedOptions[0].value,
+ "BC",
+ "address-level1 should have 'BC' selected in the dropdown"
+ );
+
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: addresses[0],
+ }
+ );
+ addresses = await getAddresses();
+
+ is(addresses.length, 1, "only one address is in storage");
+ is(addresses[0]["address-level1"], "BC", "address-level1 changed");
+ await removeAddresses([addresses[0].guid]);
+
+ addresses = await getAddresses();
+ is(addresses.length, 0, "Address storage is empty");
+ }
+);
+
+add_task(async function test_editSparseAddress() {
+ let record = { ...TEST_ADDRESS_1 };
+ info("delete some usually required properties");
+ delete record["street-address"];
+ delete record["address-level1"];
+ delete record["address-level2"];
+ await testDialog(
+ EDIT_ADDRESS_DIALOG_URL,
+ win => {
+ is(
+ win.document.querySelectorAll(":-moz-ui-invalid").length,
+ 0,
+ "Check no fields are visually invalid"
+ );
+ EventUtils.synthesizeKey("VK_TAB", {}, win);
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("test", {}, win);
+ is(
+ win.document.querySelector("#save").disabled,
+ false,
+ "Save button should be enabled after an edit"
+ );
+ win.document.querySelector("#cancel").click();
+ },
+ {
+ record,
+ }
+ );
+});
+
+add_task(async function test_saveAddressCA() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Canada", {}, win);
+
+ await TestUtils.waitForCondition(() => {
+ return (
+ doc.querySelector("#address-level1-container > .label-text")
+ .textContent == "Province"
+ );
+ }, "Wait for the mutation observer to change the labels");
+ is(
+ doc.querySelector("#address-level1-container > .label-text").textContent,
+ "Province",
+ "CA address-level1 label should be 'Province'"
+ );
+ is(
+ doc.querySelector("#postal-code-container > .label-text").textContent,
+ "Postal Code",
+ "CA postal-code label should be 'Postal Code'"
+ );
+ is(
+ doc.querySelector("#address-level3-container").style.display,
+ "none",
+ "CA address-level3 should be hidden"
+ );
+
+ // Input address info and verify move through form with tab keys
+ doc.querySelector("#given-name").focus();
+ let keyInputs = [
+ TEST_ADDRESS_CA_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.organization,
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["address-level1"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["postal-code"],
+ "VK_TAB",
+ // TEST_ADDRESS_1.country, // Country is already selected above
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.tel,
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.email,
+ "VK_TAB",
+ ];
+ if (AppConstants.platform != "win") {
+ keyInputs.push("VK_TAB", "VK_RETURN");
+ } else {
+ keyInputs.push("VK_RETURN");
+ }
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+ });
+ let addresses = await getAddresses();
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_CA_1)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ await removeAllRecords();
+});
+
+add_task(async function test_saveAddressDE() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Germany", {}, win);
+ await TestUtils.waitForCondition(() => {
+ return (
+ doc.querySelector("#postal-code-container > .label-text").textContent ==
+ "Postal Code"
+ );
+ }, "Wait for the mutation observer to change the labels");
+ is(
+ doc.querySelector("#postal-code-container > .label-text").textContent,
+ "Postal Code",
+ "DE postal-code label should be 'Postal Code'"
+ );
+ is(
+ doc.querySelector("#address-level1-container").style.display,
+ "none",
+ "DE address-level1 should be hidden"
+ );
+ is(
+ doc.querySelector("#address-level3-container").style.display,
+ "none",
+ "DE address-level3 should be hidden"
+ );
+ // Input address info and verify move through form with tab keys
+ doc.querySelector("#given-name").focus();
+ let keyInputs = [
+ TEST_ADDRESS_DE_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.organization,
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["postal-code"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["address-level2"],
+ "VK_TAB",
+ // TEST_ADDRESS_1.country, // Country is already selected above
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.tel,
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.email,
+ "VK_TAB",
+ ];
+ if (AppConstants.platform != "win") {
+ keyInputs.push("VK_TAB", "VK_RETURN");
+ } else {
+ keyInputs.push("VK_RETURN");
+ }
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+ });
+ let addresses = await getAddresses();
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_DE_1)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ await removeAllRecords();
+});
+
+/**
+ * Test saving an address for a region from regionNames.properties but not in
+ * addressReferences.js (libaddressinput).
+ */
+add_task(async function test_saveAddress_nolibaddressinput() {
+ const TEST_ADDRESS = {
+ ...TEST_ADDRESS_IE_1,
+ ...{
+ "address-level3": undefined,
+ country: "XG",
+ },
+ };
+
+ isnot(
+ FormAutofillUtils.getCountryAddressData("XG").key,
+ "XG",
+ "Check that the region we're testing with isn't in libaddressinput"
+ );
+
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Gaza Strip", {}, win);
+ await TestUtils.waitForCondition(() => {
+ return (
+ doc.querySelector("#postal-code-container > .label-text").textContent ==
+ "Postal Code"
+ );
+ }, "Wait for the mutation observer to change the labels");
+ is(
+ doc.querySelector("#postal-code-container > .label-text").textContent,
+ "Postal Code",
+ "XG postal-code label should be 'Postal Code'"
+ );
+ isnot(
+ doc.querySelector("#address-level1-container").style.display,
+ "none",
+ "XG address-level1 should be hidden"
+ );
+ is(
+ doc.querySelector("#address-level2").localName,
+ "input",
+ "XG address-level2 should be an <input>"
+ );
+ // Input address info and verify move through form with tab keys
+ doc.querySelector("#given-name").focus();
+ let keyInputs = [
+ TEST_ADDRESS["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS.organization,
+ "VK_TAB",
+ TEST_ADDRESS["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS["address-level1"],
+ "VK_TAB",
+ TEST_ADDRESS["postal-code"],
+ "VK_TAB",
+ // TEST_ADDRESS_1.country, // Country is already selected above
+ "VK_TAB",
+ TEST_ADDRESS.tel,
+ "VK_TAB",
+ TEST_ADDRESS.email,
+ "VK_TAB",
+ ];
+ if (AppConstants.platform != "win") {
+ keyInputs.push("VK_TAB", "VK_RETURN");
+ } else {
+ keyInputs.push("VK_RETURN");
+ }
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+ });
+ let addresses = await getAddresses();
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ await removeAllRecords();
+});
+
+add_task(async function test_saveAddressIE() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Ireland", {}, win);
+ await TestUtils.waitForCondition(() => {
+ return (
+ doc.querySelector("#postal-code-container > .label-text").textContent ==
+ "Eircode"
+ );
+ }, "Wait for the mutation observer to change the labels");
+ is(
+ doc.querySelector("#postal-code-container > .label-text").textContent,
+ "Eircode",
+ "IE postal-code label should be 'Eircode'"
+ );
+ is(
+ doc.querySelector("#address-level1-container > .label-text").textContent,
+ "County",
+ "IE address-level1 should be 'County'"
+ );
+ is(
+ doc.querySelector("#address-level3-container > .label-text").textContent,
+ "Townland",
+ "IE address-level3 should be 'Townland'"
+ );
+
+ // Input address info and verify move through form with tab keys
+ doc.querySelector("#given-name").focus();
+ let keyInputs = [
+ TEST_ADDRESS_IE_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1.organization,
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["address-level3"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["address-level1"],
+ "VK_TAB",
+ TEST_ADDRESS_IE_1["postal-code"],
+ "VK_TAB",
+ // TEST_ADDRESS_1.country, // Country is already selected above
+ "VK_TAB",
+ TEST_ADDRESS_IE_1.tel,
+ "VK_TAB",
+ TEST_ADDRESS_IE_1.email,
+ "VK_TAB",
+ ];
+ if (AppConstants.platform != "win") {
+ keyInputs.push("VK_TAB", "VK_RETURN");
+ } else {
+ keyInputs.push("VK_RETURN");
+ }
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+ });
+
+ let addresses = await getAddresses();
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_IE_1)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ await removeAllRecords();
+});
+
+add_task(async function test_countryAndStateFieldLabels() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+
+ let mutableLabels = [
+ "postal-code-container",
+ "address-level1-container",
+ "address-level2-container",
+ "address-level3-container",
+ ].map(containerID =>
+ doc.getElementById(containerID).querySelector(":scope > .label-text")
+ );
+
+ for (let countryOption of doc.querySelector("#country").options) {
+ if (countryOption.value == "") {
+ info("Skipping the empty country option");
+ continue;
+ }
+
+ // Clear L10N textContent to not leave leftovers between country tests
+ for (let labelEl of mutableLabels) {
+ doc.l10n.setAttributes(labelEl, "");
+ labelEl.textContent = "";
+ }
+
+ info(`Selecting '${countryOption.label}' (${countryOption.value})`);
+ EventUtils.synthesizeKey(countryOption.label, {}, win);
+
+ let l10nResolve;
+ let l10nReady = new Promise(resolve => {
+ l10nResolve = resolve;
+ });
+ let verifyL10n = () => {
+ if (mutableLabels.every(labelEl => labelEl.textContent)) {
+ win.removeEventListener("MozAfterPaint", verifyL10n);
+ l10nResolve();
+ }
+ };
+ win.addEventListener("MozAfterPaint", verifyL10n);
+ await l10nReady;
+
+ // Check that the labels were filled
+ for (let labelEl of mutableLabels) {
+ isnot(
+ labelEl.textContent,
+ "",
+ "Ensure textContent is non-empty for: " + countryOption.value
+ );
+ }
+
+ let stateOptions = doc.querySelector("#address-level1").options;
+ /* eslint-disable max-len */
+ let expectedStateOptions = {
+ BS: {
+ // The Bahamas is an interesting testcase because they have some keys that are full names, and others are replaced with ISO IDs.
+ keys: "Abaco~AK~Andros~BY~BI~CI~Crooked Island~Eleuthera~EX~Grand Bahama~HI~IN~LI~MG~N.P.~RI~RC~SS~SW".split(
+ "~"
+ ),
+ names:
+ "Abaco Islands~Acklins~Andros Island~Berry Islands~Bimini~Cat Island~Crooked Island~Eleuthera~Exuma and Cays~Grand Bahama~Harbour Island~Inagua~Long Island~Mayaguana~New Providence~Ragged Island~Rum Cay~San Salvador~Spanish Wells".split(
+ "~"
+ ),
+ },
+ US: {
+ keys: "AL~AK~AS~AZ~AR~AA~AE~AP~CA~CO~CT~DE~DC~FL~GA~GU~HI~ID~IL~IN~IA~KS~KY~LA~ME~MH~MD~MA~MI~FM~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~MP~OH~OK~OR~PW~PA~PR~RI~SC~SD~TN~TX~UT~VT~VI~VA~WA~WV~WI~WY".split(
+ "~"
+ ),
+ names:
+ "Alabama~Alaska~American Samoa~Arizona~Arkansas~Armed Forces (AA)~Armed Forces (AE)~Armed Forces (AP)~California~Colorado~Connecticut~Delaware~District of Columbia~Florida~Georgia~Guam~Hawaii~Idaho~Illinois~Indiana~Iowa~Kansas~Kentucky~Louisiana~Maine~Marshall Islands~Maryland~Massachusetts~Michigan~Micronesia~Minnesota~Mississippi~Missouri~Montana~Nebraska~Nevada~New Hampshire~New Jersey~New Mexico~New York~North Carolina~North Dakota~Northern Mariana Islands~Ohio~Oklahoma~Oregon~Palau~Pennsylvania~Puerto Rico~Rhode Island~South Carolina~South Dakota~Tennessee~Texas~Utah~Vermont~Virgin Islands~Virginia~Washington~West Virginia~Wisconsin~Wyoming".split(
+ "~"
+ ),
+ },
+ };
+ /* eslint-enable max-len */
+
+ if (expectedStateOptions[countryOption.value]) {
+ let { keys, names } = expectedStateOptions[countryOption.value];
+ is(
+ stateOptions.length,
+ keys.length + 1,
+ "stateOptions should list all options plus a blank entry"
+ );
+ is(stateOptions[0].value, "", "First State option should be blank");
+ for (let i = 1; i < stateOptions.length; i++) {
+ is(
+ stateOptions[i].value,
+ keys[i - 1],
+ "Each State should be listed in alphabetical name order (key)"
+ );
+ is(
+ stateOptions[i].text,
+ names[i - 1],
+ "Each State should be listed in alphabetical name order (name)"
+ );
+ }
+ }
+ // Dispatch a dummy key event so that <select>'s incremental search is cleared.
+ EventUtils.synthesizeKey("VK_ACCEPT", {}, win);
+ }
+
+ doc.querySelector("#cancel").click();
+ });
+});
+
+add_task(async function test_combined_name_fields() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ let givenNameField = doc.querySelector("#given-name");
+ let addtlNameField = doc.querySelector("#additional-name");
+ let familyNameField = doc.querySelector("#family-name");
+
+ function getComputedPropertyValue(field, property) {
+ return win.getComputedStyle(field).getPropertyValue(property);
+ }
+ function checkNameComputedPropertiesMatch(
+ field,
+ property,
+ value,
+ checkFn = is
+ ) {
+ checkFn(
+ getComputedPropertyValue(field, property),
+ value,
+ `Check ${field.id}'s ${property} is ${value}`
+ );
+ }
+ function checkNameFieldBorders(borderColorUnfocused, borderColorFocused) {
+ info("checking the perimeter colors");
+ checkNameComputedPropertiesMatch(
+ givenNameField,
+ "border-top-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ addtlNameField,
+ "border-top-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ familyNameField,
+ "border-top-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ familyNameField,
+ "border-right-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ givenNameField,
+ "border-bottom-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ addtlNameField,
+ "border-bottom-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ familyNameField,
+ "border-bottom-color",
+ borderColorFocused
+ );
+ checkNameComputedPropertiesMatch(
+ givenNameField,
+ "border-left-color",
+ borderColorFocused
+ );
+
+ info("checking the internal borders");
+ checkNameComputedPropertiesMatch(
+ givenNameField,
+ "border-right-width",
+ "0px"
+ );
+ checkNameComputedPropertiesMatch(
+ addtlNameField,
+ "border-left-width",
+ "2px"
+ );
+ checkNameComputedPropertiesMatch(
+ addtlNameField,
+ "border-left-color",
+ borderColorFocused,
+ isnot
+ );
+ checkNameComputedPropertiesMatch(
+ addtlNameField,
+ "border-right-width",
+ "2px"
+ );
+ checkNameComputedPropertiesMatch(
+ addtlNameField,
+ "border-right-color",
+ borderColorFocused,
+ isnot
+ );
+ checkNameComputedPropertiesMatch(
+ familyNameField,
+ "border-left-width",
+ "0px"
+ );
+ }
+
+ // Set these variables since the test doesn't run from a subdialog and
+ // therefore doesn't get the additional common CSS files injected.
+ let borderColor = "rgb(0, 255, 0)";
+ let borderColorFocused = "rgb(0, 0, 255)";
+ doc.body.style.setProperty("--in-content-box-border-color", borderColor);
+ doc.body.style.setProperty(
+ "--in-content-focus-outline-color",
+ borderColorFocused
+ );
+
+ givenNameField.focus();
+ checkNameFieldBorders(borderColor, borderColorFocused);
+
+ addtlNameField.focus();
+ checkNameFieldBorders(borderColor, borderColorFocused);
+
+ familyNameField.focus();
+ checkNameFieldBorders(borderColor, borderColorFocused);
+
+ info("unfocusing the name fields");
+ let cancelButton = doc.querySelector("#cancel");
+ cancelButton.focus();
+ borderColor = getComputedPropertyValue(givenNameField, "border-top-color");
+ isnot(
+ borderColor,
+ borderColorFocused,
+ "Check that the border color is different"
+ );
+ checkNameFieldBorders(borderColor, borderColor);
+
+ cancelButton.click();
+ });
+});
+
+add_task(async function test_combined_name_fields_error() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ let givenNameField = doc.querySelector("#given-name");
+ info("mark the given name field as invalid");
+ givenNameField.value = "";
+ givenNameField.focus();
+ ok(
+ givenNameField.matches(":-moz-ui-invalid"),
+ "Check field is visually invalid"
+ );
+
+ let givenNameLabel = doc.querySelector("#given-name-container .label-text");
+ // Override pointer-events so that we can use elementFromPoint to know if
+ // the label text is visible.
+ givenNameLabel.style.pointerEvents = "auto";
+ let givenNameLabelRect = givenNameLabel.getBoundingClientRect();
+ // Get the center of the label
+ let el = doc.elementFromPoint(
+ givenNameLabelRect.left + givenNameLabelRect.width / 2,
+ givenNameLabelRect.top + givenNameLabelRect.height / 2
+ );
+
+ is(
+ el,
+ givenNameLabel,
+ "Check that the label text is visible in the error state"
+ );
+ is(
+ win.getComputedStyle(givenNameField).getPropertyValue("border-top-color"),
+ "rgba(0, 0, 0, 0)",
+ "Border should be transparent so that only the error outline shows"
+ );
+ doc.querySelector("#cancel").click();
+ });
+});
+
+add_task(async function test_hiddenFieldNotSaved() {
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ let doc = win.document;
+ doc.querySelector("#address-level2").focus();
+ EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level2"], {}, win);
+ doc.querySelector("#address-level1").focus();
+ EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level1"], {}, win);
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Germany", {}, win);
+ doc.querySelector("#save").focus();
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ });
+ let addresses = await getAddresses();
+ is(addresses[0].country, "DE", "check country");
+ is(
+ addresses[0]["address-level2"],
+ TEST_ADDRESS_1["address-level2"],
+ "check address-level2"
+ );
+ is(
+ addresses[0]["address-level1"],
+ undefined,
+ "address-level1 should not be saved"
+ );
+
+ await removeAllRecords();
+});
+
+add_task(async function test_hiddenFieldRemovedWhenCountryChanged() {
+ let addresses = await getAddresses();
+ ok(!addresses.length, "no addresses at start of test");
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+ let doc = win.document;
+ doc.querySelector("#address-level2").focus();
+ EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level2"], {}, win);
+ doc.querySelector("#address-level1").focus();
+ while (
+ doc.querySelector("#address-level1").value !=
+ TEST_ADDRESS_1["address-level1"]
+ ) {
+ EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level1"][0], {}, win);
+ }
+ doc.querySelector("#save").focus();
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ });
+ addresses = await getAddresses();
+ is(addresses[0].country, "US", "check country");
+ is(
+ addresses[0]["address-level2"],
+ TEST_ADDRESS_1["address-level2"],
+ "check address-level2"
+ );
+ is(
+ addresses[0]["address-level1"],
+ TEST_ADDRESS_1["address-level1"],
+ "check address-level1"
+ );
+
+ await testDialog(
+ EDIT_ADDRESS_DIALOG_URL,
+ win => {
+ let doc = win.document;
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Germany", {}, win);
+ win.document.querySelector("#save").click();
+ },
+ {
+ record: addresses[0],
+ }
+ );
+ addresses = await getAddresses();
+
+ is(addresses.length, 1, "only one address is in storage");
+ is(
+ addresses[0]["address-level2"],
+ TEST_ADDRESS_1["address-level2"],
+ "check address-level2"
+ );
+ is(
+ addresses[0]["address-level1"],
+ undefined,
+ "address-level1 should be removed"
+ );
+ is(addresses[0].country, "DE", "country changed");
+ await removeAllRecords();
+});
+
+add_task(async function test_countrySpecificFieldsGetRequiredness() {
+ Region._setHomeRegion("RO", false);
+ await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
+ let doc = win.document;
+ is(
+ doc.querySelector("#country").value,
+ "RO",
+ "Default country set to Romania"
+ );
+ let provinceField = doc.getElementById("address-level1");
+ ok(
+ !provinceField.required,
+ "address-level1 should not be marked as required"
+ );
+ ok(provinceField.disabled, "address-level1 should be marked as disabled");
+ is(
+ provinceField.parentNode.style.display,
+ "none",
+ "address-level1 is hidden for Romania"
+ );
+
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("United States", {}, win);
+
+ await TestUtils.waitForCondition(
+ () => {
+ provinceField = doc.getElementById("address-level1");
+ return provinceField.parentNode.style.display != "none";
+ },
+ "Wait for address-level1 to become visible",
+ 10
+ );
+
+ ok(provinceField.required, "address-level1 should be marked as required");
+ ok(
+ !provinceField.disabled,
+ "address-level1 should not be marked as disabled"
+ );
+
+ // Dispatch a dummy key event so that <select>'s incremental search is cleared.
+ EventUtils.synthesizeKey("VK_ACCEPT", {}, win);
+
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Romania", {}, win);
+
+ await TestUtils.waitForCondition(
+ () => {
+ provinceField = doc.getElementById("address-level1");
+ return provinceField.parentNode.style.display == "none";
+ },
+ "Wait for address-level1 to become hidden",
+ 10
+ );
+
+ ok(
+ provinceField.required,
+ "address-level1 will still be marked as required"
+ );
+ ok(provinceField.disabled, "address-level1 should be marked as disabled");
+
+ doc.querySelector("#cancel").click();
+ });
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_fathom_cc.js b/browser/extensions/formautofill/test/browser/browser_fathom_cc.js
new file mode 100644
index 0000000000..2e465fd503
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_fathom_cc.js
@@ -0,0 +1,204 @@
+/**
+ * By default this test only tests 1 sample. This is to avoid publishing all samples we have
+ * to the codebase. If you update the Fathom CC model, please follow the instruction below
+ * and run the test. Doing this makes sure the optimized (Native implementation) CC fathom model produces
+ * exactly the same result as the non-optimized model (JS implementation, See CreditCardRuleset.sys.mjs).
+ *
+ * To test this:
+ * 1. Run the test setup script (fathom/test-setup.sh) to download all samples to the local
+ * directory. Note that you need to have the permission to access the fathom-form-autofill
+ * 2. Set `gTestAutofillRepoSample` to true
+ * 3. Run this test
+ */
+
+"use strict";
+
+const eligibleElementSelector =
+ "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=month], select, button";
+
+const skippedSamples = [
+ // TOOD: Crash while running the following testcases. Since this is not caused by the fathom CC
+ // model, we just skip those for now
+ "EN_B105b.html",
+ "EN_B312a.html",
+ "EN_B118b.html",
+ "EN_B48c.html",
+
+ // This sample is skipped because of Bug 1754256 (Support lookaround regex for native fathom CC implementation).
+ "DE_B378b.html",
+];
+
+async function run_test(path, dirs) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.creditCards.heuristics.mode", 1]],
+ });
+
+ // Collect files we are going to test.
+ let files = [];
+ for (let dir of dirs) {
+ let entries = new FileUtils.File(getTestFilePath(path + dir))
+ .directoryEntries;
+
+ while (entries.hasMoreElements()) {
+ let entry = entries.nextFile;
+ if (skippedSamples.includes(entry.leafName)) {
+ continue;
+ }
+
+ if (entry.leafName.endsWith(".html")) {
+ files.push(path + dir + entry.leafName);
+ }
+ }
+ }
+
+ ok(files.length, "no sample files found");
+
+ let summary = {};
+ for (let file of files) {
+ info("Testing " + file + "...");
+
+ await BrowserTestUtils.withNewTab(BASE_URL + file, async browser => {
+ summary[file] = await SpecialPowers.spawn(
+ browser,
+ [{ eligibleElementSelector, file }],
+ obj => {
+ const { FormAutofillHeuristics } = ChromeUtils.importESModule(
+ "resource://gre/modules/shared/FormAutofillHeuristics.sys.mjs"
+ );
+ const { FormAutofillUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/shared/FormAutofillUtils.sys.mjs"
+ );
+
+ let eligibleFields = [];
+ let nodeList = content.document.querySelectorAll(
+ obj.eligibleElementSelector
+ );
+ for (let i = 0; i < nodeList.length; i++) {
+ if (FormAutofillUtils.isCreditCardOrAddressFieldType(nodeList[i])) {
+ eligibleFields.push(nodeList[i]);
+ }
+ }
+ let failedFields = [];
+
+ info("Running CC fathom model");
+ let nativeConfidencesKeyedByType =
+ ChromeUtils.getFormAutofillConfidences(eligibleFields);
+ let jsConfidencesKeyedByType =
+ FormAutofillHeuristics.getFormAutofillConfidences(eligibleFields);
+
+ if (eligibleFields.length != nativeConfidencesKeyedByType.length) {
+ ok(
+ false,
+ `Get the wrong number of confidence value from the native model`
+ );
+ }
+ if (eligibleFields.length != jsConfidencesKeyedByType.length) {
+ ok(
+ false,
+ `Get the wrong number of confidence value from the js model`
+ );
+ }
+
+ // This value should sync with the number of supported types in
+ // CreditCardRuleset.sys.mjs (See `get types()` in `this.creditCardRulesets`).
+ const EXPECTED_NUM_OF_CONFIDENCE = 2;
+ for (let i = 0; i < eligibleFields.length; i++) {
+ if (
+ Object.keys(nativeConfidencesKeyedByType[i]).length !=
+ EXPECTED_NUM_OF_CONFIDENCE
+ ) {
+ ok(
+ false,
+ `Native CC model doesn't get confidence value for all types`
+ );
+ }
+ if (
+ Object.keys(jsConfidencesKeyedByType[i]).length !=
+ EXPECTED_NUM_OF_CONFIDENCE
+ ) {
+ ok(
+ false,
+ `JS CC model doesn't get confidence value for all types`
+ );
+ }
+
+ for (let [type, confidence] of Object.entries(
+ nativeConfidencesKeyedByType[i]
+ )) {
+ // Fix to 10 digit to ignore rounding error between js and c++.
+ let nativeConfidence = confidence.toFixed(10);
+ let jsConfidence =
+ jsConfidencesKeyedByType[i][
+ FormAutofillUtils.formAutofillConfidencesKeyToCCFieldType(
+ type
+ )
+ ].toFixed(10);
+ if (jsConfidence != nativeConfidence) {
+ info(
+ `${obj.file}: Element(id=${eligibleFields[i].id} doesn't have the same confidence value when rule type is ${type}`
+ );
+ if (!failedFields.includes(i)) {
+ failedFields.push(i);
+ }
+ }
+ }
+ }
+ ok(
+ !failedFields.length,
+ `${obj.file}: has the same confidences value on both models`
+ );
+ return {
+ tested: eligibleFields.length,
+ passed: eligibleFields.length - failedFields.length,
+ };
+ }
+ );
+ });
+ }
+
+ // Generating summary report
+ let total_tested_samples = 0;
+ let total_passed_samples = 0;
+ let total_tested_fields = 0;
+ let total_passed_fields = 0;
+ info("=====Summary=====");
+ for (const [k, v] of Object.entries(summary)) {
+ total_tested_samples++;
+ if (v.tested == v.passed) {
+ total_passed_samples++;
+ } else {
+ info("Failed Case:" + k);
+ }
+ total_tested_fields += v.tested;
+ total_passed_fields += v.passed;
+ }
+ info(
+ "Passed Samples/Test Samples: " +
+ total_passed_samples +
+ "/" +
+ total_tested_samples
+ );
+ info(
+ "Passed Fields/Test Fields: " +
+ total_passed_fields +
+ "/" +
+ total_tested_fields
+ );
+}
+
+add_task(async function test_native_cc_model() {
+ const path = "fathom/";
+ const dirs = ["testing/"];
+ await run_test(path, dirs);
+});
+
+add_task(async function test_native_cc_model_autofill_repo() {
+ const path = "fathom/autofill-repo-samples/";
+ const dirs = ["validation/", "training/", "testing/"];
+ if (await IOUtils.exists(getTestFilePath(path))) {
+ // Just to ignore timeout failure while running the test on the local
+ requestLongerTimeout(10);
+
+ await run_test(path, dirs);
+ }
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js b/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js
new file mode 100644
index 0000000000..40709eb984
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js
@@ -0,0 +1,142 @@
+"use strict";
+
+add_task(async function test_first_time_save() {
+ let addresses = await getAddresses();
+ is(addresses.length, 0, "No address in storage");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FTU_PREF, true],
+ [ENABLED_AUTOFILL_ADDRESSES_PREF, true],
+ [AUTOFILL_ADDRESSES_AVAILABLE_PREF, "on"],
+ [ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF, true],
+ ],
+ });
+
+ let onAdded = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences#privacy"
+ );
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#organization",
+ newValues: {
+ "#organization": "Sesame Street",
+ "#street-address": "123 Sesame Street",
+ "#tel": "1-345-345-3456",
+ },
+ });
+
+ await onPopupShown;
+ let cb = getDoorhangerCheckbox();
+ ok(cb.hidden, "Sync checkbox should be hidden");
+ // Open the panel via main button
+ await clickDoorhangerButton(MAIN_BUTTON);
+ let tab = await tabPromise;
+ ok(tab, "Privacy panel opened");
+ BrowserTestUtils.removeTab(tab);
+ }
+ );
+ await onAdded;
+
+ addresses = await getAddresses();
+ is(addresses.length, 1, "Address saved");
+ let ftuPref = SpecialPowers.getBoolPref(FTU_PREF);
+ is(ftuPref, false, "First time use flag is false");
+});
+
+add_task(async function test_non_first_time_save() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [ENABLED_AUTOFILL_ADDRESSES_PREF, true],
+ [AUTOFILL_ADDRESSES_AVAILABLE_PREF, "on"],
+ [ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF, true],
+ ],
+ });
+ let addresses = await getAddresses();
+ let ftuPref = SpecialPowers.getBoolPref(FTU_PREF);
+ is(ftuPref, false, "First time use flag is false");
+ is(addresses.length, 1, "1 address in storage");
+
+ let onAdded = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#organization",
+ newValues: {
+ "#organization": "Mozilla",
+ "#street-address": "331 E. Evelyn Avenue",
+ "#tel": "1-650-903-0800",
+ },
+ });
+
+ await sleep(1000);
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onAdded;
+
+ addresses = await getAddresses();
+ is(addresses.length, 2, "Another address saved");
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_first_time_save_with_sync_account() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [FTU_PREF, true],
+ [ENABLED_AUTOFILL_ADDRESSES_PREF, true],
+ [AUTOFILL_ADDRESSES_AVAILABLE_PREF, "on"],
+ [SYNC_USERNAME_PREF, "foo@bar.com"],
+ [SYNC_ADDRESSES_PREF, false],
+ ],
+ });
+
+ let onAdded = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+ let tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences#privacy-address-autofill"
+ );
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#organization",
+ newValues: {
+ "#organization": "Foobar",
+ "#email": "foo@bar.com",
+ "#tel": "1-234-567-8900",
+ },
+ });
+
+ await onPopupShown;
+ let cb = getDoorhangerCheckbox();
+ ok(!cb.hidden, "Sync checkbox should be visible");
+
+ is(cb.checked, false, "Checkbox state should match addresses sync state");
+ cb.click();
+ is(
+ SpecialPowers.getBoolPref(SYNC_ADDRESSES_PREF),
+ true,
+ "addresses sync should be enabled after checked"
+ );
+ // Open the panel via main button
+ await clickDoorhangerButton(MAIN_BUTTON);
+ let tab = await tabPromise;
+ ok(tab, "Privacy panel opened");
+ BrowserTestUtils.removeTab(tab);
+ }
+ );
+ await onAdded;
+
+ let ftuPref = SpecialPowers.getBoolPref(FTU_PREF);
+ is(ftuPref, false, "First time use flag is false");
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_manageAddressesDialog.js b/browser/extensions/formautofill/test/browser/browser_manageAddressesDialog.js
new file mode 100644
index 0000000000..b070df0fda
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_manageAddressesDialog.js
@@ -0,0 +1,105 @@
+"use strict";
+
+const TEST_SELECTORS = {
+ selRecords: "#addresses",
+ btnRemove: "#remove",
+ btnAdd: "#add",
+ btnEdit: "#edit",
+};
+
+const DIALOG_SIZE = "width=600,height=400";
+
+add_task(async function test_manageAddressesInitialState() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: MANAGE_ADDRESSES_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 btnEdit = content.document.querySelector(args.btnEdit);
+ let btnAdd = content.document.querySelector(args.btnAdd);
+
+ is(selRecords.length, 0, "No address");
+ is(btnAdd.disabled, false, "Add button enabled");
+ is(btnRemove.disabled, true, "Remove button disabled");
+ is(btnEdit.disabled, true, "Edit button disabled");
+ });
+ }
+ );
+});
+
+add_task(async function test_cancelManageAddressDialogWithESC() {
+ let win = window.openDialog(MANAGE_ADDRESSES_DIALOG_URL);
+ await waitForFocusAndFormReady(win);
+ let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ await unloadPromise;
+ ok(true, "Manage addresses dialog is closed with ESC key");
+});
+
+add_task(async function test_removingSingleAndMultipleAddresses() {
+ await setStorage(TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3);
+
+ let win = window.openDialog(MANAGE_ADDRESSES_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);
+
+ is(selRecords.length, 3, "Three addresses");
+
+ 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, 2, "Two addresses left");
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeMouseAtCenter(
+ selRecords.children[1],
+ { 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 addresses are removed");
+
+ win.close();
+});
+
+add_task(async function test_removingAdressViaKeyboardDelete() {
+ await setStorage(TEST_ADDRESS_1);
+ let win = window.openDialog(MANAGE_ADDRESSES_DIALOG_URL, null, DIALOG_SIZE);
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+
+ is(selRecords.length, 1, "One address");
+
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 0, "No addresses left");
+
+ win.close();
+});
+
+add_task(async function test_addressesDialogWatchesStorageChanges() {
+ let win = window.openDialog(MANAGE_ADDRESSES_DIALOG_URL, null, DIALOG_SIZE);
+ await waitForFocusAndFormReady(win);
+
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+
+ await setStorage(TEST_ADDRESS_1);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 1, "One address is shown");
+
+ await removeAddresses([selRecords.options[0].value]);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 0, "Address is removed");
+ win.close();
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js b/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js
new file mode 100644
index 0000000000..2d5759ca66
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js
@@ -0,0 +1,439 @@
+"use strict";
+
+const PAGE_PREFS = "about:preferences";
+const PAGE_PRIVACY = PAGE_PREFS + "#privacy";
+const SELECTORS = {
+ group: "#formAutofillGroupBox",
+ addressAutofillCheckbox: "#addressAutofill checkbox",
+ creditCardAutofillCheckbox: "#creditCardAutofill checkbox",
+ savedAddressesBtn: "#addressAutofill button",
+ savedCreditCardsBtn: "#creditCardAutofill button",
+ addressAutofillLearnMore: "#addressAutofillLearnMore",
+ creditCardAutofillLearnMore: "#creditCardAutofillLearnMore",
+ reauthCheckbox: "#creditCardReauthenticate checkbox",
+};
+
+const { FormAutofill } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofill.sys.mjs"
+);
+
+// Visibility of form autofill group should be hidden when opening
+// preferences page.
+add_task(async function test_aboutPreferences() {
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PREFS },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.group).hidden,
+ true,
+ "Form Autofill group should be hidden"
+ );
+ });
+ }
+ );
+});
+
+// Visibility of form autofill group should be visible when opening
+// directly to privacy page. Checkbox is checked by default.
+add_task(async function test_aboutPreferencesPrivacy() {
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.group).hidden,
+ false,
+ "Form Autofill group should be visible"
+ );
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox)
+ .checked,
+ true,
+ "Autofill addresses checkbox should be checked"
+ );
+ is(
+ content.document.querySelector(selectors.creditCardAutofillCheckbox)
+ .checked,
+ true,
+ "Autofill credit cards checkbox should be checked"
+ );
+ ok(
+ content.document
+ .querySelector(selectors.addressAutofillLearnMore)
+ .href.includes("autofill-card-address"),
+ "Autofill addresses learn more link should contain autofill-card-address"
+ );
+ ok(
+ content.document
+ .querySelector(selectors.creditCardAutofillLearnMore)
+ .href.includes("credit-card-autofill"),
+ "Autofill credit cards learn more link should contain credit-card-autofill"
+ );
+ });
+ }
+ );
+});
+
+add_task(async function test_openManageAutofillDialogs() {
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ const args = [
+ SELECTORS,
+ MANAGE_ADDRESSES_DIALOG_URL,
+ MANAGE_CREDIT_CARDS_DIALOG_URL,
+ ];
+ await SpecialPowers.spawn(
+ browser,
+ [args],
+ ([selectors, addrUrl, ccUrl]) => {
+ function testManageDialogOpened(expectedUrl) {
+ return {
+ open: openUrl => is(openUrl, expectedUrl, "Manage dialog called"),
+ };
+ }
+
+ let realgSubDialog = content.window.gSubDialog;
+ content.window.gSubDialog = testManageDialogOpened(addrUrl);
+ content.document.querySelector(selectors.savedAddressesBtn).click();
+ content.window.gSubDialog = testManageDialogOpened(ccUrl);
+ content.document.querySelector(selectors.savedCreditCardsBtn).click();
+ content.window.gSubDialog = realgSubDialog;
+ }
+ );
+ }
+ );
+});
+
+add_task(async function test_autofillCheckboxes() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_ADDRESSES_PREF, false]],
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [[ENABLED_AUTOFILL_CREDITCARDS_PREF, false]],
+ });
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ // Checkbox should be unchecked when form autofill addresses and credit cards are disabled.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.group).hidden,
+ false,
+ "Form Autofill group should be visible"
+ );
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox)
+ .checked,
+ false,
+ "Checkbox should be unchecked when Autofill Addresses is disabled"
+ );
+ is(
+ content.document.querySelector(selectors.creditCardAutofillCheckbox)
+ .checked,
+ false,
+ "Checkbox should be unchecked when Autofill Credit Cards is disabled"
+ );
+ content.document
+ .querySelector(selectors.addressAutofillCheckbox)
+ .scrollIntoView({ block: "center", behavior: "instant" });
+ });
+
+ info("test toggling the checkboxes");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTORS.addressAutofillCheckbox,
+ {},
+ browser
+ );
+ is(
+ Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF),
+ true,
+ "Check address autofill is now enabled"
+ );
+
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ content.document
+ .querySelector(selectors.creditCardAutofillCheckbox)
+ .scrollIntoView({ block: "center", behavior: "instant" });
+ });
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTORS.creditCardAutofillCheckbox,
+ {},
+ browser
+ );
+ is(
+ Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF),
+ true,
+ "Check credit card autofill is now enabled"
+ );
+ }
+ );
+});
+
+add_task(async function test_creditCardNotAvailable() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[AUTOFILL_CREDITCARDS_AVAILABLE_PREF, false]],
+ });
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.group).hidden,
+ false,
+ "Form Autofill group should be visible"
+ );
+ ok(
+ !content.document.querySelector(selectors.creditCardAutofillCheckbox),
+ "Autofill credit cards checkbox should not exist"
+ );
+ });
+ }
+ );
+});
+
+add_task(async function test_reauth() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[AUTOFILL_CREDITCARDS_AVAILABLE_PREF, "on"]],
+ });
+ let { OSKeyStore } = ChromeUtils.importESModule(
+ "resource://gre/modules/OSKeyStore.sys.mjs"
+ );
+
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(
+ browser,
+ [SELECTORS, OSKeyStore.canReauth()],
+ (selectors, canReauth) => {
+ is(
+ canReauth,
+ !!content.document.querySelector(selectors.reauthCheckbox),
+ "Re-authentication checkbox should be available if OSKeyStore.canReauth() is `true`"
+ );
+ }
+ );
+ }
+ );
+});
+
+add_task(async function test_addressAutofillNotAvailable() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[AUTOFILL_ADDRESSES_AVAILABLE_PREF, "off"]],
+ });
+
+ let autofillAddressEnabledValue = Services.prefs.getBoolPref(
+ ENABLED_AUTOFILL_ADDRESSES_PREF
+ );
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.group).hidden,
+ false,
+ "Form Autofill group should be visible"
+ );
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox),
+ null,
+ "Address checkbox should not exist when address autofill is not enabled"
+ );
+ is(
+ content.document.querySelector(selectors.creditCardAutofillCheckbox)
+ .checked,
+ true,
+ "Checkbox should be checked when Autofill Credit Cards is enabled"
+ );
+ });
+ info("test toggling the credit card autofill checkbox");
+
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ content.document
+ .querySelector(selectors.creditCardAutofillCheckbox)
+ .scrollIntoView({ block: "center", behavior: "instant" });
+ });
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTORS.creditCardAutofillCheckbox,
+ {},
+ browser
+ );
+ is(
+ Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF),
+ false,
+ "Check credit card autofill is now disabled"
+ );
+ is(
+ Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF),
+ autofillAddressEnabledValue,
+ "Address autofill enabled's value should not change due to credit card checkbox interaction"
+ );
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox),
+ null,
+ "Address checkbox should exist due to interaction with credit card checkbox"
+ );
+ });
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_addressAutofillNotAvailableViaRegion() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.supported", "detect"],
+ ["extensions.formautofill.creditCards.enabled", true],
+ ["browser.search.region", "FR"],
+ [ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF, "US,CA"],
+ ],
+ });
+
+ const addressAutofillEnabledPrefValue = Services.prefs.getBoolPref(
+ ENABLED_AUTOFILL_ADDRESSES_PREF
+ );
+ const addressAutofillAvailablePrefValue = Services.prefs.getCharPref(
+ AUTOFILL_ADDRESSES_AVAILABLE_PREF
+ );
+ is(
+ FormAutofill.isAutofillAddressesAvailable,
+ false,
+ "Address autofill should not be available due to unsupported region"
+ );
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.group).hidden,
+ false,
+ "Form Autofill group should be visible"
+ );
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox),
+ null,
+ "Address checkbox should not exist due to address autofill not being available"
+ );
+ is(
+ content.document.querySelector(selectors.creditCardAutofillCheckbox)
+ .checked,
+ true,
+ "Checkbox should be checked when Autofill Credit Cards is enabled"
+ );
+ });
+ info("test toggling the credit card autofill checkbox");
+
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ content.document
+ .querySelector(selectors.creditCardAutofillCheckbox)
+ .scrollIntoView({ block: "center", behavior: "instant" });
+ });
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ SELECTORS.creditCardAutofillCheckbox,
+ {},
+ browser
+ );
+ is(
+ Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF),
+ false,
+ "Check credit card autofill is now disabled"
+ );
+ is(
+ Services.prefs.getCharPref(AUTOFILL_ADDRESSES_AVAILABLE_PREF),
+ addressAutofillAvailablePrefValue,
+ "Address autofill availability should not change due to interaction with credit card checkbox"
+ );
+ is(
+ addressAutofillEnabledPrefValue,
+ Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF),
+ "Address autofill enabled pref should not change due to credit card checkbox"
+ );
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox),
+ null,
+ "Address checkbox should not exist due to address autofill not being available"
+ );
+ });
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Checkboxes should be disabled based on whether or not they are locked.
+add_task(async function test_aboutPreferencesPrivacy() {
+ Services.prefs.lockPref(ENABLED_AUTOFILL_ADDRESSES_PREF);
+ Services.prefs.lockPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+ registerCleanupFunction(function () {
+ Services.prefs.unlockPref(ENABLED_AUTOFILL_ADDRESSES_PREF);
+ Services.prefs.unlockPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
+ });
+ let finalPrefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_PRIVACY },
+ async function (browser) {
+ await finalPrefPaneLoaded;
+ await SpecialPowers.spawn(browser, [SELECTORS], selectors => {
+ is(
+ content.document.querySelector(selectors.addressAutofillCheckbox)
+ .disabled,
+ true,
+ "Autofill addresses checkbox should be disabled"
+ );
+ is(
+ content.document.querySelector(selectors.creditCardAutofillCheckbox)
+ .disabled,
+ true,
+ "Autofill credit cards checkbox should be disabled"
+ );
+ });
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_remoteiframe.js b/browser/extensions/formautofill/test/browser/browser_remoteiframe.js
new file mode 100644
index 0000000000..4dddd27a09
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_remoteiframe.js
@@ -0,0 +1,127 @@
+"use strict";
+
+const IFRAME_URL_PATH = BASE_URL + "autocomplete_iframe.html";
+
+// Start by adding a few addresses to storage.
+add_task(async function setup_storage() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [AUTOFILL_ADDRESSES_AVAILABLE_PREF, "on"],
+ [ENABLED_AUTOFILL_ADDRESSES_PREF, true],
+ [ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF, true],
+ ],
+ });
+ await setStorage(TEST_ADDRESS_2, TEST_ADDRESS_4, TEST_ADDRESS_5);
+});
+
+// Verify that form fillin works in a remote iframe, and that changing
+// a field updates storage.
+add_task(async function test_iframe_autocomplete() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ IFRAME_URL_PATH,
+ true
+ );
+ let browser = tab.linkedBrowser;
+ let iframeBC = browser.browsingContext.children[1];
+ await openPopupOnSubframe(browser, iframeBC, "#street-address");
+
+ // Highlight the first item in the list. We want to verify
+ // that the warning text is correct to ensure that the preview is
+ // performed properly.
+
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, iframeBC);
+ await expectWarningText(browser, "Autofills address");
+
+ // Highlight and select the second item in the list
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, iframeBC);
+ await expectWarningText(browser, "Also autofills organization, email");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ let onLoaded = BrowserTestUtils.browserLoaded(browser, true);
+ await SpecialPowers.spawn(iframeBC, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ content.document.getElementById("street-address").value ==
+ "32 Vassar Street MIT Room 32-G524" &&
+ content.document.getElementById("country").value == "US" &&
+ content.document.getElementById("organization").value ==
+ "World Wide Web Consortium"
+ );
+ });
+ });
+
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(iframeBC, {
+ focusSelector: "#organization",
+ newValues: {
+ "#tel": "+16172535702",
+ },
+ });
+ await onPopupShown;
+ await onLoaded;
+
+ let onUpdated = waitForStorageChangedEvents("update");
+ await clickDoorhangerButton(MAIN_BUTTON);
+ await onUpdated;
+
+ // Check that the tel number was updated properly.
+ let addresses = await getAddresses();
+ is(addresses.length, 3, "Still 3 address in storage");
+ is(addresses[1].tel, "+16172535702", "Verify the tel field");
+
+ // Fill in the details again and then clear the form from the dropdown.
+ await openPopupOnSubframe(browser, iframeBC, "#street-address");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, iframeBC);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ await waitForAutofill(iframeBC, "#tel", "+16172535702");
+
+ // Open the dropdown and select the Clear Form item.
+ await openPopupOnSubframe(browser, iframeBC, "#street-address");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, iframeBC);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ await SpecialPowers.spawn(iframeBC, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ content.document.getElementById("street-address").value == "" &&
+ content.document.getElementById("country").value == "" &&
+ content.document.getElementById("organization").value == ""
+ );
+ });
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
+
+// Choose preferences from the autocomplete dropdown within an iframe.
+add_task(async function test_iframe_autocomplete_preferences() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ IFRAME_URL_PATH,
+ true
+ );
+ let browser = tab.linkedBrowser;
+ let iframeBC = browser.browsingContext.children[1];
+ await openPopupOnSubframe(browser, iframeBC, "#organization");
+
+ await expectWarningText(browser, "Also autofills address, phone, email");
+
+ const prefTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ PRIVACY_PREF_URL
+ );
+
+ // Select the preferences item.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info(`expecting tab: about:preferences#privacy opened`);
+ const prefTab = await prefTabPromise;
+ info(`expecting tab: about:preferences#privacy removed`);
+ BrowserTestUtils.removeTab(prefTab);
+
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_submission_in_private_mode.js b/browser/extensions/formautofill/test/browser/browser_submission_in_private_mode.js
new file mode 100644
index 0000000000..05adf40935
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_submission_in_private_mode.js
@@ -0,0 +1,37 @@
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+add_task(async function test_add_address() {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let addresses = await getAddresses();
+
+ is(addresses.length, 0, "No address in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: privateWin.gBrowser, url: FORM_URL },
+ async function (privateBrowser) {
+ await focusUpdateSubmitForm(privateBrowser, {
+ focusSelector: "#organization",
+ newValues: {
+ "#organization": "Mozilla",
+ "#street-address": "331 E. Evelyn Avenue",
+ "#tel": "1-650-903-0800",
+ },
+ });
+ }
+ );
+
+ await ensureNoAddressSaved();
+
+ await BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js b/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js
new file mode 100644
index 0000000000..73fd903de6
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js
@@ -0,0 +1,189 @@
+"use strict";
+
+/**
+ * Note that this testcase is built based on the assumption that subtests are
+ * run in order. So if you're going to add a new subtest, please add it in the end.
+ */
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.addresses.capture.enabled", true]],
+ });
+});
+
+add_task(async function test_update_address() {
+ await setStorage(TEST_ADDRESS_1);
+ let addresses = await getAddresses();
+ is(addresses.length, 1, "1 address in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ // Autofill address fields
+ await openPopupOn(browser, "form #organization");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(browser, "#tel", addresses[0].tel);
+
+ // Update address fields and submit
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#organization",
+ newValues: {
+ "#organization": "Mozilla",
+ },
+ });
+
+ await onPopupShown;
+
+ // Choose to update address by doorhanger
+ let onUpdated = waitForStorageChangedEvents("update");
+ await clickDoorhangerButton(MAIN_BUTTON);
+ await onUpdated;
+ }
+ );
+
+ addresses = await getAddresses();
+ is(addresses.length, 1, "Still 1 address in storage");
+ is(addresses[0].organization, "Mozilla", "Verify the organization field");
+});
+
+add_task(async function test_create_new_address() {
+ let addresses = await getAddresses();
+ is(addresses.length, 1, "1 address in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ // Autofill address fields
+ await openPopupOn(browser, "form #tel");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(browser, "#tel", addresses[0].tel);
+
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#tel",
+ newValues: {
+ "#tel": "+1234567890",
+ },
+ });
+
+ await onPopupShown;
+
+ // Choose to add address by doorhanger
+ let onAdded = waitForStorageChangedEvents("add");
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ await onAdded;
+ }
+ );
+
+ addresses = await getAddresses();
+ is(addresses.length, 2, "2 addresses in storage");
+ is(addresses[1].tel, "+1234567890", "Verify the tel field");
+});
+
+add_task(async function test_create_new_address_merge() {
+ let addresses = await getAddresses();
+ is(addresses.length, 2, "2 addresses in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ await openPopupOn(browser, "form #tel");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(browser, "#tel", addresses[1].tel);
+
+ // Choose the latest address and revert to the original phone number
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#tel",
+ newValues: {
+ "#tel": "+16172535702",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+
+ addresses = await getAddresses();
+ is(addresses.length, 2, "Still 2 addresses in storage");
+});
+
+add_task(async function test_submit_untouched_fields() {
+ let addresses = await getAddresses();
+ is(addresses.length, 2, "2 addresses in storage");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: FORM_URL },
+ async function (browser) {
+ await openPopupOn(browser, "form #organization");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(browser, "#tel", addresses[0].tel);
+
+ let onPopupShown = waitForPopupShown();
+ await SpecialPowers.spawn(browser, [], async function () {
+ let form = content.document.getElementById("form");
+ let tel = form.querySelector("#tel");
+ tel.value = "12345"; // ".value" won't change the highlight status.
+ });
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#tel",
+ newValues: {
+ "#organization": "Organization",
+ },
+ });
+ await onPopupShown;
+
+ let onUpdated = waitForStorageChangedEvents("update");
+ await clickDoorhangerButton(MAIN_BUTTON);
+ await onUpdated;
+ }
+ );
+
+ addresses = await getAddresses();
+ is(addresses.length, 2, "Still 2 addresses in storage");
+ is(addresses[0].organization, "Organization", "organization should change");
+ is(addresses[0].tel, "+16172535702", "tel should remain unchanged");
+});
+
+add_task(async function test_submit_reduced_fields() {
+ let addresses = await getAddresses();
+ is(addresses.length, 2, "2 addresses in storage");
+
+ let url = BASE_URL + "autocomplete_simple_basic.html";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url },
+ async function (browser) {
+ await openPopupOn(browser, "form#simple input[name=tel]");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+ await waitForAutofill(browser, "form #simple_tel", "6172535702");
+
+ let onPopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ formSelector: "form#simple",
+ focusSelector: "form #simple_tel",
+ newValues: {
+ "input[name=tel]": "123456789",
+ },
+ });
+
+ await onPopupShown;
+
+ let onUpdated = waitForStorageChangedEvents("update");
+ await clickDoorhangerButton(MAIN_BUTTON);
+ await onUpdated;
+ }
+ );
+
+ addresses = await getAddresses();
+ is(addresses.length, 2, "Still 2 addresses in storage");
+ is(addresses[0].tel, "123456789", "tel should should be changed");
+ is(addresses[0]["postal-code"], "02139", "postal code should be kept");
+});
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 */
diff --git a/browser/extensions/formautofill/test/browser/empty.html b/browser/extensions/formautofill/test/browser/empty.html
new file mode 100644
index 0000000000..1ad28bb1f7
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/empty.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Empty file</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/extensions/formautofill/test/browser/fathom/test-setup.sh b/browser/extensions/formautofill/test/browser/fathom/test-setup.sh
new file mode 100755
index 0000000000..1547c4a395
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/test-setup.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+# This script download samples for testing
+
+clean=1
+cleanall=0
+sample_dir=autofill-repo-samples
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -c) clean=1 ;;
+ -cc) cleanall=1 ;;
+ esac
+ shift
+done
+
+# Check out source code
+if ! [ -d "fathom-form-autofill" ]; then
+ echo "Get samples from repo..."
+ git clone https://github.com/mozilla-services/fathom-form-autofill
+fi
+
+if ! [ -d "samples" ]; then
+ echo "Copy samples..."
+ mkdir $sample_dir
+ cp -r fathom-form-autofill/samples/testing $sample_dir
+ cp -r fathom-form-autofill/samples/training $sample_dir
+ cp -r fathom-form-autofill/samples/validation $sample_dir
+else
+ echo "\`samples\` directory already exists"
+fi
+
+if [ "$clean" = 1 ] || [ "$cleanall" = 1 ]; then
+ echo "Cleanup..."
+ rm -rf fathom-form-autofill
+fi
+
+if [ "$cleanall" = 1 ]; then
+ rm -rf $sample_dir
+fi
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/1.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/1.svg
new file mode 100644
index 0000000000..179f3b5cce
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
+ <path fill="#CCC" fill-rule="evenodd" d="M30 5V1a1 1 0 0 0-1-1H1a1 1 0 0 0-1 1v4h30zm0 4v10a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V9h30z"/>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/10.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/10.svg
new file mode 100644
index 0000000000..619b82106d
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/10.svg
@@ -0,0 +1 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="348.333px" height="348.333px" viewBox="0 0 348.333 348.334" style="enable-background:new 0 0 348.333 348.334;" xml:space="preserve"><g><path fill="#565656" d="M336.559,68.611L231.016,174.165l105.543,105.549c15.699,15.705,15.699,41.145,0,56.85c-7.844,7.844-18.128,11.769-28.407,11.769c-10.296,0-20.581-3.919-28.419-11.769L174.167,231.003L68.609,336.563c-7.843,7.844-18.128,11.769-28.416,11.769c-10.285,0-20.563-3.919-28.413-11.769c-15.699-15.698-15.699-41.139,0-56.85l105.54-105.549L11.774,68.611c-15.699-15.699-15.699-41.145,0-56.844c15.696-15.687,41.127-15.687,56.829,0l105.563,105.554L279.721,11.767c15.705-15.687,41.139-15.687,56.832,0C352.258,27.466,352.258,52.912,336.559,68.611z"/></g></svg> \ No newline at end of file
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/11.png b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/11.png
new file mode 100644
index 0000000000..446ba13cec
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/11.png
Binary files differ
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/12.gif b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/12.gif
new file mode 100644
index 0000000000..b3aa80d843
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/12.gif
Binary files differ
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/13.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/13.svg
new file mode 100644
index 0000000000..ed16723fa0
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/13.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="164.105px" height="48.177px" viewBox="0 21.835 164.105 48.177" enable-background="new 0 21.835 164.105 48.177"
+ xml:space="preserve">
+<path fill="#78BE20" d="M134.879,52.052l4.932-16.246l4.919,16.246H134.879z M98.851,57.406h-3.028V34.901h2.989
+ c4.53,0,9.571,1.875,9.571,11.2C108.383,54.361,103.918,57.406,98.851,57.406 M18.927,52.052l5.033-16.436l4.94,16.436H18.927z
+ M148.76,23.221h-16.905l-9.64,27.871c0.427-2.314,0.504-4.164,0.504-4.968c0-11.677-6.697-22.902-22.437-22.902H82.209
+ l0.056,27.724c-1.565-7.876-8.77-9.987-17.731-12.708c-3.348-1.03-5.186-2.666-4.516-4.193c0.574-1.332,2.626-1.75,5.118-1.405
+ c3.802,0.543,6.838,1.813,9.763,3.401l3.995-9.885c-0.902-0.442-7.374-4.323-15.485-4.323c-11.311,0-18.535,5.511-18.535,13.644
+ c0,7.249,4.469,11.462,12.686,13.823c8.839,2.562,11.079,3.589,10.814,6.13c-0.231,2.187-5.708,4.853-19.11-3.384l-3.982,8.23
+ L32.989,23.221h-16.9L0.115,69.285h13.321l2.078-6.524h16.797l1.987,6.524h13.985l-1.585-4.507
+ c4.729,2.73,10.591,5.228,17.604,5.228c10.747,0,16.555-5.86,17.939-11.477v10.756h18.025c10.74,0,16.361-5.118,19.298-10.591
+ l-3.687,10.591h13.312l2.162-6.524h16.781l1.931,6.524h14.001L148.76,23.221z"/>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/14.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/14.svg
new file mode 100644
index 0000000000..21f7d71bf6
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/14.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
+ <defs>
+ <path id="a" d="M11.5 17a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1zm1.003-3h-1a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5zm-9.218 5h17.43a.5.5 0 0 0 .436-.746l-8.716-15.42a.5.5 0 0 0-.87 0l-8.716 15.42a.5.5 0 0 0 .436.746zM13.74 1.08l9.572 16.936A2 2 0 0 1 21.573 21H2.427a2 2 0 0 1-1.741-2.984L10.259 1.08a2 2 0 0 1 3.482 0z"/>
+ </defs>
+ <g fill="none" fill-rule="evenodd" transform="translate(4 6)">
+ <mask id="b" fill="#fff">
+ <use xlink:href="#a"/>
+ </mask>
+ <use fill="#d43030" xlink:href="#a"/>
+ <g fill="#d43030" mask="url(#b)">
+ <path d="M-4-6h32v32H-4z"/>
+ </g>
+ </g>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/15.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/15.svg
new file mode 100644
index 0000000000..44c5c294d8
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/15.svg
@@ -0,0 +1 @@
+<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M17 19.5a.5.5 0 01-.5.5h-1a.5.5 0 01-.5-.5V17h-2.5a.5.5 0 01-.5-.5v-1a.5.5 0 01.5-.5H15v-2.5a.5.5 0 01.5-.5h1a.5.5 0 01.5.5V15h2.5a.5.5 0 01.5.5v1a.5.5 0 01-.5.5H17zm-7.611 4.504L12.394 22H25.5a.5.5 0 00.5-.5v-11a.5.5 0 00-.5-.5h-19a.5.5 0 00-.5.5v11a.5.5 0 00.5.5H9v1.796a.25.25 0 00.389.208zM7 27.066V24H6a2 2 0 01-2-2V10a2 2 0 012-2h20a2 2 0 012 2v12a2 2 0 01-2 2H13l-5.223 3.482A.5.5 0 017 27.066z" fill="#3d3d3d"/></svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/16.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/16.svg
new file mode 100644
index 0000000000..05105ed133
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/16.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="111px" height="33px" viewBox="0 0 111 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title>ASDA Logo - White</title>
+ <g id="Footer-Amends" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" transform="translate(-1.000000, -1.000000)" fill="#FFFFFF">
+ <g id="Logo" transform="translate(1.000000, 1.000000)">
+ <path d="M100.697948,1.012292 L89.3366436,1.012292 L82.8584522,19.7642556 C83.148104,18.2068119 83.1992581,16.9618991 83.1992581,16.4207628 C83.1992581,8.56536178 78.6975861,1.012292 68.1226373,1.012292 L55.9778554,1.012292 L56.013619,19.6652524 C54.9634097,14.3658639 50.1220389,12.9452287 44.1009128,11.1157202 C41.8504643,10.4218107 40.6150151,9.3208018 41.0659906,8.29407301 C41.4513073,7.398942 42.8306965,7.11767529 44.5051631,7.34860889 C47.0601003,7.71502058 49.0999533,8.56990728 51.0644038,9.63765645 L53.7484443,2.98659136 C53.1426778,2.68836218 48.7941359,0.0782470703 43.3442306,0.0782470703 C35.744192,0.0782470703 30.8900881,3.78626686 30.8900881,9.25771912 C30.8900881,14.1342651 33.8930113,16.9692162 39.4152188,18.5582567 C45.353524,20.2807797 46.8595806,20.9735805 46.6813163,22.6821344 C46.5266361,24.1539896 42.8459763,25.947245 33.8399749,20.4062798 L31.1644602,25.9433647 L22.9041793,1.012292 L11.5474142,1.012292 L0.81413124,32.0042908 L9.76565687,32.0042908 L11.5,27 L22,27 L23.7839856,32.0042908 L33.1815042,32.0042908 L32.1156829,28.9725528 C35.293438,30.8094893 39.2329685,32.4896615 43.945236,32.4896615 C51.1667121,32.4896615 55.0690396,28.5473822 56,24.7678539 L56,32.0042908 L68.1107899,32.0042908 C75.3288336,32.0042908 79.1053795,28.561573 81.0783558,24.878498 L78.600814,32.0042908 L87.5461392,32.0042908 L89.3366436,27 L100,27 L101.571997,32.0042908 L110.981142,32.0042908 L100.697948,1.012292 Z M13.5,20 L16.8375459,9.35239857 L20,20 L13.5,20 Z M65,24.0118596 L65,9 L67.1348759,9 C70.178213,9 73.5,10.2318086 73.5,16.5059298 C73.5,22.063414 70.5657441,24.0118596 67.1610065,24.0118596 L65,24.0118596 Z M91.5,20 L94.6839085,9.47878566 L98,20 L91.5,20 Z" id="Fill-3"></path>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/17.bin b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/17.bin
new file mode 100644
index 0000000000..5fa299eb94
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/17.bin
Binary files differ
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/18.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/18.svg
new file mode 100644
index 0000000000..6f66ac60a2
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/18.svg
@@ -0,0 +1 @@
+<svg width="136" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M79.039 7.346c0 1.784-.449 3.186-1.346 4.206-.897 1.021-2.152 1.532-3.767 1.532-1.641 0-2.905-.505-3.791-1.513-.887-1.008-1.335-2.422-1.346-4.24 0-1.815.449-3.221 1.346-4.22.897-1 2.165-1.498 3.805-1.496 1.6 0 2.85.507 3.748 1.523.899 1.015 1.35 2.418 1.351 4.208zm-8.88 0c0 1.51.32 2.654.963 3.434.642.78 1.577 1.17 2.804 1.168 1.234 0 2.166-.388 2.796-1.165.63-.777.945-1.923.947-3.437 0-1.498-.314-2.634-.942-3.41-.627-.774-1.557-1.163-2.787-1.164-1.235 0-2.173.39-2.815 1.17-.642.78-.964 1.915-.964 3.404h-.002zm16.891 5.587V7.535c0-.68-.155-1.188-.466-1.523-.31-.336-.795-.504-1.455-.504-.874 0-1.514.236-1.922.708-.407.472-.61 1.251-.61 2.339v4.378h-1.265V4.575h1.028l.204 1.143h.062a2.583 2.583 0 011.076-.955 3.541 3.541 0 011.564-.339c1.006 0 1.763.242 2.271.727.508.484.762 1.26.762 2.327v5.455H87.05zm7.392.151c-1.234 0-2.208-.376-2.922-1.128-.714-.752-1.073-1.796-1.077-3.132 0-1.346.332-2.415.996-3.208a3.302 3.302 0 012.672-1.19 3.151 3.151 0 012.484 1.034c.61.689.915 1.597.915 2.723v.808h-5.75c.024.98.272 1.725.742 2.233a2.57 2.57 0 001.986.762 6.727 6.727 0 002.667-.566v1.128c-.409.18-.834.319-1.27.414-.476.09-.96.13-1.443.122zm-.344-7.597c-.609-.03-1.2.21-1.615.656a3.022 3.022 0 00-.705 1.814h4.378c0-.798-.18-1.409-.538-1.832a1.884 1.884 0 00-1.52-.638zm9.192 7.446h-1.294V2.94h-3.53V1.79h8.341v1.152h-3.517zm8.824-8.506c.335-.004.67.027.998.091l-.175 1.173c-.3-.07-.607-.108-.915-.113a2.228 2.228 0 00-1.733.824c-.488.57-.745 1.3-.72 2.05v4.48h-1.266V4.576h1.044l.146 1.547h.062c.27-.5.654-.93 1.119-1.257a2.521 2.521 0 011.44-.438zm3.748.148v5.422c0 .682.155 1.19.466 1.523.31.334.795.501 1.456.503.873 0 1.512-.238 1.916-.716.403-.477.605-1.256.605-2.338V4.575h1.265v8.342h-1.044l-.183-1.12h-.068c-.26.412-.633.74-1.076.945a3.627 3.627 0 01-1.574.328c-1.016 0-1.776-.241-2.282-.724-.506-.482-.759-1.255-.759-2.317V4.575h1.278zm13.781 6.079a2.09 2.09 0 01-.87 1.797c-.579.422-1.392.633-2.437.633-1.107 0-1.971-.18-2.592-.539v-1.16c.412.207.845.368 1.292.48.434.113.88.172 1.33.174.526.03 1.051-.08 1.522-.317a1.076 1.076 0 00.11-1.798 6.663 6.663 0 00-1.649-.807 8.931 8.931 0 01-1.658-.775 2.258 2.258 0 01-.737-.73 1.923 1.923 0 01-.24-.981 1.884 1.884 0 01.832-1.615c.554-.393 1.314-.59 2.28-.59a6.668 6.668 0 012.636.539l-.449 1.028a6.052 6.052 0 00-2.28-.52 2.624 2.624 0 00-1.345.27.872.872 0 00-.457.777.947.947 0 00.172.57c.15.188.338.341.552.45.474.237.963.443 1.464.616.99.36 1.659.718 2.007 1.077.36.383.546.896.517 1.42zm4.755 1.386c.217 0 .434-.016.648-.049.167-.024.332-.058.495-.102v.968a2.312 2.312 0 01-.605.165c-.238.04-.48.062-.721.064-1.615 0-2.422-.851-2.422-2.554v-4.97h-1.198v-.61l1.198-.539.538-1.784h.732v1.946h2.422v.982h-2.422v4.922c-.028.416.1.829.358 1.157.25.273.607.42.977.403z" fill="#6FBE4A"/><text transform="translate(.79 .615)" fill="#696969" font-family="ArialMT, Arial" font-size="12.109"><tspan x=".034" y="11">Powered by</tspan></text></g></svg> \ No newline at end of file
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/2.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/2.svg
new file mode 100644
index 0000000000..01977c77b0
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/2.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
+ <g fill="none" fill-rule="evenodd">
+ <rect width="30" height="20" fill="#353A48" rx="1"/>
+ <circle cx="11" cy="10" r="6" fill="#ED0006"/>
+ <circle cx="20" cy="10" r="6" fill="#F9A000"/>
+ <path fill="#FF8150" d="M15.5 6a5.731 5.731 0 0 1 1.597 4 5.731 5.731 0 0 1-1.597 4 5.731 5.731 0 0 1-1.597-4c0-1.567.612-2.983 1.597-4z"/>
+ </g>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/3.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/3.svg
new file mode 100644
index 0000000000..bb024231ef
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/3.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 22"><defs><style>.cls-1{fill:#191f70;}.cls-2{fill:#fff;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1-2"><path class="cls-1" d="M.91,0H31.09A1,1,0,0,1,32,1V21a1,1,0,0,1-.91,1H.91A1,1,0,0,1,0,21V1A1,1,0,0,1,.91,0Z"/><path class="cls-2" d="M16.14,9.52c0,1.23,1.09,1.91,1.92,2.32s1.15.68,1.15,1.05c0,.58-.69.83-1.32.84a4.55,4.55,0,0,1-2.26-.54l-.4,1.87a6.82,6.82,0,0,0,2.45.45c2.31,0,3.82-1.15,3.83-2.91,0-2.24-3.1-2.38-3.08-3.38,0-.31.29-.63.93-.71a4.07,4.07,0,0,1,2.17.37l.39-1.8a6,6,0,0,0-2.06-.37c-2.17,0-3.7,1.16-3.73,2.81m9.51-2.66a1,1,0,0,0-.94.63l-3.3,7.89h2.31l.46-1.27H27l.26,1.27h2L27.52,6.86ZM26,9.17l.67,3.19H24.81ZM13.33,6.86l-1.81,8.51h2.2l1.82-8.51Zm-3.25,0-2.29,5.8L6.86,7.73a1,1,0,0,0-1-.87H2.09l0,.25a9.22,9.22,0,0,1,2.17.72.93.93,0,0,1,.53.75l1.75,6.79H8.82l3.57-8.51Z"/></g></g></svg> \ No newline at end of file
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/4.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/4.svg
new file mode 100644
index 0000000000..634943eee9
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/4.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="109" viewBox="0 0 24 109">
+ <g fill="none" fill-rule="nonzero">
+ <rect width="24" height="109" fill="#3D3D3D" rx="12"/>
+ <path stroke="#EEE" stroke-linecap="square" stroke-width="2" d="M5.5 50.5h13M5.5 55.25h13M5.5 60.25h13"/>
+ </g>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/5.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/5.svg
new file mode 100644
index 0000000000..857064edc9
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/5.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
+ <g fill="none" fill-rule="evenodd">
+ <rect width="30" height="20" fill="#007ECD" rx="1"/>
+ <path fill="#FFF" d="M9.72 13H8.47l-.498-1.363H5.69L5.222 13H4l2.22-6h1.218l2.283 6zm-2.119-2.374L6.816 8.4l-.77 2.226H7.6zM10.316 13V7h1.723l1.034 4.093L14.096 7h1.727v6h-1.07V8.277L13.622 13h-1.109l-1.128-4.723V13h-1.07zm6.65 0V7h4.228v1.015h-3.077v1.33h2.863v1.011h-2.863v1.633h3.186V13h-4.337zm4.733 0l1.949-3.131L21.882 7h1.346l1.143 1.928L25.491 7h1.334l-1.773 2.914L27 13h-1.388l-1.264-2.075L23.08 13H21.7z"/>
+ </g>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/6.svg b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/6.svg
new file mode 100644
index 0000000000..0678ad42f7
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/6.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
+ <g fill="none" fill-rule="evenodd">
+ <rect width="30" height="20" fill="#000052" rx="1"/>
+ <circle cx="11" cy="10" r="6" fill="#06C"/>
+ <circle cx="20" cy="10" r="6" fill="#C00"/>
+ <path fill="#C70B8C" d="M15.5 6a5.731 5.731 0 0 1 1.597 4 5.731 5.731 0 0 1-1.597 4 5.731 5.731 0 0 1-1.597-4c0-1.567.612-2.983 1.597-4z"/>
+ </g>
+</svg>
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/7.woff2 b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/7.woff2
new file mode 100644
index 0000000000..52b6d6916a
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/7.woff2
Binary files differ
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/8.woff2 b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/8.woff2
new file mode 100644
index 0000000000..e379beda77
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/8.woff2
Binary files differ
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/9.woff2 b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/9.woff2
new file mode 100644
index 0000000000..efa300c564
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/resources/sample/9.woff2
Binary files differ
diff --git a/browser/extensions/formautofill/test/browser/fathom/testing/sample.html b/browser/extensions/formautofill/test/browser/fathom/testing/sample.html
new file mode 100644
index 0000000000..ddb0f5f2da
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/fathom/testing/sample.html
@@ -0,0 +1,20 @@
+<html lang="en-GB"><!--
+ Page saved with SingleFile
+ url: https://www.asda.com/account?request_origin=asda&redirect_uri=https%3A%2F%2Fwww.asda.com%2Fgood-living%2Ftag%2Frecipes
+ saved date: Wed Mar 16 2022 11:21:53 GMT+0200 (Eastern European Standard Time)
+--><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0,shrink-to-fit=no"><meta name="referrer" content="no-referrer-when-downgrade"><title>My Account | Account Settings – Asda Groceries</title><style>:root{--sf-img-16: url("resources/sample/1.svg");--sf-img-19: url("resources/sample/2.svg");--sf-img-20: url("resources/sample/3.svg");--sf-img-15: url("resources/sample/4.svg");--sf-img-17: url("resources/sample/5.svg");--sf-img-18: url("resources/sample/6.svg")}</style><style>html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}article *,article *:before,article *:after{box-sizing:border-box}@font-face{font-family:"SourceSansProBold";src:url(resources/sample/7.woff2) format("woff2")}@font-face{font-family:"SourceSansProSemiBold";src:url(resources/sample/8.woff2) format("woff2")}@font-face{font-family:"SourceSansProRegular";src:url(resources/sample/9.woff2) format("woff2")}h1,h2,h3,h4,p,a{color:#191919;padding-bottom:8px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1{font-size:1.25em;font-weight:700;color:#191919;padding-bottom:16px;line-height:1.25}.theme-ghs h1{font-family:SourceSansProBold,sans-serif}.theme-george h1{font-family:LatoBold,sans-serif}h2{font-size:1.125em;font-weight:800}.theme-ghs h2{font-family:SourceSansProBold,sans-serif}.theme-george h2{font-family:LatoBold,sans-serif}h3{font-size:1em;font-weight:600}.theme-ghs h3{font-family:SourceSansProSemiBold,sans-serif}.theme-george h3{font-family:LatoRegular,sans-serif}h4{font-size:.875em;font-weight:400}.theme-ghs h4{font-family:SourceSansProRegular,sans-serif}.theme-george h4{font-family:LatoRegular,sans-serif}p{color:off-black;padding-bottom:16px;font-size:.875em;letter-spacing:.2px;line-height:1.3}.theme-ghs p{font-family:SourceSansProRegular,sans-serif}.theme-george p{font-family:LatoRegular,sans-serif}a{margin-bottom:16px;padding-bottom:0;font-weight:500;font-size:1em}.theme-ghs a{color:#0073b1;text-decoration:none;font-family:SourceSansProBold,sans-serif}.theme-george a{color:#191919;text-decoration:underline;font-family:LatoRegular,sans-serif}.button-as-link{display:inline;background-color:transparent;border:0;padding:0;width:auto!important;height:auto!important;color:#0073b1;margin-left:0;padding-bottom:16px;font-weight:500;text-decoration:none}.theme-ghs .button-as-link{font-size:.875em}.theme-george .button-as-link{font-size:12px}.theme-ghs .button-as-link{color:#0073b1}.theme-george .button-as-link{color:#191919}.short-password-error{bottom:62px}.blank-password-error{bottom:50px}a:hover{cursor:pointer}.theme-ghs strong{font-family:SourceSansProBold,sans-serif}.theme-george strong{font-family:LatoBold,sans-serif}.input-error{font-family:SourceSansProRegular,sans-serif;font-size:.75em;color:#d43030;line-height:1.125em}.theme-ghs .input-error{font-family:SourceSansProRegular,sans-serif;font-size:.75em;color:#d43030;clear:both}.theme-george .input-error{font-family:LatoBold,sans-serif;font-size:.875em;color:#da291c;clear:both}.theme-ghs .input-error a{font-family:SourceSansProRegular,sans-serif;color:#d43030;text-decoration:underline}.theme-george .input-error a{font-family:LatoBold,sans-serif;color:#da291c;text-decoration:underline}.left{float:left!important}.right{float:right!important;text-align:right!important}.theme-ghs .right{padding-top:3px;line-height:1.5}.theme-george .right{line-height:1.5}.center{text-align:center!important}.red{color:#da0500!important}.orange{color:#fdb45b!important}.pink{color:#ec938e!important}.yellow{color:#fae100!important}.green{color:#68a51c!important}.blue{color:#0073b1!important}.grey{color:grey1!important}.white{color:#fff!important}.black{color:#191919!important}.regular{font-weight:400!important}.bold{font-weight:700!important}@media (min-width:768px){.main-container.login{margin-top:32px}}form{border-bottom:1px solid #ccc;padding:8px 0 16px;margin-bottom:16px}form label.for-username{padding-bottom:4px;display:block}.theme-ghs form label.for-username{font-family:SourceSansProRegular,sans-serif;color:#3d3d3d;font-size:.875em;padding:0 0 4px}.theme-george form label.for-username{font-family:LatoRegular,sans-serif;color:#191919;font-size:14px;padding:0 0 8px}form .username-box.error{height:74px}form .username-box .alert{top:4px}form .login-links{width:100%;height:30px;padding:10px 0;margin-bottom:10px}form .login-links a{font-size:14px}form .form-error{margin-bottom:20px;margin-top:5px;font-family:SourceSansProRegular,sans-serif;font-size:.75em;color:#da291c;line-height:16px;clear:both}form .form-error a{color:#da291c;font-family:SourceSansProRegular,sans-serif;text-decoration:underline}form .dropdown-wrapper.phone-login-dropdown{float:none}form .dropdown-wrapper.phone-login-dropdown .dropdown-list::-webkit-scrollbar-thumb{height:150px}form .dropdown-wrapper{width:100%}form .dropdown-wrapper .list-wrapper .dropdown-list{width:498px;border:solid 1px #ccc;height:192px;padding:20px 20px}form .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{display:flex;font-size:14px;font-weight:normal;padding-bottom:12px;justify-content:start}form .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:auto;text-align:left;padding-right:15px}form .dropdown-header.phone-login-dropdown-header{height:44px;width:unset;border-radius:4px;border:solid 1px #ccc;margin-bottom:10px}form .dropdown-header.phone-login-dropdown-header .country-code{flex-grow:1;font-weight:normal;font-size:14px;margin-left:10px}form .remember-me{bottom:38px;width:130px}.theme-ghs form .remember-me{text-decoration:none}.theme-george form .remember-me{text-decoration:underline}a{user-select:none}a.login-link{color:#da291c;text-decoration:underline}.login-container .softLogin-form{border-bottom:0;margin:0;padding:8px 0 0}.login-container button.reset-pwd{border:0;font-size:14px;padding:0px;height:auto;width:auto;margin:0 0 16px 0;letter-spacing:inherit;text-transform:inherit;-webkit-font-smoothing:antialiased}.theme-ghs .login-container button.reset-pwd{color:#0073b1;text-decoration:none;font-family:SourceSansProBold,sans-serif}.theme-george .login-container button.reset-pwd{color:#191919;text-decoration:underline;font-family:LatoRegular,sans-serif}.reset-password-container .code-link{display:inline-block;padding-bottom:0;margin-top:16px}.reset-password-container button.basic{margin-top:20px}.reset-password-container form{border-bottom:0;margin-bottom:0}.reset-password-container .input-box input::placeholder{font-size:14px;color:#767676}.reset-code form{padding-bottom:0}.reset-code .input-box input.half,.reset-code button{width:48%;margin-right:0}.reset-code .reset-code-container{position:relative;padding-bottom:27px}.reset-code .reset-code-container .input-error{position:absolute;top:57px}.reset-code .reset-code-container .alert{top:20px;left:38%}@media (min-width:768px){.reset-code .reset-code-container .alert{left:44%}}.reset-code .code-label{display:inline;padding-bottom:0}.req-new-code{margin-top:8px;margin-bottom:8px;display:inline-block}.theme-ghs .req-new-code{font-family:SourceSansProBold,sans-serif;text-decoration:none;padding:0;text-transform:none}.theme-george .req-new-code{font-family:LatoBold,sans-serif;text-decoration:underline;font-size:14px;padding:0;text-transform:none}.try-other-email{font-size:1em}@media (min-width:768px){.reset-code .input-box input.half{width:54%}.reset-code button{width:44%}}.recaptcha-container{border-bottom:1px #ccc solid;margin-bottom:20px}.recaptcha-container .g-recaptcha{margin:10px 0 20px 0}.reset-confirmation{clear:fix();height:240px}.reset-confirmation button.full{margin-bottom:16px}.reset-confirmation button.full{width:100%;margin:0 0 16px;float:left}.theme-ghs .reset-confirmation button.next-dest a{color:#68a51c}.theme-george .reset-confirmation button.next-dest a{color:#191919}.password-strength{clear:both;font-family:SourceSansProRegular,sans-serif}.password-strength .strength-label{font-family:SourceSansProSemiBold,sans-serif;font-size:.875em}.password-strength .password-strength-measure{color:#767676;font-size:.875em;margin-top:5px;display:flex;justify-content:space-between}.theme-ghs .password-strength .password-strength-measure{font-family:SourceSansProRegular,sans-serif}.theme-george .password-strength .password-strength-measure{font-family:LatoRegular,sans-serif}.password-strength .strength-text{font-size:.875em}.password-strength .strength-indicator{display:inline-block;height:5px;border-radius:5px;position:relative;bottom:5px}.password-strength .strength-indicator.invalid{background-color:#c2c2c2;width:10%}.password-strength .strength-indicator.weak{background:#d43030;width:20%}.password-strength .strength-indicator.fair{background:#fbc42c;width:50%}.password-strength .strength-indicator.strong{background:#68a51c;width:100%}.password-strength .strength-indicator-bar{background:#e9e9e9;width:100%;height:5px;border-radius:5px}.password-strength .strength-desc{margin-top:16px}.password-strength .strength-desc button{font-size:.875em;display:inline;background-color:transparent;border:0;padding:0;color:#0073b1;margin-left:0;text-transform:none;letter-spacing:normal;margin-bottom:16px;font-weight:500}.theme-ghs .password-strength .strength-desc button{color:#0073b1;letter-spacing:.5px}.theme-george .password-strength .strength-desc button{color:#191919;letter-spacing:.5px}.password-strength .strength-desc button .chevron{padding-left:6px}.password-strength .strength-desc button .chevron.flipped{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg);padding-top:0;padding-bottom:0;padding-left:0;padding-right:6px}.password-strength .strength-desc .strength-para{margin-bottom:16px}.password-strength .strength-desc .strength-para p{padding:0}.password-strength .strength-desc .strength-para ul{list-style-type:unset;list-style-position:inside;margin-top:8px}.password-strength .strength-desc .strength-para ul li{font-family:SourceSansProRegular,sans-serif;font-size:.875em;margin-left:8px;margin-bottom:4px}.postcode-wrap{width:100%;float:left;margin-bottom:8px}.postcode-wrap .custom-input.half{width:50%;text-transform:uppercase}.postcode-wrap .custom-input.half+.input-error{clear:both;line-height:17px;padding-top:5px}.regCheckTnC .register-link{font-size:100%}.theme-ghs .regCheckTnC .register-link{font-family:SourceSansProBold,sans-serif}.theme-george .regCheckTnC .register-link{font-family:LatoBold,sans-serif}.regCheckTnC .input-error{font-size:.875em}.register-container form{border-bottom:0;padding-bottom:0;margin-bottom:0}.register-container form button+p{padding-top:20px;padding-bottom:0}.register-container .register-link{font-size:100%}.theme-ghs .register-container .register-link{font-family:SourceSansProBold,sans-serif}.theme-george .register-container .register-link{font-family:LatoBold,sans-serif}.register-container .george-reward-link{font-size:14px;font-family:LatoBold,sans-serif}.password-container{position:relative}.password-container .button-show-password{position:absolute;padding-bottom:0;text-decoration:none;right:12px;text-align:center;display:inline-block;margin:0;width:40px!important;transition:all 200ms ease;padding:0}.theme-ghs .password-container .button-show-password{font-family:SourceSansProSemiBold,sans-serif;top:30px;text-transform:none;font-size:1em}.theme-george .password-container .button-show-password{font-family:LatoRegular,sans-serif;top:37px;text-transform:uppercase;font-size:.75em}.password-container .input-box input.show-password{padding-right:60px}.password-container .show-password{padding-right:65px;margin-bottom:7px}.co-third-party-login__container .primary{margin-bottom:16px}.co-third-party-login__container h3{margin:7.2px 0 24px 0}.co-login-disclaimer p{padding:16px 0 0}.co-login-disclaimer a{font-family:SourceSansProRegular;font-weight:bold;font-size:.95em}form#cd-reg-form{border-bottom:0;padding-bottom:0;margin-bottom:0}form#cd-reg-form .form-error{margin-bottom:20px;margin-top:5px;font-family:SourceSansProRegular,sans-serif;font-size:.75em;color:#da291c;line-height:16px;clear:both}form#cd-reg-form input:last-child{margin-bottom:16px}a{user-select:none}a.login-link{color:#da291c;text-decoration:underline}.login-container .softLogin-form{border-bottom:0;margin:0;padding:8px 0 0}.page-mask{position:fixed;width:100vw;height:100vh;left:0;top:0;background-color:#000;opacity:.5}.cd-modal{position:absolute;z-index:1000;top:10vh;left:calc(50% - 200px);width:400px;height:auto;border-radius:2px;background-color:#fff;box-shadow:0 5px 10px 0 rgba(0,0,0,.2)}.cd-modal h1{padding:20px;border-bottom:1px solid #ccc}.cd-modal p{padding:20px 40px 20px 20px;border-bottom:1px solid #ccc}.theme-george .register-container a.tc-link-register{font-family:LatoBold,sans-serif}.theme-george .register-container .password-strength .strength-desc button{text-decoration:underline}.app{display:flex;flex-wrap:wrap;align-items:flex-start;flex-direction:row}.container-wrapper{height:auto;flex-basis:calc((100% * 1) - 16px);min-width:0;margin:8px;margin-left:calc(100% * 0 + (16px / 2))!important;min-height:calc(100vh - 200px)}.theme-ghs .container-wrapper{min-height:calc(100vh - 300px)}.theme-george .container-wrapper{min-height:calc(100vh - 261px)}@media (min-width:481px){.container-wrapper{flex-basis:calc((100% * 0.6666666667) - 16px);min-width:0;margin:8px;margin-left:calc(100% * 0.1666666667 + (16px / 2))!important;min-height:calc(100vh - 200px)}}@media (min-width:768px){.container-wrapper{flex-basis:calc((100% * 0.5) - 16px);min-width:0;margin:8px;margin-left:calc(100% * 0.25 + (16px / 2))!important}}@media (min-width:961px){.container-wrapper{flex-basis:calc((100% * 0.3333333333) - 16px);min-width:0;margin:8px;margin-left:calc(100% * 0.3333333333 + (16px / 2))!important}}.main-container{height:auto;padding:4px;max-width:432px;margin:0 auto}@media (min-width:481px){.main-container{border:1px solid #ccc}.theme-ghs .main-container{padding:20px;box-shadow:0 0}.theme-george .main-container{padding:32px;box-shadow:4px 4px 4px 0 rgba(0,0,0,.06)}}footer{width:100%;margin-top:60px}.theme-ghs footer{background-color:#191919;min-height:100px}.theme-george footer{background-color:#fff;min-height:60px}.theme-ghs footer{border-top:0}.theme-george footer{border-top:1px solid #cbcbcb}footer .up-arrow{margin-left:5px;-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg);height:10px;width:10px}.footer-wrapper{position:relative;max-width:1024px;margin:0 auto;padding:16px}.footer-top{position:absolute;display:flex;justify-content:space-between;flex-direction:row;top:-50px}.footer-top .back-top button{font-weight:600;letter-spacing:.2px;color:#191919;font-family:SourceSansProSemiBold,sans-serif;font-size:13px;display:inline;background-color:transparent;border:0;padding:0}.footer-top a.website-feedback-link{align-items:center;color:#191919;display:none}.footer-top .feedback-icon{width:30px;height:30px;margin-right:5px}.footer-main{text-align:center}.footer-logo{margin:40px 0 40px}.footer-george-logo{font-family:LatoRegular,sans-serif}@media (min-width:320px){.footer-george-logo{position:absolute;bottom:16px;width:95%;text-align:center}}.footer-links{font-family:SourceSansProSemiBold,sans-serif;margin:8px 0;display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-ms-justify-content:center;justify-content:center}.footer-links ul{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;-moz-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;max-width:320px;-webkit-justify-content:center;-ms-justify-content:center;justify-content:center;width:100%}.footer-links li{font-size:.8125em;letter-spacing:.2px}.theme-ghs .footer-links li{color:#fff}.theme-george .footer-links li{color:#191919}.theme-george .footer-links li{border-right:1px solid #cbcbcb}.theme-ghs .footer-links li{padding:4px}.theme-george .footer-links li{padding:0}.theme-ghs .footer-links li{padding-right:0}.theme-george .footer-links li{padding-right:4px}.theme-ghs .footer-links li{margin:0}.theme-george .footer-links li{margin:4px}.footer-links li a{font-size:100%}.theme-ghs .footer-links li a{color:#fff}.theme-george .footer-links li a{color:#191919}.theme-ghs .footer-links li a{font-family:SourceSansProSemiBold,sans-serif}.theme-george .footer-links li a{font-family:LatoRegular,sans-serif}.theme-george .footer-links li a{text-decoration:none}.footer-links li a:hover{text-decoration:underline}.footer-links li:last-child{border-right:0}@media (min-width:768px){footer{min-height:102px}.footer-main{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-ms-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-moz-align-items:center;-ms-align-items:center;align-items:center;width:100%}.footer-logo{margin:0}.footer-links{margin:0;order:-1;-webkit-align-self:flex-end;-moz-align-self:flex-end;-ms-align-self:flex-end;align-self:flex-end}.footer-links ul{max-width:100%}.footer-top .back-top{top:-55px}}@media (min-width:320px){.theme-ghs footer{height:205px}.theme-george footer{height:92px}.theme-ghs .footer-wrapper{height:205px}.theme-george .footer-wrapper{height:92px}.footer-top{top:-50px}}@media (min-width:768px){.theme-ghs footer{height:100px}.theme-george footer{height:60px}.theme-ghs .footer-wrapper{height:100px}.theme-george .footer-wrapper{height:60px}.footer-george-logo{font-family:LatoRegular,sans-serif;position:relative;bottom:0;width:auto;text-align:left}}header{height:92px;width:100%;background-color:#fff;transition-property:margin-top;transition-duration:.5s;transition-delay:.5s}.theme-ghs header{box-shadow:0 2px 4px 0 #7f7f7f;margin-bottom:32px}.theme-george header{box-shadow:0 2px 4px 0 #cbcbcb;margin-bottom:32px}header[data-apply-onetrust=true]{margin-top:150px}header .header-container{display:flex;height:92px;justify-content:space-between;align-items:center;background-color:#fff;width:100%;max-width:1024px;margin:0 auto;padding:8px}header .header-container #logo{padding:0}header .header-container #logo img{width:93px}.theme-ghs header .header-container nav{margin-bottom:0}.theme-george header .header-container nav{margin-bottom:8px}header .header-container nav button{width:142px;letter-spacing:normal;text-transform:none}.theme-ghs header .header-container nav button{width:200px;background-image:none}.theme-george header .header-container nav button{width:120px;border:0;background-image:none}header .header-container nav button:hover{box-shadow:none}header .header-container nav .need-help{display:none}@media (min-width:320px){header{margin-bottom:24px;height:48px;transition-property:margin-top;transition-duration:.5s;transition-delay:.5s}header[data-apply-onetrust=true]{margin-top:150px}.theme-ghs header{margin-bottom:24px}.theme-george header{margin-bottom:24px}header .header-container{height:48px;padding:13px 16px 10px}header .header-container #logo{margin-bottom:0}header .header-container nav{margin-bottom:0}.theme-ghs header .header-container nav{margin-bottom:2px}.theme-george header .header-container nav{margin-bottom:2px}.theme-ghs header .header-container nav button{font-family:SourceSansProSemiBold,sans-serif;width:144px;margin-right:0;margin-left:8px}.theme-george header .header-container nav button{font-family:LatoRegular,sans-serif;width:88px;padding:16px 0 16px 12px;text-align:right;font-size:12px;margin-right:0;margin-left:8px}header .header-container nav button.basic:hover{background-image:none}}@media (min-width:481px){header{height:92px;margin-bottom:24px;padding-top:0;transition-property:margin-top;transition-duration:.5s;transition-delay:.5s}header[data-apply-onetrust=true]{margin-top:125px}header .header-container{height:92px;width:100%;padding:16px}.theme-ghs header .header-container{padding-top:20px}.theme-george header .header-container{padding-top:31px}header .header-container #logo img{width:113px}.theme-george header .header-container nav button{border:0}}@media (min-width:768px){header{transition-property:margin-top;transition-duration:.5s;transition-delay:.5s}header[data-apply-onetrust=true]{margin-top:140px}.theme-ghs header .header-container{padding-top:20px}.theme-george header .header-container{padding-top:31px}.theme-ghs header .header-container nav button{text-align:center}.theme-george header .header-container nav button{text-align:center;padding:0}header .header-container nav .need-help{display:inline-block}.theme-ghs header .header-container nav .need-help{background-image:none;text-transform:none;font-size:18px;width:200px}.theme-george header .header-container nav .need-help{border-right:1px solid #cbcbcb;border-radius:0;background-image:none;text-transform:none;font-size:14px;width:120px}.theme-ghs header .header-container nav .back-shop{background-image:none;width:200px;text-transform:none;font-size:18px}.theme-george header .header-container nav .back-shop{background-image:none;width:120px;text-transform:none;font-size:14px}}@media (min-width:961px){header{padding-top:0;transition-property:margin-top;transition-duration:.5s;transition-delay:.5s}.theme-ghs header{margin-bottom:32px}.theme-george header{margin-bottom:32px}header[data-apply-onetrust=true]{margin-top:75px}header .header-container{height:92px}.theme-ghs header .header-container{padding-top:20px}.theme-george header .header-container{padding-top:31px}header .header-container #logo img{width:140px}.theme-ghs header .header-container nav button{font-family:SourceSansProSemiBold,sans-serif}.theme-george header .header-container nav button{width:150px;border:0;border-radius:0;padding:0;height:18px;font-family:LatoRegular,sans-serif;margin:0}}@media (min-width:768px){.layout__section{min-height:640px}}@media (min-width:1024px){.layout__section{display:flex}.layout__main{flex:1 1 auto}}@media (min-width:1280px){.site-width.layout__section{margin:0 auto;max-width:1392px;padding:0 16px}.site-width.layout__main{flex:1 1 auto}}.onetrust-consent-sdk-box[data-apply-ot-banner-popup=true] .onetrust-pc-dark-filter.ot-hide{display:block!important}.onetrust-consent-sdk-box #onetrust-banner-sdk:focus{outline-color:none!important;outline-width:0px!important}.onetrust-consent-sdk-box #onetrust-banner-sdk{background-color:#fff!important;max-width:500px;min-width:500px;width:100%;top:23%;left:50%!important;transform:translate(-50%,-25%);padding:16px 22px 16px 24px;border-radius:2px;z-index:2147483646!important}.onetrust-consent-sdk-box #onetrust-banner-sdk #onetrust-accept-btn-handler{background-color:#0073b1;border-color:#0073b1;color:#fff;border-radius:4px}.onetrust-consent-sdk-box #onetrust-banner-sdk #onetrust-pc-btn-handler{background-color:#fff;border:1px solid #0073b1;color:#0073b1;border-radius:4px}.onetrust-consent-sdk-box #onetrust-banner-sdk #onetrust-group-container button{color:#0073b1;display:inline}.onetrust-consent-sdk-box #onetrust-banner-sdk #onetrust-policy-title,.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-dpd-title{width:100%!important}.onetrust-consent-sdk-box #onetrust-banner-sdk #onetrust-policy-text{width:100%!important;line-height:1.5}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-b-addl-desc{width:100%!important;border-right:0;color:#000}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-dpd-content .ot-dpd-desc{line-height:1.5}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-dpd-container{width:100%!important;padding:0!important}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row{display:flex;flex-direction:column}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-group-container{width:100%}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-group-container p,.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-group-container h3{color:#000}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-button-group-parent{position:relative!important;width:100%;left:auto!important;top:auto;transform:translateY(0%);right:0%}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-button-group-parent #onetrust-button-group{display:flex;justify-content:space-evenly}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-button-group-parent #onetrust-button-group #onetrust-pc-btn-handler,.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container .ot-sdk-row #onetrust-button-group-parent #onetrust-button-group #onetrust-accept-btn-handler{margin-right:0px;max-width:112px;width:100%;margin-top:1em}.onetrust-consent-sdk-box #onetrust-banner-sdk #onetrust-policy{margin:0px}.onetrust-consent-sdk-box #onetrust-pc-sdk{background-color:#fff!important;color:#000;max-width:500px!important;min-width:100%;padding:7px 16px 25px}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-pc-content{overflow-x:hidden}.onetrust-consent-sdk-box #onetrust-pc-sdk *:focus{outline:none!important}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-fltr-cnt,.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-anchor{background-color:#fff!important}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-fltr-cnt{width:auto}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-sel-blk{background-color:#fff}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-pc-header{padding:0}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-pc-title,.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-pc-content,.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-category-title{color:#000!important;width:auto;margin:0;padding:0;padding-bottom:5px}.onetrust-consent-sdk-box #onetrust-pc-sdk #onetrust-group-container button{color:#0073b1}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-pc-desc,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-accordion-layout .ot-cat-header{color:#000!important;line-height:1.5!important;min-height:20px}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-lst-title span{color:#000!important;line-height:1.5}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-name{color:#000!important;font-weight:400!important;line-height:1.5}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-pc-desc{margin-bottom:10px}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-pc-desc .privacy-notice-link{color:#0073b1;text-decoration:none}.onetrust-consent-sdk-box #onetrust-pc-sdk #accept-recommended-btn-handler,.onetrust-consent-sdk-box #onetrust-pc-sdk .save-preference-btn-handler,.onetrust-consent-sdk-box #onetrust-pc-sdk #filter-apply-handler,.onetrust-consent-sdk-box #onetrust-pc-sdk #filter-cancel-handler{background-color:#0073b1!important;border-color:#0073b1!important;color:#fff!important;margin-bottom:10px;border-radius:4px;margin-right:25px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-cat-grp{margin-top:0px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-acc-grpdesc,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-subgrp-desc,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-subgrp h5,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-disc h4,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-dets p,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-dets span,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-label-txt{color:#000!important;font-size:12px!important}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-dets h4,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-pur h4,.onetrust-consent-sdk-box #onetrust-pc-sdk #clear-filters-handler{color:#000!important}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-vlst-cntr a,.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-ven-link{color:#0073b1!important;padding:0px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-vlst-cntr button{color:#0073b1!important}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-tgl input:checked+.ot-switch .ot-switch-nob{background-color:#0073b1;border:1px solid #fff}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-tgl input:checked+.ot-switch .ot-switch-nob:before{background-color:#fff;border-color:#fff;width:9px;height:9px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-switch{width:28px;height:11px;padding:1px 1px 1px 4px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-switch-nob:before{background-color:#fff;width:9px;height:9px;border-radius:5.5px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-switch-nob{background-color:#767676;border-radius:5.5px;padding-left:1px}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-acc-hdr{min-height:auto}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-always-active{color:#000}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-pc-footer-logo a{margin-right:25px}.onetrust-consent-sdk-box #onetrust-pc-sdk #filter-btn-handler{width:30px;height:30px;border-radius:4px;background-color:#538316}.onetrust-consent-sdk-box #onetrust-pc-sdk #ot-sel-blk{background-color:#fff!important}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-chkbox label::before{border:1px solid #538316}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-chkbox input:checked~label::before{background-color:#538316}.onetrust-consent-sdk-box #onetrust-pc-sdk .ot-plus-minus{width:15px;height:15px}.onetrust-consent-sdk-box #onetrust-pc-sdk #vendor-search-handler{border-radius:4px}@media (max-width:767px){.onetrust-consent-sdk-box #onetrust-banner-sdk{top:28%;max-width:480px;min-width:325px;height:fit-content}.onetrust-consent-sdk-box #onetrust-banner-sdk .ot-sdk-container{padding:0px}}@media (min-width:768px)and (max-width:1023px){.onetrust-consent-sdk-box #onetrust-banner-sdk{top:23%;max-width:452px;min-width:452px;height:fit-content!important}}@media (min-width:768px){.onetrust-consent-sdk-box #onetrust-pc-sdk{min-width:500px!important}}@media (min-width:1024px){.onetrust-consent-sdk-box #onetrust-banner-sdk{height:fit-content}}button,a.reg-link,.register-nav-link{width:200px;height:40px;border:1px solid #191919;border-radius:4px;text-align:center;user-select:none;margin:0 5px;line-height:14px;font-weight:500;letter-spacing:.5px;cursor:pointer;background:transparent;transition:all 200ms ease}.theme-ghs button,.theme-ghs a.reg-link,.theme-ghs .register-nav-link{font-family:SourceSansProSemiBold,sans-serif;height:40px;font-size:18px;padding:11px}.theme-george button,.theme-george a.reg-link,.theme-george .register-nav-link{font-family:LatoBold,sans-serif;text-transform:uppercase;height:42px;font-size:14px;padding:14px}button a,a.reg-link a,.register-nav-link a{color:inherit;font-size:inherit;font-weight:inherit;font-family:inherit}.theme-george button a,.theme-george a.reg-link a,.theme-george .register-nav-link a{text-decoration:none}button.primary,a.reg-link.primary,.register-nav-link.primary{color:#fff;text-decoration:none}.theme-ghs button.primary,.theme-ghs a.reg-link.primary,.theme-ghs .register-nav-link.primary{background-color:#0073b1;border-color:#0073b1}.theme-george button.primary,.theme-george a.reg-link.primary,.theme-george .register-nav-link.primary{background-image:linear-gradient(to bottom,#424242 35%,black);border-color:#191919}button.primary:hover,a.reg-link.primary:hover,.register-nav-link.primary:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.theme-ghs button.primary:hover,.theme-ghs a.reg-link.primary:hover,.theme-ghs .register-nav-link.primary:hover{background-color:#005a8b}.theme-george button.primary:hover,.theme-george a.reg-link.primary:hover,.theme-george .register-nav-link.primary:hover{background-image:linear-gradient(to bottom,black 35%,#424242)}.theme-ghs button.secondary,.theme-ghs a.reg-link.secondary,.theme-ghs .register-nav-link.secondary{color:#fff;background-color:#68a51c;border-color:#68a51c}.theme-george button.secondary,.theme-george a.reg-link.secondary,.theme-george .register-nav-link.secondary{color:#fff;background-color:#191919;background-image:linear-gradient(to bottom,#424242 35%,black);border-color:#191919;text-transform:uppercase}button.secondary:hover,a.reg-link.secondary:hover,.register-nav-link.secondary:hover{background-color:#538316;box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.theme-george button.secondary:hover,.theme-george a.reg-link.secondary:hover,.theme-george .register-nav-link.secondary:hover{background-image:linear-gradient(to bottom,black 35%,#424242)}.theme-ghs button.secondary:disabled,.theme-ghs a.reg-link.secondary:disabled,.theme-ghs .register-nav-link.secondary:disabled{background-color:#68a51c;opacity:.5;cursor:not-allowed}.theme-george button.secondary:disabled,.theme-george a.reg-link.secondary:disabled,.theme-george .register-nav-link.secondary:disabled{background-color:#191919;background-image:linear-gradient(to bottom,#424242 35%,black);opacity:.5;cursor:not-allowed}.theme-ghs button.secondary.empty,.theme-ghs a.reg-link.secondary.empty,.theme-ghs .register-nav-link.secondary.empty{border-color:#68a51c;color:#68a51c;background-color:#fff}.theme-george button.secondary.empty,.theme-george a.reg-link.secondary.empty,.theme-george .register-nav-link.secondary.empty{border-color:#191919;color:#fff}.theme-ghs button.destructive,.theme-ghs a.reg-link.destructive,.theme-ghs .register-nav-link.destructive{color:#d43030;background-color:#fff;border-color:#d43030}button.destructive:hover,a.reg-link.destructive:hover,.register-nav-link.destructive:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.theme-ghs button.destructive:hover,.theme-ghs a.reg-link.destructive:hover,.theme-ghs .register-nav-link.destructive:hover{border-width:2px}.theme-ghs button.basic,.theme-ghs a.reg-link.basic,.theme-ghs .register-nav-link.basic{background-color:#fff;border-color:#7ebd2f;color:#68a51c}.theme-george button.basic,.theme-george a.reg-link.basic,.theme-george .register-nav-link.basic{background-image:linear-gradient(to bottom,#fdfdfd,#d8d8d8);border-color:#b9b9b9;color:#191919}button.basic:hover,a.reg-link.basic:hover,.register-nav-link.basic:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.theme-ghs button.basic:hover,.theme-ghs a.reg-link.basic:hover,.theme-ghs .register-nav-link.basic:hover{border-width:2px}.theme-george button.basic:hover,.theme-george a.reg-link.basic:hover,.theme-george .register-nav-link.basic:hover{background-image:linear-gradient(to bottom,#d8d8d8,white);border-width:1px}button.full,a.reg-link.full,.register-nav-link.full{width:100%;border-radius:4px;margin:0}@media (min-width:320px){button.half,a.reg-link.half,.register-nav-link.half{width:100%}}@media (min-width:768px){button.half,a.reg-link.half,.register-nav-link.half{width:40%;float:left;margin-right:8px}}button.center,a.reg-link.center,.register-nav-link.center{margin:20px auto;display:block;float:none}@keyframes circle1s{0%{left:0;top:0;width:15px;height:15px;z-index:100}25%{left:26.25px;top:0;width:15px;height:15px;z-index:100}50%{left:52.5px;top:0;width:15px;height:15px;z-index:100}75%{left:52.5px;top:7.5px;width:0;height:0;z-index:0}76%{left:7.5px;top:7.5px;width:0;height:0;z-index:0}100%{left:0;top:0;width:15px;height:15px;z-index:100}}@keyframes circle2s{0%{left:26.25px;top:0;width:15px;height:15px;z-index:100}25%{left:52.5px;top:0;width:15px;height:15px;z-index:100}50%{left:52.5px;top:7.5px;width:0;height:0;z-index:0}51%{left:7.5px;top:7.5px;width:0;height:0;z-index:0}75%{left:0;top:0;width:15px;height:15px;z-index:100}100%{left:26.25px;top:0;width:15px;height:15px;z-index:100}}@keyframes circle3s{0%{left:52.5px;top:0;width:15px;height:15px;z-index:100}25%{left:52.5px;top:7.5px;width:0;height:0;z-index:0}26%{left:7.5px;top:7.5px;width:0;height:0;z-index:0}50%{left:0;top:0;width:15px;height:15px;z-index:100}75%{left:26.25px;top:0;width:15px;height:15px;z-index:100}100%{left:52.5px;top:0;width:15px;height:15px;z-index:100}}@keyframes circle4s{0%{left:7.5px;top:7.5px;width:0;height:0;z-index:0}25%{left:0;top:0;width:15px;height:15px;z-index:100}50%{left:26.25px;top:0;width:15px;height:15px;z-index:100}75%{left:52.5px;top:0;width:15px;height:15px;z-index:100}100%{left:52.5px;top:7.5px;width:0;height:0;z-index:0}}button #loading,a.reg-link #loading,.register-nav-link #loading{display:block;margin:0 auto;width:67.5px;height:15px;position:relative}.theme-ghs button #loading,.theme-ghs a.reg-link #loading,.theme-ghs .register-nav-link #loading{margin-top:0}.theme-george button #loading,.theme-george a.reg-link #loading,.theme-george .register-nav-link #loading{margin-top:-2px}button #loading .animation,a.reg-link #loading .animation,.register-nav-link #loading .animation{position:relative}button #loading .animation .circle,a.reg-link #loading .animation .circle,.register-nav-link #loading .animation .circle{position:absolute;border-radius:100%;border:1px solid #fff;animation-name:circle1s;animation-duration:1.1s;animation-iteration-count:infinite}.theme-ghs button #loading .animation .circle,.theme-ghs a.reg-link #loading .animation .circle,.theme-ghs .register-nav-link #loading .animation .circle{background-color:#fff;border-color:#fff}.theme-george button #loading .animation .circle,.theme-george a.reg-link #loading .animation .circle,.theme-george .register-nav-link #loading .animation .circle{background-color:#d9dddf;border-color:#d9dddf}button #loading .animation .circle:nth-child(2),a.reg-link #loading .animation .circle:nth-child(2),.register-nav-link #loading .animation .circle:nth-child(2){animation-name:circle2s}.theme-ghs button #loading .animation .circle:nth-child(2),.theme-ghs a.reg-link #loading .animation .circle:nth-child(2),.theme-ghs .register-nav-link #loading .animation .circle:nth-child(2){background-color:#fff;border-color:#fff}.theme-george button #loading .animation .circle:nth-child(2),.theme-george a.reg-link #loading .animation .circle:nth-child(2),.theme-george .register-nav-link #loading .animation .circle:nth-child(2){background-color:#b8babd;border-color:#b8babd}button #loading .animation .circle:nth-child(3),a.reg-link #loading .animation .circle:nth-child(3),.register-nav-link #loading .animation .circle:nth-child(3){animation-name:circle3s}.theme-ghs button #loading .animation .circle:nth-child(3),.theme-ghs a.reg-link #loading .animation .circle:nth-child(3),.theme-ghs .register-nav-link #loading .animation .circle:nth-child(3){background-color:#fff;border-color:#fff}.theme-george button #loading .animation .circle:nth-child(3),.theme-george a.reg-link #loading .animation .circle:nth-child(3),.theme-george .register-nav-link #loading .animation .circle:nth-child(3){background-color:#a3a3ab;border-color:#a3a3ab}button #loading .animation .circle:nth-child(4),a.reg-link #loading .animation .circle:nth-child(4),.register-nav-link #loading .animation .circle:nth-child(4){animation-name:circle4s}.theme-ghs button #loading .animation .circle:nth-child(4),.theme-ghs a.reg-link #loading .animation .circle:nth-child(4),.theme-ghs .register-nav-link #loading .animation .circle:nth-child(4){background-color:#fff;border-color:#fff}.theme-george button #loading .animation .circle:nth-child(4),.theme-george a.reg-link #loading .animation .circle:nth-child(4),.theme-george .register-nav-link #loading .animation .circle:nth-child(4){background-color:#efefef;border-color:#efefef}a.reg-link,.register-nav-link{display:block;-webkit-font-smoothing:auto}.saved{width:88px;margin:0 auto;display:block}.saved .tick{float:left;margin-right:8px;margin-top:-2px}.saved .saved-text{width:auto;float:left}label.check-box{clear:both;margin-bottom:16px;display:block;font-size:.875em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;padding-left:34px;line-height:1.5;color:#3d3d3d}.theme-ghs label.check-box{font-family:SourceSansProRegular,sans-serif}.theme-george label.check-box{font-family:LatoRegular,sans-serif}label.check-box .checkmark{position:absolute;top:0;left:0;border:1px solid;background-color:#fff}.theme-ghs label.check-box .checkmark{width:20px;height:20px;border-radius:2px;border-color:#68a51c}.theme-george label.check-box .checkmark{width:24px;height:24px;border-radius:5px;border-color:#ccc}label.check-box .checkmark.error{border-color:#d43030}label.check-box .checkmark:after{content:"";position:absolute;display:none;border:solid #fff;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.theme-ghs label.check-box .checkmark:after{left:6px;top:0;width:6px;height:14px;border-width:0 2px 2px 0}.theme-george label.check-box .checkmark:after{left:7px;top:3px;width:8px;height:13px;border-width:0 3px 3px 0}.theme-ghs label.check-box .checkmark.disabled{background-color:#fff;border:1px solid #7ebd2f;opacity:.5;cursor:not-allowed!important}.theme-george label.check-box .checkmark.disabled{background-color:#d8d8d8;border:1px solid #d8d8d8;opacity:.5;cursor:not-allowed!important}label.check-box input[type=checkbox]{position:absolute;opacity:0;height:20px;width:20px;margin:0;left:0}label.check-box input[type=checkbox]:focus~.checkmark,label.check-box input[type=checkbox]:active~.checkmark{outline:#75aee8 auto 5px}.theme-ghs label.check-box input[type=checkbox]:checked~.checkmark{background-color:#68a51c}.theme-george label.check-box input[type=checkbox]:checked~.checkmark{background-color:#191919}label.check-box input[type=checkbox]:checked~.checkmark:after{display:block}label.check-box-tc{padding-left:54px}label.check-box-tc .checkmark{width:40px;height:40px;border-radius:8px;border:solid 2px #767676;background-color:#fff}label.check-box-tc .checkmark:after{left:14px;top:6px;width:12.3px;height:17.8px;border:solid #68a51c;border-width:0 3px 3px 0}label.check-box-tc input[type=checkbox]:checked~.checkmark{background-color:#fff}.input-error{margin-bottom:8px;margin-top:4px}.input-box{position:relative}.input-box__wrapper{position:relative;overflow-wrap:break-word}.theme-ghs .input-box__wrapper.read-only{font-size:1em;font-family:SourceSansProRegular,sans-serif;color:#3d3d3d}.theme-george .input-box__wrapper.read-only{font-size:.875em;font-family:LatoRegular,sans-serif;color:#191919}.input-box.half{width:calc(50% - 4px);margin-right:4px;float:left}.input-box.half.last{margin-left:4px;margin-right:0}.input-box.custom-icon{position:relative}.input-box.custom-icon:before{content:"";position:absolute}.input-box.custom-icon input{padding-left:38px}.input-box label{padding-bottom:4px;display:block}.theme-ghs .input-box label{font-family:SourceSansProRegular,sans-serif;color:#3d3d3d;font-size:.875em;padding:0 0 4px}.theme-george .input-box label{font-family:LatoRegular,sans-serif;color:#191919;font-size:14px;padding:0 0 8px}.input-box .alert{position:absolute;top:3px;right:12px}.theme-ghs .input-box .alert{display:block}.theme-george .input-box .alert{display:none}.input-box input{width:100%;border-radius:4px;border:1px solid #ccc;clear:both;transition:border 200ms ease,box-shadow 200ms ease}.theme-ghs .input-box input{height:40px;font-size:1em;padding:12px;margin-bottom:12px;font-family:SourceSansProRegular,sans-serif;color:#3d3d3d}.theme-george .input-box input{height:42px;font-size:.875em;padding:14px 12px;margin-bottom:16px;font-family:LatoRegular,sans-serif;color:#191919}.input-box input:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1);cursor:default}.input-box input:focus{border-color:#767676}.input-box input::placeholder{color:#767676}.input-box input.half{width:40%;float:left;margin-right:10}.input-box input.error{margin-bottom:0}.theme-ghs .input-box input.error{border:2px solid #d43030}.theme-george .input-box input.error{border:1px solid #da291c}.input-box input.read-only{border:0;outline:0;padding:0;margin:0;height:20px;pointer-events:none;background:0;box-shadow:none}.input-box.check-box-grouped label.check-box{width:20px;padding-left:unset;float:left}.input-box.check-box-grouped input[type=checkbox]{width:20px;top:0;left:0;height:20px;padding:0;margin:0}.input-box.check-box-grouped label{line-height:20px;padding-left:34px}.input-box.check-box-grouped .input-error{margin-top:-10px}.theme-ghs .input-error{margin:4px 0 16px}.theme-george .input-error{margin:8px 0 16px}.input-warning{padding:8px;margin:-1px 0 10px 16px;background-color:#ebf1f5;color:#000;font-size:.75em;font-family:SourceSansProRegular,sans-serif;z-index:100;display:inline-block;position:relative}.input-warning .triangle{position:absolute;top:-20px}.input-label{padding-bottom:4px;display:block}.theme-ghs .input-label{font-family:SourceSansProRegular,sans-serif;color:#3d3d3d;font-size:.875em}.theme-george .input-label{font-family:LatoRegular,sans-serif;color:#191919;font-size:14px}label.radio{margin-bottom:16px;display:block;font-size:.875em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;padding-left:28px;padding-right:16px;line-height:1.5}.theme-ghs label.radio{font-family:SourceSansProRegular,sans-serif}.theme-george label.radio{font-family:LatoRegular,sans-serif}label.radio .checkmark{position:absolute;top:0;left:0;height:20px;width:20px;background-color:#fff;border-radius:90px;transition:all 250ms ease}.theme-ghs label.radio .checkmark{border:1px solid #68a51c}.theme-george label.radio .checkmark{border:1px solid #191919}label.radio .checkmark:hover{border-width:2px}label.radio .checkmark.error{border-color:#d43030}label.radio.property-type-radio{float:left}label.radio input[type=radio]{position:absolute;opacity:0}label.radio input[type=radio]:focus~.checkmark,label.radio input[type=radio]:active~.checkmark{outline:#75aee8 auto 5px}.theme-ghs label.radio input[type=radio]:checked~.checkmark{border:1px solid #68a51c}.theme-george label.radio input[type=radio]:checked~.checkmark{border:1px solid #191919}label.radio input[type=radio]:checked~.checkmark:before{content:"";display:inline-block;width:18px;height:18px;position:relative;border-radius:100%;vertical-align:top;text-align:center;cursor:pointer;transition:all 250ms ease}.theme-ghs label.radio input[type=radio]:checked~.checkmark:before{background-color:#68a51c}.theme-george label.radio input[type=radio]:checked~.checkmark:before{background-color:#fff}.theme-ghs label.radio input[type=radio]:checked~.checkmark:before{box-shadow:inset 0 0 0 4px #fff}.theme-george label.radio input[type=radio]:checked~.checkmark:before{box-shadow:inset 0 0 0 5px #191919}@keyframes circle1{0%{left:0;top:0;width:40px;height:40px;z-index:100}25%{left:70px;top:0;width:40px;height:40px;z-index:100}50%{left:140px;top:0;width:40px;height:40px;z-index:100}75%{left:140px;top:20px;width:0;height:0;z-index:0}76%{left:20px;top:20px;width:0;height:0;z-index:0}100%{left:0;top:0;width:40px;height:40px;z-index:100}}@keyframes circle2{0%{left:70px;top:0;width:40px;height:40px;z-index:100}25%{left:140px;top:0;width:40px;height:40px;z-index:100}50%{left:140px;top:20px;width:0;height:0;z-index:0}51%{left:20px;top:20px;width:0;height:0;z-index:0}75%{left:0;top:0;width:40px;height:40px;z-index:100}100%{left:70px;top:0;width:40px;height:40px;z-index:100}}@keyframes circle3{0%{left:140px;top:0;width:40px;height:40px;z-index:100}25%{left:140px;top:20px;width:0;height:0;z-index:0}26%{left:20px;top:20px;width:0;height:0;z-index:0}50%{left:0;top:0;width:40px;height:40px;z-index:100}75%{left:70px;top:0;width:40px;height:40px;z-index:100}100%{left:140px;top:0;width:40px;height:40px;z-index:100}}@keyframes circle4{0%{left:20px;top:20px;width:0;height:0;z-index:0}25%{left:0;top:0;width:40px;height:40px;z-index:100}50%{left:70px;top:0;width:40px;height:40px;z-index:100}75%{left:140px;top:0;width:40px;height:40px;z-index:100}100%{left:140px;top:20px;width:0;height:0;z-index:0}}#loading{display:block;margin:calc(50vh - 171px) auto;width:180px;height:40px}#loading .animation{position:relative}#loading .animation .circle{position:absolute;background-color:#d9dddf;border-radius:100%;border:1px solid #d9dddf;animation-name:circle1;animation-duration:1.1s;animation-iteration-count:infinite}#loading .animation .circle:nth-child(2){background-color:#b8babd;border-color:#b8babd;animation-name:circle2}#loading .animation .circle:nth-child(3){background-color:#a3a3ab;border-color:#a3a3ab;animation-name:circle3}#loading .animation .circle:nth-child(4){background-color:#efefef;border-color:#efefef;animation-name:circle4}.asda-backdrop{bottom:0;left:0;position:fixed;right:0;top:0;z-index:10}.asda-backdrop[data-color=transparent]{background-color:transparent}.asda-backdrop[data-color=black]{background-color:rgba(68,70,82,.88)}.dropdown-list-item.selected,.dropdown-list-item:hover{color:#fff;background-color:#767676}.dropdown-wrapper-single{user-select:none;position:relative;width:265px}.dropdown-wrapper-single .dropdown-header{border:1px solid #ccc}.dropdown-wrapper-single .dropdown-header .dropdown-header-name{font-weight:400}.dropdown-wrapper-single .dropdown-list{border:1px solid #ccc;border-top:0}.dropdown-wrapper{font-family:SourceSansProRegular,sans-serif;font-size:24px;font-weight:600;font-style:normal;font-stretch:normal;line-height:normal;letter-spacing:.3px;float:left;margin-right:16px}.dropdown-wrapper .dropdown-header{display:flex;align-items:center;justify-content:space-between;width:176px;height:78px;border:1px solid #767676;cursor:default;position:relative;background-color:#fff;padding:20px}.dropdown-wrapper .dropdown-header.active{border-bottom:0px;top:1px;z-index:11}.dropdown-wrapper .list-wrapper .dropdown-list{z-index:10;position:absolute;width:560px;height:636px;border:1px solid #767676;background-color:#fff;padding:20px 75px 20px 20px;overflow-y:scroll;color:#767676}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{width:100%;padding:8px 0px;line-height:1.54px;cursor:default;display:flex;align-items:center;justify-content:space-between;text-overflow:ellipsis}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item:nth-child(5){padding-bottom:18px;border-bottom:2px solid #979797}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:420px;text-align:left;padding-left:15px}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{width:80px;background-color:none}.dropdown-wrapper .dropdown-list::-webkit-scrollbar-thumb{background-image:var(--sf-img-15);height:109px;background-repeat:no-repeat}.dropdown-wrapper .dropdown-list-item.selected,.dropdown-wrapper .dropdown-list-item:hover{color:#fff;background-color:#767676}div[class^=country-flag-]{width:34px;height:20px;background-size:34px 20px}div[class^=country-flag-] img{width:34px;height:20px;background-repeat:no-repeat;background-size:34px 20px}.modal-wrapper{position:absolute;z-index:1000;top:206px;width:802px;height:300px;border-radius:14px;box-shadow:2px 3px 8px 3px rgba(0,0,0,.1);border:solid 1px #ccc;background-color:#fff;padding:30px;font-family:SourceSansProRegular,sans-serif;font-weight:600;font-style:normal;font-stretch:normal;line-height:normal;color:#3d3d3d}.phone-input{width:100%}@media (min-width:320px){.phone-input{display:flex;flex-direction:column}}@media (min-width:481px){.phone-input{flex-direction:row;height:52px}}.phone-input .dropdown-wrapper{float:left;height:42px;margin-right:8px;color:#3d3d3d;font-family:SourceSansProRegular,sans-serif;transition:all 200ms ease;font-size:1em;user-select:none}@media (min-width:320px){.phone-input .dropdown-wrapper{width:100%}}@media (min-width:481px){.phone-input .dropdown-wrapper{width:32%}}.phone-input .dropdown-wrapper .dropdown-header{cursor:pointer;width:100%;height:40px;padding:0 8px;border-radius:4px;border:1px solid #ccc;transition:box-shadow 200ms ease}.phone-input .dropdown-wrapper .dropdown-header.active{height:41px;border-top-color:#767676;border-right-color:#767676;border-left-color:#767676;border-bottom-left-radius:0;border-bottom-right-radius:0;top:0}.phone-input .dropdown-wrapper .dropdown-header.active .country-code{transition:none;margin-top:-1px}.phone-input .dropdown-wrapper .dropdown-header.active div[class^=country-flag-] img{transition:none;margin-top:-1px}.phone-input .dropdown-wrapper .dropdown-header.active .down-arrow{transform:rotate(0.5turn);padding-bottom:4px}.phone-input .dropdown-wrapper .dropdown-header:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.phone-input .dropdown-wrapper .dropdown-header .country-code{font-size:16px;padding-left:4px}@media (min-width:320px){.phone-input .dropdown-wrapper .dropdown-header .country-code{flex:1;padding-left:15px;display:flex;align-items:center}}.phone-input .dropdown-wrapper .dropdown-header .country-code .country-name{width:100px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:215px}@media (min-width:481px){.phone-input .dropdown-wrapper .dropdown-header .country-code .country-name{display:none}}.phone-input .dropdown-wrapper .dropdown-header .down-arrow{width:20px;padding-top:4px}.phone-input .dropdown-wrapper .dropdown-header div[class^=country-flag-] img{width:32px;background-repeat:no-repeat;background-size:34px 20px}.phone-input .dropdown-wrapper .list-wrapper{position:relative}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list{margin-top:-1px;z-index:10;position:absolute;height:200px;border:1px solid #767676;border-radius:0 0 4px 4px;background-color:#fff;padding:0;overflow-y:scroll;color:#767676}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list{width:100%}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list{width:372px}}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list::-webkit-scrollbar *{background:transparent}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{color:#191919;height:40px;font-size:1em}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item:hover{background-color:#f6f6f6}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{padding:12px 8px 12px 8px;height:48px}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{padding:12px 80px 12px 8px;height:40px}}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{line-height:initial}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:calc(100% - 32px)}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:420px}}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{text-align:right}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{width:60px}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{width:80px}}@media (min-width:320px){.phone-input .input-box{width:100%;margin-top:12px}}@media (min-width:481px){.phone-input .input-box{width:calc(68% - 8px);margin-top:0px}}.divider{width:100%;border-top:1px solid #cbcbcb;margin-bottom:24px}.react-datepicker__year-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle{margin-left:-8px;position:absolute}.react-datepicker__year-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker__year-read-view--down-arrow::before,.react-datepicker__month-read-view--down-arrow::before,.react-datepicker__month-year-read-view--down-arrow::before,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before{box-sizing:content-box;position:absolute;border:8px solid transparent;height:0;width:1px}.react-datepicker__year-read-view--down-arrow::before,.react-datepicker__month-read-view--down-arrow::before,.react-datepicker__month-year-read-view--down-arrow::before,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before{content:"";z-index:-1;border-width:8px;left:-8px}.theme-ghs .react-datepicker__year-read-view--down-arrow::before,.theme-ghs .react-datepicker__month-read-view--down-arrow::before,.theme-ghs .react-datepicker__month-year-read-view--down-arrow::before,.theme-ghs .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=top] .theme-ghs .react-datepicker__triangle::before,.theme-ghs .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .theme-ghs .react-datepicker__triangle::before{border-bottom-color:#538316}.theme-george .react-datepicker__year-read-view--down-arrow::before,.theme-george .react-datepicker__month-read-view--down-arrow::before,.theme-george .react-datepicker__month-year-read-view--down-arrow::before,.theme-george .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=top] .theme-george .react-datepicker__triangle::before,.theme-george .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .theme-george .react-datepicker__triangle::before{border-bottom-color:#020202}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle{top:0;margin-top:-8px}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before{border-top:0}.theme-ghs .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .theme-ghs .react-datepicker__triangle,.theme-ghs .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .theme-ghs .react-datepicker__triangle::before{border-bottom-color:#538316}.theme-george .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=bottom] .theme-george .react-datepicker__triangle,.theme-george .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .theme-george .react-datepicker__triangle::before{border-bottom-color:#020202}.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before{top:-1px}.theme-ghs .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .theme-ghs .react-datepicker__triangle::before{border-bottom-color:#538316}.theme-george .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=bottom] .theme-george .react-datepicker__triangle::before{border-bottom-color:#020202}.react-datepicker__year-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle{bottom:0;margin-bottom:-8px}.react-datepicker__year-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker__year-read-view--down-arrow::before,.react-datepicker__month-read-view--down-arrow::before,.react-datepicker__month-year-read-view--down-arrow::before,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before{border-bottom:0}.theme-ghs .react-datepicker__year-read-view--down-arrow,.theme-ghs .react-datepicker__month-read-view--down-arrow,.theme-ghs .react-datepicker__month-year-read-view--down-arrow,.theme-ghs .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=top] .theme-ghs .react-datepicker__triangle,.theme-ghs .react-datepicker__year-read-view--down-arrow::before,.theme-ghs .react-datepicker__month-read-view--down-arrow::before,.theme-ghs .react-datepicker__month-year-read-view--down-arrow::before,.theme-ghs .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=top] .theme-ghs .react-datepicker__triangle::before{border-top-color:#538316}.theme-george .react-datepicker__year-read-view--down-arrow,.theme-george .react-datepicker__month-read-view--down-arrow,.theme-george .react-datepicker__month-year-read-view--down-arrow,.theme-george .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle,.react-datepicker-popper[data-placement^=top] .theme-george .react-datepicker__triangle,.theme-george .react-datepicker__year-read-view--down-arrow::before,.theme-george .react-datepicker__month-read-view--down-arrow::before,.theme-george .react-datepicker__month-year-read-view--down-arrow::before,.theme-george .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=top] .theme-george .react-datepicker__triangle::before{border-top-color:#020202}.react-datepicker__year-read-view--down-arrow::before,.react-datepicker__month-read-view--down-arrow::before,.react-datepicker__month-year-read-view--down-arrow::before,.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before{bottom:-1px}.theme-ghs .react-datepicker__year-read-view--down-arrow::before,.theme-ghs .react-datepicker__month-read-view--down-arrow::before,.theme-ghs .react-datepicker__month-year-read-view--down-arrow::before,.theme-ghs .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=top] .theme-ghs .react-datepicker__triangle::before{border-top-color:#538316}.theme-george .react-datepicker__year-read-view--down-arrow::before,.theme-george .react-datepicker__month-read-view--down-arrow::before,.theme-george .react-datepicker__month-year-read-view--down-arrow::before,.theme-george .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle::before,.react-datepicker-popper[data-placement^=top] .theme-george .react-datepicker__triangle::before{border-top-color:#020202}.react-datepicker-wrapper{display:inline-block;padding:0;border:0;width:100%}.react-datepicker_input-default{font-size:13px;border-radius:4px;line-height:16px;padding:6px 10px 5px;width:100%;height:40px}.theme-ghs .react-datepicker_input-default{border:1px solid #538316}.theme-george .react-datepicker_input-default{border:1px solid #020202}.react-datepicker_input-default:hover{box-shadow:0px 2px 10px 0px rgba(0,0,0,.1)}.theme-ghs .react-datepicker_input-default:focus{border-color:#37570f}.theme-george .react-datepicker_input-default:focus{border-color:#000}.react-datepicker{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:.8rem;background-color:#fff;border-radius:.3rem;display:inline-block;position:relative}.theme-ghs .react-datepicker{color:#000;border:1px solid #538316}.theme-george .react-datepicker{color:#000;border:1px solid #020202}.react-datepicker--time-only .react-datepicker__triangle{left:35px}.react-datepicker--time-only .react-datepicker__time-container{border-left:0}.react-datepicker--time-only .react-datepicker__time{border-radius:.3rem}.react-datepicker--time-only .react-datepicker__time-box{border-radius:.3rem}.react-datepicker__triangle{position:absolute;left:50px}.react-datepicker-popper{z-index:1}.react-datepicker-popper[data-placement=bottom-end] .react-datepicker__triangle,.react-datepicker-popper[data-placement=top-end] .react-datepicker__triangle{left:auto;right:50px}.react-datepicker-popper[data-placement^=top]{margin-bottom:10px}.react-datepicker-popper[data-placement^=right]{margin-left:8px}.react-datepicker-popper[data-placement^=right] .react-datepicker__triangle{left:auto;right:42px}.react-datepicker-popper[data-placement^=left]{margin-right:8px}.react-datepicker-popper[data-placement^=left] .react-datepicker__triangle{left:42px;right:auto}.react-datepicker__header{text-align:center;border-top-left-radius:.3rem;border-top-right-radius:.3rem;padding-top:8px;position:relative}.theme-ghs .react-datepicker__header{background-color:#538316;border-bottom:1px solid #538316}.theme-george .react-datepicker__header{background-color:#020202;border-bottom:1px solid #020202}.react-datepicker__header--time{padding-bottom:8px;padding-left:5px;padding-right:5px}.react-datepicker__year-dropdown-container--select,.react-datepicker__month-dropdown-container--select,.react-datepicker__month-year-dropdown-container--select,.react-datepicker__year-dropdown-container--scroll,.react-datepicker__month-dropdown-container--scroll,.react-datepicker__month-year-dropdown-container--scroll{display:inline-block;margin:0 2px}.react-datepicker__current-month,.react-datepicker-time__header,.react-datepicker-year-header{margin-top:0;font-weight:bold;font-size:.944rem;margin-bottom:8px}.theme-ghs .react-datepicker__current-month,.theme-ghs .react-datepicker-time__header,.theme-ghs .react-datepicker-year-header{color:#fff}.theme-george .react-datepicker__current-month,.theme-george .react-datepicker-time__header,.theme-george .react-datepicker-year-header{color:#fff}.react-datepicker-time__header{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.react-datepicker__navigation{background:0;line-height:1.7rem;text-align:center;cursor:pointer;position:absolute;top:10px;width:0;padding:0;border:.45rem solid transparent;z-index:1;height:10px;width:10px;text-indent:-999em;overflow:hidden}.react-datepicker__navigation--previous{left:10px;padding:0!important;height:0!important}.theme-ghs .react-datepicker__navigation--previous{border-right-color:#ccc}.theme-george .react-datepicker__navigation--previous{border-right-color:#ccc}.theme-ghs .react-datepicker__navigation--previous:hover{border-right-color:#b3b3b3}.theme-george .react-datepicker__navigation--previous:hover{border-right-color:#b3b3b3}.react-datepicker__navigation--previous--disabled,.react-datepicker__navigation--previous--disabled:hover{cursor:default}.theme-ghs .react-datepicker__navigation--previous--disabled,.theme-ghs .react-datepicker__navigation--previous--disabled:hover{border-right-color:#e6e6e6}.theme-george .react-datepicker__navigation--previous--disabled,.theme-george .react-datepicker__navigation--previous--disabled:hover{border-right-color:#e6e6e6}.react-datepicker__navigation--next{right:10px;padding:0!important;height:0!important}.theme-ghs .react-datepicker__navigation--next{border-left-color:#ccc}.theme-george .react-datepicker__navigation--next{border-left-color:#ccc}.react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button){right:80px}.theme-ghs .react-datepicker__navigation--next:hover{border-left-color:#b3b3b3}.theme-george .react-datepicker__navigation--next:hover{border-left-color:#b3b3b3}.react-datepicker__navigation--next--disabled,.react-datepicker__navigation--next--disabled:hover{cursor:default}.theme-ghs .react-datepicker__navigation--next--disabled,.theme-ghs .react-datepicker__navigation--next--disabled:hover{border-left-color:#e6e6e6}.theme-george .react-datepicker__navigation--next--disabled,.theme-george .react-datepicker__navigation--next--disabled:hover{border-left-color:#e6e6e6}.react-datepicker__navigation--years{position:relative;top:0;display:block;margin-left:auto;margin-right:auto}.react-datepicker__navigation--years-previous{top:4px}.theme-ghs .react-datepicker__navigation--years-previous{border-top-color:#ccc}.theme-george .react-datepicker__navigation--years-previous{border-top-color:#ccc}.theme-ghs .react-datepicker__navigation--years-previous:hover{border-top-color:#b3b3b3}.theme-george .react-datepicker__navigation--years-previous:hover{border-top-color:#b3b3b3}.react-datepicker__navigation--years-upcoming{top:-4px}.theme-ghs .react-datepicker__navigation--years-upcoming{border-bottom-color:#ccc}.theme-george .react-datepicker__navigation--years-upcoming{border-bottom-color:#ccc}.theme-ghs .react-datepicker__navigation--years-upcoming:hover{border-bottom-color:#b3b3b3}.theme-george .react-datepicker__navigation--years-upcoming:hover{border-bottom-color:#b3b3b3}.react-datepicker__month-container{float:left}.react-datepicker__month{margin:.4rem;text-align:center}.react-datepicker__month .react-datepicker__month-text,.react-datepicker__month .react-datepicker__quarter-text{display:inline-block;width:4rem;margin:2px}.react-datepicker__input-time-container{clear:both;width:100%;float:left;margin:5px 0 10px 15px;text-align:left}.react-datepicker__input-time-container .react-datepicker-time__caption{display:inline-block}.react-datepicker__input-time-container .react-datepicker-time__input-container{display:inline-block}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input{display:inline-block;margin-left:10px}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input{width:85px}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-inner-spin-button,.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]{-moz-appearance:textfield}.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__delimiter{margin-left:5px;display:inline-block}.react-datepicker__time-container{float:right;width:85px}.theme-ghs .react-datepicker__time-container{border-left:1px solid #538316}.theme-george .react-datepicker__time-container{border-left:1px solid #020202}.react-datepicker__time-container--with-today-button{display:inline;border:1px solid #aeaeae;border-radius:.3rem;position:absolute;right:-72px;top:0}.react-datepicker__time-container .react-datepicker__time{position:relative;background:#fff}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box{width:85px;overflow-x:hidden;margin:0 auto;text-align:center}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list{list-style:none;margin:0;height:calc(195px + (1.7rem / 2));overflow-y:scroll;padding-right:0px;padding-left:0px;width:100%;box-sizing:content-box}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item{height:30px;padding:5px 10px;white-space:nowrap}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover{cursor:pointer}.theme-ghs .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover{background-color:#538316}.theme-george .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover{background-color:#020202}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected{color:#fff;font-weight:bold}.theme-ghs .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected{background-color:#538316}.theme-ghs .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover{background-color:#538316}.theme-george .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected{background-color:#020202}.theme-george .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover{background-color:#020202}.theme-ghs .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled{color:#ccc}.theme-george .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled{color:#ccc}.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled:hover{cursor:default;background-color:transparent}.react-datepicker__week-number{display:inline-block;width:1.7rem;line-height:1.7rem;text-align:center;margin:.166rem}.theme-ghs .react-datepicker__week-number{color:#ccc}.theme-george .react-datepicker__week-number{color:#ccc}.react-datepicker__week-number.react-datepicker__week-number--clickable{cursor:pointer}.react-datepicker__week-number.react-datepicker__week-number--clickable:hover{border-radius:.3rem}.theme-ghs .react-datepicker__week-number.react-datepicker__week-number--clickable:hover{background-color:#538316}.theme-george .react-datepicker__week-number.react-datepicker__week-number--clickable:hover{background-color:#020202}.react-datepicker__day-names,.react-datepicker__week{white-space:nowrap}.react-datepicker__day,.react-datepicker__day-name,.react-datepicker__time-name{display:inline-block;width:1.7rem;line-height:1.7rem;text-align:center;margin:.166rem}.theme-ghs .react-datepicker__day,.theme-ghs .react-datepicker__day-name,.theme-ghs .react-datepicker__time-name{color:#000}.theme-george .react-datepicker__day,.theme-george .react-datepicker__day-name,.theme-george .react-datepicker__time-name{color:#000}.theme-ghs .react-datepicker__day-name{color:#fff}.theme-george .react-datepicker__day-name{color:#fff}.react-datepicker__month--selected,.react-datepicker__month--in-selecting-range,.react-datepicker__month--in-range,.react-datepicker__quarter--selected,.react-datepicker__quarter--in-selecting-range,.react-datepicker__quarter--in-range{border-radius:.3rem;color:#fff}.theme-ghs .react-datepicker__month--selected,.theme-ghs .react-datepicker__month--in-selecting-range,.theme-ghs .react-datepicker__month--in-range,.theme-ghs .react-datepicker__quarter--selected,.theme-ghs .react-datepicker__quarter--in-selecting-range,.theme-ghs .react-datepicker__quarter--in-range{background-color:#538316}.theme-ghs .react-datepicker__month--selected:hover,.theme-ghs .react-datepicker__month--in-selecting-range:hover,.theme-ghs .react-datepicker__month--in-range:hover,.theme-ghs .react-datepicker__quarter--selected:hover,.theme-ghs .react-datepicker__quarter--in-selecting-range:hover,.theme-ghs .react-datepicker__quarter--in-range:hover{background-color:#456d12}.theme-george .react-datepicker__month--selected,.theme-george .react-datepicker__month--in-selecting-range,.theme-george .react-datepicker__month--in-range,.theme-george .react-datepicker__quarter--selected,.theme-george .react-datepicker__quarter--in-selecting-range,.theme-george .react-datepicker__quarter--in-range{background-color:#020202}.theme-george .react-datepicker__month--selected:hover,.theme-george .react-datepicker__month--in-selecting-range:hover,.theme-george .react-datepicker__month--in-range:hover,.theme-george .react-datepicker__quarter--selected:hover,.theme-george .react-datepicker__quarter--in-selecting-range:hover,.theme-george .react-datepicker__quarter--in-range:hover{background-color:#000}.react-datepicker__month--disabled,.react-datepicker__quarter--disabled{pointer-events:none}.theme-ghs .react-datepicker__month--disabled,.theme-ghs .react-datepicker__quarter--disabled{color:#ccc}.theme-george .react-datepicker__month--disabled,.theme-george .react-datepicker__quarter--disabled{color:#ccc}.react-datepicker__month--disabled:hover,.react-datepicker__quarter--disabled:hover{cursor:default;background-color:transparent}.react-datepicker__day,.react-datepicker__day-name,.react-datepicker__month-text,.react-datepicker__quarter-text{cursor:pointer}.react-datepicker__day:hover,.react-datepicker__day-name:hover,.react-datepicker__month-text:hover,.react-datepicker__quarter-text:hover{border-radius:.3rem}.theme-ghs .react-datepicker__day:hover,.theme-ghs .react-datepicker__day-name:hover,.theme-ghs .react-datepicker__month-text:hover,.theme-ghs .react-datepicker__quarter-text:hover{background-color:#68a51c;color:#fff}.theme-george .react-datepicker__day:hover,.theme-george .react-datepicker__day-name:hover,.theme-george .react-datepicker__month-text:hover,.theme-george .react-datepicker__quarter-text:hover{background-color:#3d3d3d;color:#fff}.react-datepicker__day--today,.react-datepicker__month-text--today,.react-datepicker__quarter-text--today{font-weight:bold}.react-datepicker__day--highlighted,.react-datepicker__month-text--highlighted,.react-datepicker__quarter-text--highlighted{border-radius:.3rem;color:#fff}.theme-ghs .react-datepicker__day--highlighted,.theme-ghs .react-datepicker__month-text--highlighted,.theme-ghs .react-datepicker__quarter-text--highlighted{background-color:#68a51c}.theme-ghs .react-datepicker__day--highlighted:hover,.theme-ghs .react-datepicker__month-text--highlighted:hover,.theme-ghs .react-datepicker__quarter-text--highlighted:hover{background-color:#5a8f18}.theme-george .react-datepicker__day--highlighted,.theme-george .react-datepicker__month-text--highlighted,.theme-george .react-datepicker__quarter-text--highlighted{background-color:#3d3d3d}.theme-george .react-datepicker__day--highlighted:hover,.theme-george .react-datepicker__month-text--highlighted:hover,.theme-george .react-datepicker__quarter-text--highlighted:hover{background-color:#303030}.react-datepicker__day--highlighted-custom-1,.react-datepicker__month-text--highlighted-custom-1,.react-datepicker__quarter-text--highlighted-custom-1{color:#f0f}.react-datepicker__day--highlighted-custom-2,.react-datepicker__month-text--highlighted-custom-2,.react-datepicker__quarter-text--highlighted-custom-2{color:green}.theme-ghs .react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--in-range),.theme-ghs .react-datepicker__month-text--in-selecting-range:not(.react-datepicker__day--in-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--in-range),.theme-ghs .react-datepicker__quarter-text--in-selecting-range:not(.react-datepicker__day--in-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--in-range){background-color:rgba(83,131,22,.5)}.theme-george .react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--in-range),.theme-george .react-datepicker__month-text--in-selecting-range:not(.react-datepicker__day--in-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--in-range),.theme-george .react-datepicker__quarter-text--in-selecting-range:not(.react-datepicker__day--in-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--in-range){background-color:rgba(2,2,2,.5)}.theme-ghs .react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__quarter-text--in-selecting-range),.theme-ghs .react-datepicker__month--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__quarter-text--in-selecting-range),.theme-ghs .react-datepicker__month--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__quarter-text--in-selecting-range){background-color:#538316;color:#000}.theme-george .react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__quarter-text--in-selecting-range),.theme-george .react-datepicker__month--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__quarter-text--in-selecting-range),.theme-george .react-datepicker__month--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,.react-datepicker__month-text--in-selecting-range,.react-datepicker__quarter-text--in-selecting-range){background-color:#020202;color:#000}.react-datepicker__day--selected,.react-datepicker__day--in-selecting-range,.react-datepicker__day--in-range,.react-datepicker__month-text--selected,.react-datepicker__month-text--in-selecting-range,.react-datepicker__month-text--in-range,.react-datepicker__quarter-text--selected,.react-datepicker__quarter-text--in-selecting-range,.react-datepicker__quarter-text--in-range{border-radius:.3rem}.theme-ghs .react-datepicker__day--selected,.theme-ghs .react-datepicker__day--in-selecting-range,.theme-ghs .react-datepicker__day--in-range,.theme-ghs .react-datepicker__month-text--selected,.theme-ghs .react-datepicker__month-text--in-selecting-range,.theme-ghs .react-datepicker__month-text--in-range,.theme-ghs .react-datepicker__quarter-text--selected,.theme-ghs .react-datepicker__quarter-text--in-selecting-range,.theme-ghs .react-datepicker__quarter-text--in-range{color:#fff;background-color:#538316}.theme-ghs .react-datepicker__day--selected:hover,.theme-ghs .react-datepicker__day--in-selecting-range:hover,.theme-ghs .react-datepicker__day--in-range:hover,.theme-ghs .react-datepicker__month-text--selected:hover,.theme-ghs .react-datepicker__month-text--in-selecting-range:hover,.theme-ghs .react-datepicker__month-text--in-range:hover,.theme-ghs .react-datepicker__quarter-text--selected:hover,.theme-ghs .react-datepicker__quarter-text--in-selecting-range:hover,.theme-ghs .react-datepicker__quarter-text--in-range:hover{background-color:#456d12}.theme-george .react-datepicker__day--selected,.theme-george .react-datepicker__day--in-selecting-range,.theme-george .react-datepicker__day--in-range,.theme-george .react-datepicker__month-text--selected,.theme-george .react-datepicker__month-text--in-selecting-range,.theme-george .react-datepicker__month-text--in-range,.theme-george .react-datepicker__quarter-text--selected,.theme-george .react-datepicker__quarter-text--in-selecting-range,.theme-george .react-datepicker__quarter-text--in-range{color:#fff;background-color:#020202}.theme-george .react-datepicker__day--selected:hover,.theme-george .react-datepicker__day--in-selecting-range:hover,.theme-george .react-datepicker__day--in-range:hover,.theme-george .react-datepicker__month-text--selected:hover,.theme-george .react-datepicker__month-text--in-selecting-range:hover,.theme-george .react-datepicker__month-text--in-range:hover,.theme-george .react-datepicker__quarter-text--selected:hover,.theme-george .react-datepicker__quarter-text--in-selecting-range:hover,.theme-george .react-datepicker__quarter-text--in-range:hover{background-color:#000}.react-datepicker__day--keyboard-selected,.react-datepicker__month-text--keyboard-selected,.react-datepicker__quarter-text--keyboard-selected{border-radius:.3rem}.theme-ghs .react-datepicker__day--keyboard-selected,.theme-ghs .react-datepicker__month-text--keyboard-selected,.theme-ghs .react-datepicker__quarter-text--keyboard-selected{color:#fff;background-color:#6faf1d}.theme-ghs .react-datepicker__day--keyboard-selected:hover,.theme-ghs .react-datepicker__month-text--keyboard-selected:hover,.theme-ghs .react-datepicker__quarter-text--keyboard-selected:hover{background-color:#456d12}.theme-george .react-datepicker__day--keyboard-selected,.theme-george .react-datepicker__month-text--keyboard-selected,.theme-george .react-datepicker__quarter-text--keyboard-selected{color:#fff;background-color:#1c1c1c}.theme-george .react-datepicker__day--keyboard-selected:hover,.theme-george .react-datepicker__month-text--keyboard-selected:hover,.theme-george .react-datepicker__quarter-text--keyboard-selected:hover{background-color:#000}.react-datepicker__day--disabled,.react-datepicker__month-text--disabled,.react-datepicker__quarter-text--disabled{cursor:default}.theme-ghs .react-datepicker__day--disabled,.theme-ghs .react-datepicker__month-text--disabled,.theme-ghs .react-datepicker__quarter-text--disabled{color:#ccc}.theme-george .react-datepicker__day--disabled,.theme-george .react-datepicker__month-text--disabled,.theme-george .react-datepicker__quarter-text--disabled{color:#ccc}.react-datepicker__day--disabled:hover,.react-datepicker__month-text--disabled:hover,.react-datepicker__quarter-text--disabled:hover{background-color:transparent}.theme-ghs .react-datepicker__month-text.react-datepicker__month--selected:hover,.theme-ghs .react-datepicker__month-text.react-datepicker__month--in-range:hover,.theme-ghs .react-datepicker__month-text.react-datepicker__quarter--selected:hover,.theme-ghs .react-datepicker__month-text.react-datepicker__quarter--in-range:hover,.theme-ghs .react-datepicker__quarter-text.react-datepicker__month--selected:hover,.theme-ghs .react-datepicker__quarter-text.react-datepicker__month--in-range:hover,.theme-ghs .react-datepicker__quarter-text.react-datepicker__quarter--selected:hover,.theme-ghs .react-datepicker__quarter-text.react-datepicker__quarter--in-range:hover{background-color:#538316}.theme-george .react-datepicker__month-text.react-datepicker__month--selected:hover,.theme-george .react-datepicker__month-text.react-datepicker__month--in-range:hover,.theme-george .react-datepicker__month-text.react-datepicker__quarter--selected:hover,.theme-george .react-datepicker__month-text.react-datepicker__quarter--in-range:hover,.theme-george .react-datepicker__quarter-text.react-datepicker__month--selected:hover,.theme-george .react-datepicker__quarter-text.react-datepicker__month--in-range:hover,.theme-george .react-datepicker__quarter-text.react-datepicker__quarter--selected:hover,.theme-george .react-datepicker__quarter-text.react-datepicker__quarter--in-range:hover{background-color:#020202}.theme-ghs .react-datepicker__month-text:hover,.theme-ghs .react-datepicker__quarter-text:hover{background-color:#538316}.theme-george .react-datepicker__month-text:hover,.theme-george .react-datepicker__quarter-text:hover{background-color:#020202}.react-datepicker__input-container{position:relative;display:inline-block;width:100%}.react-datepicker__year-read-view,.react-datepicker__month-read-view,.react-datepicker__month-year-read-view{border:1px solid transparent;border-radius:.3rem}.react-datepicker__year-read-view:hover,.react-datepicker__month-read-view:hover,.react-datepicker__month-year-read-view:hover{cursor:pointer}.theme-ghs .react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow,.theme-ghs .react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow,.theme-ghs .react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow,.theme-ghs .react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow,.theme-ghs .react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow,.theme-ghs .react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow{border-top-color:#b3b3b3}.theme-george .react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow,.theme-george .react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow,.theme-george .react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow,.theme-george .react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow,.theme-george .react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow,.theme-george .react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow{border-top-color:#b3b3b3}.react-datepicker__year-read-view--down-arrow,.react-datepicker__month-read-view--down-arrow,.react-datepicker__month-year-read-view--down-arrow{float:right;margin-left:20px;top:8px;position:relative;border-width:.45rem}.theme-ghs .react-datepicker__year-read-view--down-arrow,.theme-ghs .react-datepicker__month-read-view--down-arrow,.theme-ghs .react-datepicker__month-year-read-view--down-arrow{border-top-color:#ccc}.theme-george .react-datepicker__year-read-view--down-arrow,.theme-george .react-datepicker__month-read-view--down-arrow,.theme-george .react-datepicker__month-year-read-view--down-arrow{border-top-color:#ccc}.react-datepicker__year-dropdown,.react-datepicker__month-dropdown,.react-datepicker__month-year-dropdown{position:absolute;width:50%;left:25%;top:30px;z-index:1;text-align:center;border-radius:.3rem}.theme-ghs .react-datepicker__year-dropdown,.theme-ghs .react-datepicker__month-dropdown,.theme-ghs .react-datepicker__month-year-dropdown{background-color:#538316;border:1px solid #538316}.theme-george .react-datepicker__year-dropdown,.theme-george .react-datepicker__month-dropdown,.theme-george .react-datepicker__month-year-dropdown{background-color:#020202;border:1px solid #020202}.react-datepicker__year-dropdown:hover,.react-datepicker__month-dropdown:hover,.react-datepicker__month-year-dropdown:hover{cursor:pointer}.react-datepicker__year-dropdown--scrollable,.react-datepicker__month-dropdown--scrollable,.react-datepicker__month-year-dropdown--scrollable{height:150px;overflow-y:scroll}.react-datepicker__year-option,.react-datepicker__month-option,.react-datepicker__month-year-option{line-height:20px;width:100%;display:block;margin-left:auto;margin-right:auto}.react-datepicker__year-option:first-of-type,.react-datepicker__month-option:first-of-type,.react-datepicker__month-year-option:first-of-type{border-top-left-radius:.3rem;border-top-right-radius:.3rem}.react-datepicker__year-option:last-of-type,.react-datepicker__month-option:last-of-type,.react-datepicker__month-year-option:last-of-type{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-bottom-left-radius:.3rem;border-bottom-right-radius:.3rem}.theme-ghs .react-datepicker__year-option:hover,.theme-ghs .react-datepicker__month-option:hover,.theme-ghs .react-datepicker__month-year-option:hover{background-color:#ccc}.theme-ghs .react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming,.theme-ghs .react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming,.theme-ghs .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming{border-bottom-color:#b3b3b3}.theme-ghs .react-datepicker__year-option:hover .react-datepicker__navigation--years-previous,.theme-ghs .react-datepicker__month-option:hover .react-datepicker__navigation--years-previous,.theme-ghs .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous{border-top-color:#b3b3b3}.theme-george .react-datepicker__year-option:hover,.theme-george .react-datepicker__month-option:hover,.theme-george .react-datepicker__month-year-option:hover{background-color:#ccc}.theme-george .react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming,.theme-george .react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming,.theme-george .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming{border-bottom-color:#b3b3b3}.theme-george .react-datepicker__year-option:hover .react-datepicker__navigation--years-previous,.theme-george .react-datepicker__month-option:hover .react-datepicker__navigation--years-previous,.theme-george .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous{border-top-color:#b3b3b3}.react-datepicker__year-option--selected,.react-datepicker__month-option--selected,.react-datepicker__month-year-option--selected{position:absolute;left:15px}.react-datepicker__close-icon{cursor:pointer;background-color:transparent;border:0;outline:0;padding:0px 6px 0px 0px;position:absolute;top:0;right:0;height:100%;display:table-cell;vertical-align:middle}.react-datepicker__close-icon::after{cursor:pointer;color:#fff;border-radius:50%;height:16px;width:16px;padding:2px;font-size:12px;line-height:1;text-align:center;display:table-cell;vertical-align:middle;content:"\D7"}.theme-ghs .react-datepicker__close-icon::after{background-color:#538316}.theme-george .react-datepicker__close-icon::after{background-color:#020202}.react-datepicker__today-button{cursor:pointer;text-align:center;font-weight:bold;padding:5px 0;clear:left}.theme-ghs .react-datepicker__today-button{background:#538316;border-top:1px solid #538316}.theme-george .react-datepicker__today-button{background:#020202;border-top:1px solid #020202}.react-datepicker__portal{position:fixed;width:100vw;height:100vh;background-color:rgba(0,0,0,.8);left:0;top:0;justify-content:center;align-items:center;display:flex;z-index:2147483647}.react-datepicker__portal .react-datepicker__day-name,.react-datepicker__portal .react-datepicker__day,.react-datepicker__portal .react-datepicker__time-name{width:3rem;line-height:3rem}@media (max-width:400px),(max-height:550px){.react-datepicker__portal .react-datepicker__day-name,.react-datepicker__portal .react-datepicker__day,.react-datepicker__portal .react-datepicker__time-name{width:2rem;line-height:2rem}}.react-datepicker__portal .react-datepicker__current-month,.react-datepicker__portal .react-datepicker-time__header{font-size:1.44rem}.react-datepicker__portal .react-datepicker__navigation{border:.81rem solid transparent}.theme-ghs .react-datepicker__portal .react-datepicker__navigation--previous{border-right-color:#ccc}.theme-ghs .react-datepicker__portal .react-datepicker__navigation--previous:hover{border-right-color:#b3b3b3}.theme-george .react-datepicker__portal .react-datepicker__navigation--previous{border-right-color:#ccc}.theme-george .react-datepicker__portal .react-datepicker__navigation--previous:hover{border-right-color:#b3b3b3}.react-datepicker__portal .react-datepicker__navigation--previous--disabled,.react-datepicker__portal .react-datepicker__navigation--previous--disabled:hover{cursor:default}.theme-ghs .react-datepicker__portal .react-datepicker__navigation--previous--disabled,.theme-ghs .react-datepicker__portal .react-datepicker__navigation--previous--disabled:hover{border-right-color:#e6e6e6}.theme-george .react-datepicker__portal .react-datepicker__navigation--previous--disabled,.theme-george .react-datepicker__portal .react-datepicker__navigation--previous--disabled:hover{border-right-color:#e6e6e6}.theme-ghs .react-datepicker__portal .react-datepicker__navigation--next{border-left-color:#ccc}.theme-ghs .react-datepicker__portal .react-datepicker__navigation--next:hover{border-left-color:#b3b3b3}.theme-george .react-datepicker__portal .react-datepicker__navigation--next{border-left-color:#ccc}.theme-george .react-datepicker__portal .react-datepicker__navigation--next:hover{border-left-color:#b3b3b3}.react-datepicker__portal .react-datepicker__navigation--next--disabled,.react-datepicker__portal .react-datepicker__navigation--next--disabled:hover{cursor:default}.theme-ghs .react-datepicker__portal .react-datepicker__navigation--next--disabled,.theme-ghs .react-datepicker__portal .react-datepicker__navigation--next--disabled:hover{border-left-color:#e6e6e6}.theme-george .react-datepicker__portal .react-datepicker__navigation--next--disabled,.theme-george .react-datepicker__portal .react-datepicker__navigation--next--disabled:hover{border-left-color:#e6e6e6}.datepicker-wrapper{position:relative}.datepicker-wrapper--error input{border:2px solid #d43030}.theme-ghs .what-can-i-expect,.theme-george .what-can-i-expect{font-family:SourceSansProRegular,sans-serif;padding:2px 0 28px 0}.theme-ghs .what-can-i-expect__button,.theme-george .what-can-i-expect__button{font-size:14px;padding:0px;text-transform:inherit}.theme-ghs .what-can-i-expect__chevron,.theme-george .what-can-i-expect__chevron{padding-left:6px}.theme-ghs .what-can-i-expect__chevron--flipped,.theme-george .what-can-i-expect__chevron--flipped{transform:rotate(180deg)}.theme-ghs .what-can-i-expect__description,.theme-george .what-can-i-expect__description{color:#3d3d3d;font-size:14px;line-height:1.5;letter-spacing:.2px;list-style:disc;padding-left:20px;padding-top:14px}.theme-ghs .what-can-i-expect__description li,.theme-george .what-can-i-expect__description li{padding-bottom:16px}.theme-ghs .what-can-i-expect__description li:last-child,.theme-george .what-can-i-expect__description li:last-child{padding-bottom:0px}.theme-george .what-can-i-expect__button{text-decoration:underline}</style><style>html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}article *,article *:before,article *:after{box-sizing:border-box}@font-face{font-family:"SourceSansProBold";src:url(resources/sample/7.woff2) format("woff2")}@font-face{font-family:"SourceSansProSemiBold";src:url(resources/sample/8.woff2) format("woff2")}@font-face{font-family:"SourceSansProRegular";src:url(resources/sample/9.woff2) format("woff2")}h1,h2,h3,h4,p,a{color:#191919;padding-bottom:8px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1{font-size:1.25em;font-weight:700;color:#191919;padding-bottom:16px;line-height:1.25}.theme-ghs h1{font-family:SourceSansProBold,sans-serif}.theme-george h1{font-family:LatoBold,sans-serif}h2{font-size:1.125em;font-weight:800}.theme-ghs h2{font-family:SourceSansProBold,sans-serif}.theme-george h2{font-family:LatoBold,sans-serif}h3{font-size:1em;font-weight:600}.theme-ghs h3{font-family:SourceSansProSemiBold,sans-serif}.theme-george h3{font-family:LatoRegular,sans-serif}h4{font-size:.875em;font-weight:400}.theme-ghs h4{font-family:SourceSansProRegular,sans-serif}.theme-george h4{font-family:LatoRegular,sans-serif}p{color:off-black;padding-bottom:16px;font-size:.875em;letter-spacing:.2px;line-height:1.3}.theme-ghs p{font-family:SourceSansProRegular,sans-serif}.theme-george p{font-family:LatoRegular,sans-serif}a{margin-bottom:16px;padding-bottom:0;font-weight:500;font-size:1em}.theme-ghs a{color:#0073b1;text-decoration:none;font-family:SourceSansProBold,sans-serif}.theme-george a{color:#191919;text-decoration:underline;font-family:LatoRegular,sans-serif}.button-as-link{display:inline;background-color:transparent;border:0;padding:0;width:auto!important;height:auto!important;color:#0073b1;margin-left:0;padding-bottom:16px;font-weight:500;text-decoration:none}.theme-ghs .button-as-link{font-size:.875em}.theme-george .button-as-link{font-size:12px}.theme-ghs .button-as-link{color:#0073b1}.theme-george .button-as-link{color:#191919}.short-password-error{bottom:62px}.blank-password-error{bottom:50px}a:hover{cursor:pointer}.theme-ghs strong{font-family:SourceSansProBold,sans-serif}.theme-george strong{font-family:LatoBold,sans-serif}.input-error{font-family:SourceSansProRegular,sans-serif;font-size:.75em;color:#d43030;line-height:1.125em}.theme-ghs .input-error{font-family:SourceSansProRegular,sans-serif;font-size:.75em;color:#d43030;clear:both}.theme-george .input-error{font-family:LatoBold,sans-serif;font-size:.875em;color:#da291c;clear:both}.theme-ghs .input-error a{font-family:SourceSansProRegular,sans-serif;color:#d43030;text-decoration:underline}.theme-george .input-error a{font-family:LatoBold,sans-serif;color:#da291c;text-decoration:underline}.left{float:left!important}.right{float:right!important;text-align:right!important}.theme-ghs .right{padding-top:3px;line-height:1.5}.theme-george .right{line-height:1.5}.center{text-align:center!important}.red{color:#da0500!important}.orange{color:#fdb45b!important}.pink{color:#ec938e!important}.yellow{color:#fae100!important}.green{color:#68a51c!important}.blue{color:#0073b1!important}.grey{color:grey1!important}.white{color:#fff!important}.black{color:#191919!important}.regular{font-weight:400!important}.bold{font-weight:700!important}html{overflow-x:hidden;width:100vw}.co-account{min-height:calc(100vh - 294px);height:auto;width:calc(100% - 32px);min-height:calc(100vh - 356px);margin:0 16px;position:relative}.theme-ghs .co-account{min-height:calc(100vh - 300px)}.theme-george .co-account{min-height:calc(100vh - 261px)}@media (min-width:481px){.co-account{width:416px;margin:0 auto}.theme-ghs .co-account{min-height:calc(100vh - 284px)}.theme-george .co-account{min-height:calc(100vh - 245px)}}.co-account{height:auto;margin:0 auto 60px}@media (min-width:481px){.co-account{width:406px}}.co-sign-in-details__password-container,.co-sign-in-details__email-container,.co-sign-in-details__phone-container{position:relative;margin-bottom:20px;height:auto;display:block}.co-sign-in-details__password-container{margin-bottom:0}.co-sign-in-details__email-container--read-only .input-box{max-width:86%}.with-whats-this{height:32px}.with-whats-this .whats-this-label{float:left}.address-phone-book .address-section,.address-phone-book .contact-section{margin-bottom:16px}@media (min-width:320px){.address-phone-book .address-section label[for^=account-postcode],.address-phone-book .contact-section label[for^=account-postcode]{height:32px;display:flex;flex-direction:column-reverse}}@media (min-width:481px){.address-phone-book .address-section label[for^=account-postcode],.address-phone-book .contact-section label[for^=account-postcode]{height:auto}}.address-phone-book .address-section .list .list-item,.address-phone-book .contact-section .list .list-item{display:flex}.address-phone-book .address-section .list .list-item .list-item-delete-button img,.address-phone-book .contact-section .list .list-item .list-item-delete-button img{margin-top:0}.permission-centre button.add_phone{margin-bottom:16px}.wallet-card .co-list-card{display:flex;flex-direction:column}.wallet-card .co-list-card .list-item .list-item-radio{padding-right:0}.wallet-card .co-edit-card{margin-top:16px;padding-bottom:16px;border-bottom:1px solid #ccc}.wallet-card .modal{font-weight:400}.wallet-card .co-add-card{padding-top:10px}.wallet-card .co-add-card .custom-icon input{padding-left:50px}.wallet-card .co-add-card .custom-icon:before{left:10px;top:30px;width:30px;height:20px;background-image:var(--sf-img-16);z-index:1}.wallet-card .co-add-card .custom-icon.amex:before{background-image:var(--sf-img-17)}.wallet-card .co-add-card .custom-icon.maestro:before{background-image:var(--sf-img-18)}.wallet-card .co-add-card .custom-icon.mastercard:before{background-image:var(--sf-img-19)}.wallet-card .co-add-card .custom-icon.visa:before{background-image:var(--sf-img-20)}.wallet-card .co-add-card .date-input:before{top:30px;left:10px}.theme-ghs .wallet-card .co-add-card .date-input:before{top:30px}.theme-george .wallet-card .co-add-card .date-input:before{top:35px}@media (min-width:320px){.wallet-card .co-add-card label[for^=account-postcode]{height:32px;display:flex;flex-direction:column-reverse}}@media (min-width:481px){.wallet-card .co-add-card label[for^=account-postcode]{height:auto}}.wallet-card .date-input{position:relative}.wallet-card .date-input input{letter-spacing:.8px}.wallet-card .date-input:before{content:" / ";pointer-events:none;white-space:pre;position:absolute;top:28px;left:13px;display:block;width:40px;height:19px;color:#191919;font-size:1.25em;z-index:1}.co-personal-details__colleague-details{position:relative;margin-bottom:20px;height:auto;display:block}.underlined-link-toggle{border:0;padding:0!important;text-align:left;font-size:14px;width:auto;height:auto;margin:0;text-decoration:underline}.theme-ghs .underlined-link-toggle{color:#0073b1;letter-spacing:normal}.theme-george .underlined-link-toggle{color:#191919;letter-spacing:normal}.co-account .leave-btc .destructive{font-size:19px;margin-top:36px}.co-account .leave-btc .close-icon{position:absolute;top:8px;right:8px;width:auto;height:auto;border:0;padding:0;margin:0;background:transparent}.co-account .leave-btc .modal{width:400px;top:25%}.co-account .leave-btc .modal .modal-title,.co-account .leave-btc .modal .modal-content{border-bottom:0;padding-bottom:0px}.co-account .leave-btc .modal .modal-footer{flex-direction:column-reverse;gap:16px}.co-account .leave-btc .modal .modal-footer button{font-size:19px;font-weight:bold}.co-account .leave-btc .modal .modal-footer button.destructive{margin-top:0px}@media (min-width:481px){.leave-btc .modal{left:calc(50% - 200px)}}.panel{position:relative;border:1px solid #ccc}.theme-ghs .panel{border-radius:4px;margin:0 0 8px}.theme-george .panel{border-radius:0;margin:0 0 16px}.theme-ghs .panel.open{border-color:#ccc;box-shadow:none}.theme-george .panel.open{border-color:#9b9b9b;box-shadow:4px 4px 4px 0 rgba(0,0,0,.08)}.panel__header{width:100%;border:1px solid transparent;text-align:right;line-height:40px;margin:0;overflow:hidden;text-transform:none;letter-spacing:normal}.theme-ghs .panel__header{box-shadow:none;background:#f6f6f6;height:52px;padding:6px 16px;border-radius:4px}.theme-george .panel__header{box-shadow:4px 4px 4px 0 rgba(0,0,0,.08);background:#fff;height:52px;padding:6px 16px;border-radius:0}.panel__header:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.panel__header.add-bottom-border{border:1px solid transparent;border-bottom:1px solid transparent}.theme-ghs .panel__header.add-bottom-border{border-bottom:1px solid #ccc;box-shadow:none}.theme-george .panel__header.add-bottom-border{border-bottom:1px solid #9b9b9b;box-shadow:none}.panel__chevron{padding:16px 0}.panel__toggle{color:#0979b2}.theme-ghs .panel__toggle{font-family:SourceSansProRegular,sans-serif;font-size:18px;font-weight:600}.theme-george .panel__toggle{font-family:LatoRegular,sans-serif;font-size:14px;font-weight:500}.panel__title{float:left;text-transform:none}.theme-ghs .panel__title{font-family:SourceSansProRegular,sans-serif;font-size:18px;font-weight:600}.theme-george .panel__title{font-family:LatoRegular,sans-serif;font-size:14px;font-weight:normal}.panel__section-title{font-size:16px;text-align:left;color:#000;padding-bottom:16px}.theme-ghs .panel__section-title{font-family:SourceSansProRegular,sans-serif}.theme-george .panel__section-title{font-family:LatoRegular,sans-serif}.panel__sub-title{font-size:12px;letter-spacing:.2px;text-align:left;color:#000}.panel__anchor{display:block;padding:0;float:right}.panel__main{transition:height .2s cubic-bezier(0.4,0,0.2,1);padding:16px}.panel__main .expanded_container{padding:16px;float:left;width:100%}.panel__footer{height:40px;line-height:40px;padding:0 10px}@media (min-width:320px){.panel__anchor{padding:0 10px 0 25px}}@media (min-width:768px){.panel--no-border{border:0}}.button-container{width:100%;height:44px;display:block;margin-top:4px}@media (min-width:320px){.button-container{height:auto}}@media (min-width:481px){.button-container{height:44px}}.button-container .half{margin:0 2% 0 0}@media (min-width:320px){.button-container .half.half{width:100%;margin:0 0 8px}}@media (min-width:481px){.button-container .half.half{width:calc(50% - 8px);float:right;margin-right:8px}.button-container .half.half:nth-child(1){margin:0 0 0 2%}}h4.address-form-title{padding-bottom:12px}.theme-ghs h4.address-form-title{font-family:SourceSansProSemiBold,sans-serif}.theme-george h4.address-form-title{font-family:LatoRegular,sans-serif}h4.address-form-title button{height:20px;padding:0;font-size:14px;text-transform:none}.link-toggle{border:0;padding:0;text-align:left;font-size:14px;width:auto;height:auto;margin:0}.theme-ghs .link-toggle{font-family:SourceSansProRegular,sans-serif;color:#0073b1;text-decoration:none;letter-spacing:normal}.theme-george .link-toggle{font-family:LatoBold,sans-serif;color:#191919;text-decoration:underline;letter-spacing:normal}.link-toggle:hover{text-decoration:underline}label.radio-group-label{font-size:16px;width:100%;display:block;padding-bottom:12px}.theme-ghs label.radio-group-label{font-family:SourceSansProRegular,sans-serif}.theme-george label.radio-group-label{font-family:LatoRegular,sans-serif}.theme-ghs .find-button{margin-bottom:16px}.theme-george .find-button{margin-bottom:16px}.select-box{position:relative}.select-box label{padding-bottom:4px;display:block}.theme-ghs .select-box label{font-family:SourceSansProRegular,sans-serif;color:#3d3d3d;font-size:.875em;padding:0 0 4px}.theme-george .select-box label{font-family:LatoRegular,sans-serif;color:#191919;font-size:14px;padding:0 0 8px}.select-box .select-arrow{position:absolute;top:36px;right:12px;z-index:-100}.theme-ghs .select-box .select-arrow{top:36px}.theme-george .select-box .select-arrow{top:41px}.theme-ghs .select-box .select-arrow.no-label{top:18px}.theme-george .select-box .select-arrow.no-label{top:18px}.select-box select{width:100%;height:40px;font-size:1em;color:#767676;border-radius:4px;border:1px solid #ccc;clear:both;appearance:none;-webkit-appearance:none;cursor:pointer;background-color:transparent;padding:0 30px 0 12px;margin-bottom:8px;transition:all 200ms ease}.theme-ghs .select-box select{height:40px;font-size:1em;margin-bottom:12px;font-family:SourceSansProRegular,sans-serif;color:#3d3d3d}.theme-george .select-box select{height:42px;font-size:.875em;margin-bottom:16px;font-family:LatoRegular,sans-serif;color:#191919}.select-box select:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.select-box select:focus{border-color:#767676}.select-box select.half{width:calc(50% - 10px);float:left;margin-right:8px}.select-box select.error{border:2px solid #d43030;margin-bottom:0}.select-box.half{width:calc(50% - 5px);float:left;margin-right:10px}.select-box.no-gutter{margin:0}.select-error{margin-bottom:8px;margin-top:4px}.text-area label{padding-bottom:4px;display:block}.theme-ghs .text-area label{font-family:SourceSansProRegular,sans-serif;color:#3d3d3d;font-size:.875em}.theme-george .text-area label{font-family:LatoRegular,sans-serif;color:#191919;font-size:14px}.text-area textarea{width:100%;height:110px;font-size:1em;border-radius:4px;border:1px solid #ccc;clear:both;padding:8px;margin-bottom:8px;color:#191919;font-family:SourceSansProRegular,sans-serif;resize:none}.text-area textarea::-webkit-input-placeholder{color:#767676}.text-area textarea:-moz-placeholder{color:#767676}.text-area textarea::-moz-placeholder{color:#767676}.text-area textarea:-ms-input-placeholder{color:#767676}.text-area textarea.half{width:calc(50% - 10px);float:left;margin-right:8px}.text-area textarea.error{border:solid 1px #d43030;margin-bottom:0}.text-area.half{width:calc(50% - 5px);float:left;margin-right:10px}.text-area.no-gutter{margin:0}.input-error{margin-bottom:8px;margin-top:4px}.co-account__title{font-size:22px;padding-bottom:8px}.theme-ghs .co-account__title{font-family:SourceSansProRegular,sans-serif;padding-bottom:8px}.theme-george .co-account__title{font-family:LatoRegular,sans-serif;padding-bottom:32px}.co-account__description{font-size:14px;padding-bottom:16px}.theme-ghs .co-account__description{font-family:SourceSansProRegular,sans-serif}.theme-george .co-account__description{font-family:LatoRegular,sans-serif}a.george-account-link{font-family:LatoRegular,sans-serif;font-size:14px;color:#191919;display:block}a.george-account-link img{margin-right:8px}.edit-link{position:absolute;right:0;top:22px;padding:0;border:0;width:auto;height:auto;margin:0;font-size:18px;background:transparent;transition:all 200ms ease;letter-spacing:normal}.theme-ghs .edit-link{padding:0;height:auto;text-transform:none}.theme-george .edit-link{padding:0;height:auto;text-transform:none}.edit-link:hover{text-decoration:underline}.edit-link.change{top:16px}.theme-ghs .edit-link{font-family:SourceSansProRegular,sans-serif;color:#0073b1;text-decoration:none}.theme-george .edit-link{font-family:LatoBold,sans-serif;color:#191919;text-decoration:underline}.dropdown-list-item.selected,.dropdown-list-item:hover{color:#fff;background-color:#767676}.dropdown-wrapper-single{user-select:none;position:relative;width:265px}.dropdown-wrapper-single .dropdown-header{border:1px solid #ccc}.dropdown-wrapper-single .dropdown-header .dropdown-header-name{font-weight:400}.dropdown-wrapper-single .dropdown-list{border:1px solid #ccc;border-top:0}.dropdown-wrapper{font-family:SourceSansProRegular,sans-serif;font-size:24px;font-weight:600;font-style:normal;font-stretch:normal;line-height:normal;letter-spacing:.3px;float:left;margin-right:16px}.dropdown-wrapper .dropdown-header{display:flex;align-items:center;justify-content:space-between;width:176px;height:78px;border:1px solid #767676;cursor:default;position:relative;background-color:#fff;padding:20px}.dropdown-wrapper .dropdown-header.active{border-bottom:0px;top:1px;z-index:11}.dropdown-wrapper .list-wrapper .dropdown-list{z-index:10;position:absolute;width:560px;height:636px;border:1px solid #767676;background-color:#fff;padding:20px 75px 20px 20px;overflow-y:scroll;color:#767676}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{width:100%;padding:8px 0px;line-height:1.54px;cursor:default;display:flex;align-items:center;justify-content:space-between;text-overflow:ellipsis}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item:nth-child(5){padding-bottom:18px;border-bottom:2px solid #979797}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:420px;text-align:left;padding-left:15px}.dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{width:80px;background-color:none}.dropdown-wrapper .dropdown-list::-webkit-scrollbar-thumb{background-image:var(--sf-img-15);height:109px;background-repeat:no-repeat}.dropdown-wrapper .dropdown-list-item.selected,.dropdown-wrapper .dropdown-list-item:hover{color:#fff;background-color:#767676}div[class^=country-flag-]{width:34px;height:20px;background-size:34px 20px}div[class^=country-flag-] img{width:34px;height:20px;background-repeat:no-repeat;background-size:34px 20px}.list{font-family:SourceSansProRegular,sans-serif;font-size:14px;padding:8px 0 16px}.list-header{width:100%;padding-bottom:16px}.list-header-name{float:left;width:68%;margin-right:5%;font-family:SourceSansProSemiBold,sans-serif}.list-header-default{float:left;color:gray}.list-item{width:100%;height:auto;min-height:48px;border-bottom:1px solid #ccc;padding:0}.list-item.editing{border-bottom:0}.list-item-name{width:68%;text-overflow:ellipsis;float:left;white-space:nowrap;overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;margin-right:6%;padding:16px 0 0}.list-item-radio{clear:none;float:left;margin-top:13px}.list-item-edit-button{display:block;width:auto;height:auto;border:0;padding:0;color:#000;font-family:SourceSansProRegular,sans-serif;font-size:14px;clear:both}.theme-ghs .list-item-edit-button{height:auto;padding:0;margin:0 0 16px 42px;font-size:14px;text-transform:none;letter-spacing:normal;text-align:left;cursor:pointer;font-family:SourceSansProRegular,sans-serif;color:#0073b1;text-decoration:none}.theme-george .list-item-edit-button{height:auto;padding:0;margin:0 0 16px 42px;font-size:14px;text-transform:none;letter-spacing:normal;text-align:left;cursor:pointer;font-family:LatoRegular,sans-serif;color:#191919;text-decoration:underline}.list-item-edit-button:hover{text-decoration:underline}.list-item-delete-button{display:block;width:auto;border:0;float:right;color:#000;font-family:SourceSansProRegular,sans-serif;font-size:14px}.theme-ghs .list-item-delete-button{height:auto;padding:0;margin:0;font-size:14px;text-transform:none;letter-spacing:normal;font-family:SourceSansProRegular,sans-serif;text-decoration:underline;cursor:pointer}.theme-george .list-item-delete-button{height:auto;padding:0;margin:0;font-size:14px;text-transform:none;letter-spacing:normal;font-family:LatoRegular,sans-serif;text-decoration:underline;cursor:pointer}.list-item-delete-button:hover{text-decoration:underline}.list-item-delete-button img{margin-top:6px;float:right}.phone-input{width:100%}@media (min-width:320px){.phone-input{display:flex;flex-direction:column}}@media (min-width:481px){.phone-input{flex-direction:row;height:52px}}.phone-input .dropdown-wrapper{float:left;height:42px;margin-right:8px;color:#3d3d3d;font-family:SourceSansProRegular,sans-serif;transition:all 200ms ease;font-size:1em;user-select:none}@media (min-width:320px){.phone-input .dropdown-wrapper{width:100%}}@media (min-width:481px){.phone-input .dropdown-wrapper{width:32%}}.phone-input .dropdown-wrapper .dropdown-header{cursor:pointer;width:100%;height:40px;padding:0 8px;border-radius:4px;border:1px solid #ccc;transition:box-shadow 200ms ease}.phone-input .dropdown-wrapper .dropdown-header.active{height:41px;border-top-color:#767676;border-right-color:#767676;border-left-color:#767676;border-bottom-left-radius:0;border-bottom-right-radius:0;top:0}.phone-input .dropdown-wrapper .dropdown-header.active .country-code{transition:none;margin-top:-1px}.phone-input .dropdown-wrapper .dropdown-header.active div[class^=country-flag-] img{transition:none;margin-top:-1px}.phone-input .dropdown-wrapper .dropdown-header.active .down-arrow{transform:rotate(0.5turn);padding-bottom:4px}.phone-input .dropdown-wrapper .dropdown-header:hover{box-shadow:0 2px 10px 0 rgba(61,61,61,.1)}.phone-input .dropdown-wrapper .dropdown-header .country-code{font-size:16px;padding-left:4px}@media (min-width:320px){.phone-input .dropdown-wrapper .dropdown-header .country-code{flex:1;padding-left:15px;display:flex;align-items:center}}.phone-input .dropdown-wrapper .dropdown-header .country-code .country-name{width:100px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:215px}@media (min-width:481px){.phone-input .dropdown-wrapper .dropdown-header .country-code .country-name{display:none}}.phone-input .dropdown-wrapper .dropdown-header .down-arrow{width:20px;padding-top:4px}.phone-input .dropdown-wrapper .dropdown-header div[class^=country-flag-] img{width:32px;background-repeat:no-repeat;background-size:34px 20px}.phone-input .dropdown-wrapper .list-wrapper{position:relative}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list{margin-top:-1px;z-index:10;position:absolute;height:200px;border:1px solid #767676;border-radius:0 0 4px 4px;background-color:#fff;padding:0;overflow-y:scroll;color:#767676}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list{width:100%}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list{width:372px}}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list::-webkit-scrollbar *{background:transparent}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{color:#191919;height:40px;font-size:1em}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item:hover{background-color:#f6f6f6}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{padding:12px 8px 12px 8px;height:48px}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item{padding:12px 80px 12px 8px;height:40px}}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{line-height:initial}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:calc(100% - 32px)}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-name{width:420px}}.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{text-align:right}@media (min-width:320px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{width:60px}}@media (min-width:481px){.phone-input .dropdown-wrapper .list-wrapper .dropdown-list .dropdown-list-item .country-code{width:80px}}@media (min-width:320px){.phone-input .input-box{width:100%;margin-top:12px}}@media (min-width:481px){.phone-input .input-box{width:calc(68% - 8px);margin-top:0px}}.card-name{line-height:initial}.card-name .custom-icon:before{content:"";width:30px;margin-right:12px;margin-top:0px;height:20px;float:left;background-image:var(--sf-img-16)}.card-name .custom-icon.amex:before{background-image:var(--sf-img-17)}.card-name .custom-icon.maestro:before{background-image:var(--sf-img-18)}.card-name .custom-icon.mastercard:before{background-image:var(--sf-img-19)}.card-name .custom-icon.visa:before{background-image:var(--sf-img-20)}.card-name .card-info{font-size:12px;clear:both;padding-left:42px;padding-bottom:12px;color:#767676}.card-name .card-info .highlight-red{color:#d43030}.modal{position:absolute;z-index:1000;top:8px;left:8px;display:block;width:536px;max-width:calc(100% - 16px);height:auto;border-radius:8px;box-shadow:2px 3px 8px 3px rgba(0,0,0,.1);border:solid 1px #ccc;background-color:#fff;font-style:normal;font-stretch:normal;line-height:normal;color:#3d3d3d}.theme-ghs .modal{font-family:SourceSansProRegular,sans-serif}.theme-george .modal{font-family:LatoRegular,sans-serif}.modal-title{font-size:22px;padding:16px;border-bottom:solid 1px #ccc}.theme-ghs .modal-title{font-family:SourceSansProSemiBold,sans-serif}.theme-george .modal-title{font-family:LatoRegular,sans-serif}.modal-content{font-size:16px;padding:16px;border-bottom:solid 1px #ccc}.modal-footer{display:flex;justify-content:center;padding:16px}.modal-footer .half{width:45%}.modal-footer .full{width:100%}.input-error.server-error{margin:0 0 12px}.theme-ghs .help-icon{width:auto;height:auto;border:0;padding:0;margin:-7px 0 0 4px;float:left}.theme-george .help-icon{width:auto;height:auto;border:0;padding:0;margin:-7px 0 0 4px;float:left}.bubble{position:absolute;top:100px;left:16px;width:288px;height:auto;background-color:#191919;color:#fff;padding:12px 30px 12px 12px;border-radius:8px;font-size:14px;z-index:1000;font-family:SourceSansProRegular,sans-serif;box-shadow:0 2px 10px 0 rgba(0,0,0,.2);line-height:18px}.bubble .close-icon{position:absolute;top:8px;right:8px;width:auto;height:auto;border:0;padding:0;margin:0;background:transparent}.bubble .triangle{width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #191919;left:20px;top:-10px;position:absolute}.alert-box{display:flex;flex-direction:row;width:auto;height:auto;border-radius:4px;background-color:rgba(247,204,0,.2);margin-bottom:16px;padding:10px 24px 10px 8px}.alert-box img{width:24px;height:24px;object-fit:contain}.alert-box .alert-message{padding:0;margin-left:8px;font-size:16px;color:#3d3d3d}.divider{width:100%;border-top:1px solid #cbcbcb;margin-bottom:24px}</style><meta http-equiv="origin-trial" content="A3v9QjmVUCOO7YqFMKHP/NKbn6kY1G1pa2S1TfeXJZUD/tysMONTy6lV0Jkou3rrCjSKRGbqTrgTaZkm1XJ7pQUAAACKeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXRhZ21hbmFnZXIuY29tOjQ0MyIsImZlYXR1cmUiOiJDb252ZXJzaW9uTWVhc3VyZW1lbnQiLCJleHBpcnkiOjE2NDMxNTUxOTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9"><style id="onetrust-style">#onetrust-banner-sdk{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}#onetrust-banner-sdk .onetrust-vendors-list-handler{cursor:pointer;color:#1f96db;font-size:inherit;font-weight:bold;text-decoration:none;margin-left:5px}#onetrust-banner-sdk .onetrust-vendors-list-handler:hover{color:#1f96db}#onetrust-banner-sdk:focus{outline:2px solid #000;outline-offset:-2px}#onetrust-banner-sdk a:focus{outline:2px solid #000}#onetrust-banner-sdk #onetrust-accept-btn-handler,#onetrust-banner-sdk #onetrust-reject-all-handler,#onetrust-banner-sdk #onetrust-pc-btn-handler{outline-offset:1px}#onetrust-banner-sdk .ot-close-icon,#onetrust-pc-sdk .ot-close-icon,#ot-sync-ntfy .ot-close-icon{background-image:url("resources/sample/10.svg");background-size:contain;background-repeat:no-repeat;background-position:center;height:12px;width:12px}#onetrust-banner-sdk .powered-by-logo,#onetrust-banner-sdk .ot-pc-footer-logo a,#onetrust-pc-sdk .powered-by-logo,#onetrust-pc-sdk .ot-pc-footer-logo a,#ot-sync-ntfy .powered-by-logo,#ot-sync-ntfy .ot-pc-footer-logo a{background-size:contain;background-repeat:no-repeat;background-position:center;height:25px;width:152px;display:block}#onetrust-banner-sdk h3 *,#onetrust-banner-sdk h4 *,#onetrust-banner-sdk h6 *,#onetrust-banner-sdk button *,#onetrust-banner-sdk a[data-parent-id] *,#onetrust-pc-sdk h3 *,#onetrust-pc-sdk h4 *,#onetrust-pc-sdk h6 *,#onetrust-pc-sdk button *,#onetrust-pc-sdk a[data-parent-id] *,#ot-sync-ntfy h3 *,#ot-sync-ntfy h4 *,#ot-sync-ntfy h6 *,#ot-sync-ntfy button *,#ot-sync-ntfy a[data-parent-id] *{font-size:inherit;font-weight:inherit;color:inherit}#onetrust-banner-sdk .ot-hide,#onetrust-pc-sdk .ot-hide,#ot-sync-ntfy .ot-hide{display:none!important}#onetrust-pc-sdk .ot-sdk-row .ot-sdk-column{padding:0}#onetrust-pc-sdk .ot-sdk-container{padding-right:0}#onetrust-pc-sdk .ot-sdk-row{flex-direction:initial;width:100%}#onetrust-pc-sdk [type="checkbox"]:checked,#onetrust-pc-sdk [type="checkbox"]:not(:checked){pointer-events:initial}#onetrust-pc-sdk [type="checkbox"]:disabled+label::before,#onetrust-pc-sdk [type="checkbox"]:disabled+label:after,#onetrust-pc-sdk [type="checkbox"]:disabled+label{pointer-events:none;opacity:0.7}#onetrust-pc-sdk #vendor-list-content{transform:translate3d(0,0,0)}#onetrust-pc-sdk li input[type="checkbox"]{z-index:1}#onetrust-pc-sdk li .ot-checkbox label{z-index:2}#onetrust-pc-sdk li .ot-checkbox input[type="checkbox"]{height:auto;width:auto}#onetrust-pc-sdk li .host-title a,#onetrust-pc-sdk li .ot-host-name a,#onetrust-pc-sdk li .accordion-text,#onetrust-pc-sdk li .ot-acc-txt{z-index:2;position:relative}#onetrust-pc-sdk input{margin:3px 0.1ex}#onetrust-pc-sdk .pc-logo,#onetrust-pc-sdk .ot-pc-logo{height:60px;width:180px;background-position:center;background-size:contain;background-repeat:no-repeat}#onetrust-pc-sdk .screen-reader-only,#onetrust-pc-sdk .ot-scrn-rdr,.ot-sdk-cookie-policy .screen-reader-only,.ot-sdk-cookie-policy .ot-scrn-rdr{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}#onetrust-pc-sdk.ot-fade-in,.onetrust-pc-dark-filter.ot-fade-in,#onetrust-banner-sdk.ot-fade-in{animation-name:onetrust-fade-in;animation-duration:400ms;animation-timing-function:ease-in-out}#onetrust-pc-sdk.ot-hide{display:none!important}.onetrust-pc-dark-filter.ot-hide{display:none!important}#ot-sdk-btn.ot-sdk-show-settings,#ot-sdk-btn.optanon-show-settings{color:#68b631;border:1px solid #68b631;height:auto;white-space:normal;word-wrap:break-word;padding:0.8em 2em;font-size:0.8em;line-height:1.2;cursor:pointer;-moz-transition:0.1s ease;-o-transition:0.1s ease;-webkit-transition:1s ease;transition:0.1s ease}#ot-sdk-btn.ot-sdk-show-settings:hover,#ot-sdk-btn.optanon-show-settings:hover{color:#fff;background-color:#68b631}.onetrust-pc-dark-filter{background:rgba(0,0,0,0.5);z-index:2147483646;width:100%;height:100%;overflow:hidden;position:fixed;top:0;bottom:0;left:0}@keyframes onetrust-fade-in{0%{opacity:0}100%{opacity:1}}.ot-cookie-label{text-decoration:underline}@media only screen and (min-width:426px) and (max-width:896px) and (orientation:landscape){#onetrust-pc-sdk p{font-size:0.75em}}#onetrust-banner-sdk .banner-option-input:focus+label{outline:1px solid #000;outline-style:auto}.category-vendors-list-handler+a:focus,.category-vendors-list-handler+a:focus-visible{outline:2px solid #000}#onetrust-banner-sdk,#onetrust-pc-sdk,#ot-sdk-cookie-policy,#ot-sync-ntfy{font-size:16px}#onetrust-banner-sdk *,#onetrust-banner-sdk ::after,#onetrust-banner-sdk ::before,#onetrust-pc-sdk *,#onetrust-pc-sdk ::after,#onetrust-pc-sdk ::before,#ot-sdk-cookie-policy *,#ot-sdk-cookie-policy ::after,#ot-sdk-cookie-policy ::before,#ot-sync-ntfy *,#ot-sync-ntfy ::after,#ot-sync-ntfy ::before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}#onetrust-banner-sdk div,#onetrust-banner-sdk span,#onetrust-banner-sdk h1,#onetrust-banner-sdk h2,#onetrust-banner-sdk h3,#onetrust-banner-sdk h4,#onetrust-banner-sdk h5,#onetrust-banner-sdk h6,#onetrust-banner-sdk p,#onetrust-banner-sdk img,#onetrust-banner-sdk svg,#onetrust-banner-sdk button,#onetrust-banner-sdk section,#onetrust-banner-sdk a,#onetrust-banner-sdk label,#onetrust-banner-sdk input,#onetrust-banner-sdk ul,#onetrust-banner-sdk li,#onetrust-banner-sdk nav,#onetrust-banner-sdk table,#onetrust-banner-sdk thead,#onetrust-banner-sdk tr,#onetrust-banner-sdk td,#onetrust-banner-sdk tbody,#onetrust-banner-sdk .ot-main-content,#onetrust-banner-sdk .ot-toggle,#onetrust-banner-sdk #ot-content,#onetrust-banner-sdk #ot-pc-content,#onetrust-banner-sdk .checkbox,#onetrust-pc-sdk div,#onetrust-pc-sdk span,#onetrust-pc-sdk h1,#onetrust-pc-sdk h2,#onetrust-pc-sdk h3,#onetrust-pc-sdk h4,#onetrust-pc-sdk h5,#onetrust-pc-sdk h6,#onetrust-pc-sdk p,#onetrust-pc-sdk img,#onetrust-pc-sdk svg,#onetrust-pc-sdk button,#onetrust-pc-sdk section,#onetrust-pc-sdk a,#onetrust-pc-sdk label,#onetrust-pc-sdk input,#onetrust-pc-sdk ul,#onetrust-pc-sdk li,#onetrust-pc-sdk nav,#onetrust-pc-sdk table,#onetrust-pc-sdk thead,#onetrust-pc-sdk tr,#onetrust-pc-sdk td,#onetrust-pc-sdk tbody,#onetrust-pc-sdk .ot-main-content,#onetrust-pc-sdk .ot-toggle,#onetrust-pc-sdk #ot-content,#onetrust-pc-sdk #ot-pc-content,#onetrust-pc-sdk .checkbox,#ot-sdk-cookie-policy div,#ot-sdk-cookie-policy span,#ot-sdk-cookie-policy h1,#ot-sdk-cookie-policy h2,#ot-sdk-cookie-policy h3,#ot-sdk-cookie-policy h4,#ot-sdk-cookie-policy h5,#ot-sdk-cookie-policy h6,#ot-sdk-cookie-policy p,#ot-sdk-cookie-policy img,#ot-sdk-cookie-policy svg,#ot-sdk-cookie-policy button,#ot-sdk-cookie-policy section,#ot-sdk-cookie-policy a,#ot-sdk-cookie-policy label,#ot-sdk-cookie-policy input,#ot-sdk-cookie-policy ul,#ot-sdk-cookie-policy li,#ot-sdk-cookie-policy nav,#ot-sdk-cookie-policy table,#ot-sdk-cookie-policy thead,#ot-sdk-cookie-policy tr,#ot-sdk-cookie-policy td,#ot-sdk-cookie-policy tbody,#ot-sdk-cookie-policy .ot-main-content,#ot-sdk-cookie-policy .ot-toggle,#ot-sdk-cookie-policy #ot-content,#ot-sdk-cookie-policy #ot-pc-content,#ot-sdk-cookie-policy .checkbox,#ot-sync-ntfy div,#ot-sync-ntfy span,#ot-sync-ntfy h1,#ot-sync-ntfy h2,#ot-sync-ntfy h3,#ot-sync-ntfy h4,#ot-sync-ntfy h5,#ot-sync-ntfy h6,#ot-sync-ntfy p,#ot-sync-ntfy img,#ot-sync-ntfy svg,#ot-sync-ntfy button,#ot-sync-ntfy section,#ot-sync-ntfy a,#ot-sync-ntfy label,#ot-sync-ntfy input,#ot-sync-ntfy ul,#ot-sync-ntfy li,#ot-sync-ntfy nav,#ot-sync-ntfy table,#ot-sync-ntfy thead,#ot-sync-ntfy tr,#ot-sync-ntfy td,#ot-sync-ntfy tbody,#ot-sync-ntfy .ot-main-content,#ot-sync-ntfy .ot-toggle,#ot-sync-ntfy #ot-content,#ot-sync-ntfy #ot-pc-content,#ot-sync-ntfy .checkbox{font-family:inherit;font-weight:normal;-webkit-font-smoothing:auto;letter-spacing:normal;line-height:normal;padding:0;margin:0;height:auto;min-height:0;max-height:none;width:auto;min-width:0;max-width:none;border-radius:0;border:none;clear:none;float:none;position:static;bottom:auto;left:auto;right:auto;top:auto;text-align:left;text-decoration:none;text-indent:0;text-shadow:none;text-transform:none;white-space:normal;background:none;overflow:visible;vertical-align:baseline;visibility:visible;z-index:auto;box-shadow:none}#onetrust-banner-sdk label:before,#onetrust-banner-sdk label:after,#onetrust-banner-sdk .checkbox:after,#onetrust-banner-sdk .checkbox:before,#onetrust-pc-sdk label:before,#onetrust-pc-sdk label:after,#onetrust-pc-sdk .checkbox:after,#onetrust-pc-sdk .checkbox:before,#ot-sdk-cookie-policy label:before,#ot-sdk-cookie-policy label:after,#ot-sdk-cookie-policy .checkbox:after,#ot-sdk-cookie-policy .checkbox:before,#ot-sync-ntfy label:before,#ot-sync-ntfy label:after,#ot-sync-ntfy .checkbox:after,#ot-sync-ntfy .checkbox:before{content:"";content:none}#onetrust-banner-sdk .ot-sdk-container,#onetrust-pc-sdk .ot-sdk-container,#ot-sdk-cookie-policy .ot-sdk-container{position:relative;width:100%;max-width:100%;margin:0 auto;padding:0 20px;box-sizing:border-box}#onetrust-banner-sdk .ot-sdk-column,#onetrust-banner-sdk .ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-column,#onetrust-pc-sdk .ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-column,#ot-sdk-cookie-policy .ot-sdk-columns{width:100%;float:left;box-sizing:border-box;padding:0;display:initial}@media (min-width:400px){#onetrust-banner-sdk .ot-sdk-container,#onetrust-pc-sdk .ot-sdk-container,#ot-sdk-cookie-policy .ot-sdk-container{width:90%;padding:0}}@media (min-width:550px){#onetrust-banner-sdk .ot-sdk-container,#onetrust-pc-sdk .ot-sdk-container,#ot-sdk-cookie-policy .ot-sdk-container{width:100%}#onetrust-banner-sdk .ot-sdk-column,#onetrust-banner-sdk .ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-column,#onetrust-pc-sdk .ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-column,#ot-sdk-cookie-policy .ot-sdk-columns{margin-left:4%}#onetrust-banner-sdk .ot-sdk-column:first-child,#onetrust-banner-sdk .ot-sdk-columns:first-child,#onetrust-pc-sdk .ot-sdk-column:first-child,#onetrust-pc-sdk .ot-sdk-columns:first-child,#ot-sdk-cookie-policy .ot-sdk-column:first-child,#ot-sdk-cookie-policy .ot-sdk-columns:first-child{margin-left:0}#onetrust-banner-sdk .ot-sdk-two.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-two.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-two.ot-sdk-columns{width:13.3333333333%}#onetrust-banner-sdk .ot-sdk-three.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-three.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-three.ot-sdk-columns{width:22%}#onetrust-banner-sdk .ot-sdk-four.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-four.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-four.ot-sdk-columns{width:30.6666666667%}#onetrust-banner-sdk .ot-sdk-eight.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-eight.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-eight.ot-sdk-columns{width:65.3333333333%}#onetrust-banner-sdk .ot-sdk-nine.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-nine.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-nine.ot-sdk-columns{width:74%}#onetrust-banner-sdk .ot-sdk-ten.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-ten.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-ten.ot-sdk-columns{width:82.6666666667%}#onetrust-banner-sdk .ot-sdk-eleven.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-eleven.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-eleven.ot-sdk-columns{width:91.3333333333%}#onetrust-banner-sdk .ot-sdk-twelve.ot-sdk-columns,#onetrust-pc-sdk .ot-sdk-twelve.ot-sdk-columns,#ot-sdk-cookie-policy .ot-sdk-twelve.ot-sdk-columns{width:100%;margin-left:0}}#onetrust-banner-sdk h1,#onetrust-banner-sdk h2,#onetrust-banner-sdk h3,#onetrust-banner-sdk h4,#onetrust-banner-sdk h5,#onetrust-banner-sdk h6,#onetrust-pc-sdk h1,#onetrust-pc-sdk h2,#onetrust-pc-sdk h3,#onetrust-pc-sdk h4,#onetrust-pc-sdk h5,#onetrust-pc-sdk h6,#ot-sdk-cookie-policy h1,#ot-sdk-cookie-policy h2,#ot-sdk-cookie-policy h3,#ot-sdk-cookie-policy h4,#ot-sdk-cookie-policy h5,#ot-sdk-cookie-policy h6{margin-top:0;font-weight:600;font-family:inherit}#onetrust-banner-sdk h1,#onetrust-pc-sdk h1,#ot-sdk-cookie-policy h1{font-size:1.5rem;line-height:1.2}#onetrust-banner-sdk h2,#onetrust-pc-sdk h2,#ot-sdk-cookie-policy h2{font-size:1.5rem;line-height:1.25}#onetrust-banner-sdk h3,#onetrust-pc-sdk h3,#ot-sdk-cookie-policy h3{font-size:1.5rem;line-height:1.3}#onetrust-banner-sdk h4,#onetrust-pc-sdk h4,#ot-sdk-cookie-policy h4{font-size:1.5rem;line-height:1.35}#onetrust-banner-sdk h5,#onetrust-pc-sdk h5,#ot-sdk-cookie-policy h5{font-size:1.5rem;line-height:1.5}#onetrust-banner-sdk h6,#onetrust-pc-sdk h6,#ot-sdk-cookie-policy h6{font-size:1.5rem;line-height:1.6}@media (min-width:550px){#onetrust-banner-sdk h1,#onetrust-pc-sdk h1,#ot-sdk-cookie-policy h1{font-size:1.5rem}#onetrust-banner-sdk h2,#onetrust-pc-sdk h2,#ot-sdk-cookie-policy h2{font-size:1.5rem}#onetrust-banner-sdk h3,#onetrust-pc-sdk h3,#ot-sdk-cookie-policy h3{font-size:1.5rem}#onetrust-banner-sdk h4,#onetrust-pc-sdk h4,#ot-sdk-cookie-policy h4{font-size:1.5rem}#onetrust-banner-sdk h5,#onetrust-pc-sdk h5,#ot-sdk-cookie-policy h5{font-size:1.5rem}#onetrust-banner-sdk h6,#onetrust-pc-sdk h6,#ot-sdk-cookie-policy h6{font-size:1.5rem}}#onetrust-banner-sdk p,#onetrust-pc-sdk p,#ot-sdk-cookie-policy p{margin:0 0 1em 0;font-family:inherit;line-height:normal}#onetrust-banner-sdk a,#onetrust-pc-sdk a,#ot-sdk-cookie-policy a{color:#565656;text-decoration:underline}#onetrust-banner-sdk a:hover,#onetrust-pc-sdk a:hover,#ot-sdk-cookie-policy a:hover{color:#565656;text-decoration:none}#onetrust-banner-sdk .ot-sdk-button,#onetrust-banner-sdk button,#onetrust-pc-sdk .ot-sdk-button,#onetrust-pc-sdk button,#ot-sdk-cookie-policy .ot-sdk-button,#ot-sdk-cookie-policy button{margin-bottom:1rem;font-family:inherit}#onetrust-banner-sdk .ot-sdk-button,#onetrust-banner-sdk button,#onetrust-pc-sdk .ot-sdk-button,#onetrust-pc-sdk button,#ot-sdk-cookie-policy .ot-sdk-button,#ot-sdk-cookie-policy button{display:inline-block;height:38px;padding:0 30px;color:#555;text-align:center;font-size:0.9em;font-weight:400;line-height:38px;letter-spacing:0.01em;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:2px;border:1px solid #bbb;cursor:pointer;box-sizing:border-box}#onetrust-banner-sdk .ot-sdk-button:hover,#onetrust-banner-sdk :not(.ot-leg-btn-container)>button:hover,#onetrust-banner-sdk :not(.ot-leg-btn-container)>button:focus,#onetrust-pc-sdk .ot-sdk-button:hover,#onetrust-pc-sdk :not(.ot-leg-btn-container)>button:hover,#onetrust-pc-sdk :not(.ot-leg-btn-container)>button:focus,#ot-sdk-cookie-policy .ot-sdk-button:hover,#ot-sdk-cookie-policy :not(.ot-leg-btn-container)>button:hover,#ot-sdk-cookie-policy :not(.ot-leg-btn-container)>button:focus{color:#333;border-color:#888;opacity:0.7}#onetrust-banner-sdk .ot-sdk-button:focus,#onetrust-banner-sdk :not(.ot-leg-btn-container)>button:focus,#onetrust-pc-sdk .ot-sdk-button:focus,#onetrust-pc-sdk :not(.ot-leg-btn-container)>button:focus,#ot-sdk-cookie-policy .ot-sdk-button:focus,#ot-sdk-cookie-policy :not(.ot-leg-btn-container)>button:focus{outline:2px solid #000}#onetrust-banner-sdk .ot-sdk-button.ot-sdk-button-primary,#onetrust-banner-sdk button.ot-sdk-button-primary,#onetrust-banner-sdk input[type="submit"].ot-sdk-button-primary,#onetrust-banner-sdk input[type="reset"].ot-sdk-button-primary,#onetrust-banner-sdk input[type="button"].ot-sdk-button-primary,#onetrust-pc-sdk .ot-sdk-button.ot-sdk-button-primary,#onetrust-pc-sdk button.ot-sdk-button-primary,#onetrust-pc-sdk input[type="submit"].ot-sdk-button-primary,#onetrust-pc-sdk input[type="reset"].ot-sdk-button-primary,#onetrust-pc-sdk input[type="button"].ot-sdk-button-primary,#ot-sdk-cookie-policy .ot-sdk-button.ot-sdk-button-primary,#ot-sdk-cookie-policy button.ot-sdk-button-primary,#ot-sdk-cookie-policy input[type="submit"].ot-sdk-button-primary,#ot-sdk-cookie-policy input[type="reset"].ot-sdk-button-primary,#ot-sdk-cookie-policy input[type="button"].ot-sdk-button-primary{color:#fff;background-color:#33c3f0;border-color:#33c3f0}#onetrust-banner-sdk .ot-sdk-button.ot-sdk-button-primary:hover,#onetrust-banner-sdk button.ot-sdk-button-primary:hover,#onetrust-banner-sdk input[type="submit"].ot-sdk-button-primary:hover,#onetrust-banner-sdk input[type="reset"].ot-sdk-button-primary:hover,#onetrust-banner-sdk input[type="button"].ot-sdk-button-primary:hover,#onetrust-banner-sdk .ot-sdk-button.ot-sdk-button-primary:focus,#onetrust-banner-sdk button.ot-sdk-button-primary:focus,#onetrust-banner-sdk input[type="submit"].ot-sdk-button-primary:focus,#onetrust-banner-sdk input[type="reset"].ot-sdk-button-primary:focus,#onetrust-banner-sdk input[type="button"].ot-sdk-button-primary:focus,#onetrust-pc-sdk .ot-sdk-button.ot-sdk-button-primary:hover,#onetrust-pc-sdk button.ot-sdk-button-primary:hover,#onetrust-pc-sdk input[type="submit"].ot-sdk-button-primary:hover,#onetrust-pc-sdk input[type="reset"].ot-sdk-button-primary:hover,#onetrust-pc-sdk input[type="button"].ot-sdk-button-primary:hover,#onetrust-pc-sdk .ot-sdk-button.ot-sdk-button-primary:focus,#onetrust-pc-sdk button.ot-sdk-button-primary:focus,#onetrust-pc-sdk input[type="submit"].ot-sdk-button-primary:focus,#onetrust-pc-sdk input[type="reset"].ot-sdk-button-primary:focus,#onetrust-pc-sdk input[type="button"].ot-sdk-button-primary:focus,#ot-sdk-cookie-policy .ot-sdk-button.ot-sdk-button-primary:hover,#ot-sdk-cookie-policy button.ot-sdk-button-primary:hover,#ot-sdk-cookie-policy input[type="submit"].ot-sdk-button-primary:hover,#ot-sdk-cookie-policy input[type="reset"].ot-sdk-button-primary:hover,#ot-sdk-cookie-policy input[type="button"].ot-sdk-button-primary:hover,#ot-sdk-cookie-policy .ot-sdk-button.ot-sdk-button-primary:focus,#ot-sdk-cookie-policy button.ot-sdk-button-primary:focus,#ot-sdk-cookie-policy input[type="submit"].ot-sdk-button-primary:focus,#ot-sdk-cookie-policy input[type="reset"].ot-sdk-button-primary:focus,#ot-sdk-cookie-policy input[type="button"].ot-sdk-button-primary:focus{color:#fff;background-color:#1eaedb;border-color:#1eaedb}#onetrust-banner-sdk input[type="text"],#onetrust-pc-sdk input[type="text"],#ot-sdk-cookie-policy input[type="text"]{height:38px;padding:6px 10px;background-color:#fff;border:1px solid #d1d1d1;border-radius:4px;box-shadow:none;box-sizing:border-box}#onetrust-banner-sdk input[type="text"],#onetrust-pc-sdk input[type="text"],#ot-sdk-cookie-policy input[type="text"]{-webkit-appearance:none;-moz-appearance:none;appearance:none}#onetrust-banner-sdk input[type="text"]:focus,#onetrust-pc-sdk input[type="text"]:focus,#ot-sdk-cookie-policy input[type="text"]:focus{border:1px solid #000;outline:0}#onetrust-banner-sdk label,#onetrust-pc-sdk label,#ot-sdk-cookie-policy label{display:block;margin-bottom:0.5rem;font-weight:600}#onetrust-banner-sdk input[type="checkbox"],#onetrust-pc-sdk input[type="checkbox"],#ot-sdk-cookie-policy input[type="checkbox"]{display:inline}#onetrust-banner-sdk ul,#onetrust-pc-sdk ul,#ot-sdk-cookie-policy ul{list-style:circle inside}#onetrust-banner-sdk ul,#onetrust-pc-sdk ul,#ot-sdk-cookie-policy ul{padding-left:0;margin-top:0}#onetrust-banner-sdk ul ul,#onetrust-pc-sdk ul ul,#ot-sdk-cookie-policy ul ul{margin:1.5rem 0 1.5rem 3rem;font-size:90%}#onetrust-banner-sdk li,#onetrust-pc-sdk li,#ot-sdk-cookie-policy li{margin-bottom:1rem}#onetrust-banner-sdk th,#onetrust-banner-sdk td,#onetrust-pc-sdk th,#onetrust-pc-sdk td,#ot-sdk-cookie-policy th,#ot-sdk-cookie-policy td{padding:12px 15px;text-align:left;border-bottom:1px solid #e1e1e1}#onetrust-banner-sdk button,#onetrust-pc-sdk button,#ot-sdk-cookie-policy button{margin-bottom:1rem;font-family:inherit}#onetrust-banner-sdk .ot-sdk-container:after,#onetrust-banner-sdk .ot-sdk-row:after,#onetrust-pc-sdk .ot-sdk-container:after,#onetrust-pc-sdk .ot-sdk-row:after,#ot-sdk-cookie-policy .ot-sdk-container:after,#ot-sdk-cookie-policy .ot-sdk-row:after{content:"";display:table;clear:both}#onetrust-banner-sdk .ot-sdk-row,#onetrust-pc-sdk .ot-sdk-row,#ot-sdk-cookie-policy .ot-sdk-row{margin:0;max-width:none;display:block}#onetrust-banner-sdk{box-shadow:0 0 18px rgba(0,0,0,.2)}#onetrust-banner-sdk.otFlat{position:fixed;z-index:2147483645;bottom:0;right:0;left:0;background-color:#fff;max-height:90%;overflow-x:hidden;overflow-y:auto}#onetrust-banner-sdk.otFlat.top{top:0px;bottom:auto}#onetrust-banner-sdk.otRelFont{font-size:1rem}#onetrust-banner-sdk>.ot-sdk-container{overflow:hidden}#onetrust-banner-sdk::-webkit-scrollbar{width:11px}#onetrust-banner-sdk::-webkit-scrollbar-thumb{border-radius:10px;background:#c1c1c1}#onetrust-banner-sdk{scrollbar-arrow-color:#c1c1c1;scrollbar-darkshadow-color:#c1c1c1;scrollbar-face-color:#c1c1c1;scrollbar-shadow-color:#c1c1c1}#onetrust-banner-sdk #onetrust-policy{margin:1.25em 0 .625em 2em;overflow:hidden}#onetrust-banner-sdk #onetrust-policy .ot-gv-list-handler{float:left;font-size:.82em;padding:0;margin-bottom:0;border:0;line-height:normal;height:auto;width:auto}#onetrust-banner-sdk #onetrust-policy-title{font-size:1.2em;line-height:1.3;margin-bottom:10px}#onetrust-banner-sdk #onetrust-policy-text{clear:both;text-align:left;font-size:.88em;line-height:1.4}#onetrust-banner-sdk #onetrust-policy-text *{font-size:inherit;line-height:inherit}#onetrust-banner-sdk #onetrust-policy-text a{font-weight:bold;margin-left:5px}#onetrust-banner-sdk #onetrust-policy-title,#onetrust-banner-sdk #onetrust-policy-text{color:dimgray;float:left}#onetrust-banner-sdk #onetrust-button-group-parent{min-height:1px;text-align:center}#onetrust-banner-sdk #onetrust-button-group{display:inline-block}#onetrust-banner-sdk #onetrust-accept-btn-handler,#onetrust-banner-sdk #onetrust-reject-all-handler,#onetrust-banner-sdk #onetrust-pc-btn-handler{background-color:#68b631;color:#fff;border-color:#68b631;margin-right:1em;min-width:125px;height:auto;white-space:normal;word-break:break-word;word-wrap:break-word;padding:12px 10px;line-height:1.2;font-size:.813em;font-weight:600}#onetrust-banner-sdk #onetrust-pc-btn-handler.cookie-setting-link{background-color:#fff;border:none;color:#68b631;text-decoration:underline;padding-left:0;padding-right:0}#onetrust-banner-sdk .onetrust-close-btn-ui{width:44px;height:44px;background-size:12px;border:none;position:relative;margin:auto;padding:0}#onetrust-banner-sdk .banner_logo{display:none}#onetrust-banner-sdk .ot-b-addl-desc{clear:both;float:left;display:block}#onetrust-banner-sdk #banner-options{float:left;display:table;margin-right:0;margin-left:1em;width:calc(100% - 1em)}#onetrust-banner-sdk .banner-option-input{cursor:pointer;width:auto;height:auto;border:none;padding:0;padding-right:3px;margin:0 0 10px;font-size:.82em;line-height:1.4}#onetrust-banner-sdk .banner-option-input *{pointer-events:none;font-size:inherit;line-height:inherit}#onetrust-banner-sdk .banner-option-input[aria-expanded=true]~.banner-option-details{display:block;height:auto}#onetrust-banner-sdk .banner-option-input[aria-expanded=true] .ot-arrow-container{transform:rotate(90deg)}#onetrust-banner-sdk .banner-option{margin-bottom:12px;margin-left:0;border:none;float:left;padding:0}#onetrust-banner-sdk .banner-option:first-child{padding-left:2px}#onetrust-banner-sdk .banner-option:not(:first-child){padding:0;border:none}#onetrust-banner-sdk .banner-option-header{cursor:pointer;display:inline-block}#onetrust-banner-sdk .banner-option-header :first-child{color:dimgray;font-weight:bold;float:left}#onetrust-banner-sdk .banner-option-header .ot-arrow-container{display:inline-block;border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid dimgray;margin-left:10px;vertical-align:middle}#onetrust-banner-sdk .banner-option-details{display:none;font-size:.83em;line-height:1.5;padding:10px 0px 5px 10px;margin-right:10px;height:0px}#onetrust-banner-sdk .banner-option-details *{font-size:inherit;line-height:inherit;color:dimgray}#onetrust-banner-sdk .ot-arrow-container,#onetrust-banner-sdk .banner-option-details{transition:all 300ms ease-in 0s;-webkit-transition:all 300ms ease-in 0s;-moz-transition:all 300ms ease-in 0s;-o-transition:all 300ms ease-in 0s}#onetrust-banner-sdk .ot-dpd-container{float:left}#onetrust-banner-sdk .ot-dpd-title{margin-bottom:10px}#onetrust-banner-sdk .ot-dpd-title,#onetrust-banner-sdk .ot-dpd-desc{font-size:.88em;line-height:1.4;color:dimgray}#onetrust-banner-sdk .ot-dpd-title *,#onetrust-banner-sdk .ot-dpd-desc *{font-size:inherit;line-height:inherit}#onetrust-banner-sdk.ot-iab-2 #onetrust-policy-text *{margin-bottom:0}#onetrust-banner-sdk.ot-iab-2 .onetrust-vendors-list-handler{display:block;margin-left:0;margin-top:5px;clear:both;margin-bottom:0;padding:0;border:0;height:auto;width:auto}#onetrust-banner-sdk.ot-iab-2 #onetrust-button-group button{display:block}#onetrust-banner-sdk.ot-close-btn-link{padding-top:25px}#onetrust-banner-sdk.ot-close-btn-link #onetrust-close-btn-container{top:15px;transform:none;right:15px}#onetrust-banner-sdk.ot-close-btn-link #onetrust-close-btn-container button{padding:0;white-space:pre-wrap;border:none;height:auto;line-height:1.5;text-decoration:underline;font-size:.69em}#onetrust-banner-sdk #onetrust-policy-text,#onetrust-banner-sdk .ot-dpd-desc,#onetrust-banner-sdk .ot-b-addl-desc{font-size:.813em;line-height:1.5}#onetrust-banner-sdk .ot-dpd-desc{margin-bottom:10px}#onetrust-banner-sdk .ot-dpd-desc>.ot-b-addl-desc{margin-top:10px;margin-bottom:10px;font-size:1em}@media only screen and (max-width:425px){#onetrust-banner-sdk #onetrust-close-btn-container{position:absolute;top:6px;right:2px}#onetrust-banner-sdk #onetrust-policy{margin-left:0;margin-top:3em}#onetrust-banner-sdk #onetrust-button-group{display:block}#onetrust-banner-sdk #onetrust-accept-btn-handler,#onetrust-banner-sdk #onetrust-reject-all-handler,#onetrust-banner-sdk #onetrust-pc-btn-handler{width:100%}#onetrust-banner-sdk .onetrust-close-btn-ui{top:auto;transform:none}#onetrust-banner-sdk #onetrust-policy-title{display:inline;float:none}#onetrust-banner-sdk #banner-options{margin:0;padding:0;width:100%}}@media only screen and (min-width:426px)and (max-width:896px){#onetrust-banner-sdk #onetrust-close-btn-container{position:absolute;top:0;right:0}#onetrust-banner-sdk #onetrust-policy{margin-left:1em;margin-right:1em}#onetrust-banner-sdk .onetrust-close-btn-ui{top:10px;right:10px}#onetrust-banner-sdk:not(.ot-iab-2) #onetrust-group-container{width:95%}#onetrust-banner-sdk.ot-iab-2 #onetrust-group-container{width:100%}#onetrust-banner-sdk #onetrust-button-group-parent{width:100%;position:relative;margin-left:0}#onetrust-banner-sdk #onetrust-button-group button{display:inline-block}#onetrust-banner-sdk #onetrust-button-group{margin-right:0;text-align:center}#onetrust-banner-sdk .has-reject-all-button #onetrust-pc-btn-handler{float:left}#onetrust-banner-sdk .has-reject-all-button #onetrust-reject-all-handler,#onetrust-banner-sdk .has-reject-all-button #onetrust-accept-btn-handler{float:right}#onetrust-banner-sdk .has-reject-all-button #onetrust-button-group{width:calc(100% - 2em);margin-right:0}#onetrust-banner-sdk .has-reject-all-button #onetrust-pc-btn-handler.cookie-setting-link{padding-left:0px;text-align:left}#onetrust-banner-sdk.ot-buttons-fw .ot-sdk-three button{width:100%;text-align:center}#onetrust-banner-sdk.ot-buttons-fw #onetrust-button-group-parent button{float:none}#onetrust-banner-sdk.ot-buttons-fw #onetrust-pc-btn-handler.cookie-setting-link{text-align:center}}@media only screen and (min-width:550px){#onetrust-banner-sdk .banner-option:not(:first-child){border-left:1px solid #d8d8d8;padding-left:25px}}@media only screen and (min-width:425px)and (max-width:550px){#onetrust-banner-sdk.ot-iab-2 #onetrust-button-group,#onetrust-banner-sdk.ot-iab-2 #onetrust-policy,#onetrust-banner-sdk.ot-iab-2 .banner-option{width:100%}}@media only screen and (min-width:769px){#onetrust-banner-sdk #onetrust-button-group{margin-right:30%}#onetrust-banner-sdk #banner-options{margin-left:2em;margin-right:5em;margin-bottom:1.25em;width:calc(100% - 7em)}}@media only screen and (min-width:897px)and (max-width:1023px){#onetrust-banner-sdk.vertical-align-content #onetrust-button-group-parent{position:absolute;top:50%;left:75%;transform:translateY(-50%)}#onetrust-banner-sdk #onetrust-close-btn-container{top:50%;margin:auto;transform:translate(-50%,-50%);position:absolute;padding:0;right:0}#onetrust-banner-sdk #onetrust-close-btn-container button{position:relative;margin:0;right:-22px;top:2px}}@media only screen and (min-width:1024px){#onetrust-banner-sdk #onetrust-close-btn-container{top:50%;margin:auto;transform:translate(-50%,-50%);position:absolute;right:0}#onetrust-banner-sdk #onetrust-close-btn-container button{right:-12px}#onetrust-banner-sdk #onetrust-policy{margin-left:2em}#onetrust-banner-sdk.vertical-align-content #onetrust-button-group-parent{position:absolute;top:50%;left:60%;transform:translateY(-50%)}#onetrust-banner-sdk.ot-iab-2 #onetrust-policy-title{width:50%}#onetrust-banner-sdk.ot-iab-2 #onetrust-policy-text,#onetrust-banner-sdk.ot-iab-2 :not(.ot-dpd-desc)>.ot-b-addl-desc{margin-bottom:1em;width:50%;border-right:1px solid #d8d8d8;padding-right:1rem}#onetrust-banner-sdk.ot-iab-2 #onetrust-policy-text{margin-bottom:0;padding-bottom:1em}#onetrust-banner-sdk.ot-iab-2 :not(.ot-dpd-desc)>.ot-b-addl-desc{margin-bottom:0;padding-bottom:1em}#onetrust-banner-sdk.ot-iab-2 .ot-dpd-container{width:45%;padding-left:1rem;display:inline-block;float:none}#onetrust-banner-sdk.ot-iab-2 .ot-dpd-title{line-height:1.7}#onetrust-banner-sdk.ot-iab-2 #onetrust-button-group-parent{left:auto;right:4%;margin-left:0}#onetrust-banner-sdk.ot-iab-2 #onetrust-button-group button{display:block}#onetrust-banner-sdk:not(.ot-iab-2) #onetrust-button-group-parent{margin:auto;width:30%}#onetrust-banner-sdk:not(.ot-iab-2) #onetrust-group-container{width:60%}#onetrust-banner-sdk #onetrust-button-group{margin-right:auto}#onetrust-banner-sdk #onetrust-accept-btn-handler,#onetrust-banner-sdk #onetrust-reject-all-handler,#onetrust-banner-sdk #onetrust-pc-btn-handler{margin-top:1em}}@media only screen and (min-width:890px){#onetrust-banner-sdk.ot-buttons-fw:not(.ot-iab-2) #onetrust-button-group-parent{padding-left:3%;padding-right:4%;margin-left:0}#onetrust-banner-sdk.ot-buttons-fw:not(.ot-iab-2) #onetrust-button-group{margin-right:0;margin-top:1.25em;width:100%}#onetrust-banner-sdk.ot-buttons-fw:not(.ot-iab-2) #onetrust-button-group button{width:100%;margin-bottom:5px;margin-top:5px}#onetrust-banner-sdk.ot-buttons-fw:not(.ot-iab-2) #onetrust-button-group button:last-of-type{margin-bottom:20px}}@media only screen and (min-width:1280px){#onetrust-banner-sdk:not(.ot-iab-2) #onetrust-group-container{width:55%}#onetrust-banner-sdk:not(.ot-iab-2) #onetrust-button-group-parent{width:44%;padding-left:2%;padding-right:2%}#onetrust-banner-sdk:not(.ot-iab-2).vertical-align-content #onetrust-button-group-parent{position:absolute;left:55%}}#onetrust-consent-sdk #onetrust-banner-sdk{background-color:#191919}#onetrust-consent-sdk #onetrust-policy-title,#onetrust-consent-sdk #onetrust-policy-text,#onetrust-consent-sdk .ot-b-addl-desc,#onetrust-consent-sdk .ot-dpd-desc,#onetrust-consent-sdk .ot-dpd-title,#onetrust-consent-sdk #onetrust-policy-text *:not(.onetrust-vendors-list-handler),#onetrust-consent-sdk .ot-dpd-desc *:not(.onetrust-vendors-list-handler),#onetrust-consent-sdk #onetrust-banner-sdk #banner-options *,#onetrust-banner-sdk .ot-cat-header{color:#FFFFFF}#onetrust-consent-sdk #onetrust-banner-sdk .banner-option-details{background-color:#E9E9E9}#onetrust-consent-sdk #onetrust-banner-sdk a[href],#onetrust-consent-sdk #onetrust-banner-sdk a[href] font,#onetrust-consent-sdk #onetrust-banner-sdk .ot-link-btn{color:#FFFFFF}#onetrust-consent-sdk #onetrust-accept-btn-handler,#onetrust-banner-sdk #onetrust-reject-all-handler{background-color:#FDC301;border-color:#FDC301;color:#000000}#onetrust-consent-sdk #onetrust-banner-sdk *:focus,#onetrust-consent-sdk #onetrust-banner-sdk:focus{outline-color:#000000;outline-width:1px}#onetrust-consent-sdk #onetrust-pc-btn-handler,#onetrust-consent-sdk #onetrust-pc-btn-handler.cookie-setting-link{color:#191919;border-color:#191919;background-color:#FDC301}#onetrust-consent-sdk{font-family:Arial,sans-serif}#onetrust-consent-sdk #onetrust-policy-title{font-size:12pt;margin-bottom:5px}#onetrust-consent-sdk #onetrust-policy-text{margin-bottom:0px}#onetrust-consent-sdk #onetrust-pc-btn-handler.cookie-setting-link{color:#fff}@media only screen and (max-width:1024px){#onetrust-banner-sdk #onetrust-policy{margin-top:5px}}@media only screen and (max-width:425px){#onetrust-banner-sdk #onetrust-button-group{display:flex}}#onetrust-pc-sdk.otPcCenter{overflow:hidden;position:fixed;margin:0 auto;top:5%;right:0;left:0;width:40%;max-width:575px;min-width:575px;border-radius:2.5px;z-index:2147483647;background-color:#fff;-webkit-box-shadow:0px 2px 10px -3px #999;-moz-box-shadow:0px 2px 10px -3px #999;box-shadow:0px 2px 10px -3px #999}#onetrust-pc-sdk.otPcCenter[dir=rtl]{right:0;left:0}#onetrust-pc-sdk.otRelFont{font-size:1rem}#onetrust-pc-sdk #ot-addtl-venlst .ot-arw-cntr,#onetrust-pc-sdk #ot-addtl-venlst .ot-plus-minus,#onetrust-pc-sdk .ot-hide-tgl{visibility:hidden}#onetrust-pc-sdk #ot-addtl-venlst .ot-arw-cntr *,#onetrust-pc-sdk #ot-addtl-venlst .ot-plus-minus *,#onetrust-pc-sdk .ot-hide-tgl *{visibility:hidden}#onetrust-pc-sdk #ot-gn-venlst .ot-ven-item .ot-acc-hdr{min-height:40px}#onetrust-pc-sdk .ot-pc-header{height:39px;padding:10px 0 10px 30px;border-bottom:1px solid #e9e9e9}#onetrust-pc-sdk #ot-pc-title,#onetrust-pc-sdk #ot-category-title,#onetrust-pc-sdk .ot-cat-header,#onetrust-pc-sdk #ot-lst-title,#onetrust-pc-sdk .ot-ven-hdr .ot-ven-name,#onetrust-pc-sdk .ot-always-active{font-weight:bold;color:dimgray}#onetrust-pc-sdk .ot-cat-header{float:left;font-weight:600;font-size:.875em;line-height:1.5;max-width:90%;vertical-align:middle}#onetrust-pc-sdk .ot-always-active-group .ot-cat-header{width:55%;font-weight:700}#onetrust-pc-sdk .ot-cat-item p{clear:both;float:left;margin-top:10px;margin-bottom:5px;line-height:1.5;font-size:.812em;color:dimgray}#onetrust-pc-sdk .ot-close-icon{height:44px;width:44px;background-size:10px}#onetrust-pc-sdk #ot-pc-title{float:left;font-size:1em;line-height:1.5;margin-bottom:10px;margin-top:10px;width:100%}#onetrust-pc-sdk #accept-recommended-btn-handler{margin-right:10px;margin-bottom:25px;outline-offset:-1px}#onetrust-pc-sdk #ot-pc-desc{clear:both;width:100%;font-size:.812em;line-height:1.5;margin-bottom:25px}#onetrust-pc-sdk #ot-pc-desc a{margin-left:5px}#onetrust-pc-sdk #ot-pc-desc *{font-size:inherit;line-height:inherit}#onetrust-pc-sdk #ot-pc-desc ul li{padding:10px 0px}#onetrust-pc-sdk a{color:#656565;cursor:pointer}#onetrust-pc-sdk a:hover{color:#3860be}#onetrust-pc-sdk label{margin-bottom:0}#onetrust-pc-sdk #vdr-lst-dsc{font-size:.812em;line-height:1.5;padding:10px 15px 5px 15px}#onetrust-pc-sdk button{max-width:394px;padding:12px 30px;line-height:1;word-break:break-word;word-wrap:break-word;white-space:normal;font-weight:bold;height:auto}#onetrust-pc-sdk .ot-link-btn{padding:0;margin-bottom:0;border:0;font-weight:normal;line-height:normal;width:auto;height:auto}#onetrust-pc-sdk #ot-pc-content{position:absolute;overflow-y:scroll;padding-left:0px;padding-right:30px;top:60px;bottom:110px;margin:1px 3px 0 30px;width:calc(100% - 63px)}#onetrust-pc-sdk .ot-cat-grp .ot-always-active{float:right;clear:none;color:#3860be;margin:0;font-size:.813em;line-height:1.3}#onetrust-pc-sdk .ot-pc-scrollbar::-webkit-scrollbar-track{margin-right:20px}#onetrust-pc-sdk .ot-pc-scrollbar::-webkit-scrollbar{width:11px}#onetrust-pc-sdk .ot-pc-scrollbar::-webkit-scrollbar-thumb{border-radius:10px;background:#d8d8d8}#onetrust-pc-sdk input[type=checkbox]:focus+.ot-acc-hdr{outline:#000 1px solid}#onetrust-pc-sdk .ot-pc-scrollbar{scrollbar-arrow-color:#d8d8d8;scrollbar-darkshadow-color:#d8d8d8;scrollbar-face-color:#d8d8d8;scrollbar-shadow-color:#d8d8d8}#onetrust-pc-sdk .save-preference-btn-handler{margin-right:20px}#onetrust-pc-sdk .ot-pc-refuse-all-handler{margin-right:10px}#onetrust-pc-sdk #ot-pc-desc .privacy-notice-link{margin-left:0}#onetrust-pc-sdk .ot-subgrp-cntr{display:inline-block;clear:both;width:100%;padding-top:15px}#onetrust-pc-sdk .ot-switch+.ot-subgrp-cntr{padding-top:10px}#onetrust-pc-sdk ul.ot-subgrps{margin:0;font-size:initial}#onetrust-pc-sdk ul.ot-subgrps li p,#onetrust-pc-sdk ul.ot-subgrps li h5{font-size:.813em;line-height:1.4;color:dimgray}#onetrust-pc-sdk ul.ot-subgrps .ot-switch{min-height:auto}#onetrust-pc-sdk ul.ot-subgrps .ot-switch-nob{top:0}#onetrust-pc-sdk ul.ot-subgrps .ot-acc-hdr{display:inline-block;width:100%}#onetrust-pc-sdk ul.ot-subgrps .ot-acc-txt{margin:0}#onetrust-pc-sdk ul.ot-subgrps li{padding:0;border:none}#onetrust-pc-sdk ul.ot-subgrps li h5{position:relative;top:5px;font-weight:bold;margin-bottom:0;float:left}#onetrust-pc-sdk li.ot-subgrp{margin-left:20px;overflow:auto}#onetrust-pc-sdk li.ot-subgrp>h5{width:calc(100% - 100px)}#onetrust-pc-sdk .ot-cat-item p>ul,#onetrust-pc-sdk li.ot-subgrp p>ul{margin:0px;list-style:disc;margin-left:15px;font-size:inherit}#onetrust-pc-sdk .ot-cat-item p>ul li,#onetrust-pc-sdk li.ot-subgrp p>ul li{font-size:inherit;padding-top:10px;padding-left:0px;padding-right:0px;border:none}#onetrust-pc-sdk .ot-cat-item p>ul li:last-child,#onetrust-pc-sdk li.ot-subgrp p>ul li:last-child{padding-bottom:10px}#onetrust-pc-sdk .ot-pc-logo{height:40px;width:120px;display:inline-block}#onetrust-pc-sdk .ot-pc-footer{position:absolute;bottom:0px;width:100%;max-height:160px;border-top:1px solid #d8d8d8}#onetrust-pc-sdk.ot-ftr-stacked .ot-pc-refuse-all-handler{margin-bottom:0px}#onetrust-pc-sdk.ot-ftr-stacked #ot-pc-content{bottom:160px}#onetrust-pc-sdk.ot-ftr-stacked .ot-pc-footer button{width:100%;max-width:none}#onetrust-pc-sdk.ot-ftr-stacked .ot-btn-container{margin:0 30px;width:calc(100% - 60px);padding-right:0}#onetrust-pc-sdk .ot-pc-footer-logo{height:30px;width:100%;text-align:right;background:#f4f4f4}#onetrust-pc-sdk .ot-pc-footer-logo a{display:inline-block;margin-top:5px;margin-right:10px}#onetrust-pc-sdk[dir=rtl] .ot-pc-footer-logo{direction:rtl}#onetrust-pc-sdk[dir=rtl] .ot-pc-footer-logo a{margin-right:25px}#onetrust-pc-sdk .ot-tgl{float:right;position:relative;z-index:1}#onetrust-pc-sdk .ot-tgl input:checked+.ot-switch .ot-switch-nob{background-color:#cddcf2;border:1px solid #3860be}#onetrust-pc-sdk .ot-tgl input:checked+.ot-switch .ot-switch-nob:before{-webkit-transform:translateX(20px);-ms-transform:translateX(20px);transform:translateX(20px);background-color:#3860be;border-color:#3860be}#onetrust-pc-sdk .ot-tgl input:focus+.ot-switch{outline:#000 solid 1px}#onetrust-pc-sdk .ot-switch{position:relative;display:inline-block;width:45px;height:25px}#onetrust-pc-sdk .ot-switch-nob{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#f2f1f1;border:1px solid #ddd;transition:all .2s ease-in 0s;-moz-transition:all .2s ease-in 0s;-o-transition:all .2s ease-in 0s;-webkit-transition:all .2s ease-in 0s;border-radius:20px}#onetrust-pc-sdk .ot-switch-nob:before{position:absolute;content:"";height:21px;width:21px;bottom:1px;background-color:#7d7d7d;-webkit-transition:.4s;transition:.4s;border-radius:20px}#onetrust-pc-sdk .ot-chkbox input:checked~label::before{background-color:#3860be}#onetrust-pc-sdk .ot-chkbox input+label::after{content:none;color:#fff}#onetrust-pc-sdk .ot-chkbox input:checked+label::after{content:""}#onetrust-pc-sdk .ot-chkbox input:focus+label::before{outline-style:solid;outline-width:2px;outline-style:auto}#onetrust-pc-sdk .ot-chkbox label{position:relative;display:inline-block;padding-left:30px;cursor:pointer;font-weight:500}#onetrust-pc-sdk .ot-chkbox label::before,#onetrust-pc-sdk .ot-chkbox label::after{position:absolute;content:"";display:inline-block;border-radius:3px}#onetrust-pc-sdk .ot-chkbox label::before{height:18px;width:18px;border:1px solid #3860be;left:0px;top:auto}#onetrust-pc-sdk .ot-chkbox label::after{height:5px;width:9px;border-left:3px solid;border-bottom:3px solid;transform:rotate(-45deg);-o-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-webkit-transform:rotate(-45deg);left:4px;top:5px}#onetrust-pc-sdk .ot-label-txt{display:none}#onetrust-pc-sdk .ot-chkbox input,#onetrust-pc-sdk .ot-tgl input{position:absolute;opacity:0;width:0;height:0}#onetrust-pc-sdk .ot-arw-cntr{float:right;position:relative;pointer-events:none}#onetrust-pc-sdk .ot-arw-cntr .ot-arw{width:16px;height:16px;margin-left:5px;color:dimgray;display:inline-block;vertical-align:middle;-webkit-transition:all 150ms ease-in 0s;-moz-transition:all 150ms ease-in 0s;-o-transition:all 150ms ease-in 0s;transition:all 150ms ease-in 0s}#onetrust-pc-sdk input:checked~.ot-acc-hdr .ot-arw,#onetrust-pc-sdk button[aria-expanded=true]~.ot-acc-hdr .ot-arw-cntr svg{transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg);-webkit-transform:rotate(90deg)}#onetrust-pc-sdk input[type=checkbox]:focus+.ot-acc-hdr{outline:#000 1px solid}#onetrust-pc-sdk .ot-tgl-cntr,#onetrust-pc-sdk .ot-arw-cntr{display:inline-block}#onetrust-pc-sdk .ot-tgl-cntr{width:45px;float:right;margin-top:2px}#onetrust-pc-sdk #ot-lst-cnt .ot-tgl-cntr{margin-top:10px}#onetrust-pc-sdk .ot-always-active-subgroup{width:auto;padding-left:0px!important;top:3px;position:relative}#onetrust-pc-sdk .ot-label-status{padding-left:5px;font-size:.75em;display:none}#onetrust-pc-sdk .ot-arw-cntr{margin-top:-1px}#onetrust-pc-sdk .ot-arw-cntr svg{-webkit-transition:all 300ms ease-in 0s;-moz-transition:all 300ms ease-in 0s;-o-transition:all 300ms ease-in 0s;transition:all 300ms ease-in 0s;height:10px;width:10px}#onetrust-pc-sdk input:checked~.ot-acc-hdr .ot-arw{transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg);-webkit-transform:rotate(90deg)}#onetrust-pc-sdk .ot-arw{width:10px;margin-left:15px;transition:all 300ms ease-in 0s;-webkit-transition:all 300ms ease-in 0s;-moz-transition:all 300ms ease-in 0s;-o-transition:all 300ms ease-in 0s}#onetrust-pc-sdk .ot-vlst-cntr{margin-bottom:0}#onetrust-pc-sdk .ot-hlst-cntr{margin-top:5px;display:inline-block;width:100%}#onetrust-pc-sdk .category-vendors-list-handler,#onetrust-pc-sdk .category-vendors-list-handler+a,#onetrust-pc-sdk .category-host-list-handler{clear:both;color:#3860be;margin-left:0;font-size:.813em;text-decoration:none;float:left;overflow:hidden}#onetrust-pc-sdk .category-vendors-list-handler:hover,#onetrust-pc-sdk .category-vendors-list-handler+a:hover,#onetrust-pc-sdk .category-host-list-handler:hover{color:#1883fd}#onetrust-pc-sdk .category-vendors-list-handler+a{clear:none}#onetrust-pc-sdk .category-vendors-list-handler+a::after{content:"";height:15px;width:15px;background-repeat:no-repeat;margin-left:5px;float:right;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 511.626 511.627'%3E%3Cg fill='%231276CE'%3E%3Cpath d='M392.857 292.354h-18.274c-2.669 0-4.859.855-6.563 2.573-1.718 1.708-2.573 3.897-2.573 6.563v91.361c0 12.563-4.47 23.315-13.415 32.262-8.945 8.945-19.701 13.414-32.264 13.414H82.224c-12.562 0-23.317-4.469-32.264-13.414-8.945-8.946-13.417-19.698-13.417-32.262V155.31c0-12.562 4.471-23.313 13.417-32.259 8.947-8.947 19.702-13.418 32.264-13.418h200.994c2.669 0 4.859-.859 6.57-2.57 1.711-1.713 2.566-3.9 2.566-6.567V82.221c0-2.662-.855-4.853-2.566-6.563-1.711-1.713-3.901-2.568-6.57-2.568H82.224c-22.648 0-42.016 8.042-58.102 24.125C8.042 113.297 0 132.665 0 155.313v237.542c0 22.647 8.042 42.018 24.123 58.095 16.086 16.084 35.454 24.13 58.102 24.13h237.543c22.647 0 42.017-8.046 58.101-24.13 16.085-16.077 24.127-35.447 24.127-58.095v-91.358c0-2.669-.856-4.859-2.574-6.57-1.713-1.718-3.903-2.573-6.565-2.573z'/%3E%3Cpath d='M506.199 41.971c-3.617-3.617-7.905-5.424-12.85-5.424H347.171c-4.948 0-9.233 1.807-12.847 5.424-3.617 3.615-5.428 7.898-5.428 12.847s1.811 9.233 5.428 12.85l50.247 50.248-186.147 186.151c-1.906 1.903-2.856 4.093-2.856 6.563 0 2.479.953 4.668 2.856 6.571l32.548 32.544c1.903 1.903 4.093 2.852 6.567 2.852s4.665-.948 6.567-2.852l186.148-186.148 50.251 50.248c3.614 3.617 7.898 5.426 12.847 5.426s9.233-1.809 12.851-5.426c3.617-3.616 5.424-7.898 5.424-12.847V54.818c-.001-4.952-1.814-9.232-5.428-12.847z'/%3E%3C/g%3E%3C/svg%3E")}#onetrust-pc-sdk .back-btn-handler{font-size:1em;text-decoration:none}#onetrust-pc-sdk .back-btn-handler:hover{opacity:.6}#onetrust-pc-sdk #ot-lst-title h3{display:inline-block;word-break:break-word;word-wrap:break-word;margin-bottom:0;color:#656565;font-size:1em;font-weight:bold;margin-left:15px}#onetrust-pc-sdk #ot-lst-title{margin:10px 0 10px 0px;font-size:1em;text-align:left}#onetrust-pc-sdk #ot-pc-hdr{margin:0 0 0 30px;height:auto;width:auto}#onetrust-pc-sdk #ot-pc-hdr input::placeholder{color:#d4d4d4;font-style:italic}#onetrust-pc-sdk #vendor-search-handler{height:31px;width:100%;border-radius:50px;font-size:.8em;padding-right:35px;padding-left:15px;float:left;margin-left:15px}#onetrust-pc-sdk .ot-ven-name{display:block;width:auto;padding-right:5px}#onetrust-pc-sdk #ot-lst-cnt{overflow-y:auto;margin-left:20px;margin-right:7px;width:calc(100% - 27px);max-height:calc(100% - 80px);height:100%;transform:translate3d(0,0,0)}#onetrust-pc-sdk #ot-pc-lst{width:100%;bottom:100px;position:absolute;top:60px}#onetrust-pc-sdk #ot-pc-lst:not(.ot-enbl-chr) .ot-tgl-cntr .ot-arw-cntr,#onetrust-pc-sdk #ot-pc-lst:not(.ot-enbl-chr) .ot-tgl-cntr .ot-arw-cntr *{visibility:hidden}#onetrust-pc-sdk #ot-pc-lst .ot-tgl-cntr{right:12px;position:absolute}#onetrust-pc-sdk #ot-pc-lst .ot-arw-cntr{float:right;position:relative}#onetrust-pc-sdk #ot-pc-lst .ot-arw{margin-left:10px}#onetrust-pc-sdk #ot-pc-lst .ot-acc-hdr{overflow:hidden;cursor:pointer}#onetrust-pc-sdk .ot-vlst-cntr{overflow:hidden}#onetrust-pc-sdk #ot-sel-blk{overflow:hidden;width:100%;position:sticky;position:-webkit-sticky;top:0;z-index:3}#onetrust-pc-sdk #ot-back-arw{height:12px;width:12px}#onetrust-pc-sdk .ot-lst-subhdr{width:100%;display:inline-block}#onetrust-pc-sdk .ot-search-cntr{float:left;width:78%;position:relative}#onetrust-pc-sdk .ot-search-cntr>svg{width:30px;height:30px;position:absolute;float:left;right:-15px}#onetrust-pc-sdk .ot-fltr-cntr{float:right;right:50px;position:relative}#onetrust-pc-sdk #filter-btn-handler{background-color:#3860be;border-radius:17px;display:inline-block;position:relative;width:32px;height:32px;-moz-transition:.1s ease;-o-transition:.1s ease;-webkit-transition:1s ease;transition:.1s ease;padding:0;margin:0}#onetrust-pc-sdk #filter-btn-handler:hover{background-color:#3860be}#onetrust-pc-sdk #filter-btn-handler svg{width:12px;height:12px;margin:3px 10px 0 10px;display:block;position:static;right:auto;top:auto}#onetrust-pc-sdk .ot-ven-link{color:#3860be;text-decoration:none;font-weight:100;display:inline-block;padding-top:10px;transform:translate(0,1%);-o-transform:translate(0,1%);-ms-transform:translate(0,1%);-webkit-transform:translate(0,1%);position:relative;z-index:2}#onetrust-pc-sdk .ot-ven-link *{font-size:inherit}#onetrust-pc-sdk .ot-ven-link:hover{text-decoration:underline}#onetrust-pc-sdk .ot-ven-hdr{width:calc(100% - 160px);height:auto;float:left;word-break:break-word;word-wrap:break-word;vertical-align:middle;padding-bottom:3px}#onetrust-pc-sdk .ot-ven-link{letter-spacing:.03em;font-size:.75em;font-weight:400}#onetrust-pc-sdk .ot-ven-dets{border-radius:2px;background-color:#f8f8f8}#onetrust-pc-sdk .ot-ven-dets li:first-child p:first-child{border-top:none}#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc:not(:first-child){border-top:1px solid #e9e9e9}#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc:nth-child(n+3) p{display:inline-block}#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc:nth-child(n+3) p:nth-of-type(odd){width:30%}#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc:nth-child(n+3) p:nth-of-type(even){width:50%;word-break:break-word;word-wrap:break-word}#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc p,#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc h4{padding-top:5px;padding-bottom:5px;display:block}#onetrust-pc-sdk .ot-ven-dets .ot-ven-disc h4{display:inline-block}#onetrust-pc-sdk .ot-ven-dets p,#onetrust-pc-sdk .ot-ven-dets h4,#onetrust-pc-sdk .ot-ven-dets span{font-size:.69em;text-align:left;vertical-align:middle;word-break:break-word;word-wrap:break-word;margin:0;padding-bottom:10px;padding-left:15px;color:#2e3644}#onetrust-pc-sdk .ot-ven-dets h4{padding-top:5px}#onetrust-pc-sdk .ot-ven-dets span{color:dimgray;padding:0;vertical-align:baseline}#onetrust-pc-sdk .ot-ven-dets .ot-ven-pur h4{border-top:1px solid #e9e9e9;border-bottom:1px solid #e9e9e9;padding-bottom:5px;margin-bottom:5px;font-weight:bold}#onetrust-pc-sdk #ot-host-lst .ot-sel-all{float:right;position:relative;margin-right:42px;top:10px}#onetrust-pc-sdk #ot-host-lst .ot-sel-all input[type=checkbox]{width:auto;height:auto}#onetrust-pc-sdk #ot-host-lst .ot-sel-all label{height:20px;width:20px;padding-left:0px}#onetrust-pc-sdk #ot-host-lst .ot-acc-txt{overflow:hidden;width:95%}#onetrust-pc-sdk .ot-host-hdr{position:relative;z-index:1;pointer-events:none;width:calc(100% - 125px);float:left}#onetrust-pc-sdk .ot-host-name,#onetrust-pc-sdk .ot-host-desc{display:inline-block;width:90%}#onetrust-pc-sdk .ot-host-name{pointer-events:none}#onetrust-pc-sdk .ot-host-hdr>a{text-decoration:underline;font-size:.82em;position:relative;z-index:2;float:left;margin-bottom:5px;pointer-events:initial}#onetrust-pc-sdk .ot-host-name+a{margin-top:5px}#onetrust-pc-sdk .ot-host-name,#onetrust-pc-sdk .ot-host-name a,#onetrust-pc-sdk .ot-host-desc,#onetrust-pc-sdk .ot-host-info{color:dimgray;word-break:break-word;word-wrap:break-word}#onetrust-pc-sdk .ot-host-name,#onetrust-pc-sdk .ot-host-name a{font-weight:bold;font-size:.82em;line-height:1.3}#onetrust-pc-sdk .ot-host-name a{font-size:1em}#onetrust-pc-sdk .ot-host-expand{margin-top:3px;margin-bottom:3px;clear:both;display:block;color:#3860be;font-size:.72em;font-weight:normal}#onetrust-pc-sdk .ot-host-expand *{font-size:inherit}#onetrust-pc-sdk .ot-host-desc,#onetrust-pc-sdk .ot-host-info{font-size:.688em;line-height:1.4;font-weight:normal}#onetrust-pc-sdk .ot-host-desc{margin-top:10px}#onetrust-pc-sdk .ot-host-opt{margin:0;font-size:inherit;display:inline-block;width:100%}#onetrust-pc-sdk .ot-host-opt li>div div{font-size:.8em;padding:5px 0}#onetrust-pc-sdk .ot-host-opt li>div div:nth-child(1){width:30%;float:left}#onetrust-pc-sdk .ot-host-opt li>div div:nth-child(2){width:70%;float:left;word-break:break-word;word-wrap:break-word}#onetrust-pc-sdk .ot-host-info{border:none;display:inline-block;width:calc(100% - 10px);padding:10px;margin-bottom:10px;background-color:#f8f8f8}#onetrust-pc-sdk .ot-host-info>div{overflow:auto}#onetrust-pc-sdk #no-results{text-align:center;margin-top:30px}#onetrust-pc-sdk #no-results p{font-size:1em;color:#2e3644;word-break:break-word;word-wrap:break-word}#onetrust-pc-sdk #no-results p span{font-weight:bold}#onetrust-pc-sdk #ot-fltr-modal{width:100%;height:auto;display:none;-moz-transition:.2s ease;-o-transition:.2s ease;-webkit-transition:2s ease;transition:.2s ease;overflow:hidden;opacity:1;right:0}#onetrust-pc-sdk #ot-fltr-modal .ot-label-txt{display:inline-block;font-size:.85em;color:dimgray}#onetrust-pc-sdk #ot-fltr-cnt{z-index:2147483646;background-color:#fff;position:absolute;height:90%;max-height:300px;width:325px;left:210px;margin-top:10px;margin-bottom:20px;padding-right:10px;border-radius:3px;-webkit-box-shadow:0px 0px 12px 2px #c7c5c7;-moz-box-shadow:0px 0px 12px 2px #c7c5c7;box-shadow:0px 0px 12px 2px #c7c5c7}#onetrust-pc-sdk .ot-fltr-scrlcnt{overflow-y:auto;overflow-x:hidden;clear:both;max-height:calc(100% - 60px)}#onetrust-pc-sdk #ot-anchor{border:12px solid transparent;display:none;position:absolute;z-index:2147483647;right:55px;top:75px;transform:rotate(45deg);-o-transform:rotate(45deg);-ms-transform:rotate(45deg);-webkit-transform:rotate(45deg);background-color:#fff;-webkit-box-shadow:-3px -3px 5px -2px #c7c5c7;-moz-box-shadow:-3px -3px 5px -2px #c7c5c7;box-shadow:-3px -3px 5px -2px #c7c5c7}#onetrust-pc-sdk .ot-fltr-btns{margin-left:15px}#onetrust-pc-sdk #filter-apply-handler{margin-right:15px}#onetrust-pc-sdk .ot-fltr-opt{margin-bottom:25px;margin-left:15px;width:75%;position:relative}#onetrust-pc-sdk .ot-fltr-opt p{display:inline-block;margin:0;font-size:.9em;color:#2e3644}#onetrust-pc-sdk .ot-chkbox label span{font-size:.85em;color:dimgray}#onetrust-pc-sdk .ot-chkbox input[type=checkbox]+label::after{content:none;color:#fff}#onetrust-pc-sdk .ot-chkbox input[type=checkbox]:checked+label::after{content:""}#onetrust-pc-sdk .ot-chkbox input[type=checkbox]:focus+label::before{outline-style:solid;outline-width:2px;outline-style:auto}#onetrust-pc-sdk #ot-selall-vencntr,#onetrust-pc-sdk #ot-selall-adtlvencntr,#onetrust-pc-sdk #ot-selall-hostcntr,#onetrust-pc-sdk #ot-selall-licntr,#onetrust-pc-sdk #ot-selall-gnvencntr{right:15px;position:relative;width:20px;height:20px;float:right}#onetrust-pc-sdk #ot-selall-vencntr label,#onetrust-pc-sdk #ot-selall-adtlvencntr label,#onetrust-pc-sdk #ot-selall-hostcntr label,#onetrust-pc-sdk #ot-selall-licntr label,#onetrust-pc-sdk #ot-selall-gnvencntr label{float:left;padding-left:0}#onetrust-pc-sdk #ot-ven-lst:first-child{border-top:1px solid #e2e2e2}#onetrust-pc-sdk ul{list-style:none;padding:0}#onetrust-pc-sdk ul li{position:relative;margin:0;padding:15px 15px 15px 10px;border-bottom:1px solid #e2e2e2}#onetrust-pc-sdk ul li h3{font-size:.75em;color:#656565;margin:0;display:inline-block;width:70%;height:auto;word-break:break-word;word-wrap:break-word}#onetrust-pc-sdk ul li p{margin:0;font-size:.7em}#onetrust-pc-sdk ul li input[type=checkbox]{position:absolute;cursor:pointer;width:100%;height:100%;opacity:0;margin:0;top:0;left:0}#onetrust-pc-sdk .ot-cat-item>button:focus,#onetrust-pc-sdk .ot-acc-cntr>button:focus,#onetrust-pc-sdk li>button:focus{outline:#000 solid 2px}#onetrust-pc-sdk .ot-cat-item>button,#onetrust-pc-sdk .ot-acc-cntr>button,#onetrust-pc-sdk li>button{position:absolute;cursor:pointer;width:100%;height:100%;margin:0;top:0;left:0;z-index:1;max-width:none;border:none}#onetrust-pc-sdk .ot-cat-item>button[aria-expanded=false]~.ot-acc-txt,#onetrust-pc-sdk .ot-acc-cntr>button[aria-expanded=false]~.ot-acc-txt,#onetrust-pc-sdk li>button[aria-expanded=false]~.ot-acc-txt{margin-top:0;max-height:0;opacity:0;overflow:hidden;width:100%;transition:.25s ease-out;display:none}#onetrust-pc-sdk .ot-cat-item>button[aria-expanded=true]~.ot-acc-txt,#onetrust-pc-sdk .ot-acc-cntr>button[aria-expanded=true]~.ot-acc-txt,#onetrust-pc-sdk li>button[aria-expanded=true]~.ot-acc-txt{transition:.1s ease-in;margin-top:10px;width:100%;overflow:auto;display:block}#onetrust-pc-sdk .ot-cat-item>button[aria-expanded=true]~.ot-acc-grpcntr,#onetrust-pc-sdk .ot-acc-cntr>button[aria-expanded=true]~.ot-acc-grpcntr,#onetrust-pc-sdk li>button[aria-expanded=true]~.ot-acc-grpcntr{width:auto;margin-top:0px;padding-bottom:10px}#onetrust-pc-sdk .ot-host-item>button:focus,#onetrust-pc-sdk .ot-ven-item>button:focus{outline:0;border:2px solid #000}#onetrust-pc-sdk .ot-hide-acc>button{pointer-events:none}#onetrust-pc-sdk .ot-hide-acc .ot-plus-minus>*,#onetrust-pc-sdk .ot-hide-acc .ot-arw-cntr>*{visibility:hidden}#onetrust-pc-sdk .ot-hide-acc .ot-acc-hdr{min-height:30px}#onetrust-pc-sdk.ot-addtl-vendors #ot-lst-cnt:not(.ot-host-cnt){padding-right:10px;width:calc(100% - 37px);margin-top:10px;max-height:calc(100% - 90px)}#onetrust-pc-sdk.ot-addtl-vendors #ot-lst-cnt:not(.ot-host-cnt) #ot-sel-blk{background-color:#f9f9fc;border:1px solid #e2e2e2;width:calc(100% - 2px);padding-bottom:5px;padding-top:5px}#onetrust-pc-sdk.ot-addtl-vendors #ot-lst-cnt:not(.ot-host-cnt) .ot-sel-all{padding-right:34px}#onetrust-pc-sdk.ot-addtl-vendors #ot-lst-cnt:not(.ot-host-cnt) .ot-sel-all-chkbox{width:auto}#onetrust-pc-sdk.ot-addtl-vendors #ot-lst-cnt:not(.ot-host-cnt) ul li{border:1px solid #e2e2e2;margin-bottom:10px}#onetrust-pc-sdk.ot-addtl-vendors #ot-lst-cnt:not(.ot-host-cnt) .ot-acc-cntr>.ot-acc-hdr{padding:10px 0 10px 15px}#onetrust-pc-sdk.ot-addtl-vendors .ot-sel-all-chkbox{float:right}#onetrust-pc-sdk.ot-addtl-vendors .ot-plus-minus~.ot-sel-all-chkbox{right:34px}#onetrust-pc-sdk.ot-addtl-vendors #ot-ven-lst:first-child{border-top:none}#onetrust-pc-sdk .ot-acc-cntr{position:relative;border-left:1px solid #e2e2e2;border-right:1px solid #e2e2e2;border-bottom:1px solid #e2e2e2}#onetrust-pc-sdk .ot-acc-cntr input{z-index:1}#onetrust-pc-sdk .ot-acc-cntr>.ot-acc-hdr{background-color:#f9f9fc;padding:5px 0 5px 15px;width:auto}#onetrust-pc-sdk .ot-acc-cntr>.ot-acc-hdr .ot-plus-minus{vertical-align:middle;top:auto}#onetrust-pc-sdk .ot-acc-cntr>.ot-acc-hdr .ot-arw-cntr{right:10px}#onetrust-pc-sdk .ot-acc-cntr>.ot-acc-hdr input{z-index:2}#onetrust-pc-sdk .ot-acc-cntr>input[type=checkbox]:checked~.ot-acc-hdr{border-bottom:1px solid #e2e2e2}#onetrust-pc-sdk .ot-acc-cntr>.ot-acc-txt{padding-left:10px;padding-right:10px}#onetrust-pc-sdk .ot-acc-cntr button[aria-expanded=true]~.ot-acc-txt{width:auto}#onetrust-pc-sdk .ot-acc-cntr .ot-addtl-venbox{display:none}#onetrust-pc-sdk .ot-vlst-cntr{margin-bottom:0;width:100%}#onetrust-pc-sdk .ot-vensec-title{font-size:.813em;vertical-align:middle;display:inline-block}#onetrust-pc-sdk .category-vendors-list-handler,#onetrust-pc-sdk .category-vendors-list-handler+a{margin-left:0;margin-top:10px}#onetrust-pc-sdk #ot-selall-vencntr.line-through label::after,#onetrust-pc-sdk #ot-selall-adtlvencntr.line-through label::after,#onetrust-pc-sdk #ot-selall-licntr.line-through label::after,#onetrust-pc-sdk #ot-selall-hostcntr.line-through label::after,#onetrust-pc-sdk #ot-selall-gnvencntr.line-through label::after{height:auto;border-left:0;transform:none;-o-transform:none;-ms-transform:none;-webkit-transform:none;left:5px;top:9px}#onetrust-pc-sdk #ot-category-title{float:left;padding-bottom:10px;font-size:1em;width:100%}#onetrust-pc-sdk .ot-cat-grp{margin-top:10px}#onetrust-pc-sdk .ot-cat-item{line-height:1.1;margin-top:10px;display:inline-block;width:100%}#onetrust-pc-sdk .ot-btn-container{text-align:right}#onetrust-pc-sdk .ot-btn-container button{display:inline-block;font-size:.75em;letter-spacing:.08em;margin-top:19px}#onetrust-pc-sdk #close-pc-btn-handler.ot-close-icon{position:absolute;top:10px;right:0;z-index:1;padding:0;background-color:transparent;border:none}#onetrust-pc-sdk #close-pc-btn-handler.ot-close-icon:hover{opacity:.7}#onetrust-pc-sdk #close-pc-btn-handler.ot-close-icon svg{display:block;height:10px;width:10px}#onetrust-pc-sdk #clear-filters-handler{margin-top:20px;margin-bottom:10px;float:right;max-width:200px;text-decoration:none;color:#3860be;font-size:.9em;font-weight:bold;background-color:transparent;border-color:transparent;padding:1px}#onetrust-pc-sdk #clear-filters-handler:hover{color:#2285f7}#onetrust-pc-sdk #clear-filters-handler:focus{outline:#000 solid 1px}#onetrust-pc-sdk .ot-accordion-layout.ot-cat-item{position:relative;border-radius:2px;margin:0;padding:0;border:1px solid #d8d8d8;border-top:none;width:calc(100% - 2px);float:left}#onetrust-pc-sdk .ot-accordion-layout.ot-cat-item:first-of-type{margin-top:10px;border-top:1px solid #d8d8d8}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-grpdesc{padding-left:20px;padding-right:20px;width:calc(100% - 40px);font-size:.812em;margin-bottom:10px;margin-top:15px}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-grpdesc>ul{padding-top:10px}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-grpdesc>ul li{padding-top:0;line-height:1.5;padding-bottom:10px}#onetrust-pc-sdk .ot-accordion-layout div+.ot-acc-grpdesc{margin-top:5px}#onetrust-pc-sdk .ot-accordion-layout .ot-vlst-cntr:first-child{margin-top:10px}#onetrust-pc-sdk .ot-accordion-layout .ot-vlst-cntr:last-child,#onetrust-pc-sdk .ot-accordion-layout .ot-hlst-cntr:last-child{margin-bottom:5px}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-hdr{padding-top:11.5px;padding-bottom:11.5px;padding-left:20px;padding-right:20px;width:calc(100% - 40px);display:inline-block}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-txt{width:100%;padding:0px}#onetrust-pc-sdk .ot-accordion-layout .ot-subgrp-cntr{padding-left:20px;padding-right:15px;padding-bottom:0;width:calc(100% - 35px)}#onetrust-pc-sdk .ot-accordion-layout .ot-subgrp{padding-right:5px}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-grpcntr{z-index:1;position:relative}#onetrust-pc-sdk .ot-accordion-layout .ot-cat-header+.ot-arw-cntr{position:absolute;top:50%;transform:translateY(-50%);right:20px;margin-top:-2px}#onetrust-pc-sdk .ot-accordion-layout .ot-cat-header+.ot-arw-cntr .ot-arw{width:15px;height:20px;margin-left:5px;color:dimgray}#onetrust-pc-sdk .ot-accordion-layout .ot-cat-header{float:none;color:#2e3644;margin:0;display:inline-block;height:auto;word-wrap:break-word;min-height:inherit}#onetrust-pc-sdk .ot-accordion-layout .ot-vlst-cntr,#onetrust-pc-sdk .ot-accordion-layout .ot-hlst-cntr{padding-left:20px;width:calc(100% - 20px);display:inline-block;margin-top:0px;padding-bottom:2px}#onetrust-pc-sdk .ot-accordion-layout .ot-acc-hdr{position:relative;min-height:25px}#onetrust-pc-sdk .ot-accordion-layout h4~.ot-tgl,#onetrust-pc-sdk .ot-accordion-layout h4~.ot-always-active{position:absolute;top:50%;transform:translateY(-50%);right:20px}#onetrust-pc-sdk .ot-accordion-layout h4~.ot-tgl+.ot-tgl{right:95px}#onetrust-pc-sdk .ot-accordion-layout .category-vendors-list-handler,#onetrust-pc-sdk .ot-accordion-layout .category-vendors-list-handler+a{margin-top:5px}#onetrust-pc-sdk .ot-enbl-chr h4~.ot-tgl,#onetrust-pc-sdk .ot-enbl-chr h4~.ot-always-active{right:45px}#onetrust-pc-sdk .ot-enbl-chr h4~.ot-tgl+.ot-tgl{right:120px}#onetrust-pc-sdk .ot-enbl-chr .ot-pli-hdr.ot-leg-border-color span:first-child{width:90px}#onetrust-pc-sdk .ot-enbl-chr li.ot-subgrp>h5+.ot-tgl-cntr{padding-right:25px}#onetrust-pc-sdk .ot-plus-minus{width:20px;height:20px;font-size:1.5em;position:relative;display:inline-block;margin-right:5px;top:3px}#onetrust-pc-sdk .ot-plus-minus span{position:absolute;background:#27455c;border-radius:1px}#onetrust-pc-sdk .ot-plus-minus span:first-of-type{top:25%;bottom:25%;width:10%;left:45%}#onetrust-pc-sdk .ot-plus-minus span:last-of-type{left:25%;right:25%;height:10%;top:45%}#onetrust-pc-sdk button[aria-expanded=true]~.ot-acc-hdr .ot-arw,#onetrust-pc-sdk button[aria-expanded=true]~.ot-acc-hdr .ot-plus-minus span:first-of-type,#onetrust-pc-sdk button[aria-expanded=true]~.ot-acc-hdr .ot-plus-minus span:last-of-type{transform:rotate(90deg)}#onetrust-pc-sdk button[aria-expanded=true]~.ot-acc-hdr .ot-plus-minus span:last-of-type{left:50%;right:50%}#onetrust-pc-sdk #ot-selall-vencntr label,#onetrust-pc-sdk #ot-selall-adtlvencntr label,#onetrust-pc-sdk #ot-selall-hostcntr label,#onetrust-pc-sdk #ot-selall-licntr label{position:relative;display:inline-block;width:20px;height:20px}#onetrust-pc-sdk .ot-host-item .ot-plus-minus,#onetrust-pc-sdk .ot-ven-item .ot-plus-minus{float:left;margin-right:8px;top:10px}#onetrust-pc-sdk .ot-ven-item ul{list-style:none inside;font-size:100%;margin:0}#onetrust-pc-sdk .ot-ven-item ul li{margin:0!important;padding:0;border:none!important}#onetrust-pc-sdk .ot-pli-hdr{color:#77808e;overflow:hidden;padding-top:7.5px;padding-bottom:7.5px;width:calc(100% - 2px);border-top-left-radius:3px;border-top-right-radius:3px}#onetrust-pc-sdk .ot-pli-hdr span:first-child{top:50%;transform:translateY(50%);max-width:90px}#onetrust-pc-sdk .ot-pli-hdr span:last-child{padding-right:10px;max-width:95px;text-align:center}#onetrust-pc-sdk .ot-li-title{float:right;font-size:.813em}#onetrust-pc-sdk .ot-pli-hdr.ot-leg-border-color{background-color:#f4f4f4;border:1px solid #d8d8d8}#onetrust-pc-sdk .ot-pli-hdr.ot-leg-border-color span:first-child{text-align:left;width:70px}#onetrust-pc-sdk li.ot-subgrp>h5,#onetrust-pc-sdk .ot-cat-header{width:calc(100% - 130px)}#onetrust-pc-sdk li.ot-subgrp>h5+.ot-tgl-cntr{padding-left:13px}#onetrust-pc-sdk .ot-acc-grpcntr .ot-acc-grpdesc{margin-bottom:5px}#onetrust-pc-sdk .ot-acc-grpcntr .ot-subgrp-cntr{border-top:1px solid #d8d8d8}#onetrust-pc-sdk .ot-acc-grpcntr .ot-vlst-cntr+.ot-subgrp-cntr{border-top:none}#onetrust-pc-sdk .ot-acc-hdr .ot-arw-cntr+.ot-tgl-cntr,#onetrust-pc-sdk .ot-acc-txt h4+.ot-tgl-cntr{padding-left:13px}#onetrust-pc-sdk .ot-pli-hdr~.ot-cat-item .ot-subgrp>h5,#onetrust-pc-sdk .ot-pli-hdr~.ot-cat-item .ot-cat-header{width:calc(100% - 145px)}#onetrust-pc-sdk .ot-pli-hdr~.ot-cat-item h5+.ot-tgl-cntr,#onetrust-pc-sdk .ot-pli-hdr~.ot-cat-item .ot-cat-header+.ot-tgl{padding-left:28px}#onetrust-pc-sdk .ot-sel-all-hdr,#onetrust-pc-sdk .ot-sel-all-chkbox{display:inline-block;width:100%;position:relative}#onetrust-pc-sdk .ot-sel-all-chkbox{z-index:1}#onetrust-pc-sdk .ot-sel-all{margin:0;position:relative;padding-right:23px;float:right}#onetrust-pc-sdk .ot-consent-hdr,#onetrust-pc-sdk .ot-li-hdr{float:right;font-size:.812em;line-height:normal;text-align:center;word-break:break-word;word-wrap:break-word}#onetrust-pc-sdk .ot-li-hdr{max-width:100px;padding-right:10px}#onetrust-pc-sdk .ot-consent-hdr{max-width:55px}#onetrust-pc-sdk #ot-selall-licntr{display:block;width:21px;height:auto;float:right;position:relative;right:80px}#onetrust-pc-sdk #ot-selall-licntr label{position:absolute}#onetrust-pc-sdk .ot-ven-ctgl{margin-left:66px}#onetrust-pc-sdk .ot-ven-litgl+.ot-arw-cntr{margin-left:81px}#onetrust-pc-sdk .ot-enbl-chr .ot-host-cnt .ot-tgl-cntr{width:auto}#onetrust-pc-sdk #ot-lst-cnt:not(.ot-host-cnt) .ot-tgl-cntr{width:auto;top:auto;height:20px}#onetrust-pc-sdk #ot-lst-cnt .ot-chkbox{position:relative;display:inline-block;width:20px;height:20px}#onetrust-pc-sdk #ot-lst-cnt .ot-chkbox label{position:absolute;padding:0;width:20px;height:20px}#onetrust-pc-sdk .ot-acc-grpdesc+.ot-leg-btn-container{padding-left:20px;padding-right:20px;width:calc(100% - 40px);margin-bottom:5px}#onetrust-pc-sdk .ot-subgrp .ot-leg-btn-container{margin-bottom:5px}#onetrust-pc-sdk #ot-ven-lst .ot-leg-btn-container{margin-top:10px}#onetrust-pc-sdk .ot-leg-btn-container{display:inline-block;width:100%;margin-bottom:10px}#onetrust-pc-sdk .ot-leg-btn-container button{height:auto;padding:6.5px 8px;margin-bottom:0;letter-spacing:0;font-size:.75em;line-height:normal}#onetrust-pc-sdk .ot-leg-btn-container svg{display:none;height:14px;width:14px;padding-right:5px;vertical-align:sub}#onetrust-pc-sdk .ot-active-leg-btn{cursor:default;pointer-events:none}#onetrust-pc-sdk .ot-active-leg-btn svg{display:inline-block}#onetrust-pc-sdk .ot-remove-objection-handler{text-decoration:underline;padding:0;font-size:.75em;font-weight:600;line-height:1;padding-left:10px}#onetrust-pc-sdk .ot-obj-leg-btn-handler span{font-weight:bold;text-align:center;font-size:inherit;line-height:1.5}#onetrust-pc-sdk.ot-close-btn-link #close-pc-btn-handler{border:none;height:auto;line-height:1.5;text-decoration:underline;font-size:.69em;background:none;right:15px;top:15px;width:auto;font-weight:normal}#onetrust-pc-sdk[dir=rtl] #ot-back-arw,#onetrust-pc-sdk[dir=rtl] input~.ot-acc-hdr .ot-arw{transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);-webkit-transform:rotate(180deg)}#onetrust-pc-sdk[dir=rtl] input:checked~.ot-acc-hdr .ot-arw{transform:rotate(270deg);-o-transform:rotate(270deg);-ms-transform:rotate(270deg);-webkit-transform:rotate(270deg)}#onetrust-pc-sdk[dir=rtl] .ot-chkbox label::after{transform:rotate(45deg);-webkit-transform:rotate(45deg);-o-transform:rotate(45deg);-ms-transform:rotate(45deg);border-left:0;border-right:3px solid}#onetrust-pc-sdk[dir=rtl] .ot-search-cntr>svg{right:0}@media only screen and (max-width:600px){#onetrust-pc-sdk.otPcCenter{left:0;min-width:100%;height:100%;top:0;border-radius:0}#onetrust-pc-sdk #ot-pc-content,#onetrust-pc-sdk.ot-ftr-stacked .ot-btn-container{margin:1px 3px 0 10px;padding-right:10px;width:calc(100% - 23px)}#onetrust-pc-sdk .ot-btn-container button{max-width:none;letter-spacing:.01em}#onetrust-pc-sdk #close-pc-btn-handler{top:10px;right:17px}#onetrust-pc-sdk p{font-size:.7em}#onetrust-pc-sdk #ot-pc-hdr{margin:10px 10px 0 5px;width:calc(100% - 15px)}#onetrust-pc-sdk .vendor-search-handler{font-size:1em}#onetrust-pc-sdk #ot-back-arw{margin-left:12px}#onetrust-pc-sdk #ot-lst-cnt{margin:0;padding:0 5px 0 10px;min-width:95%}#onetrust-pc-sdk .switch+p{max-width:80%}#onetrust-pc-sdk .ot-ftr-stacked button{width:100%}#onetrust-pc-sdk #ot-fltr-cnt{max-width:320px;width:90%;border-top-right-radius:0;border-bottom-right-radius:0;margin:0;margin-left:15px;left:auto;right:40px;top:85px}#onetrust-pc-sdk .ot-fltr-opt{margin-left:25px;margin-bottom:10px}#onetrust-pc-sdk .ot-pc-refuse-all-handler{margin-bottom:0}#onetrust-pc-sdk #ot-fltr-cnt{right:40px}}@media only screen and (max-width:476px){#onetrust-pc-sdk .ot-fltr-cntr,#onetrust-pc-sdk #ot-fltr-cnt{right:10px}#onetrust-pc-sdk #ot-anchor{right:25px}#onetrust-pc-sdk button{width:100%}#onetrust-pc-sdk:not(.ot-addtl-vendors) #ot-pc-lst:not(.ot-enbl-chr) .ot-sel-all{padding-right:9px}#onetrust-pc-sdk:not(.ot-addtl-vendors) #ot-pc-lst:not(.ot-enbl-chr) .ot-tgl-cntr{right:0}}@media only screen and (max-width:896px)and (max-height:425px)and (orientation:landscape){#onetrust-pc-sdk.otPcCenter{left:0;top:0;min-width:100%;height:100%;border-radius:0}#onetrust-pc-sdk #ot-anchor{left:initial;right:50px}#onetrust-pc-sdk #ot-lst-title{margin-top:12px}#onetrust-pc-sdk #ot-lst-title *{font-size:inherit}#onetrust-pc-sdk #ot-pc-hdr input{margin-right:0;padding-right:45px}#onetrust-pc-sdk .switch+p{max-width:85%}#onetrust-pc-sdk #ot-sel-blk{position:static}#onetrust-pc-sdk #ot-pc-lst{overflow:auto}#onetrust-pc-sdk #ot-lst-cnt{max-height:none;overflow:initial}#onetrust-pc-sdk #ot-lst-cnt.no-results{height:auto}#onetrust-pc-sdk input{font-size:1em!important}#onetrust-pc-sdk p{font-size:.6em}#onetrust-pc-sdk #ot-fltr-modal{width:100%;top:0}#onetrust-pc-sdk ul li p,#onetrust-pc-sdk .category-vendors-list-handler,#onetrust-pc-sdk .category-vendors-list-handler+a,#onetrust-pc-sdk .category-host-list-handler{font-size:.6em}#onetrust-pc-sdk.ot-shw-fltr #ot-anchor{display:none!important}#onetrust-pc-sdk.ot-shw-fltr #ot-pc-lst{height:100%!important;overflow:hidden;top:0px}#onetrust-pc-sdk.ot-shw-fltr #ot-fltr-cnt{margin:0;height:100%;max-height:none;padding:10px;top:0;width:calc(100% - 20px);position:absolute;right:0;left:0;max-width:none}#onetrust-pc-sdk.ot-shw-fltr .ot-fltr-scrlcnt{max-height:calc(100% - 65px)}}#onetrust-consent-sdk #onetrust-pc-sdk,#onetrust-consent-sdk #ot-search-cntr,#onetrust-consent-sdk #onetrust-pc-sdk .ot-switch.ot-toggle,#onetrust-consent-sdk #onetrust-pc-sdk ot-grp-hdr1 .checkbox,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-title:after,#onetrust-consent-sdk #onetrust-pc-sdk #ot-sel-blk,#onetrust-consent-sdk #onetrust-pc-sdk #ot-fltr-cnt,#onetrust-consent-sdk #onetrust-pc-sdk #ot-anchor{background-color:#FFFFFF}#onetrust-consent-sdk #onetrust-pc-sdk h3,#onetrust-consent-sdk #onetrust-pc-sdk h4,#onetrust-consent-sdk #onetrust-pc-sdk h5,#onetrust-consent-sdk #onetrust-pc-sdk h6,#onetrust-consent-sdk #onetrust-pc-sdk p,#onetrust-consent-sdk #onetrust-pc-sdk #ot-ven-lst .ot-ven-opts p,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-desc,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-title,#onetrust-consent-sdk #onetrust-pc-sdk .ot-li-title,#onetrust-consent-sdk #onetrust-pc-sdk .ot-sel-all-hdr span,#onetrust-consent-sdk #onetrust-pc-sdk #ot-host-lst .ot-host-info,#onetrust-consent-sdk #onetrust-pc-sdk #ot-fltr-modal #modal-header,#onetrust-consent-sdk #onetrust-pc-sdk .ot-checkbox label span,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-lst #ot-sel-blk p,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-lst #ot-lst-title h3,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-lst .back-btn-handler p,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-lst .ot-ven-name,#onetrust-consent-sdk #onetrust-pc-sdk #ot-pc-lst #ot-ven-lst .consent-category,#onetrust-consent-sdk #onetrust-pc-sdk .ot-leg-btn-container .ot-inactive-leg-btn,#onetrust-consent-sdk #onetrust-pc-sdk .ot-label-status,#onetrust-consent-sdk #onetrust-pc-sdk .ot-chkbox label span,#onetrust-consent-sdk #onetrust-pc-sdk #clear-filters-handler{color:#696969}#onetrust-consent-sdk #onetrust-pc-sdk .privacy-notice-link,#onetrust-consent-sdk #onetrust-pc-sdk .category-vendors-list-handler,#onetrust-consent-sdk #onetrust-pc-sdk .category-vendors-list-handler+a,#onetrust-consent-sdk #onetrust-pc-sdk .category-host-list-handler,#onetrust-consent-sdk #onetrust-pc-sdk .ot-ven-link,#onetrust-consent-sdk #onetrust-pc-sdk #ot-host-lst .ot-host-name a,#onetrust-consent-sdk #onetrust-pc-sdk #ot-host-lst .ot-acc-hdr .ot-host-expand,#onetrust-consent-sdk #onetrust-pc-sdk #ot-host-lst .ot-host-info a{color:#3860BE}#onetrust-consent-sdk #onetrust-pc-sdk .category-vendors-list-handler:hover{opacity:.7}#onetrust-consent-sdk #onetrust-pc-sdk .ot-acc-grpcntr.ot-acc-txt,#onetrust-consent-sdk #onetrust-pc-sdk .ot-acc-txt .ot-subgrp-tgl .ot-switch.ot-toggle{background-color:#F8F8F8}#onetrust-consent-sdk #onetrust-pc-sdk #ot-host-lst .ot-host-info,#onetrust-consent-sdk #onetrust-pc-sdk .ot-acc-txt .ot-ven-dets{background-color:#F8F8F8}#onetrust-consent-sdk #onetrust-pc-sdk button:not(#clear-filters-handler):not(.ot-close-icon):not(#filter-btn-handler):not(.ot-remove-objection-handler):not(.ot-obj-leg-btn-handler):not([aria-expanded]):not(.ot-link-btn),#onetrust-consent-sdk #onetrust-pc-sdk .ot-leg-btn-container .ot-active-leg-btn{background-color:#FDC301;border-color:#FDC301;color:#0e100e}#onetrust-consent-sdk #onetrust-pc-sdk .ot-active-menu{border-color:#FDC301}#onetrust-consent-sdk #onetrust-pc-sdk .ot-leg-btn-container .ot-remove-objection-handler{background-color:transparent;border:1px solid transparent}#onetrust-consent-sdk #onetrust-pc-sdk .ot-leg-btn-container .ot-inactive-leg-btn{background-color:#FFFFFF;color:#78808E;border-color:#78808E}#onetrust-consent-sdk #onetrust-pc-sdk .ot-tgl input:focus+.ot-switch,.ot-switch .ot-switch-nob,.ot-switch .ot-switch-nob:before,#onetrust-pc-sdk .ot-checkbox input[type="checkbox"]:focus+label::before,#onetrust-pc-sdk .ot-chkbox input[type="checkbox"]:focus+label::before{outline-color:#000000;outline-width:1px}#onetrust-pc-sdk .ot-host-item>button:focus,#onetrust-pc-sdk .ot-ven-item>button:focus{border:1px solid #000000}#onetrust-consent-sdk #onetrust-pc-sdk *:focus,#onetrust-consent-sdk #onetrust-pc-sdk .ot-vlst-cntr>a:focus{outline:1px solid #000000}#onetrust-consent-sdk{font-family:Arial,sans-serif}#onetrust-pc-sdk .ot-always-active{font-size:14px;font-weight:bold;color:#09a501}#onetrust-consent-sdk #onetrust-pc-sdk .active-group{border-color:#FFFFFF}#onetrust-pc-sdk .ot-button-group button{font-weight:bold}#onetrust-consent-sdk #onetrust-pc-sdk .ot-button-group .onetrust-close-btn-handler{background-color:#d8d8d8!important;border-color:#d8d8d8!important}#onetrust-consent-sdk h3{font-size:14px!important}#onetrust-pc-sdk .pc-header{padding:10px}#onetrust-pc-sdk .ot-toggle .checkbox input:checked+label:after{background:#6cc04a}#onetrust-pc-sdk #pc-policy-text a{color:inherit}#onetrust-pc-sdk #close-pc-btn-handler.ot-close-icon{display:none}#onetrust-pc-sdk .group-description ul{font-size:inherit}#onetrust-pc-sdk .group-description ul li{list-style:none}.ot-sdk-cookie-policy{font-family:inherit;font-size:16px}.ot-sdk-cookie-policy.otRelFont{font-size:1rem}.ot-sdk-cookie-policy h3,.ot-sdk-cookie-policy h4,.ot-sdk-cookie-policy h6,.ot-sdk-cookie-policy p,.ot-sdk-cookie-policy li,.ot-sdk-cookie-policy a,.ot-sdk-cookie-policy th,.ot-sdk-cookie-policy #cookie-policy-description,.ot-sdk-cookie-policy .ot-sdk-cookie-policy-group,.ot-sdk-cookie-policy #cookie-policy-title{color:dimgray}.ot-sdk-cookie-policy #cookie-policy-description{margin-bottom:1em}.ot-sdk-cookie-policy h4{font-size:1.2em}.ot-sdk-cookie-policy h6{font-size:1em;margin-top:2em}.ot-sdk-cookie-policy th{min-width:75px}.ot-sdk-cookie-policy a,.ot-sdk-cookie-policy a:hover{background:#fff}.ot-sdk-cookie-policy thead{background-color:#f6f6f4;font-weight:bold}.ot-sdk-cookie-policy .ot-mobile-border{display:none}.ot-sdk-cookie-policy section{margin-bottom:2em}.ot-sdk-cookie-policy table{border-collapse:inherit}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy{font-family:inherit;font-size:1rem}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy h3,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy h4,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy h6,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy p,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy li,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy a,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy th,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy #cookie-policy-description,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-cookie-policy-group,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy #cookie-policy-title{color:dimgray}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy #cookie-policy-description{margin-bottom:1em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-subgroup{margin-left:1.5em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy #cookie-policy-description,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-cookie-policy-group-desc,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-table-header,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy a,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy span,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td{font-size:.9em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td span,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td a{font-size:inherit}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-cookie-policy-group{font-size:1em;margin-bottom:.6em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-cookie-policy-title{margin-bottom:1.2em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy>section{margin-bottom:1em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy th{min-width:75px}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy a,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy a:hover{background:#fff}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy thead{background-color:#f6f6f4;font-weight:bold}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-mobile-border{display:none}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy section{margin-bottom:2em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-subgroup ul li{list-style:disc;margin-left:1.5em}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-subgroup ul li h4{display:inline-block}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table{border-collapse:inherit;margin:auto;border:1px solid #d7d7d7;border-radius:5px;border-spacing:initial;width:100%;overflow:hidden}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table th,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table td{border-bottom:1px solid #d7d7d7;border-right:1px solid #d7d7d7}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table tr:last-child td{border-bottom:0px}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table tr th:last-child,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table tr td:last-child{border-right:0px}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table .ot-host,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table .ot-cookies-type{width:25%}.ot-sdk-cookie-policy[dir=rtl]{text-align:left}#ot-sdk-cookie-policy h3{font-size:1.5em}@media only screen and (max-width:530px){.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) table,.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) thead,.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) tbody,.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) th,.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) td,.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) tr{display:block}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) thead tr{position:absolute;top:-9999px;left:-9999px}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) tr{margin:0 0 1em 0}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) tr:nth-child(odd),.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) tr:nth-child(odd) a{background:#f6f6f4}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) td:before{position:absolute;height:100%;left:6px;width:40%;padding-right:10px}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) .ot-mobile-border{display:inline-block;background-color:#e4e4e4;position:absolute;height:100%;top:0;left:45%;width:2px}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) td:before{content:attr(data-label);font-weight:bold}.ot-sdk-cookie-policy:not(#ot-sdk-cookie-policy-v2) li{word-break:break-word;word-wrap:break-word}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table{overflow:hidden}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table td{border:none;border-bottom:1px solid #d7d7d7}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy thead,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy tbody,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy th,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy tr{display:block}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table .ot-host,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table .ot-cookies-type{width:auto}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy tr{margin:0 0 1em 0}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td:before{height:100%;width:40%;padding-right:10px}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td:before{content:attr(data-label);font-weight:bold}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy li{word-break:break-word;word-wrap:break-word}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy thead tr{position:absolute;top:-9999px;left:-9999px;z-index:-9999}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table tr:last-child td{border-bottom:1px solid #d7d7d7;border-right:0px}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table tr:last-child td:last-child{border-bottom:0px}}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy h5,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy h6,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy li,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy p,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy a,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy span,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy td,#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy #cookie-policy-description{color:#696969}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy th{color:#696969}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy .ot-sdk-cookie-policy-group{color:#696969}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy #cookie-policy-title{color:#696969}#ot-sdk-cookie-policy-v2.ot-sdk-cookie-policy table th{background-color:#F8F8F8}.ot-floating-button__front{background-image:url(resources/sample/11.png)}#ot-sdk-btn-floating.ot-floating-button{position:fixed;bottom:10px;opacity:0;width:50px;height:50px;line-height:15px;cursor:pointer;background-color:transparent;transform-style:preserve-3d;transition:all 300ms ease;perspective:1000px;z-index:2147483646;animation:otFloatingBtnIntro 800ms ease 0ms 1 forwards}#ot-sdk-btn-floating.ot-floating-button.ot-hide{display:none}#ot-sdk-btn-floating.ot-floating-button::before,#ot-sdk-btn-floating.ot-floating-button::after{text-transform:none;line-height:1;user-select:none;pointer-events:none;position:absolute;transform:scale(0);opacity:0;transition:all 300ms ease;display:block;height:auto}#ot-sdk-btn-floating.ot-floating-button::before{content:"";border:5px solid transparent;z-index:1001;top:50%;border-left-width:0;border-right-color:#333;right:calc(0em - 5px);transform:translate(10px,-50%)}#ot-sdk-btn-floating.ot-floating-button::after{content:attr(title);position:absolute;text-align:center;top:50%;left:calc(100% + 5px);transform:translate(10px,-50%);font-size:.75rem;min-width:3em;max-width:21em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:5px;border-radius:.3ch;box-shadow:0 1em 2em -0.5em rgba(0,0,0,.35);background:#333;color:#fff;z-index:2147483645}#ot-sdk-btn-floating.ot-floating-button:hover::before,#ot-sdk-btn-floating.ot-floating-button:hover::after{opacity:1}#ot-sdk-btn-floating.ot-floating-button:hover::before{transform:translate(0.5em,-50%) scale(1)}#ot-sdk-btn-floating.ot-floating-button:hover::after{transform:translate(0.5em,-50%) scale(1)}#ot-sdk-btn-floating.ot-floating-button.ot-pc-open .ot-floating-button__front{transform:rotateY(-180deg)}#ot-sdk-btn-floating.ot-floating-button.ot-pc-open .ot-floating-button__back{transform:rotateY(0)}#ot-sdk-btn-floating .ot-floating-button__front,#ot-sdk-btn-floating .ot-floating-button__back{position:absolute;width:100%;height:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:#6aaae4;border-radius:10px;box-shadow:0 4px 8px 0 rgba(0,0,0,.2);transition:transform .6s;transform-style:preserve-3d}#ot-sdk-btn-floating .ot-floating-button__front{background-color:#6aaae4;transform:rotateY(0)}#ot-sdk-btn-floating .ot-floating-button__front.custom-persistent-icon{background-position:center center;background-repeat:no-repeat;background-size:100%;border-radius:100px}#ot-sdk-btn-floating .ot-floating-button__front svg{width:30px;height:37px}#ot-sdk-btn-floating .ot-floating-button__back{background-color:#69c;transform:rotateY(-180deg)}#ot-sdk-btn-floating .ot-floating-button__back.custom-persistent-icon{background-position:center center;background-repeat:no-repeat;background-size:100%;border-radius:100px}#ot-sdk-btn-floating .ot-floating-button__back svg{width:24px;height:24px}#ot-sdk-btn-floating.ot-floating-button button{background-color:transparent;border:0;width:100%;height:100%;cursor:pointer}@keyframes otFloatingBtnIntro{0%{opacity:0;left:-75px}100%{opacity:1;left:1%}}@keyframes otFloatingBtnImageIntro{0%{opacity:0;transform:scale(0) rotate(-270deg)}100%{opacity:100%;transform:scale(0.95) rotate(0deg)}}</style><link type="image/x-icon" rel="shortcut icon" href="resources/sample/12.gif"><link rel="canonical" href="https://www.asda.com/account?request_origin=asda&amp;redirect_uri=https%3A%2F%2Fwww.asda.com%2Fgood-living%2Ftag%2Frecipes"><meta http-equiv="content-security-policy" content="default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;"></head><body><div id="app"><main class="theme-ghs" style><div class="app"><header><div class="header-container"><a id="logo" href="https://www.asda.com/" target="_self" tabindex="1" rel="noreferrer"><img src="resources/sample/13.svg" alt="Asda.com homepage"></a><nav><button id="need-help" class="basic need-help" type="button" tabindex="2" aria-live="polite">Need help?</button><button id="back-shop" class="basic back-shop" type="button" tabindex="3" aria-live="polite">Home</button></nav></div></header><div class="co-account"><h1 class="co-account__title">Account settings</h1><h4 class="co-account__description">Select a section below to view and edit your details</h4><div class="panel "><button class="panel__header "><h3 class="panel__title">Personal details</h3><span class="panel__toggle">View</span></button></div><div class="panel "><button class="panel__header "><h3 class="panel__title">Sign in details</h3><span class="panel__toggle">View</span></button></div><div class="acc-block"><div class="panel address-phone-book "><button class="panel__header "><h3 class="panel__title">Address &amp; phone book</h3><span class="panel__toggle">View</span></button></div></div><div class="wallet-card"><div class="panel open"><button class="panel__header add-bottom-border"><h3 class="panel__title">Wallet</h3><span class="panel__toggle">Close</span></button><main class="panel__main"><h3 class="empty-header-name">Payment Cards</h3><div class="co-add-card"><div class="custom-icon input-box"><label for="card-number">Card number</label><div class="input-box__wrapper false"><input type="tel" class=" error" placeholder spellcheck="false" autocomplete="off" maxlength="255" id="card-number" tabindex="0" aria-label="Card number" value data-fathom="cc-number"><img src="resources/sample/14.svg" alt="alert" class="alert"></div><div class="input-error" role="alert">Sorry, that's not a valid card number</div></div><div class="input-box"><label for="add-card-name">Name on card:</label><div class="input-box__wrapper false"><input type="text" class=" error" placeholder spellcheck="false" autocomplete="off" maxlength="255" id="add-card-name" tabindex="0" aria-label="Name on card:" value data-fathom="cc-name"><img src="resources/sample/14.svg" alt="alert" class="alert"></div><div class="input-error" role="alert">We do not recognise that name</div></div><div class="input-box"><label for="add-card-expiry">Expiry date</label><div class="input-box__wrapper false"><input type="tel" class=" error" placeholder="MM/YY" spellcheck="false" autocomplete="off" maxlength="5" id="add-card-expiry" tabindex="0" aria-label="Expiry date" value data-fathom="cc-exp"><img src="resources/sample/14.svg" alt="alert" class="alert"></div><div class="input-error" role="alert">Enter expiry date</div></div><h4 class="address-form-title">Search for your postcode or <button type="button" class="link-toggle">type out address in full</button></h4><div class="half input-box"><label for="account-postcode-finder">Your postcode*</label><div class="input-box__wrapper false"><input type="text" class="half-width error" placeholder spellcheck="false" autocomplete="off" maxlength="255" id="account-postcode-finder" tabindex="0" aria-label="Your postcode*" value data-fathom="zip"><img src="resources/sample/14.svg" alt="alert" class="alert"></div><div class="input-error" role="alert">Enter your postcode</div></div><div class="half last input-box"><label for="account-postcode-finder-house">House number or name</label><div class="input-box__wrapper false"><input type="text" class="half-width " placeholder spellcheck="false" autocomplete="off" maxlength="255" id="account-postcode-finder-house" tabindex="0" aria-label="House number or name" value></div></div><button id class="secondary full find-button" type="button" tabindex="0" aria-live="polite">Find</button></div></main></div></div><div class="permission-centre"><div class="panel "><button class="panel__header "><h3 class="panel__title">Permissions</h3><span class="panel__toggle">View</span></button></div></div></div><footer><div class="footer-wrapper"><div class="footer-top"><a class="website-feedback-link" href="https://ghs-optin-issues.asda.com/" target="_blank" rel="noopener noreferrer"><img class="feedback-icon" src="resources/sample/15.svg" alt="feedback icon"><span>Website Feedback</span></a></div><div class="footer-main"><div class="footer-logo"><img src="resources/sample/16.svg" alt="Asda"></div><div class="footer-links"><ul><li>© ASDA 2022</li><li><a href="https://groceries.asda.com/terms-and-conditions" target="_blank" rel="noopener noreferrer">Terms &amp; Conditions</a></li><li><a href="https://www.asda.com/privacy" target="_blank" rel="noopener noreferrer">Privacy Centre</a></li><li><a href="https://www.asda.com/help/company-details" target="_blank" rel="noopener noreferrer">Asda Company Details</a></li></ul></div></div></div></footer></div></main></div><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-NHVQ6SB" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript><iframe style="display:none;visibility:hidden" sandbox="allow-popups allow-top-navigation allow-top-navigation-by-user-activation" srcdoc="<!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;> <html><head><meta charset=&quot;utf-8&quot;><title></title><meta http-equiv=&quot;content-security-policy&quot; content=&quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&quot;></head><body style=&quot;background-color:transparent&quot;><iframe style=&quot;display:none&quot; sandbox=&quot;allow-popups allow-top-navigation allow-top-navigation-by-user-activation&quot; srcdoc=&quot;<!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD HTML 4.01 Transitional//EN&amp;quot; &amp;quot;http://www.w3.org/TR/html4/loose.dtd&amp;quot;> <html><head><meta charset=&amp;quot;utf-8&amp;quot;><title></title><meta http-equiv=&amp;quot;content-security-policy&amp;quot; content=&amp;quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&amp;quot;></head><body style=&amp;quot;background-color:transparent&amp;quot;><iframe style=&amp;quot;display:none&amp;quot; sandbox=&amp;quot;allow-popups allow-top-navigation allow-top-navigation-by-user-activation&amp;quot; srcdoc=&amp;quot;<!DOCTYPE html PUBLIC &amp;amp;quot;-//W3C//DTD HTML 4.01 Transitional//EN&amp;amp;quot; &amp;amp;quot;http://www.w3.org/TR/html4/loose.dtd&amp;amp;quot;> <html><head><meta charset=&amp;amp;quot;utf-8&amp;amp;quot;><title></title><meta http-equiv=&amp;amp;quot;content-security-policy&amp;amp;quot; content=&amp;amp;quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&amp;amp;quot;></head><body style=&amp;amp;quot;background-color:transparent&amp;amp;quot;></body></html>&amp;quot; width=&amp;quot;1&amp;quot; height=&amp;quot;1&amp;quot; frameborder=&amp;quot;0&amp;quot;></iframe></body></html>&quot; width=&quot;1&quot; height=&quot;1&quot; frameborder=&quot;0&quot;></iframe></body></html>" width="0" height="0"></iframe><iframe style="display:none;visibility:hidden" sandbox="allow-popups allow-top-navigation allow-top-navigation-by-user-activation" srcdoc="<!DOCTYPE html PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;> <html><head><meta charset=&quot;utf-8&quot;><title></title><meta http-equiv=&quot;content-security-policy&quot; content=&quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&quot;></head><body style=&quot;background-color:transparent&quot;><iframe style=&quot;display:none&quot; sandbox=&quot;allow-popups allow-top-navigation allow-top-navigation-by-user-activation&quot; srcdoc=&quot;<!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD HTML 4.01 Transitional//EN&amp;quot; &amp;quot;http://www.w3.org/TR/html4/loose.dtd&amp;quot;> <html><head><meta charset=&amp;quot;utf-8&amp;quot;><title></title><meta http-equiv=&amp;quot;content-security-policy&amp;quot; content=&amp;quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&amp;quot;></head><body style=&amp;quot;background-color:transparent&amp;quot;><iframe style=&amp;quot;display:none&amp;quot; sandbox=&amp;quot;allow-popups allow-top-navigation allow-top-navigation-by-user-activation&amp;quot; srcdoc=&amp;quot;<!DOCTYPE html PUBLIC &amp;amp;quot;-//W3C//DTD HTML 4.01 Transitional//EN&amp;amp;quot; &amp;amp;quot;http://www.w3.org/TR/html4/loose.dtd&amp;amp;quot;> <html><head><meta charset=&amp;amp;quot;utf-8&amp;amp;quot;><title></title><meta http-equiv=&amp;amp;quot;content-security-policy&amp;amp;quot; content=&amp;amp;quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&amp;amp;quot;></head><body style=&amp;amp;quot;background-color:transparent&amp;amp;quot;></body></html>&amp;quot; width=&amp;quot;1&amp;quot; height=&amp;quot;1&amp;quot; frameborder=&amp;quot;0&amp;quot;></iframe></body></html>&quot; width=&quot;1&quot; height=&quot;1&quot; frameborder=&quot;0&quot;></iframe></body></html>" width="0" height="0"></iframe><iframe style="display:none" name="__tcfapiLocator" title="CMP Locator" sandbox="allow-popups allow-top-navigation allow-top-navigation-by-user-activation" srcdoc="<html><head><meta charset=&quot;utf-8&quot;><meta http-equiv=&quot;content-security-policy&quot; content=&quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&quot;></head><body></body></html>"></iframe><div id="onetrust-consent-sdk" class="onetrust-consent-sdk-box" data-apply-ot-banner-popup="false"><div class="onetrust-pc-dark-filter ot-hide ot-fade-in"></div><div id="onetrust-pc-sdk" class="otPcCenter ot-hide ot-fade-in ot-sdk-not-webkit" aria-modal="true" role="alertdialog" aria-label="How we protect your privacy" lang="en"><!-- Close Button --><div class="ot-pc-header"><!-- Logo Tag --><div class="ot-pc-logo" role="img" aria-label="Company Logo" style="background-image:url(resources/sample/17.bin);background-position:left"></div></div><!-- Close Button --><div id="ot-pc-content" class="ot-pc-scrollbar"><h2 id="ot-pc-title">How we protect your privacy</h2><div id="ot-pc-desc">We process your data to deliver content or advertisements and measure the delivery of such content or advertisements to extract insights about our website. We share this information with our partners on the basis of consent. You may exercise your right to consent, based on a specific purpose below or at a partner level in the link under each purpose. These choices will be signaled to our vendors participating in the Transparency and Consent Framework.</div><button id="accept-recommended-btn-handler">Allow All</button><section class="ot-sdk-row ot-cat-grp"><h3 id="ot-category-title"> Manage Consent Preferences</h3><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="1"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-1" aria-labelledby="ot-header-id-1"></button><!-- Accordion header --><div class="ot-acc-hdr ot-always-active-group"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-1">Essential Cookies</h4><div class="ot-always-active">Always Active</div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-1">Essential cookies are always on. These are technical cookies that are required for the operation of our sites. Without using these our sites can’t operate properly.</p></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="2"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-2" aria-labelledby="ot-header-id-2"></button><!-- Accordion header --><div class="ot-acc-hdr"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-2">Experience Cookies</h4><div class="ot-tgl"><input type="checkbox" name="ot-group-id-2" id="ot-group-id-2" aria-checked="true" role="switch" class="category-switch-handler" data-optanongroupid="2" checked aria-labelledby="ot-header-id-2"> <label class="ot-switch" for="ot-group-id-2"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Experience Cookies</span></label> </div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-2">Right now only some of these can be turned off. We’re working on the rest. Experience cookies allow Asda to recognise and count visitors to our sites.
+This means we can check our online services are working and help us make improvements on our online services.
+Experience cookies also allow us to remember the choices you make, such as the items in your basket or your display preferences.</p></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="4"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-4" aria-labelledby="ot-header-id-4"></button><!-- Accordion header --><div class="ot-acc-hdr"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-4">Asda’s Advertising Cookies</h4><div class="ot-tgl"><input type="checkbox" name="ot-group-id-4" id="ot-group-id-4" aria-checked="true" role="switch" class="category-switch-handler" data-optanongroupid="4" checked aria-labelledby="ot-header-id-4"> <label class="ot-switch" for="ot-group-id-4"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Asda’s Advertising Cookies</span></label> </div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-4">We may record your visits to our websites, the pages you have visited and the links you have followed.
+We use this information, along with other information about you as a customer, to help make our advertising relevant to your interests both on our sites and other sites you may visit.
+We also use these to limit the number of times that you see an ad and to inform us how effective a particular ad has been.
+</p></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="STACK42"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-STACK42" aria-labelledby="ot-header-id-STACK42"></button><!-- Accordion header --><div class="ot-acc-hdr"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-STACK42">Personalised ads and content, ad and content measurement, audience insights and product development</h4><div class="ot-tgl"><input type="checkbox" name="ot-group-id-STACK42" id="ot-group-id-STACK42" aria-checked="true" role="switch" class="category-switch-handler" data-optanongroupid="STACK42" checked aria-labelledby="ot-header-id-STACK42"> <label class="ot-switch" for="ot-group-id-STACK42"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Personalised ads and content, ad and content measurement, audience insights and product development</span></label> </div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_2"><h5>Select basic ads</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_2" aria-checked="false" role="switch" data-optanongroupid="IABV2_2" class="cookie-subgroup-handler" aria-label="Select basic ads" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_2"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">Ads can be shown to you based on the content you’re viewing, the app you’re using, your approximate location, or your device type.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_3"><h5>Create a personalised ads profile</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_3" aria-checked="false" role="switch" data-optanongroupid="IABV2_3" class="cookie-subgroup-handler" aria-label="Create a personalised ads profile" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_3"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">A profile can be built about you and your interests to show you personalised ads that are relevant to you.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_4"><h5>Select personalised ads</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_4" aria-checked="false" role="switch" data-optanongroupid="IABV2_4" class="cookie-subgroup-handler" aria-label="Select personalised ads" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_4"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">Personalised ads can be shown to you based on a profile about you.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_5"><h5>Create a personalised content profile</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_5" aria-checked="false" role="switch" data-optanongroupid="IABV2_5" class="cookie-subgroup-handler" aria-label="Create a personalised content profile" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_5"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">A profile can be built about you and your interests to show you personalised content that is relevant to you.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_6"><h5>Select personalised content</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_6" aria-checked="false" role="switch" data-optanongroupid="IABV2_6" class="cookie-subgroup-handler" aria-label="Select personalised content" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_6"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">Personalised content can be shown to you based on a profile about you.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_7"><h5>Measure ad performance</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_7" aria-checked="false" role="switch" data-optanongroupid="IABV2_7" class="cookie-subgroup-handler" aria-label="Measure ad performance" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_7"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">The performance and effectiveness of ads that you see or interact with can be measured.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_8"><h5>Measure content performance</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_8" aria-checked="false" role="switch" data-optanongroupid="IABV2_8" class="cookie-subgroup-handler" aria-label="Measure content performance" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_8"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">The performance and effectiveness of content that you see or interact with can be measured.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_9"><h5>Apply market research to generate audience insights</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_9" aria-checked="false" role="switch" data-optanongroupid="IABV2_9" class="cookie-subgroup-handler" aria-label="Apply market research to generate audience insights" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_9"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">Market research can be used to learn more about the audiences who visit sites/apps and view ads.</p></li></ul></div><div class="ot-subgrp-cntr"><ul class="ot-subgrps"><li class="ot-subgrp" data-optanongroupid="IABV2_10"><h5>Develop and improve products</h5><div class="ot-tgl-cntr ot-subgrp-tgl"><div class="ot-tgl"><input type="checkbox" name="switch" id="ot-sub-group-id-IABV2_10" aria-checked="false" role="switch" data-optanongroupid="IABV2_10" class="cookie-subgroup-handler" aria-label="Develop and improve products" checked> <label class="ot-switch" for="ot-sub-group-id-IABV2_10"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Switch Label</span></label> </div></div><p class="ot-subgrp-desc">Your data can be used to improve existing systems and software, and to develop new products</p></li></ul></div><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="STACK42">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="IABV2_1"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-IABV2_1" aria-labelledby="ot-header-id-IABV2_1"></button><!-- Accordion header --><div class="ot-acc-hdr"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-IABV2_1">Store and/or access information on a device</h4><div class="ot-tgl"><input type="checkbox" name="ot-group-id-IABV2_1" id="ot-group-id-IABV2_1" aria-checked="true" role="switch" class="category-switch-handler" data-optanongroupid="IABV2_1" checked aria-labelledby="ot-header-id-IABV2_1"> <label class="ot-switch" for="ot-group-id-IABV2_1"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Store and/or access information on a device</span></label> </div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-IABV2_1">Cookies, device identifiers, or other information can be stored or accessed on your device for the purposes presented to you.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="IABV2_1">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="ISFV2_1"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-ISFV2_1" aria-labelledby="ot-header-id-ISFV2_1"></button><!-- Accordion header --><div class="ot-acc-hdr"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-ISFV2_1">Use precise geolocation data</h4><div class="ot-tgl"><input type="checkbox" name="ot-group-id-ISFV2_1" id="ot-group-id-ISFV2_1" aria-checked="true" role="switch" class="category-switch-handler" data-optanongroupid="ISFV2_1" checked aria-labelledby="ot-header-id-ISFV2_1"> <label class="ot-switch" for="ot-group-id-ISFV2_1"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Use precise geolocation data</span></label> </div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-ISFV2_1">Your precise geolocation data can be used in support of one or more purposes. This means your location can be accurate to within several meters.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="ISFV2_1">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="ISFV2_2"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-ISFV2_2" aria-labelledby="ot-header-id-ISFV2_2"></button><!-- Accordion header --><div class="ot-acc-hdr"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-ISFV2_2">Actively scan device characteristics for identification</h4><div class="ot-tgl"><input type="checkbox" name="ot-group-id-ISFV2_2" id="ot-group-id-ISFV2_2" aria-checked="true" role="switch" class="category-switch-handler" data-optanongroupid="ISFV2_2" checked aria-labelledby="ot-header-id-ISFV2_2"> <label class="ot-switch" for="ot-group-id-ISFV2_2"><span class="ot-switch-nob"></span> <span class="ot-label-txt">Actively scan device characteristics for identification</span></label> </div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-ISFV2_2">Your device can be identified based on a scan of your device's unique combination of characteristics.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="ISFV2_2">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="ISPV2_1"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-ISPV2_1" aria-labelledby="ot-header-id-ISPV2_1"></button><!-- Accordion header --><div class="ot-acc-hdr ot-always-active-group"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-ISPV2_1">Ensure security, prevent fraud, and debug</h4><div class="ot-always-active">Always Active</div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-ISPV2_1">Your data can be used to monitor for and prevent fraudulent activity, and ensure systems and processes work properly and securely.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="ISPV2_1">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="ISPV2_2"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-ISPV2_2" aria-labelledby="ot-header-id-ISPV2_2"></button><!-- Accordion header --><div class="ot-acc-hdr ot-always-active-group"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-ISPV2_2">Technically deliver ads or content</h4><div class="ot-always-active">Always Active</div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-ISPV2_2">Your device can receive and send information that allows you to see and interact with ads and content.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="ISPV2_2">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="IFEV2_1"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-IFEV2_1" aria-labelledby="ot-header-id-IFEV2_1"></button><!-- Accordion header --><div class="ot-acc-hdr ot-always-active-group"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-IFEV2_1">Match and combine offline data sources</h4><div class="ot-always-active">Always Active</div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-IFEV2_1">Data from offline data sources can be combined with your online activity in support of one or more purposes</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="IFEV2_1">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="IFEV2_2"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-IFEV2_2" aria-labelledby="ot-header-id-IFEV2_2"></button><!-- Accordion header --><div class="ot-acc-hdr ot-always-active-group"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-IFEV2_2">Link different devices</h4><div class="ot-always-active">Always Active</div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-IFEV2_2">Different devices can be determined as belonging to you or your household in support of one or more of purposes.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="IFEV2_2">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><div class="ot-accordion-layout ot-cat-item" data-optanongroupid="IFEV2_3"><button aria-expanded="false" ot-accordion="true" aria-controls="ot-desc-id-IFEV2_3" aria-labelledby="ot-header-id-IFEV2_3"></button><!-- Accordion header --><div class="ot-acc-hdr ot-always-active-group"><div class="ot-plus-minus"><span></span><span></span></div><h4 class="ot-cat-header" id="ot-header-id-IFEV2_3">Receive and use automatically-sent device characteristics for identification</h4><div class="ot-always-active">Always Active</div></div><!-- accordion detail --><div class="ot-acc-grpcntr ot-acc-txt"><p class="ot-acc-grpdesc ot-category-desc" id="ot-desc-id-IFEV2_3">Your device might be distinguished from other devices based on information it automatically sends, such as IP address or browser type.</p><div class="ot-vlst-cntr"><button class="ot-link-btn category-vendors-list-handler" aria-label="IAB Vendor Details button opens Vendor List menu" data-parent-id="IFEV2_3">List of IAB Vendors‎</button><a href="https://tcf.cookiepedia.co.uk/?lang=en" rel="noopener" target="_blank">&nbsp;|&nbsp;View Full Legal Text&nbsp;<span class="ot-scrn-rdr">Opens in a new Tab</span></a></div></div></div><!-- Groups sections starts --><!-- Group section ends --><!-- Accordion Group section starts --><!-- Accordion Group section ends --></section></div><section id="ot-pc-lst" class="ot-hide ot-hosts-ui ot-pc-scrollbar"><div id="ot-pc-hdr"><div id="ot-lst-title"><button class="ot-link-btn back-btn-handler" aria-label="Back"><svg id="ot-back-arw" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 444.531 444.531" xml:space="preserve"><title>Back Button</title><g><path fill="#656565" d="M213.13,222.409L351.88,83.653c7.05-7.043,10.567-15.657,10.567-25.841c0-10.183-3.518-18.793-10.567-25.835
+ l-21.409-21.416C323.432,3.521,314.817,0,304.637,0s-18.791,3.521-25.841,10.561L92.649,196.425
+ c-7.044,7.043-10.566,15.656-10.566,25.841s3.521,18.791,10.566,25.837l186.146,185.864c7.05,7.043,15.66,10.564,25.841,10.564
+ s18.795-3.521,25.834-10.564l21.409-21.412c7.05-7.039,10.567-15.604,10.567-25.697c0-10.085-3.518-18.746-10.567-25.978
+ L213.13,222.409z"></path></g></svg></button><h3>Performance Cookies</h3></div><div class="ot-lst-subhdr"><div class="ot-search-cntr"><p role="status" class="ot-scrn-rdr"></p><label for="vendor-search-handler" class="ot-scrn-rdr"></label> <input id="vendor-search-handler" type="text" name="vendor-search-handler" value> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 -30 110 110" aria-hidden="true"><title>Search Icon</title><path fill="#2e3644" d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23
+ s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92
+ c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17
+ s-17-7.626-17-17S14.61,6,23.984,6z"></path></svg></div><div class="ot-fltr-cntr"><button id="filter-btn-handler" aria-label="Filter" aria-haspopup="true"><svg role="presentation" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 402.577 402.577" xml:space="preserve"><title>Filter Icon</title><g><path fill="#fff" d="M400.858,11.427c-3.241-7.421-8.85-11.132-16.854-11.136H18.564c-7.993,0-13.61,3.715-16.846,11.136
+ c-3.234,7.801-1.903,14.467,3.999,19.985l140.757,140.753v138.755c0,4.955,1.809,9.232,5.424,12.854l73.085,73.083
+ c3.429,3.614,7.71,5.428,12.851,5.428c2.282,0,4.66-0.479,7.135-1.43c7.426-3.238,11.14-8.851,11.14-16.845V172.166L396.861,31.413
+ C402.765,25.895,404.093,19.231,400.858,11.427z"></path></g></svg></button></div><div id="ot-anchor"></div><section id="ot-fltr-modal"><div id="ot-fltr-cnt"><button id="clear-filters-handler">Clear</button><div class="ot-fltr-scrlcnt ot-pc-scrollbar"><div class="ot-fltr-opts"><div class="ot-fltr-opt"><div class="ot-chkbox"><input id="chkbox-id" type="checkbox" aria-checked="false" class="category-filter-handler"> <label for="chkbox-id"><span class="ot-label-txt">checkbox label</span></label> <span class="ot-label-status">label</span></div></div></div><div class="ot-fltr-btns"><button id="filter-apply-handler">Apply</button> <button id="filter-cancel-handler">Cancel</button></div></div></div></section></div></div><section id="ot-lst-cnt" class="ot-host-cnt ot-pc-scrollbar"><div id="ot-sel-blk"><div class="ot-sel-all"><div class="ot-sel-all-hdr"><span class="ot-consent-hdr">Consent</span> <span class="ot-li-hdr">Leg.Interest</span></div><div class="ot-sel-all-chkbox"><div class="ot-chkbox" id="ot-selall-hostcntr"><input id="select-all-hosts-groups-handler" type="checkbox" aria-checked="false"> <label for="select-all-hosts-groups-handler"><span class="ot-label-txt">checkbox label</span></label> <span class="ot-label-status">label</span></div><div class="ot-chkbox" id="ot-selall-vencntr"><input id="select-all-vendor-groups-handler" type="checkbox" aria-checked="false"> <label for="select-all-vendor-groups-handler"><span class="ot-label-txt">checkbox label</span></label> <span class="ot-label-status">label</span></div><div class="ot-chkbox" id="ot-selall-licntr"><input id="select-all-vendor-leg-handler" type="checkbox" aria-checked="false"> <label for="select-all-vendor-leg-handler"><span class="ot-label-txt">checkbox label</span></label> <span class="ot-label-status">label</span></div></div></div></div><div class="ot-sdk-row"><div class="ot-sdk-column"><ul id="ot-ven-lst"></ul></div></div></section></section><div class="ot-pc-footer"><div class="ot-btn-container"> <button class="save-preference-btn-handler onetrust-close-btn-handler">Confirm My Choices</button></div><!-- Footer logo --><div class="ot-pc-footer-logo"><a href="https://www.onetrust.com/products/cookie-consent/" target="_blank" rel="noopener noreferrer" style="background-image:url(resources/sample/18.svg)" aria-label="Powered by OneTrust Opens in a new Tab"></a></div></div><!-- Cookie subgroup container --><!-- Vendor list link --><!-- Cookie lost link --><!-- Toggle HTML element --><!-- Checkbox HTML --><!-- plus minus--><!-- Arrow SVG element --><!-- Accordion basic element --><span class="ot-scrn-rdr" aria-atomic="true" aria-live="polite"></span><iframe class="ot-text-resize" title="onetrust-text-resize" style="position:absolute;top:-50000px;width:100em" aria-hidden="true" sandbox="allow-popups allow-top-navigation allow-top-navigation-by-user-activation" srcdoc="<html><head><meta charset=&quot;utf-8&quot;><meta http-equiv=&quot;content-security-policy&quot; content=&quot;default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;&quot;></head><body></body></html>"></iframe></div><div id="ot-sdk-btn-floating" title="Manage Preferences" class="ot-floating-button"><div class="ot-floating-button__front custom-persistent-icon"><button type="button" class="ot-floating-button__open" aria-label="Open Preferences"></button></div><div class="ot-floating-button__back custom-persistent-icon"><button type="button" class="ot-floating-button__close"><!--?xml version="1.0" encoding="UTF-8"?--> <svg role="presentation" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Banner_02" class="ot-floating-button__svg-fill" transform="translate(-318.000000, -725.000000)" fill="#ffffff" fill-rule="nonzero"><g id="Group-2" transform="translate(305.000000, 712.000000)"><g id="icon/16px/white/close"><polygon id="Line1" points="13.3333333 14.9176256 35.0823744 36.6666667 36.6666667 35.0823744 14.9176256 13.3333333"></polygon><polygon id="Line2" transform="translate(25.000000, 25.000000) scale(-1, 1) translate(-25.000000, -25.000000) " points="13.3333333 14.9176256 35.0823744 36.6666667 36.6666667 35.0823744 14.9176256 13.3333333"></polygon></g></g></g></g></svg></button></div></div></div></body></html>
diff --git a/browser/extensions/formautofill/test/browser/focus-leak/browser.ini b/browser/extensions/formautofill/test/browser/focus-leak/browser.ini
new file mode 100644
index 0000000000..fc4aca4a35
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/focus-leak/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ doc_iframe_typecontent_input_focus.xhtml
+ doc_iframe_typecontent_input_focus_frame.html
+ ../head.js
+
+# This test is used to detect a leak.
+# Keep it isolated in a dedicated test folder to make sure the leak is cleaned
+# up as a sideeffect of another test.
+[browser_iframe_typecontent_input_focus.js]
+skip-if =
+ apple_silicon # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs
diff --git a/browser/extensions/formautofill/test/browser/focus-leak/browser_iframe_typecontent_input_focus.js b/browser/extensions/formautofill/test/browser/focus-leak/browser_iframe_typecontent_input_focus.js
new file mode 100644
index 0000000000..407c2b5163
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/focus-leak/browser_iframe_typecontent_input_focus.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL_ROOT =
+ "chrome://mochitests/content/browser/browser/extensions/formautofill/test/browser/focus-leak/";
+
+const XUL_FRAME_URI = URL_ROOT + "doc_iframe_typecontent_input_focus.xhtml";
+const INNER_HTML_FRAME_URI =
+ URL_ROOT + "doc_iframe_typecontent_input_focus_frame.html";
+
+/**
+ * Check that focusing an input in a frame with type=content embedded in a xul
+ * document does not leak.
+ */
+add_task(async function () {
+ const doc = gBrowser.ownerDocument;
+ const linkedBrowser = gBrowser.selectedTab.linkedBrowser;
+ const browserContainer = gBrowser.getBrowserContainer(linkedBrowser);
+
+ info("Load the test page in a frame with type content");
+ const frame = doc.createXULElement("iframe");
+ frame.setAttribute("type", "content");
+ browserContainer.appendChild(frame);
+
+ info("Wait for the xul iframe to be loaded");
+ const onXulFrameLoad = BrowserTestUtils.waitForEvent(frame, "load", true);
+ frame.setAttribute("src", XUL_FRAME_URI);
+ await onXulFrameLoad;
+
+ const panelFrame = frame.contentDocument.querySelector("#html-iframe");
+
+ info("Wait for the html iframe to be loaded");
+ const onFrameLoad = BrowserTestUtils.waitForEvent(panelFrame, "load", true);
+ panelFrame.setAttribute("src", INNER_HTML_FRAME_URI);
+ await onFrameLoad;
+
+ info("Focus an input inside the iframe");
+ const focusMeInput = panelFrame.contentDocument.querySelector(".focusme");
+ const onFocus = BrowserTestUtils.waitForEvent(focusMeInput, "focus");
+ await SimpleTest.promiseFocus(panelFrame);
+ focusMeInput.focus();
+ await onFocus;
+
+ // This assert is not really meaningful, the main purpose of the test is
+ // to check against leaks.
+ is(
+ focusMeInput,
+ panelFrame.contentDocument.activeElement,
+ "The .focusme input is the active element"
+ );
+
+ info("Remove the focused input");
+ focusMeInput.remove();
+});
diff --git a/browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus.xhtml b/browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus.xhtml
new file mode 100644
index 0000000000..ec3aaa2648
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus.xhtml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <box>
+ <html:iframe id="html-iframe"/>
+ </box>
+</window>
diff --git a/browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus_frame.html b/browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus_frame.html
new file mode 100644
index 0000000000..00853d8eec
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/focus-leak/doc_iframe_typecontent_input_focus_frame.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input type="text" name="test" class="focusme">
+ </body>
+</html>
diff --git a/browser/extensions/formautofill/test/browser/head.js b/browser/extensions/formautofill/test/browser/head.js
new file mode 100644
index 0000000000..2dd3d1451e
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -0,0 +1,1094 @@
+"use strict";
+
+const { OSKeyStore } = ChromeUtils.importESModule(
+ "resource://gre/modules/OSKeyStore.sys.mjs"
+);
+const { OSKeyStoreTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/OSKeyStoreTestUtils.sys.mjs"
+);
+
+const { FormAutofillParent } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofillParent.sys.mjs"
+);
+
+const MANAGE_ADDRESSES_DIALOG_URL =
+ "chrome://formautofill/content/manageAddresses.xhtml";
+const MANAGE_CREDIT_CARDS_DIALOG_URL =
+ "chrome://formautofill/content/manageCreditCards.xhtml";
+const EDIT_ADDRESS_DIALOG_URL =
+ "chrome://formautofill/content/editAddress.xhtml";
+const EDIT_CREDIT_CARD_DIALOG_URL =
+ "chrome://formautofill/content/editCreditCard.xhtml";
+const PRIVACY_PREF_URL = "about:preferences#privacy";
+
+const HTTP_TEST_PATH = "/browser/browser/extensions/formautofill/test/browser/";
+const BASE_URL = "http://mochi.test:8888" + HTTP_TEST_PATH;
+const FORM_URL = BASE_URL + "autocomplete_basic.html";
+const ADDRESS_FORM_URL =
+ "https://example.org" +
+ HTTP_TEST_PATH +
+ "address/autocomplete_address_basic.html";
+const ADDRESS_FORM_WITHOUT_AUTOCOMPLETE_URL =
+ "https://example.org" +
+ HTTP_TEST_PATH +
+ "address/without_autocomplete_address_basic.html";
+const CREDITCARD_FORM_URL =
+ "https://example.org" +
+ HTTP_TEST_PATH +
+ "creditCard/autocomplete_creditcard_basic.html";
+const CREDITCARD_FORM_IFRAME_URL =
+ "https://example.org" +
+ HTTP_TEST_PATH +
+ "creditCard/autocomplete_creditcard_iframe.html";
+const CREDITCARD_FORM_COMBINED_EXPIRY_URL =
+ "https://example.org" +
+ HTTP_TEST_PATH +
+ "creditCard/autocomplete_creditcard_cc_exp_field.html";
+const CREDITCARD_FORM_WITHOUT_AUTOCOMPLETE_URL =
+ "https://example.org" +
+ HTTP_TEST_PATH +
+ "creditCard/without_autocomplete_creditcard_basic.html";
+const EMPTY_URL = "https://example.org" + HTTP_TEST_PATH + "empty.html";
+
+const FTU_PREF = "extensions.formautofill.firstTimeUse";
+const ENABLED_AUTOFILL_ADDRESSES_PREF =
+ "extensions.formautofill.addresses.enabled";
+const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF =
+ "extensions.formautofill.addresses.capture.enabled";
+const AUTOFILL_ADDRESSES_AVAILABLE_PREF =
+ "extensions.formautofill.addresses.supported";
+const ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF =
+ "extensions.formautofill.addresses.supportedCountries";
+const AUTOFILL_CREDITCARDS_AVAILABLE_PREF =
+ "extensions.formautofill.creditCards.supported";
+const ENABLED_AUTOFILL_CREDITCARDS_PREF =
+ "extensions.formautofill.creditCards.enabled";
+const SUPPORTED_COUNTRIES_PREF = "extensions.formautofill.supportedCountries";
+const SYNC_USERNAME_PREF = "services.sync.username";
+const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
+const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
+const SYNC_CREDITCARDS_AVAILABLE_PREF =
+ "services.sync.engine.creditcards.available";
+
+const TEST_ADDRESS_1 = {
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ organization: "World Wide Web Consortium",
+ "street-address": "32 Vassar Street\nMIT Room 32-G524",
+ "address-level2": "Cambridge",
+ "address-level1": "MA",
+ "postal-code": "02139",
+ country: "US",
+ tel: "+16172535702",
+ email: "timbl@w3.org",
+};
+
+const TEST_ADDRESS_2 = {
+ "given-name": "Anonymouse",
+ "street-address": "Some Address",
+ country: "US",
+};
+
+const TEST_ADDRESS_3 = {
+ "given-name": "John",
+ "street-address": "Other Address",
+ "postal-code": "12345",
+};
+
+const TEST_ADDRESS_4 = {
+ "given-name": "Timothy",
+ "family-name": "Berners-Lee",
+ organization: "World Wide Web Consortium",
+ "street-address": "32 Vassar Street\nMIT Room 32-G524",
+ country: "US",
+ email: "timbl@w3.org",
+};
+
+// TODO: Number of field less than AUTOFILL_FIELDS_THRESHOLD
+// need to confirm whether this is intentional
+const TEST_ADDRESS_5 = {
+ tel: "+16172535702",
+};
+
+const TEST_ADDRESS_CA_1 = {
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ organization: "Mozilla",
+ "street-address": "163 W Hastings\nSuite 209",
+ "address-level2": "Vancouver",
+ "address-level1": "BC",
+ "postal-code": "V6B 1H5",
+ country: "CA",
+ tel: "+17787851540",
+ email: "timbl@w3.org",
+};
+
+const TEST_ADDRESS_DE_1 = {
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ organization: "Mozilla",
+ "street-address":
+ "Geb\u00E4ude 3, 4. Obergeschoss\nSchlesische Stra\u00DFe 27",
+ "address-level2": "Berlin",
+ "postal-code": "10997",
+ country: "DE",
+ tel: "+4930983333000",
+ email: "timbl@w3.org",
+};
+
+const TEST_ADDRESS_IE_1 = {
+ "given-name": "Bob",
+ "additional-name": "Z.",
+ "family-name": "Builder",
+ organization: "Best Co.",
+ "street-address": "123 Kilkenny St.",
+ "address-level3": "Some Townland",
+ "address-level2": "Dublin",
+ "address-level1": "Co. Dublin",
+ "postal-code": "A65 F4E2",
+ country: "IE",
+ tel: "+13534564947391",
+ email: "ie@example.com",
+};
+
+const TEST_CREDIT_CARD_1 = {
+ "cc-name": "John Doe",
+ "cc-number": "4111111111111111",
+ "cc-exp-month": 4,
+ "cc-exp-year": new Date().getFullYear(),
+};
+
+const TEST_CREDIT_CARD_2 = {
+ "cc-name": "Timothy Berners-Lee",
+ "cc-number": "4929001587121045",
+ "cc-exp-month": 12,
+ "cc-exp-year": new Date().getFullYear() + 10,
+};
+
+const TEST_CREDIT_CARD_3 = {
+ "cc-number": "5103059495477870",
+ "cc-exp-month": 1,
+ "cc-exp-year": 2000,
+};
+
+const TEST_CREDIT_CARD_4 = {
+ "cc-number": "5105105105105100",
+};
+
+const TEST_CREDIT_CARD_5 = {
+ "cc-name": "Chris P. Bacon",
+ "cc-number": "4012888888881881",
+};
+
+const MAIN_BUTTON = "button";
+const SECONDARY_BUTTON = "secondaryButton";
+const MENU_BUTTON = "menubutton";
+
+/**
+ * Collection of timeouts that are used to ensure something should not happen.
+ */
+const TIMEOUT_ENSURE_PROFILE_NOT_SAVED = 1000;
+const TIMEOUT_ENSURE_CC_DIALOG_NOT_CLOSED = 500;
+const TIMEOUT_ENSURE_AUTOCOMPLETE_NOT_SHOWN = 1000;
+
+async function ensureCreditCardDialogNotClosed(win) {
+ const unloadHandler = () => {
+ ok(false, "Credit card dialog shouldn't be closed");
+ };
+ win.addEventListener("unload", unloadHandler);
+ await new Promise(resolve =>
+ setTimeout(resolve, TIMEOUT_ENSURE_CC_DIALOG_NOT_CLOSED)
+ );
+ win.removeEventListener("unload", unloadHandler);
+}
+
+function getDisplayedPopupItems(
+ browser,
+ selector = ".autocomplete-richlistitem"
+) {
+ info("getDisplayedPopupItems");
+ const {
+ autoCompletePopup: { richlistbox: itemsBox },
+ } = browser;
+ const listItemElems = itemsBox.querySelectorAll(selector);
+
+ return [...listItemElems].filter(
+ item => item.getAttribute("collapsed") != "true"
+ );
+}
+
+async function sleep(ms = 500) {
+ await new Promise(resolve => setTimeout(resolve, ms));
+}
+
+async function ensureNoAutocompletePopup(browser) {
+ await new Promise(resolve =>
+ setTimeout(resolve, TIMEOUT_ENSURE_AUTOCOMPLETE_NOT_SHOWN)
+ );
+ const items = getDisplayedPopupItems(browser);
+ ok(!items.length, "Should not found autocomplete items");
+}
+
+/**
+ * Wait for "formautofill-storage-changed" events
+ *
+ * @param {Array<string>} eventTypes
+ * eventType must be one of the following:
+ * `add`, `update`, `remove`, `notifyUsed`, `removeAll`, `reconcile`
+ *
+ * @returns {Promise} resolves when all events are received
+ */
+async function waitForStorageChangedEvents(...eventTypes) {
+ return Promise.all(
+ eventTypes.map(type =>
+ TestUtils.topicObserved(
+ "formautofill-storage-changed",
+ (subject, data) => {
+ return data == type;
+ }
+ )
+ )
+ );
+}
+
+/**
+ * Wait until the element found matches the expected autofill value
+ *
+ * @param {object} target
+ * The target in which to run the task.
+ * @param {string} selector
+ * A selector used to query the element.
+ * @param {string} value
+ * The expected autofilling value for the element
+ */
+async function waitForAutofill(target, selector, value) {
+ await SpecialPowers.spawn(
+ target,
+ [selector, value],
+ async function (selector, val) {
+ await ContentTaskUtils.waitForCondition(() => {
+ let element = content.document.querySelector(selector);
+ return element.value == val;
+ }, "Autofill never fills");
+ }
+ );
+}
+
+/**
+ * Waits for the subDialog to be loaded
+ *
+ * @param {Window} win The window of the dialog
+ * @param {string} dialogUrl The url of the dialog that we are waiting for
+ *
+ * @returns {Promise} resolves when the sub dialog is loaded
+ */
+function waitForSubDialogLoad(win, dialogUrl) {
+ return new Promise((resolve, reject) => {
+ win.gSubDialog._dialogStack.addEventListener(
+ "dialogopen",
+ async function dialogopen(evt) {
+ let cwin = evt.detail.dialog._frame.contentWindow;
+ if (cwin.location != dialogUrl) {
+ return;
+ }
+ content.gSubDialog._dialogStack.removeEventListener(
+ "dialogopen",
+ dialogopen
+ );
+
+ resolve(cwin);
+ }
+ );
+ });
+}
+
+/**
+ * Use this function when you want to update the value of elements in
+ * a form and then submit the form. This function makes sure the form
+ * is "identified" (`identifyAutofillFields` is called) before submitting
+ * the form.
+ * This is guaranteed by first focusing on an element in the form to trigger
+ * the 'FormAutofill:FieldsIdentified' message.
+ *
+ * @param {object} target
+ * The target in which to run the task.
+ * @param {object} args
+ * @param {string} args.focusSelector
+ * A selector used to query the element to be focused
+ * @param {string} args.formId
+ * The id of the form to be updated. This function uses "form" if
+ * this argument is not present
+ * @param {string} args.formSelector
+ * A selector used to query the form element
+ * @param {object} args.newValues
+ * Elements to be updated. Key is the element selector, value is the
+ * new value of the element.
+ *
+ * @param {boolean} submit
+ * Set to true to submit the form after the task is done, false otherwise.
+ */
+async function focusUpdateSubmitForm(target, args, submit = true) {
+ let fieldsIdentifiedPromiseResolver;
+ let fieldsIdentifiedObserver = {
+ fieldsIdentified() {
+ FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
+ fieldsIdentifiedPromiseResolver();
+ },
+ };
+
+ let fieldsIdentifiedPromise = new Promise(resolve => {
+ fieldsIdentifiedPromiseResolver = resolve;
+ FormAutofillParent.addMessageObserver(fieldsIdentifiedObserver);
+ });
+
+ let alreadyFocused = await SpecialPowers.spawn(target, [args], obj => {
+ let focused = false;
+
+ let form;
+ if (obj.formSelector) {
+ form = content.document.querySelector(obj.formSelector);
+ } else {
+ form = content.document.getElementById(obj.formId ?? "form");
+ }
+ let element = form.querySelector(obj.focusSelector);
+ if (element != content.document.activeElement) {
+ info(`focus on element (id=${element.id})`);
+ element.focus();
+ } else {
+ focused = true;
+ }
+
+ for (const [selector, value] of Object.entries(obj.newValues)) {
+ element = form.querySelector(selector);
+ if (content.HTMLInputElement.isInstance(element)) {
+ element.setUserInput(value);
+ } else {
+ element.value = value;
+ }
+ }
+
+ return focused;
+ });
+
+ if (alreadyFocused) {
+ // If the element is already focused, assume the FieldsIdentified message
+ // was sent before.
+ fieldsIdentifiedPromiseResolver();
+ }
+
+ await fieldsIdentifiedPromise;
+
+ if (submit) {
+ await SpecialPowers.spawn(target, [args], obj => {
+ let form;
+ if (obj.formSelector) {
+ form = content.document.querySelector(obj.formSelector);
+ } else {
+ form = content.document.getElementById(obj.formId ?? "form");
+ }
+ info(`submit form (id=${form.id})`);
+ form.querySelector("input[type=submit]").click();
+ });
+ }
+}
+
+async function focusAndWaitForFieldsIdentified(browserOrContext, selector) {
+ info("expecting the target input being focused and identified");
+ /* eslint no-shadow: ["error", { "allow": ["selector", "previouslyFocused", "previouslyIdentified"] }] */
+
+ // If the input is previously focused, no more notifications will be
+ // sent as the notification goes along with focus event.
+ let fieldsIdentifiedPromiseResolver;
+ let fieldsIdentifiedObserver = {
+ fieldsIdentified() {
+ fieldsIdentifiedPromiseResolver();
+ },
+ };
+
+ let fieldsIdentifiedPromise = new Promise(resolve => {
+ fieldsIdentifiedPromiseResolver = resolve;
+ FormAutofillParent.addMessageObserver(fieldsIdentifiedObserver);
+ });
+
+ const { previouslyFocused, previouslyIdentified } = await SpecialPowers.spawn(
+ browserOrContext,
+ [selector],
+ async function (selector) {
+ const { FormLikeFactory } = ChromeUtils.importESModule(
+ "resource://gre/modules/FormLikeFactory.sys.mjs"
+ );
+ const input = content.document.querySelector(selector);
+ const rootElement = FormLikeFactory.findRootForField(input);
+ const previouslyFocused = content.document.activeElement == input;
+ const previouslyIdentified = rootElement.hasAttribute(
+ "test-formautofill-identified"
+ );
+
+ input.focus();
+
+ return { previouslyFocused, previouslyIdentified };
+ }
+ );
+
+ // Only wait for the fields identified notification if the
+ // focus was not previously assigned to the input.
+ if (previouslyFocused) {
+ fieldsIdentifiedPromiseResolver();
+ } else {
+ info("!previouslyFocused");
+ }
+
+ // If a browsing context was supplied, focus its parent frame as well.
+ if (
+ BrowsingContext.isInstance(browserOrContext) &&
+ browserOrContext.parent != browserOrContext
+ ) {
+ await SpecialPowers.spawn(
+ browserOrContext.parent,
+ [browserOrContext],
+ async function (browsingContext) {
+ browsingContext.embedderElement.focus();
+ }
+ );
+ }
+
+ if (previouslyIdentified) {
+ info("previouslyIdentified");
+ FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
+ return;
+ }
+
+ // Wait 500ms to ensure that "markAsAutofillField" is completely finished.
+ await fieldsIdentifiedPromise;
+ info("FieldsIdentified");
+ FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
+
+ await sleep();
+ await SpecialPowers.spawn(browserOrContext, [], async function () {
+ const { FormLikeFactory } = ChromeUtils.importESModule(
+ "resource://gre/modules/FormLikeFactory.sys.mjs"
+ );
+ FormLikeFactory.findRootForField(
+ content.document.activeElement
+ ).setAttribute("test-formautofill-identified", "true");
+ });
+}
+
+/**
+ * Run the task and wait until the autocomplete popup is opened.
+ *
+ * @param {object} browser A xul:browser.
+ * @param {Function} taskFn Task that will trigger the autocomplete popup
+ */
+async function runAndWaitForAutocompletePopupOpen(browser, taskFn) {
+ info("runAndWaitForAutocompletePopupOpen");
+ let popupShown = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "shown"
+ );
+
+ // Run the task will open the autocomplete popup
+ await taskFn();
+
+ await popupShown;
+ await BrowserTestUtils.waitForMutationCondition(
+ browser.autoCompletePopup.richlistbox,
+ { childList: true, subtree: true, attributes: true },
+ () => {
+ const listItemElems = getDisplayedPopupItems(browser);
+ return (
+ !![...listItemElems].length &&
+ [...listItemElems].every(item => {
+ return (
+ (item.getAttribute("originaltype") == "autofill-profile" ||
+ item.getAttribute("originaltype") == "autofill-insecureWarning" ||
+ item.getAttribute("originaltype") == "autofill-clear-button" ||
+ item.getAttribute("originaltype") == "autofill-footer") &&
+ item.hasAttribute("formautofillattached")
+ );
+ })
+ );
+ }
+ );
+}
+
+async function waitForPopupEnabled(browser) {
+ const {
+ autoCompletePopup: { richlistbox: itemsBox },
+ } = browser;
+ info("Wait for list elements to become enabled");
+ await BrowserTestUtils.waitForMutationCondition(
+ itemsBox,
+ { subtree: true, attributes: true, attributeFilter: ["disabled"] },
+ () => !itemsBox.querySelectorAll(".autocomplete-richlistitem")[0].disabled
+ );
+}
+
+// Wait for the popup state change notification to happen in a child process.
+function waitPopupStateInChild(bc, messageName) {
+ return SpecialPowers.spawn(bc, [messageName], expectedMessage => {
+ return new Promise(resolve => {
+ const { AutoCompleteChild } = ChromeUtils.importESModule(
+ "resource://gre/actors/AutoCompleteChild.sys.mjs"
+ );
+
+ let listener = {
+ popupStateChanged: name => {
+ if (name != expectedMessage) {
+ info("Expected " + expectedMessage + " but received " + name);
+ return;
+ }
+
+ AutoCompleteChild.removePopupStateListener(listener);
+ resolve();
+ },
+ };
+ AutoCompleteChild.addPopupStateListener(listener);
+ });
+ });
+}
+
+async function openPopupOn(browser, selector) {
+ let childNotifiedPromise = waitPopupStateInChild(
+ browser,
+ "FormAutoComplete:PopupOpened"
+ );
+ await SimpleTest.promiseFocus(browser);
+
+ await runAndWaitForAutocompletePopupOpen(browser, async () => {
+ await focusAndWaitForFieldsIdentified(browser, selector);
+ if (!selector.includes("cc-")) {
+ info(`openPopupOn: before VK_DOWN on ${selector}`);
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ }
+ });
+
+ await childNotifiedPromise;
+}
+
+async function openPopupOnSubframe(browser, frameBrowsingContext, selector) {
+ let childNotifiedPromise = waitPopupStateInChild(
+ frameBrowsingContext,
+ "FormAutoComplete:PopupOpened"
+ );
+
+ await SimpleTest.promiseFocus(browser);
+
+ await runAndWaitForAutocompletePopupOpen(browser, async () => {
+ await focusAndWaitForFieldsIdentified(frameBrowsingContext, selector);
+ if (!selector.includes("cc-")) {
+ info(`openPopupOnSubframe: before VK_DOWN on ${selector}`);
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, frameBrowsingContext);
+ }
+ });
+
+ await childNotifiedPromise;
+}
+
+async function closePopup(browser) {
+ // Return if the popup isn't open.
+ if (!browser.autoCompletePopup.popupOpen) {
+ return;
+ }
+
+ let childNotifiedPromise = waitPopupStateInChild(
+ browser,
+ "FormAutoComplete:PopupClosed"
+ );
+ let popupClosePromise = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "hidden"
+ );
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.activeElement.blur();
+ });
+
+ await popupClosePromise;
+ await childNotifiedPromise;
+}
+
+async function closePopupForSubframe(browser, frameBrowsingContext) {
+ let childNotifiedPromise = waitPopupStateInChild(
+ browser,
+ "FormAutoComplete:PopupClosed"
+ );
+
+ let popupClosePromise = BrowserTestUtils.waitForPopupEvent(
+ browser.autoCompletePopup,
+ "hidden"
+ );
+
+ await SpecialPowers.spawn(frameBrowsingContext, [], async function () {
+ content.document.activeElement.blur();
+ });
+
+ await popupClosePromise;
+ await childNotifiedPromise;
+}
+
+function emulateMessageToBrowser(name, data) {
+ let actor =
+ gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor(
+ "FormAutofill"
+ );
+ return actor.receiveMessage({ name, data });
+}
+
+function getRecords(data) {
+ info(`expecting record retrievals: ${data.collectionName}`);
+ return emulateMessageToBrowser("FormAutofill:GetRecords", data);
+}
+
+function getAddresses() {
+ return getRecords({ collectionName: "addresses" });
+}
+
+async function ensureNoAddressSaved() {
+ await new Promise(resolve =>
+ setTimeout(resolve, TIMEOUT_ENSURE_PROFILE_NOT_SAVED)
+ );
+ const addresses = await getAddresses();
+ is(addresses.length, 0, "No address was saved");
+}
+
+function getCreditCards() {
+ return getRecords({ collectionName: "creditCards" });
+}
+
+async function saveAddress(address) {
+ info("expecting address saved");
+ let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
+ await emulateMessageToBrowser("FormAutofill:SaveAddress", { address });
+ await observePromise;
+}
+
+async function saveCreditCard(creditcard) {
+ info("expecting credit card saved");
+ let creditcardClone = Object.assign({}, creditcard);
+ let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
+ await emulateMessageToBrowser("FormAutofill:SaveCreditCard", {
+ creditcard: creditcardClone,
+ });
+ await observePromise;
+}
+
+async function removeAddresses(guids) {
+ info("expecting address removed");
+ let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
+ await emulateMessageToBrowser("FormAutofill:RemoveAddresses", { guids });
+ await observePromise;
+}
+
+async function removeCreditCards(guids) {
+ info("expecting credit card removed");
+ let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
+ await emulateMessageToBrowser("FormAutofill:RemoveCreditCards", { guids });
+ await observePromise;
+}
+
+function getNotification(index = 0) {
+ let notifications = PopupNotifications.panel.childNodes;
+ ok(!!notifications.length, "at least one notification displayed");
+ ok(true, notifications.length + " notification(s)");
+ return notifications[index];
+}
+
+function waitForPopupShown() {
+ return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+}
+
+/**
+ * Clicks the popup notification button and wait for popup hidden.
+ *
+ * @param {string} button The button type in popup notification.
+ * @param {number} index The action's index in menu list.
+ */
+async function clickDoorhangerButton(button, index) {
+ let popuphidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ if (button == MAIN_BUTTON || button == SECONDARY_BUTTON) {
+ EventUtils.synthesizeMouseAtCenter(getNotification()[button], {});
+ } else if (button == MENU_BUTTON) {
+ // Click the dropmarker arrow and wait for the menu to show up.
+ info("expecting notification menu button present");
+ await BrowserTestUtils.waitForCondition(() => getNotification().menubutton);
+ await sleep(2000); // menubutton needs extra time for binding
+ let notification = getNotification();
+ ok(notification.menubutton, "notification menupopup displayed");
+ let dropdownPromise = BrowserTestUtils.waitForEvent(
+ notification.menupopup,
+ "popupshown"
+ );
+ await EventUtils.synthesizeMouseAtCenter(notification.menubutton, {});
+ info("expecting notification popup show up");
+ await dropdownPromise;
+
+ let actionMenuItem = notification.querySelectorAll("menuitem")[index];
+ await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {});
+ }
+ info("expecting notification popup hidden");
+ await popuphidden;
+}
+
+function getDoorhangerCheckbox() {
+ return getNotification().checkbox;
+}
+
+function getDoorhangerButton(button) {
+ return getNotification()[button];
+}
+
+/**
+ * Removes all addresses and credit cards from storage.
+ *
+ * **NOTE: If you add or update a record in a test, then you must wait for the
+ * respective storage event to fire before calling this function.**
+ * This is because this function doesn't guarantee that a record that
+ * is about to be added or update will also be removed,
+ * since the add or update is triggered by an asynchronous call.
+ *
+ * @see waitForStorageChangedEvents for more details about storage events to wait for
+ */
+async function removeAllRecords() {
+ let addresses = await getAddresses();
+ if (addresses.length) {
+ await removeAddresses(addresses.map(address => address.guid));
+ }
+ let creditCards = await getCreditCards();
+ if (creditCards.length) {
+ await removeCreditCards(creditCards.map(cc => cc.guid));
+ }
+}
+
+async function waitForFocusAndFormReady(win) {
+ return Promise.all([
+ new Promise(resolve => waitForFocus(resolve, win)),
+ BrowserTestUtils.waitForEvent(win, "FormReady"),
+ ]);
+}
+
+// Verify that the warning in the autocomplete popup has the expected text.
+async function expectWarningText(browser, expectedText) {
+ const {
+ autoCompletePopup: { richlistbox: itemsBox },
+ } = browser;
+ let warningBox = itemsBox.querySelector(
+ ".autocomplete-richlistitem:last-child"
+ );
+
+ while (warningBox.collapsed) {
+ warningBox = warningBox.previousSibling;
+ }
+ warningBox = warningBox._warningTextBox;
+
+ await BrowserTestUtils.waitForMutationCondition(
+ warningBox,
+ { childList: true, characterData: true },
+ () => warningBox.textContent == expectedText
+ );
+ ok(true, `Got expected warning text: ${expectedText}`);
+}
+
+async function testDialog(url, testFn, arg = undefined) {
+ // Skip this step for test cards that lack an encrypted
+ // number since they will fail to decrypt.
+ if (
+ url == EDIT_CREDIT_CARD_DIALOG_URL &&
+ arg &&
+ arg.record &&
+ arg.record["cc-number-encrypted"]
+ ) {
+ arg.record = Object.assign({}, arg.record, {
+ "cc-number": await OSKeyStore.decrypt(arg.record["cc-number-encrypted"]),
+ });
+ }
+ let win = window.openDialog(url, null, "width=600,height=600", arg);
+ await waitForFocusAndFormReady(win);
+ let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
+ await testFn(win);
+ return unloadPromise;
+}
+
+/**
+ * Initializes the test storage for a task.
+ *
+ * @param {...object} items Can either be credit card or address objects
+ */
+async function setStorage(...items) {
+ for (let item of items) {
+ if (item["cc-number"]) {
+ await saveCreditCard(item);
+ } else {
+ await saveAddress(item);
+ }
+ }
+}
+
+function verifySectionAutofillResult(sections, expectedSectionsInfo) {
+ sections.forEach((section, index) => {
+ const expectedSection = expectedSectionsInfo[index];
+
+ const fieldDetails = section.fieldDetails;
+ const expectedFieldDetails = expectedSection.fields;
+
+ info(`verify autofill section[${index}]`);
+
+ fieldDetails.forEach((field, fieldIndex) => {
+ const expeceted = expectedFieldDetails[fieldIndex];
+
+ Assert.equal(
+ field.element.value,
+ expeceted.autofill,
+ `Autofilled value for element(id=${field.element.id}, field name=${field.fieldName}) should be equal`
+ );
+ });
+ });
+}
+
+function verifySectionFieldDetails(sections, expectedSectionsInfo) {
+ sections.forEach((section, index) => {
+ const expectedSection = expectedSectionsInfo[index];
+
+ const fieldDetails = section.fieldDetails;
+ const expectedFieldDetails = expectedSection.fields;
+
+ info(`section[${index}] ${expectedSection.description ?? ""}:`);
+ info(`FieldName Prediction Results: ${fieldDetails.map(i => i.fieldName)}`);
+ info(
+ `FieldName Expected Results: ${expectedFieldDetails.map(
+ detail => detail.fieldName
+ )}`
+ );
+ Assert.equal(
+ fieldDetails.length,
+ expectedFieldDetails.length,
+ `Expected field count.`
+ );
+
+ fieldDetails.forEach((field, fieldIndex) => {
+ const expectedFieldDetail = expectedFieldDetails[fieldIndex];
+
+ const expected = {
+ ...{
+ reason: "autocomplete",
+ section: "",
+ contactType: "",
+ addressType: "",
+ },
+ ...expectedSection.default,
+ ...expectedFieldDetail,
+ };
+
+ const keys = new Set([...Object.keys(field), ...Object.keys(expected)]);
+ ["autofill", "elementWeakRef", "confidence", "part"].forEach(k =>
+ keys.delete(k)
+ );
+
+ for (const key of keys) {
+ const expectedValue = expected[key];
+ const actualValue = field[key];
+ Assert.equal(
+ actualValue,
+ expectedValue,
+ `${key} should be equal, expect ${expectedValue}, got ${actualValue}`
+ );
+ }
+ });
+
+ Assert.equal(
+ section.isValidSection(),
+ !expectedSection.invalid,
+ `Should be an ${expectedSection.invalid ? "invalid" : "valid"} section`
+ );
+ });
+}
+/**
+ * Runs heuristics test for form autofill on given patterns.
+ *
+ * @param {Array<object>} patterns - An array of test patterns to run the heuristics test on.
+ * @param {string} pattern.description - Description of this heuristic test
+ * @param {string} pattern.fixurePath - The path of the test document
+ * @param {string} pattern.fixureData - Test document by string. Use either fixurePath or fixtureData.
+ * @param {object} pattern.profile - The profile to autofill. This is required only when running autofill test
+ * @param {Array} pattern.expectedResult - The expected result of this heuristic test. See below for detailed explanation
+ *
+ * @param {string} [fixturePathPrefix=""] - The prefix to the path of fixture files.
+ * @param {object} [options={ testAutofill: false }] - An options object containing additional configuration for running the test.
+ * @param {boolean} [options.testAutofill=false] - A boolean indicating whether to run the test for autofill or not.
+ * @returns {Promise} A promise that resolves when all the tests are completed.
+ *
+ * The `patterns.expectedResult` array contains test data for different address or credit card sections.
+ * Each section in the array is represented by an object and can include the following properties:
+ * - description (optional): A string describing the section, primarily used for debugging purposes.
+ * - default (optional): An object that sets the default values for all the fields within this section.
+ * The default object contains the same keys as the individual field objects.
+ * - fields: An array of field details (class FieldDetails) within the section.
+ *
+ * Each field object can have the following keys:
+ * - fieldName: The name of the field (e.g., "street-name", "cc-name" or "cc-number").
+ * - reason: The reason for the field value (e.g., "autocomplete", "regex-heuristic" or "fathom").
+ * - section: The section to which the field belongs (e.g., "billing", "shipping").
+ * - part: The part of the field.
+ * - contactType: The contact type of the field.
+ * - addressType: The address type of the field.
+ * - autofill: Set the expected autofill value when running autofill test
+ *
+ * For more information on the field object properties, refer to the FieldDetails class.
+ *
+ * Example test data:
+ * add_heuristic_tests(
+ * [{
+ * description: "first test pattern",
+ * fixuturePath: "autocomplete_off.html",
+ * profile: {organization: "Mozilla", country: "US", tel: "123"},
+ * expectedResult: [
+ * {
+ * description: "First section"
+ * fields: [
+ * { fieldName: "organization", reason: "autocomplete", autofill: "Mozilla" },
+ * { fieldName: "country", reason: "regex-heuristic", autofill: "US" },
+ * { fieldName: "tel", reason: "regex-heuristic", autofill: "123" },
+ * ]
+ * },
+ * {
+ * default: {
+ * reason: "regex-heuristic",
+ * section: "billing",
+ * },
+ * fields: [
+ * { fieldName: "cc-number", reason: "fathom" },
+ * { fieldName: "cc-nane" },
+ * { fieldName: "cc-exp" },
+ * ],
+ * }],
+ * },
+ * {
+ * // second test pattern //
+ * }
+ * ],
+ * "/fixturepath",
+ * {testAutofill: true} // test options
+ * )
+ */
+
+async function add_heuristic_tests(
+ patterns,
+ fixturePathPrefix = "",
+ options = { testAutofill: false }
+) {
+ async function runTest(testPattern) {
+ const TEST_URL = testPattern.fixtureData
+ ? `data:text/html,${testPattern.fixtureData}`
+ : `${BASE_URL}../${fixturePathPrefix}${testPattern.fixturePath}`;
+
+ if (testPattern.fixtureData) {
+ info(`Starting test with fixture data`);
+ } else {
+ info(`Starting test fixture: ${testPattern.fixturePath ?? ""}`);
+ }
+
+ if (testPattern.description) {
+ info(`Test "${testPattern.description}"`);
+ }
+
+ if (testPattern.prefs) {
+ await SpecialPowers.pushPrefEnv({
+ set: testPattern.prefs,
+ });
+ }
+
+ await BrowserTestUtils.withNewTab(TEST_URL, async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [
+ {
+ testPattern,
+ verifySection: verifySectionFieldDetails.toString(),
+ verifyAutofill: options.testAutofill
+ ? verifySectionAutofillResult.toString()
+ : null,
+ },
+ ],
+ async obj => {
+ const { FormLikeFactory } = ChromeUtils.importESModule(
+ "resource://gre/modules/FormLikeFactory.sys.mjs"
+ );
+ const { FormAutofillHandler } = ChromeUtils.importESModule(
+ "resource://gre/modules/shared/FormAutofillHandler.sys.mjs"
+ );
+
+ const elements = Array.from(
+ content.document.querySelectorAll("input, select")
+ );
+
+ // Bug 1834768. We should simulate user behavior instead of
+ // using internal APIs.
+ const forms = elements.reduce((acc, element) => {
+ const formLike = FormLikeFactory.createFromField(element);
+ if (!acc.some(form => form.rootElement === formLike.rootElement)) {
+ acc.push(formLike);
+ }
+ return acc;
+ }, []);
+
+ const sections = forms.flatMap(form => {
+ const handler = new FormAutofillHandler(form);
+ handler.collectFormFields(false /* ignoreInvalid */);
+ return handler.sections;
+ });
+
+ Assert.equal(
+ sections.length,
+ obj.testPattern.expectedResult.length,
+ "Expected section count."
+ );
+
+ // eslint-disable-next-line no-eval
+ let verify = eval(`(() => {return (${obj.verifySection});})();`);
+ verify(sections, obj.testPattern.expectedResult);
+
+ if (obj.verifyAutofill) {
+ for (const section of sections) {
+ section.focusedInput = section.fieldDetails[0].element;
+ await section.autofillFields(
+ section.getAdaptedProfiles([obj.testPattern.profile])[0]
+ );
+ }
+
+ // eslint-disable-next-line no-eval
+ verify = eval(`(() => {return (${obj.verifyAutofill});})();`);
+ verify(sections, obj.testPattern.expectedResult);
+ }
+ }
+ );
+ });
+
+ if (testPattern.prefs) {
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+
+ patterns.forEach(testPattern => {
+ add_task(() => runTest(testPattern));
+ });
+}
+
+async function add_autofill_heuristic_tests(patterns, fixturePathPrefix = "") {
+ add_heuristic_tests(patterns, fixturePathPrefix, { testAutofill: true });
+}
+
+add_setup(function () {
+ OSKeyStoreTestUtils.setup();
+});
+
+registerCleanupFunction(async () => {
+ await removeAllRecords();
+ await OSKeyStoreTestUtils.cleanup();
+});
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser.ini b/browser/extensions/formautofill/test/browser/heuristics/browser.ini
new file mode 100644
index 0000000000..cc93d6beea
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+skip-if = toolkit == 'android' # bug 1730213
+support-files =
+ ../head.js
+ ../../fixtures/**
+
+[browser_autocomplete_off_on_form.js]
+[browser_autocomplete_off_on_inputs.js]
+[browser_basic.js]
+[browser_cc_exp.js]
+[browser_de_fields.js]
+[browser_fr_fields.js]
+[browser_ignore_invisible_fields.js]
+[browser_multiple_section.js]
+[browser_parseAddressFields.js]
+[browser_section_validation_address.js]
+[browser_sections_by_name.js]
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_form.js b/browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_form.js
new file mode 100644
index 0000000000..f93752bd1e
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_form.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global add_heuristic_tests */
+
+// Ensures that fields are identified correctly even when the containing form
+// has its autocomplete attribute set to off.
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "autocomplete_off_on_form.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-line3" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-number" },
+ { fieldName: "cc-name" },
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "address-line1" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-line2" },
+ { fieldName: "organization" },
+ { fieldName: "address-line3" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_inputs.js b/browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_inputs.js
new file mode 100644
index 0000000000..13c8d82dbb
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_autocomplete_off_on_inputs.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global add_heuristic_tests */
+
+// Ensures that fields are identified correctly even when the inputs
+// have their autocomplete attribute set to off.
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "autocomplete_off_on_inputs.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-line3" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-name", reason: "fathom" },
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "address-line1" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-line2" },
+ { fieldName: "organization" },
+ { fieldName: "address-line3" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "address-line1", reason: "regex-heuristic" },
+ { fieldName: "address-line2", reason: "regex-heuristic" },
+ { fieldName: "address-line3", reason: "regex-heuristic" },
+ { fieldName: "address-level2", reason: "regex-heuristic" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code", reason: "regex-heuristic" },
+ { fieldName: "country", reason: "regex-heuristic" },
+ { fieldName: "tel" },
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-name" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_basic.js b/browser/extensions/formautofill/test/browser/heuristics/browser_basic.js
new file mode 100644
index 0000000000..fda375a146
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_basic.js
@@ -0,0 +1,69 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "autocomplete_basic.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-line3" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-number" },
+ { fieldName: "cc-name" },
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "address-line1" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-line2" },
+ { fieldName: "organization" },
+ { fieldName: "address-line3" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_cc_exp.js b/browser/extensions/formautofill/test/browser/heuristics/browser_cc_exp.js
new file mode 100644
index 0000000000..8eb774b65c
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_cc_exp.js
@@ -0,0 +1,56 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "heuristics_cc_exp.html",
+ expectedResult: [
+ {
+ description: "form1",
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-name" },
+ { fieldName: "cc-number" },
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ ],
+ },
+ {
+ description: "form2",
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [{ fieldName: "cc-number" }, { fieldName: "cc-exp" }],
+ },
+ {
+ description: "form3",
+ fields: [
+ { fieldName: "cc-number", reason: "autocomplete" },
+ { fieldName: "cc-exp", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ description: "form4",
+ fields: [
+ { fieldName: "cc-number", reason: "autocomplete" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ description: "form5",
+ invalid: true,
+ fields: [
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_de_fields.js b/browser/extensions/formautofill/test/browser/heuristics/browser_de_fields.js
new file mode 100644
index 0000000000..0d47cb6935
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_de_fields.js
@@ -0,0 +1,32 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "heuristics_de_fields.html",
+ expectedResult: [
+ {
+ fields: [
+ { fieldName: "cc-name", reason: "fathom" },
+ { fieldName: "cc-type", reason: "regex-heuristic" },
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ fields: [
+ { fieldName: "cc-name", reason: "fathom" },
+ { fieldName: "cc-type", reason: "regex-heuristic" },
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_fr_fields.js b/browser/extensions/formautofill/test/browser/heuristics/browser_fr_fields.js
new file mode 100644
index 0000000000..a2c8add393
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_fr_fields.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "heuristics_fr_fields.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp" },
+ { fieldName: "cc-name" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_ignore_invisible_fields.js b/browser/extensions/formautofill/test/browser/heuristics/browser_ignore_invisible_fields.js
new file mode 100644
index 0000000000..d22b1d5031
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_ignore_invisible_fields.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests([
+ {
+ description: "all fields are visible",
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <input type="text" id="name" autocomplete="name" />
+ <input type="text" id="tel" autocomplete="tel" />
+ <input type="text" id="email" autocomplete="email" />
+ <input type="text" id="country" autocomplete="country"/>
+ <input type="text" id="postal-code" autocomplete="postal-code" />
+ <input type="text" id="address-line1" autocomplete="address-line1" />
+ <div>
+ <input type="text" id="address-line2" autocomplete="address-line2" />
+ </div>
+ </form>
+ </form>
+ </body>
+ </html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "name" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ { fieldName: "country" },
+ { fieldName: "postal-code" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ ],
+ },
+ ],
+ },
+ {
+ description: "some fields are invisible because of css style",
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <input type="text" id="name" autocomplete="name" />
+ <input type="text" id="tel" autocomplete="tel" />
+ <input type="text" id="email" autocomplete="email" />
+ <input type="text" id="country" autocomplete="country" hidden />
+ <input type="text" id="postal-code" autocomplete="postal-code" style="display:none" />
+ <input type="text" id="address-line1" autocomplete="address-line1" style="opacity:0" />
+ <div style="visibility: hidden">
+ <input type="text" id="address-line2" autocomplete="address-line2" />
+ </div>
+ </form>
+ </body>
+ </html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "name" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ ],
+ },
+ {
+ // hidden and style="display:none" are always considered regardless what visibility check we use
+ description:
+ "invisible fields are identified because number of elemenent in the form exceed the threshold",
+ prefs: [["extensions.formautofill.heuristics.visibilityCheckThreshold", 1]],
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <input type="text" id="name" autocomplete="name" />
+ <input type="text" id="tel" autocomplete="tel" />
+ <input type="text" id="email" autocomplete="email" />
+ <input type="text" id="country" autocomplete="country" hidden />
+ <input type="text" id="postal-code" autocomplete="postal-code" style="display:none" />
+ <input type="text" id="address-line1" autocomplete="address-line1" style="opacity:0" />
+ <div style="visibility: hidden">
+ <input type="text" id="address-line2" autocomplete="address-line2" />
+ </div>
+ </form>
+ </body>
+ </html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "name" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_multiple_section.js b/browser/extensions/formautofill/test/browser/heuristics/browser_multiple_section.js
new file mode 100644
index 0000000000..078f2240db
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_multiple_section.js
@@ -0,0 +1,118 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "multiple_section.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "shipping",
+ },
+ fields: [
+ { fieldName: "name", addressType: "" },
+ { fieldName: "organization", addressType: "" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ section: "section-my",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel", section: "", contactType: "work" },
+ { fieldName: "email", section: "", contactType: "work" },
+ ],
+ },
+ {
+ invalid: true,
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ // Even the `contactType` of these two fields are different with the
+ // above two, we still consider they are identical until supporting
+ // multiple phone number and email in one profile.
+ { fieldName: "tel", contactType: "home" },
+ { fieldName: "email", contactType: "home" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "name" },
+ { fieldName: "organization" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "tel", contactType: "work" },
+ { fieldName: "email", contactType: "work" },
+ ],
+ },
+ {
+ invalid: true,
+ default: {
+ reason: "autocomplete",
+ contactType: "home",
+ },
+ fields: [{ fieldName: "tel" }, { fieldName: "email" }],
+ },
+ ],
+ },
+ ],
+ "fixtures/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_parseAddressFields.js b/browser/extensions/formautofill/test/browser/heuristics/browser_parseAddressFields.js
new file mode 100644
index 0000000000..ffe739d417
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_parseAddressFields.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests([
+ {
+ // This bug happens only when the last element is address-lineX and
+ // the field is identified by regular expressions in `HeuristicsRegExp` but is not
+ // identified by regular expressions defined in `_parseAddressFields`
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <p><label>country: <input type="text" id="country" name="country" /></label></p>
+ <p><label>tel: <input type="text" id="tel" name="tel" /></label></p>
+ <p><label><input type="text" id="housenumber" /></label></p>
+ </form>
+ </body>
+ </html>`,
+ expectedResult: [
+ {
+ description:
+ "Address Line1 in the last element and is not updated in _parsedAddressFields",
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "address-line1" },
+ ],
+ },
+ ],
+ },
+ {
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <p><label>country: <input type="text" id="country" name="country" /></label></p>
+ <p><label>tel: <input type="text" id="tel" name="tel" /></label></p>
+ <p><label><input type="text" id="housenumber" /></label></p>
+ <p><label><input type="text" id="addrcomplement" /></label></p>
+ </form>
+ </body>
+ </html>`,
+ expectedResult: [
+ {
+ description:
+ "Address Line2 in the last element and is not updated in _parsedAddressFields",
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "country" },
+ { fieldName: "tel" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ ],
+ },
+ ],
+ },
+ {
+ // Bug 1833613
+ description:
+ "street-address field is treated as address-line1 when address-line2 is present while adddress-line1 is not",
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <input type="text" id="street-address" autocomplete="street-address"/>
+ <input type="text" id="address-line2" autocomplete="address-line2"/>
+ <input type="text" id="email" autocomplete="email"/>
+ </form>
+ </body>
+ </html>`,
+ expectedResult: [
+ {
+ fields: [
+ { fieldName: "address-line1", reason: "regexp-heuristic" },
+ { fieldName: "address-line2", reason: "autocomplete" },
+ { fieldName: "email", reason: "autocomplete" },
+ ],
+ },
+ ],
+ },
+ {
+ // Bug 1833613
+ description:
+ "street-address field should not be treated as address-line1 when address-line2 is not present",
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <input type="text" id="street-address" autocomplete="street-address"/>
+ <input type="text" id="address-line3" autocomplete="address-line3"/>
+ <input type="text" id="email" autocomplete="email"/>
+ </form>
+ </body>
+ </html>`,
+ expectedResult: [
+ {
+ fields: [
+ { fieldName: "street-address", reason: "autocomplete" },
+ { fieldName: "address-line3", reason: "autocomplete" },
+ { fieldName: "email", reason: "autocomplete" },
+ ],
+ },
+ ],
+ },
+ {
+ // Bug 1833613
+ description:
+ "street-address field should not be treated as address-line1 when address-line1 is present",
+ fixtureData: `
+ <html>
+ <body>
+ <form>
+ <input type="text" id="street-address" autocomplete="street-address"/>
+ <input type="text" id="address-line1" autocomplete="address-line1"/>
+ <input type="text" id="email" autocomplete="email"/>
+ </form>
+ </body>
+ </html>`,
+ expectedResult: [
+ {
+ fields: [
+ { fieldName: "street-address", reason: "autocomplete" },
+ { fieldName: "address-line1", reason: "autocomplete" },
+ { fieldName: "email", reason: "autocomplete" },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_section_validation_address.js b/browser/extensions/formautofill/test/browser/heuristics/browser_section_validation_address.js
new file mode 100644
index 0000000000..2e9cf42ab0
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_section_validation_address.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests([
+ {
+ description: `An address section is valid when it only contains more than three fields`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="street-address">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="email" autocomplete="email">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "email" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `An address section is invalid when it contains less than threee fields`,
+ fixtureData: `
+ <html><body>
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="email" autocomplete="email">
+
+ <input id="postal-code" autocomplete="postal-code">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ description: "A section with two fields",
+ invalid: true,
+ fields: [
+ { fieldName: "postal-code", reason: "autocomplete" },
+ { fieldName: "email", reason: "autocomplete" },
+ ],
+ },
+ {
+ description: "A section with one field",
+ invalid: true,
+ fields: [{ fieldName: "postal-code", reason: "autocomplete" }],
+ },
+ ],
+ },
+ {
+ description: `Address section validation only counts the number of different address field name in the section`,
+ fixtureData: `
+ <html><body>
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="email" autocomplete="email">
+ <input id="email" autocomplete="email">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ description:
+ "A section with three fields but has duplicated email fields",
+ invalid: true,
+ fields: [
+ { fieldName: "postal-code", reason: "autocomplete" },
+ { fieldName: "email", reason: "autocomplete" },
+ { fieldName: "email", reason: "autocomplete" },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/browser_sections_by_name.js b/browser/extensions/formautofill/test/browser/heuristics/browser_sections_by_name.js
new file mode 100644
index 0000000000..c6c8ea5759
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/browser_sections_by_name.js
@@ -0,0 +1,318 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global add_heuristic_tests */
+
+"use strict";
+
+// The following are included in this test
+// - One named billing section
+// - One named billing section and one named shipping section
+// - One named billing section and one section without name
+// - Fields without section name are merged to a section with section name
+// - Two sections without name
+
+add_heuristic_tests([
+ {
+ description: `One named billing section`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `One billing section and one shipping section`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ <input id="street-address" autocomplete="shipping street-address">
+ <input id="postal-code" autocomplete="shipping postal-code">
+ <input id="country" autocomplete="shipping country">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "shipping",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `One billing section, one shipping section, and then billing section`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="street-address" autocomplete="shipping street-address">
+ <input id="postal-code" autocomplete="shipping postal-code">
+ <input id="country" autocomplete="shipping country">
+ <input id="country" autocomplete="billing country">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "shipping",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `one section without a name and one billing section`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="street-address">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `One billing section and one section without a name`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ <input id="street-address" autocomplete="street-address">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `Fields without section name are merged (test both before and after the section with a name)`,
+ fixtureData: `
+ <html><body>
+ <input id="name" autocomplete="name">
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ <input id="street-address" autocomplete="shipping street-address">
+ <input id="postal-code" autocomplete="shipping postal-code">
+ <input id="country" autocomplete="shipping country">
+ <input id="name" autocomplete="name">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "name", addressType: "" },
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "shipping",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "name", addressType: "" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `Fields without section name are merged, but do not merge if the field already exists`,
+ fixtureData: `
+ <html><body>
+ <input id="name" autocomplete="name">
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ <input id="name" autocomplete="name">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "name", addressType: "" },
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [{ fieldName: "name", reason: "autocomplete" }],
+ },
+ ],
+ },
+ {
+ description: `Fields without section name are merged (multi-fields)`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="billing street-address">
+ <input id="postal-code" autocomplete="billing postal-code">
+ <input id="country" autocomplete="billing country">
+ <input id="email" autocomplete="email">
+ <input id="email" autocomplete="email">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ { fieldName: "email", addressType: "" },
+ { fieldName: "email", addressType: "" },
+ ],
+ },
+ ],
+ },
+ {
+ description: `Two sections without name`,
+ fixtureData: `
+ <html><body>
+ <input id="street-address" autocomplete="street-address">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ <input id="street-address" autocomplete="street-address">
+ <input id="postal-code" autocomplete="postal-code">
+ <input id="country" autocomplete="country">
+ </body></html>
+ `,
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ { fieldName: "country" },
+ ],
+ },
+ ],
+ },
+]);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser.ini b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser.ini
new file mode 100644
index 0000000000..6519ce9ae8
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+skip-if = toolkit == 'android' # bug 1730213
+support-files =
+ ../../head.js
+ ../../../fixtures/**
+
+[browser_BestBuy.js]
+[browser_CDW.js]
+[browser_CostCo.js]
+[browser_DirectAsda.js]
+[browser_Ebay.js]
+[browser_GlobalDirectAsda.js]
+[browser_HomeDepot.js]
+[browser_Lufthansa.js]
+[browser_Lush.js]
+[browser_Macys.js]
+[browser_NewEgg.js]
+[browser_OfficeDepot.js]
+[browser_QVC.js]
+[browser_Sears.js]
+[browser_Staples.js]
+[browser_Walmart.js]
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_BestBuy.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_BestBuy.js
new file mode 100644
index 0000000000..e91c19de45
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_BestBuy.js
@@ -0,0 +1,82 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout_ShippingAddress.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" }, // sign-up
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ { fieldName: "tel", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Checkout_Payment.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "street-address" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ { fieldName: "tel", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "SignIn.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" }, // sign-in
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/BestBuy/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CDW.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CDW.js
new file mode 100644
index 0000000000..feae321cb2
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CDW.js
@@ -0,0 +1,71 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout_ShippingInfo.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "postal-code" }, // EXt
+ { fieldName: "email" },
+ { fieldName: "tel" },
+ { fieldName: "tel-extension" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Checkout_BillingPaymentInfo.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "postal-code" }, // Ext
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-type" }, // ac-off
+ { fieldName: "cc-number", reason: "fathom" }, // ac-off
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ // {fieldName: "cc-csc"},
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Checkout_Logon.html",
+ expectedResult: [
+ ],
+ },
+ ],
+ "fixtures/third_party/CDW/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CostCo.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CostCo.js
new file mode 100644
index 0000000000..c83973b3c9
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_CostCo.js
@@ -0,0 +1,170 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "ShippingAddress.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "additional-name" }, // middle-name initial
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "country" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "additional-name" }, // middle-name initial
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "country" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Payment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-type" }, // ac-off
+ { fieldName: "cc-number", reason: "fathom" }, // ac-off
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ // { fieldName: "cc-csc"}, // ac-off
+ { fieldName: "cc-name", reason: "fathom" }, // ac-off
+ ],
+ },
+ {
+ invalid: true, // confidence is not high enough
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" }, // ac-off
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "additional-name" }, // middle-name initial
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "country" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "additional-name" }, // middle-name initial
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "country" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "SignIn.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ // Forgot password
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ // {fieldName: "password"},
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ // Sign up
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ }
+ ],
+ },
+ ],
+ "fixtures/third_party/CostCo/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_DirectAsda.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_DirectAsda.js
new file mode 100644
index 0000000000..6cb7979173
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_DirectAsda.js
@@ -0,0 +1,25 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Payment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-number" },
+ { fieldName: "cc-name" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/DirectAsda/"
+)
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Ebay.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Ebay.js
new file mode 100644
index 0000000000..db6047718f
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Ebay.js
@@ -0,0 +1,25 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout_Payment_FR.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "cc-number" },
+ { fieldName: "cc-exp" },
+ { fieldName: "cc-given-name" },
+ { fieldName: "cc-family-name" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Ebay/"
+)
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_GlobalDirectAsda.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_GlobalDirectAsda.js
new file mode 100644
index 0000000000..7b82009032
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_GlobalDirectAsda.js
@@ -0,0 +1,24 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Payment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/GlobalDirectAsda/"
+)
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_HomeDepot.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_HomeDepot.js
new file mode 100644
index 0000000000..8aac002b2d
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_HomeDepot.js
@@ -0,0 +1,77 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout_ShippingPayment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "email" },
+ { fieldName: "tel" },
+ { fieldName: "street-address" },
+ { fieldName: "postal-code" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ {
+ fieldName: "street-address",
+ reason: "autocomplete",
+ addressType: "billing",
+ },
+ ],
+ },
+ // THis should be fixed by visibility check
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ // FIXME: bug 1392944 - the uncommented cc-exp-month and cc-exp-year are
+ // both invisible <input> elements, and the following two <select>
+ // elements are the correct ones. BTW, they are both applied
+ // autocomplete attr.
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ { fieldName: "cc-number", reason: "fathom" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "cc-exp-month", reason: "regex-heuristic"},
+ { fieldName: "cc-exp-year", reason: "regex-heuristic"},
+ // {fieldName: "cc-csc"},
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "SignIn.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email",reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email",reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/HomeDepot/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lufthansa.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lufthansa.js
new file mode 100644
index 0000000000..801dbf66be
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lufthansa.js
@@ -0,0 +1,28 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout_Payment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-type", reason: "regex-heuristic" },
+ { fieldName: "cc-number" },
+ { fieldName: "cc-number" },
+ { fieldName: "cc-number" },
+ { fieldName: "cc-number" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Lufthansa/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lush.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lush.js
new file mode 100644
index 0000000000..a59fc966e5
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Lush.js
@@ -0,0 +1,31 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "index.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-name" },
+ { fieldName: "cc-number" },
+ ],
+ },
+ {
+ fields: [
+ { fieldName: "cc-number", reason: "autocomplete" },
+ { fieldName: "cc-name", reason: "fathom" },
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Lush/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Macys.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Macys.js
new file mode 100644
index 0000000000..e02996b565
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Macys.js
@@ -0,0 +1,88 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout_ShippingAddress.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Checkout_Payment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-type" }, // ac-off
+ { fieldName: "cc-number", reason: "fathom" }, // ac-off
+ { fieldName: "cc-exp-month" }, // ac-off
+ { fieldName: "cc-exp-year" }, // ac-off
+ // {fieldName: "cc-csc"}, // ac-off
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "SignIn.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ // Sign in
+ { fieldName: "email", reason: "regex-heuristic"},
+ // {fieldName: "password"},
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ // Forgot password
+ { fieldName: "email", reason: "regex-heuristic"},
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic"},
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Macys/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_NewEgg.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_NewEgg.js
new file mode 100644
index 0000000000..d914f02890
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_NewEgg.js
@@ -0,0 +1,109 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "ShippingInfo.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "country" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "email" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "BillingInfo.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-name" },
+ { fieldName: "cc-number" }, // ac-off
+ ],
+ },
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-name" },
+ { fieldName: "cc-number" }, // ac-off
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ // { fieldName: "cc-csc"},
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "country" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ ],
+ },
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-name" },
+ { fieldName: "cc-number" }, // ac-off
+ { fieldName: "cc-exp-month", reason: "regex-heuristic" },
+ { fieldName: "cc-exp-year", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-name" },
+ { fieldName: "cc-number" }, // ac-off
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Login.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" }, // Email Address
+ { fieldName: "email", reason: "regex-heuristic" }, // Confirm Email Address
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/NewEgg/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_OfficeDepot.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_OfficeDepot.js
new file mode 100644
index 0000000000..8c44104cb6
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_OfficeDepot.js
@@ -0,0 +1,83 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "ShippingAddress.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "postal-code" },
+ { fieldName: "address-level2" }, // City & State
+ { fieldName: "address-level2" }, // City
+ { fieldName: "address-level1" }, // State
+ { fieldName: "tel-area-code" },
+ { fieldName: "tel-local-prefix" },
+ { fieldName: "tel-local-suffix" },
+ { fieldName: "tel-extension" },
+ { fieldName: "email" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Payment.html",
+ expectedResult: [
+ {
+ invalid: true, // because non of them is identified by fathom
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ { fieldName: "cc-number" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "organization" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "postal-code" },
+ { fieldName: "address-level2" }, // City & State
+ { fieldName: "address-level2" }, // City
+ { fieldName: "address-level1" }, // state
+ { fieldName: "tel-area-code" },
+ { fieldName: "tel-local-prefix" },
+ { fieldName: "tel-local-suffix" },
+ { fieldName: "tel-extension" },
+ { fieldName: "email" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "SignIn.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/OfficeDepot/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_QVC.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_QVC.js
new file mode 100644
index 0000000000..bc0504e726
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_QVC.js
@@ -0,0 +1,96 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "YourInformation.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "tel", reason: "regex-heuristic" },
+ { fieldName: "email", reason: "regex-heuristic" },
+ // { fieldName: "bday-month"}, // select
+ // { fieldName: "bday-day"}, // select
+ // { fieldName: "bday-year"},
+ ],
+ },
+ {
+ fields: [
+ { fieldName: "cc-type", reason: "regex-heuristic" },
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp", reason: "regex-heuristic" },
+ // { fieldName: "cc-csc"},
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "cc-number", reason: "regex-heuristic" }, // txtQvcGiftCardNumber
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "PaymentMethod.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "tel", reason: "regex-heuristic" },
+ { fieldName: "email", reason: "regex-heuristic" },
+ // { fieldName: "bday-month"}, // select
+ // { fieldName: "bday-day"}, // select
+ // { fieldName: "bday-year"}, // select
+ ],
+ },
+ {
+ default: {
+ reason: "fathom",
+ },
+ fields: [
+ { fieldName: "cc-type", reason: "regex-heuristic" }, // ac-off
+ { fieldName: "cc-number" }, // ac-off
+ { fieldName: "cc-exp", reason: "regex-heuristic" },
+ // { fieldName: "cc-csc"},
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "cc-number", reason: "regex-heuristic" }, // txtQvcGiftCardNumbe, ac-off
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "SignIn.html",
+ expectedResult: [
+ {
+ // Sign in
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/QVC/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Sears.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Sears.js
new file mode 100644
index 0000000000..4c668ddad1
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Sears.js
@@ -0,0 +1,81 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "ShippingAddress.html",
+ expectedResult: [
+ {
+ invalid: true,
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "email" },
+ ]
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ // check-out, ac-off
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "tel-extension" },
+ { fieldName: "email" },
+ { fieldName: "email" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ // ac-off
+ // { fieldName: "email"},
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" },
+ { fieldName: "address-level1" },
+ { fieldName: "postal-code" },
+ { fieldName: "tel" },
+ { fieldName: "tel-extension" },
+ // { fieldName: "new-password"},
+ ],
+ },
+ {
+ invalid: true,
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ // ac-off
+ { fieldName: "email" },
+ ]
+ },
+ {
+ invalid: true,
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ // ac-off
+ { fieldName: "email" },
+ ]
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Sears/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Staples.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Staples.js
new file mode 100644
index 0000000000..4b03fe2338
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Staples.js
@@ -0,0 +1,78 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Basic.html",
+ expectedResult: [
+ {
+ // ac-off
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "address-line1" },
+ { fieldName: "email" },
+ { fieldName: "tel" },
+ { fieldName: "tel" }, // Extension
+ { fieldName: "organization" },
+ ]
+ },
+ ],
+ },
+ {
+ fixturePath: "Basic_ac_on.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "address-line1" },
+ { fieldName: "email" },
+ { fieldName: "tel" },
+ { fieldName: "tel" }, // Extension
+ { fieldName: "organization" },
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "PaymentBilling.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp" },
+ // {fieldName: "cc-csc"},
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "PaymentBilling_ac_on.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "cc-number", reason: "fathom" },
+ { fieldName: "cc-exp" },
+ // { fieldName: "cc-csc"},
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Staples/"
+);
diff --git a/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Walmart.js b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Walmart.js
new file mode 100644
index 0000000000..f2896bd9ac
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/heuristics/third_party/browser_Walmart.js
@@ -0,0 +1,93 @@
+/* global add_heuristic_tests */
+
+"use strict";
+
+add_heuristic_tests(
+ [
+ {
+ fixturePath: "Checkout.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "postal-code", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "email", reason: "regex-heuristic" },
+ // { fieldName: "password"}, // ac-off
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "email" }, // ac-off
+ // { fieldName: "password"},
+ // { fieldName: "password"}, // ac-off
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Payment.html",
+ expectedResult: [
+ {
+ default: {
+ reason: "autocomplete",
+ section: "section-payment",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "tel" },
+ ],
+ },
+ {
+ default: {
+ reason: "autocomplete",
+ section: "section-payment",
+ },
+ fields: [
+ { fieldName: "cc-number" },
+ { fieldName: "cc-exp-month" },
+ { fieldName: "cc-exp-year" },
+ // { fieldName: "cc-csc"},
+ ],
+ },
+ ],
+ },
+ {
+ fixturePath: "Shipping.html",
+ expectedResult: [
+ {
+ invalid: true,
+ fields: [
+ { fieldName: "postal-code", reason: "regex-heuristic" },
+ ],
+ },
+ {
+ default: {
+ reason: "regex-heuristic",
+ },
+ fields: [
+ { fieldName: "given-name" },
+ { fieldName: "family-name" },
+ { fieldName: "tel" },
+ { fieldName: "address-line1" },
+ { fieldName: "address-line2" },
+ { fieldName: "address-level2" }, // city
+ { fieldName: "address-level1" }, // state
+ { fieldName: "postal-code" },
+ ],
+ },
+ ],
+ },
+ ],
+ "fixtures/third_party/Walmart/"
+);