summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/browser/address
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/test/browser/address')
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser.toml47
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_autofill_nimbus.js70
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_capture_form_removal.js124
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_capture_page_navigation.js125
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_confirmation_popup.js125
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_display.js336
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_invalid_fields.js224
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_multiple_tabs.js55
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_non_mergeable_fields.js94
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_not_shown.js97
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_state.js129
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_tel.js119
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_ui.js277
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_unsupported_region.js88
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_address_telemetry.js741
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display.js216
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display_state.js77
-rw-r--r--browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_save_edited_fields.js114
-rw-r--r--browser/extensions/formautofill/test/browser/address/head_address.js1
19 files changed, 3059 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/browser/address/browser.toml b/browser/extensions/formautofill/test/browser/address/browser.toml
new file mode 100644
index 0000000000..bff24a88b0
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser.toml
@@ -0,0 +1,47 @@
+[DEFAULT]
+prefs = [
+ "extensions.formautofill.addresses.enabled=true",
+ "extensions.formautofill.addresses.capture.requiredFields=''",
+ "toolkit.telemetry.ipcBatchTimeout=0", # lower the interval for event telemetry in the content process to update the parent process
+]
+support-files = [
+ "../head.js",
+ "../../fixtures/autocomplete_address_basic.html",
+ "../../fixtures/capture_address_on_page_navigation.html",
+ "../../fixtures/without_autocomplete_address_basic.html",
+ "head_address.js",
+]
+
+["browser_address_autofill_nimbus.js"]
+
+["browser_address_capture_form_removal.js"]
+
+["browser_address_capture_page_navigation.js"]
+
+["browser_address_doorhanger_confirmation_popup.js"]
+
+["browser_address_doorhanger_display.js"]
+
+["browser_address_doorhanger_invalid_fields.js"]
+
+["browser_address_doorhanger_multiple_tabs.js"]
+
+["browser_address_doorhanger_non_mergeable_fields.js"]
+
+["browser_address_doorhanger_not_shown.js"]
+
+["browser_address_doorhanger_state.js"]
+
+["browser_address_doorhanger_tel.js"]
+
+["browser_address_doorhanger_ui.js"]
+
+["browser_address_doorhanger_unsupported_region.js"]
+
+["browser_address_telemetry.js"]
+
+["browser_edit_address_doorhanger_display.js"]
+
+["browser_edit_address_doorhanger_display_state.js"]
+
+["browser_edit_address_doorhanger_save_edited_fields.js"]
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_autofill_nimbus.js b/browser/extensions/formautofill/test/browser/address/browser_address_autofill_nimbus.js
new file mode 100644
index 0000000000..0ea100cf8f
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_autofill_nimbus.js
@@ -0,0 +1,70 @@
+"use strict";
+
+const { ExperimentAPI } = ChromeUtils.importESModule(
+ "resource://nimbus/ExperimentAPI.sys.mjs"
+);
+
+const { ExperimentFakes } = ChromeUtils.importESModule(
+ "resource://testing-common/NimbusTestUtils.sys.mjs"
+);
+
+const { FormAutofill } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofill.sys.mjs"
+);
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.experiments.enabled", false],
+ ["extensions.formautofill.addresses.supportedCountries", "FR"],
+ ],
+ });
+});
+
+add_task(async function test_address_autofill_feature_enabled() {
+ await ExperimentAPI.ready();
+
+ const cleanupExperiment = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "address-autofill-feature",
+ value: { status: true },
+ });
+
+ is(
+ NimbusFeatures["address-autofill-feature"].getVariable("status"),
+ true,
+ "Nimbus feature should be enabled"
+ );
+
+ is(
+ FormAutofill.isAutofillAddressesAvailable,
+ true,
+ "Address autofill should be available when feature is enabled in nimbus."
+ );
+
+ await cleanupExperiment();
+});
+
+add_task(async function test_address_autofill_feature_disabled() {
+ await ExperimentAPI.ready();
+
+ const cleanupExperiment = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "address-autofill-feature",
+ value: { status: false },
+ });
+
+ NimbusFeatures["address-autofill-feature"].recordExposureEvent();
+
+ is(
+ NimbusFeatures["address-autofill-feature"].getVariable("status"),
+ false,
+ "Nimbus feature shouldn't be enabled"
+ );
+
+ is(
+ FormAutofill.isAutofillAddressesAvailable,
+ false,
+ "Address autofill shouldn't be available when feature is off in nimbus."
+ );
+
+ await cleanupExperiment();
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_capture_form_removal.js b/browser/extensions/formautofill/test/browser/address/browser_address_capture_form_removal.js
new file mode 100644
index 0000000000..f94fc8241f
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_capture_form_removal.js
@@ -0,0 +1,124 @@
+"use strict";
+
+async function expectSavedAddresses(expectedAddresses) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedAddresses.length,
+ `${addresses.length} address in the storage`
+ );
+
+ for (let i = 0; i < expectedAddresses.length; i++) {
+ for (const [key, value] of Object.entries(expectedAddresses[i])) {
+ is(addresses[i][key] ?? "", value, `field ${key} should be equal`);
+ }
+ }
+ return addresses;
+}
+
+const ADDRESS_FIELD_VALUES = {
+ "given-name": "John",
+ organization: "Sesame Street",
+ "street-address": "123 Sesame Street",
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ["extensions.formautofill.heuristics.captureOnFormRemoval", true],
+ ],
+ });
+ await removeAllRecords();
+});
+
+/**
+ * Tests if the address is captured (address doorhanger is shown) after a
+ * successful xhr or fetch request followed by a form removal and
+ * that the stored address record has the right values.
+ */
+add_task(async function test_address_captured_after_form_removal() {
+ const onStorageChanged = waitForStorageChangedEvents("add");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ const onPopupShown = waitForPopupShown();
+
+ info("Update identified address fields");
+ // We don't submit the form
+ await focusUpdateSubmitForm(
+ browser,
+ {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": ADDRESS_FIELD_VALUES["given-name"],
+ "#organization": ADDRESS_FIELD_VALUES.organization,
+ "#street-address": ADDRESS_FIELD_VALUES["street-address"],
+ },
+ },
+ false
+ );
+
+ info("Infer a successfull fetch request");
+ await SpecialPowers.spawn(browser, [], async () => {
+ await content.fetch(
+ "https://example.org/browser/browser/extensions/formautofill/test/browser/empty.html"
+ );
+ });
+
+ info("Infer form removal");
+ await SpecialPowers.spawn(browser, [], async function () {
+ let form = content.document.getElementById("form");
+ form.parentNode.remove(form);
+ });
+ info("Wait for address doorhanger");
+ await onPopupShown;
+
+ info("Click Save in address doorhanger");
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ info("Wait for the address to be added to the storage.");
+ await onStorageChanged;
+
+ info("Ensure that address record was captured and saved correctly.");
+ await expectSavedAddresses([ADDRESS_FIELD_VALUES]);
+
+ await removeAllRecords();
+});
+
+/**
+ * Tests that the address is not captured without a prior fetch or xhr request event
+ */
+add_task(async function test_address_not_captured_without_prior_fetch() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ info("Update identified address fields");
+ // We don't submit the form
+ await focusUpdateSubmitForm(
+ browser,
+ {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": ADDRESS_FIELD_VALUES["given-name"],
+ "#organization": ADDRESS_FIELD_VALUES.organization,
+ "#street-address": ADDRESS_FIELD_VALUES["street-address"],
+ },
+ },
+ false
+ );
+
+ info("Infer form removal");
+ await SpecialPowers.spawn(browser, [], async function () {
+ let form = content.document.getElementById("form");
+ form.parentNode.remove(form);
+ });
+
+ info("Ensure that address doorhanger is not shown");
+ await ensureNoDoorhanger(browser);
+ }
+ );
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_capture_page_navigation.js b/browser/extensions/formautofill/test/browser/address/browser_address_capture_page_navigation.js
new file mode 100644
index 0000000000..f68f89d4fe
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_capture_page_navigation.js
@@ -0,0 +1,125 @@
+"use strict";
+
+const ADDRESS_VALUES = {
+ "#given-name": "Test User",
+ "#organization": "Sesame Street",
+ "#street-address": "123 Sesame Street",
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ["extensions.formautofill.heuristics.captureOnPageNavigation", true],
+ ],
+ });
+});
+
+/**
+ * Tests if the address is captured (address doorhanger is shown)
+ * after adding an entry to the browser's session history stack
+ */
+add_task(async function test_address_captured_after_changing_request_state() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_WITH_PAGE_NAVIGATION_BUTTONS },
+ async function (browser) {
+ const onPopupShown = waitForPopupShown();
+
+ info("Update identified address fields");
+ await focusUpdateSubmitForm(
+ browser,
+ {
+ focusSelector: "#given-name",
+ newValues: ADDRESS_VALUES,
+ },
+ false // We don't submit the form
+ );
+
+ info("Change request state");
+ await SpecialPowers.spawn(browser, [], () => {
+ const historyPushStateButton =
+ content.document.getElementById("historyPushState");
+ historyPushStateButton.click();
+ });
+
+ info("Wait for address doorhanger");
+ await onPopupShown;
+
+ ok(true, "Address doorhanger is shown");
+ }
+ );
+});
+
+/**
+ * Tests if the address is captured (address doorhanger is shown)
+ * after navigating by opening another resource
+ */
+add_task(async function test_address_captured_after_navigation_same_window() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_WITH_PAGE_NAVIGATION_BUTTONS },
+ async function (browser) {
+ const onPopupShown = waitForPopupShown();
+
+ info("Update identified address fields");
+ // We don't submit the form
+ await focusUpdateSubmitForm(
+ browser,
+ {
+ focusSelector: "#given-name",
+ newValues: ADDRESS_VALUES,
+ },
+ false // We don't submit the form
+ );
+
+ info("Navigate with window.location");
+ await SpecialPowers.spawn(browser, [], () => {
+ const windowLocationButton =
+ content.document.getElementById("windowLocation");
+ windowLocationButton.click();
+ });
+
+ info("Wait for address doorhanger");
+ await onPopupShown;
+
+ ok(true, "Address doorhanger is shown");
+ }
+ );
+});
+
+/**
+ * Test that a form submission is infered only once.
+ */
+add_task(async function test_form_submission_infered_only_once() {
+ await setStorage(TEST_ADDRESS_1);
+
+ let onUsed = waitForStorageChangedEvents("notifyUsed");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_WITH_PAGE_NAVIGATION_BUTTONS },
+ async function (browser) {
+ // Progress listener is added on address field identification
+ await openPopupOn(browser, "form #given-name");
+
+ info("Fill address input fields without changing the values");
+ await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+ await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+
+ info("Submit form");
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Progress listener is removed after form submission
+ let form = content.document.getElementById("form");
+ form.querySelector("input[type=submit]").click();
+ });
+ }
+ );
+ await onUsed;
+
+ const addresses = await getAddresses();
+
+ is(
+ addresses[0].timesUsed,
+ 1,
+ "timesUsed field set to 1, so form submission was only infered once"
+ );
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_confirmation_popup.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_confirmation_popup.js
new file mode 100644
index 0000000000..44fca889fd
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_confirmation_popup.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"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.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+add_task(async function test_save_doorhanger_show_confirmation() {
+ await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ // Show the save doorhanger
+ const onSavePopupShown = 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 onSavePopupShown;
+
+ // click the main button and expect seeing the confirmation
+ const hintShownAndVerified = verifyConfirmationHint(browser, false);
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+ info("waiting for verifyConfirmationHint <<");
+ }
+ );
+
+ await expectSavedAddresses(1);
+ await removeAllRecords();
+});
+
+add_task(async function test_update_doorhanger_show_confirmation() {
+ await setStorage(TEST_ADDRESS_3);
+ await expectSavedAddresses(1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ // Show the update doorhanger
+ const onUpdatePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": TEST_ADDRESS_3["given-name"],
+ "#street-address": `${TEST_ADDRESS_3["street-address"]} 4F`,
+ "#postal-code": TEST_ADDRESS_3["postal-code"],
+ },
+ });
+ await onUpdatePopupShown;
+
+ // click the main button and expect seeing the confirmation
+ const hintShownAndVerified = verifyConfirmationHint(browser, false);
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+ }
+ );
+
+ await expectSavedAddresses(1);
+ await removeAllRecords();
+});
+
+add_task(async function test_edit_doorhanger_show_confirmation() {
+ await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ // Show the save doorhanger
+ const onSavePopupShown = 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 onSavePopupShown;
+
+ // Show the edit doorhanger
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+
+ // click the main button and expect seeing the confirmation
+ const hintShownAndVerified = verifyConfirmationHint(browser, false);
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+ }
+ );
+
+ await expectSavedAddresses(1);
+ await removeAllRecords();
+});
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..184f802b11
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_display.js
@@ -0,0 +1,336 @@
+"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.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+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": "John",
+ "#family-name": "Doe",
+ "#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["given-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": "John",
+ "#family-name": "Doe",
+ "#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 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 SpecialPowers.spawn(browser, [], async function () {
+ let form = content.document.getElementById("form");
+ form.querySelector("input[type=submit]").click();
+ });
+
+ 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,
+ },
+ });
+
+ 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": "",
+ },
+ });
+
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ await onUsed;
+
+ await expectSavedAddresses(2);
+ await removeAllRecords();
+ }
+);
+
+add_task(
+ async function test_doorhanger_shown_when_contain_all_required_fields() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "extensions.formautofill.addresses.capture.requiredFields",
+ "street-address,postal-code,address-level1,address-level2",
+ ],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#street-address",
+ newValues: {
+ "#street-address": "32 Vassar Street\nMIT Room 32-G524",
+ "#postal-code": "02139",
+ "#address-level2": "Cambridge",
+ "#address-level1": "MA",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses(1);
+ await SpecialPowers.popPrefEnv();
+ }
+);
+
+add_task(
+ async function test_doorhanger_not_shown_when_contain_required_invalid_fields() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "extensions.formautofill.addresses.capture.requiredFields",
+ "street-address,postal-code,address-level1,address-level2",
+ ],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#street-address",
+ newValues: {
+ "#street-address": "32 Vassar Street\nMIT Room 32-G524",
+ "#postal-code": "000", // postal-code is invalid
+ "#address-level2": "Cambridge",
+ "#address-level1": "MA",
+ },
+ });
+
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ }
+);
+
+add_task(
+ async function test_doorhanger_not_shown_when_not_contain_all_required_fields() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "extensions.formautofill.addresses.capture.requiredFields",
+ "street-address,postal-code,address-level1,address-level2",
+ ],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#street-address",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#postal-code": "02139",
+ "#address-level2": "Cambridge",
+ "#address-level1": "MA",
+ },
+ });
+
+ is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+ }
+ );
+ }
+);
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_invalid_fields.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_invalid_fields.js
new file mode 100644
index 0000000000..0f0d302684
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_invalid_fields.js
@@ -0,0 +1,224 @@
+"use strict";
+
+const VALID_ADDRESS = {
+ "given-name": "John",
+ "street-address": "32 Vassar Street",
+ "address-level1": "California",
+ "address-level2": "Cambridge",
+ country: "US",
+};
+
+const INVALID_ADDRESS = {
+ "address-level1": "ZZ", // Invalid state
+ organization: "???", // Invalid: only contains punctuation
+ email: "john.doe@work@mozilla.org", // Invalid email format
+ tel: "2-800-555-1234", // Invalid: wrong country code
+ "postal-code": "1234", // Invalid: too short
+};
+
+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.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+/**
+ * Submit a form with both valid and invalid fields, we should only
+ * save address fields that are valid
+ */
+add_task(async function test_do_not_save_invalid_fields() {
+ let addresses = await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ const onSavePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": VALID_ADDRESS["given-name"],
+ "#family-name": VALID_ADDRESS["family-name"],
+ "#street-address": VALID_ADDRESS["street-address"],
+ "#address-level1": VALID_ADDRESS["address-level1"],
+ "#address-level2": VALID_ADDRESS["address-level2"],
+
+ // Invalid
+ "#organization": INVALID_ADDRESS.organization,
+ "#email": INVALID_ADDRESS.email,
+ "#tel": INVALID_ADDRESS.tel,
+ "#postal-code": INVALID_ADDRESS["postal-code"],
+ },
+ });
+ await onSavePopupShown;
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ addresses = await expectSavedAddresses(1);
+ for (const [key, value] of Object.entries(VALID_ADDRESS)) {
+ Assert.equal(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+ for (const [key, value] of Object.entries(INVALID_ADDRESS)) {
+ Assert.notEqual(
+ addresses[0][key] ?? "",
+ value,
+ `${key} field is not saved`
+ );
+ }
+ await removeAllRecords();
+});
+
+/**
+ * Submit a form with both valid and invalid fields, we should only
+ * update address fields that are valid
+ */
+add_task(async function test_do_not_update_invalid_fields() {
+ await setStorage(VALID_ADDRESS);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ const onUpdatePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": VALID_ADDRESS["given-name"],
+ "#family-name": "Doe", // To trigger an update
+ "#street-address": VALID_ADDRESS["street-address"],
+
+ // Invalid
+ "#address-level1": INVALID_ADDRESS["address-level1"],
+ "#organization": INVALID_ADDRESS.organization,
+ "#email": INVALID_ADDRESS.email,
+ "#tel": INVALID_ADDRESS.tel,
+ "#postal-code": INVALID_ADDRESS["postal-code"],
+ },
+ });
+ await onUpdatePopupShown;
+
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ const addresses = await expectSavedAddresses(1);
+
+ Assert.equal(
+ addresses[0]["family-name"],
+ "Doe",
+ `family-name field is update`
+ );
+ for (const [key, value] of Object.entries(VALID_ADDRESS)) {
+ Assert.equal(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+ for (const [key, value] of Object.entries(INVALID_ADDRESS)) {
+ Assert.notEqual(
+ addresses[0][key] ?? "",
+ value,
+ `${key} field is not saved`
+ );
+ }
+ await removeAllRecords();
+});
+
+/**
+ * it is possibile that existing records contain invalid fields (Users add those
+ * fields via preference page). When updating a record with invalid fields, we
+ * should still keep those invalid fields in the existing records
+ */
+add_task(async function test_do_not_remove_invalid_fields_of_exising_address() {
+ const STORED_ADDRESS = {
+ ...VALID_ADDRESS,
+ ...INVALID_ADDRESS,
+ };
+
+ // address-level1 in US cannot be invalid, remove it from the storage
+ delete STORED_ADDRESS["address-level1"];
+ await setStorage(STORED_ADDRESS);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ const onUpdatePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": VALID_ADDRESS["given-name"],
+ "#family-name": "Doe", // To trigger an update
+ "#street-address": VALID_ADDRESS["street-address"],
+ "#address-level2": VALID_ADDRESS["address-level2"],
+ },
+ });
+ await onUpdatePopupShown;
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ const addresses = await expectSavedAddresses(1);
+ Assert.equal(
+ addresses[0]["family-name"],
+ "Doe",
+ `family-name field is update`
+ );
+ for (const [key, value] of Object.entries(STORED_ADDRESS)) {
+ Assert.equal(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+ await removeAllRecords();
+});
+
+/**
+ * Ensure the edit address doorhanger show the invalid fields of the existing record
+ */
+add_task(async function test_do_not_show_invalid_fields_in_edit_doorhanger() {
+ const STORED_ADDRESS = {
+ ...INVALID_ADDRESS,
+ ...VALID_ADDRESS, // valid address fields will overwrite invalid address fields
+ };
+ await setStorage(STORED_ADDRESS);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ const onUpdatePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": VALID_ADDRESS["given-name"],
+ "#family-name": "Doe", // To trigger an update
+ "#street-address": VALID_ADDRESS["street-address"],
+ "#address-level2": VALID_ADDRESS["address-level2"],
+ },
+ });
+ await onUpdatePopupShown;
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ const addresses = await expectSavedAddresses(1);
+ Assert.equal(
+ addresses[0]["family-name"],
+ "Doe",
+ `family-name field is update`
+ );
+ for (const [key, value] of Object.entries(STORED_ADDRESS)) {
+ Assert.equal(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_multiple_tabs.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_multiple_tabs.js
new file mode 100644
index 0000000000..804a3c6478
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_multiple_tabs.js
@@ -0,0 +1,55 @@
+"use strict";
+
+async function expectedSavedAddresses(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.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+add_task(async function test_address_doorhanger_multiple_tabs() {
+ const URL = ADDRESS_FORM_URL;
+
+ expectedSavedAddresses(0);
+
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ await showAddressDoorhanger(tab1.linkedBrowser, {
+ "#given-name": "John",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ });
+
+ const tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ await showAddressDoorhanger(tab2.linkedBrowser, {
+ "#given-name": "Jane",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ });
+
+ info(`Save an address in the second tab`);
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ expectedSavedAddresses(1);
+
+ info(`Switch to the first tab and save the address`);
+ gBrowser.selectedTab = tab1;
+ let anchor = document.getElementById("autofill-address-notification-icon");
+ anchor.click();
+
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ expectedSavedAddresses(2);
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_non_mergeable_fields.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_non_mergeable_fields.js
new file mode 100644
index 0000000000..4805f5e724
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_non_mergeable_fields.js
@@ -0,0 +1,94 @@
+"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.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+/**
+ * Submit a form with both mergeable and non-mergeable fields, we should only
+ * update address fields that are mergeable
+ */
+add_task(async function test_do_not_update_non_mergeable_fields() {
+ const TEST_ADDRESS = {
+ "address-level1": "New York",
+ "address-level2": "New York City",
+ "street-address": "32 Vassar Street 3F",
+ email: "john.doe@mozilla.com",
+ "postal-code": "12345-1234",
+ organization: "MOZILLA",
+ country: "US",
+ };
+ await setStorage(TEST_ADDRESS);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ const onUpdatePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#address-level1": "NY", // NY == New York
+ "#address-level2": "New York", // non-mergeable
+ "#street-address": "32 Vassar Street", // non-mergeable
+ "#email": "", // non-mergeable
+ "#postal-code": "12345", // non-mergeable
+ "#organization": TEST_ADDRESS.organization.toLowerCase(),
+ "#country": "US",
+ },
+ });
+ await onUpdatePopupShown;
+
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ const addresses = await expectSavedAddresses(1);
+
+ Assert.equal(addresses[0]["given-name"], "John", `given-name is added`);
+ Assert.equal(addresses[0]["family-name"], "Doe", `family-name is added`);
+ Assert.equal(
+ addresses[0]["street-address"],
+ TEST_ADDRESS["street-address"],
+ `street-address is not updated`
+ );
+ Assert.equal(
+ addresses[0]["address-level1"],
+ TEST_ADDRESS["address-level1"],
+ `address-level1 is not updated`
+ );
+ Assert.equal(
+ addresses[0]["address-level2"],
+ TEST_ADDRESS["address-level2"],
+ `address-level2 is not updated`
+ );
+ Assert.equal(addresses[0].email, TEST_ADDRESS.email, `email is not update`);
+ Assert.equal(
+ addresses[0]["postal-code"],
+ "12345-1234",
+ `postal-code not is update`
+ );
+ Assert.equal(
+ addresses[0].organization,
+ TEST_ADDRESS.organization.toLowerCase(),
+ `organization is update`
+ );
+
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_not_shown.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_not_shown.js
new file mode 100644
index 0000000000..b1b8a6b9d2
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_not_shown.js
@@ -0,0 +1,97 @@
+"use strict";
+
+const DEFAULT_TEST_DOC = `<form id="form">
+ <input id="street-addr" autocomplete="street-address">
+ <select id="address-level1" autocomplete="address-level1">
+ <option value=""></option>
+ <option value="AL">Alabama</option>
+ <option value="AK">Alaska</option>
+ <option value="AP">Armed Forces Pacific</option>
+
+ <option value="ca">california</option>
+ <option value="AR">US-Arkansas</option>
+ <option value="US-CA">California</option>
+ <option value="CA">California</option>
+ <option value="US-AZ">US_Arizona</option>
+ <option value="Ariz">Arizonac</option>
+ </select>
+ <input id="city" autocomplete="address-level2">
+ <input id="country" autocomplete="country">
+ <input id="email" autocomplete="email">
+ <input id="tel" autocomplete="tel">
+ <input id="cc-name" autocomplete="cc-name">
+ <input id="cc-number" autocomplete="cc-number">
+ <input id="cc-exp-month" autocomplete="cc-exp-month">
+ <input id="cc-exp-year" autocomplete="cc-exp-year">
+ <select id="cc-type">
+ <option value="">Select</option>
+ <option value="visa">Visa</option>
+ <option value="mastercard">Master Card</option>
+ <option value="amex">American Express</option>
+ </select>
+ <input id="submit" type="submit">
+</form>`;
+const TARGET_ELEMENT_ID = "street-addr";
+
+const TESTCASES = [
+ {
+ description:
+ "Should not trigger address saving if the number of fields is less than 3",
+ document: DEFAULT_TEST_DOC,
+ targetElementId: TARGET_ELEMENT_ID,
+ formValue: {
+ "#street-addr": "331 E. Evelyn Avenue",
+ "#tel": "1-650-903-0800",
+ },
+ },
+ {
+ description: "Should not trigger the address save doorhanger when pref off",
+ document: DEFAULT_TEST_DOC,
+ targetElementId: TARGET_ELEMENT_ID,
+ formValue: {
+ "#street-addr": "331 E. Evelyn Avenue",
+ "#email": "test@mozilla.org",
+ "#tel": "1-650-903-0800",
+ },
+ prefs: [["extensions.formautofill.addresses.capture.enabled", false]],
+ },
+];
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+add_task(async function test_save_doorhanger_not_shown() {
+ 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 => {
+ content.document.body.innerHTML = doc;
+ });
+
+ await SimpleTest.promiseFocus(browser);
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: `#${TEST.targetElementId}`,
+ newValues: TEST.formValue,
+ });
+
+ await ensureNoDoorhanger(browser);
+ });
+
+ if (TEST.prefs) {
+ await SpecialPowers.popPrefEnv();
+ }
+ }
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_state.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_state.js
new file mode 100644
index 0000000000..a247341fef
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_state.js
@@ -0,0 +1,129 @@
+"use strict";
+
+async function expectSavedAddresses(expectedAddresses) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedAddresses.length,
+ `${addresses.length} address in the storage`
+ );
+
+ for (let i = 0; i < expectedAddresses.length; i++) {
+ for (const [key, value] of Object.entries(expectedAddresses[i])) {
+ is(addresses[i][key] ?? "", value, `field ${key} should be equal`);
+ }
+ }
+ return addresses;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.addresses.capture.enabled", true]],
+ });
+});
+
+add_task(async function test_save_doorhanger_state_invalid() {
+ const DEFAULT = {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ country: "US",
+ };
+
+ const TEST_CASES = [
+ {
+ filled: { "address-level1": "floridaa" }, // typo
+ expected: { "address-level1": "" },
+ },
+ {
+ filled: { "address-level1": "AB" }, // non-exist region code
+ expected: { "address-level1": "" },
+ },
+ ];
+
+ for (const TEST of TEST_CASES) {
+ await expectSavedAddresses([]);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": DEFAULT["given-name"],
+ "#family-name": DEFAULT["family-name"],
+ "#organization": DEFAULT.organization,
+ "#street-address": DEFAULT["street-address"],
+ "#address-level1": TEST.filled["address-level1"],
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses([Object.assign(DEFAULT, TEST.expected)]);
+ await removeAllRecords();
+ }
+});
+
+add_task(async function test_save_doorhanger_state_valid() {
+ const DEFAULT = {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ country: "US",
+ };
+
+ const TEST_CASES = [
+ {
+ filled: { "address-level1": "ca" },
+ expected: { "address-level1": "ca" },
+ },
+ {
+ filled: { "address-level1": "CA" },
+ expected: { "address-level1": "CA" },
+ },
+ {
+ filled: { "address-level1": "california" },
+ expected: { "address-level1": "california" },
+ },
+ {
+ filled: { "address-level1": "California" },
+ expected: { "address-level1": "California" },
+ },
+ ];
+
+ for (const TEST of TEST_CASES) {
+ await expectSavedAddresses([]);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": DEFAULT["given-name"],
+ "#family-name": DEFAULT["family-name"],
+ "#organization": DEFAULT.organization,
+ "#street-address": DEFAULT["street-address"],
+ "#address-level1": TEST.filled["address-level1"],
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses([Object.assign(DEFAULT, TEST.expected)]);
+ await removeAllRecords();
+ }
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_tel.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_tel.js
new file mode 100644
index 0000000000..7701816b74
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_tel.js
@@ -0,0 +1,119 @@
+"use strict";
+
+async function expectSavedAddresses(expectedAddresses) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedAddresses.length,
+ `${addresses.length} address in the storage`
+ );
+
+ for (let i = 0; i < expectedAddresses.length; i++) {
+ for (const [key, value] of Object.entries(expectedAddresses[i])) {
+ is(addresses[i][key] ?? "", value, `field ${key} should be equal`);
+ }
+ }
+ return addresses;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.addresses.capture.enabled", true]],
+ });
+});
+
+add_task(async function test_save_doorhanger_tel_invalid() {
+ const EXPECTED = [
+ {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ tel: "",
+ },
+ ];
+
+ const TEST_CASES = [
+ "1234", // length is too short
+ "1234567890123456", // length is too long
+ "12345###!!", // contains invalid characters
+ ];
+
+ for (const TEST of TEST_CASES) {
+ await expectSavedAddresses([]);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ "#tel": TEST,
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses(EXPECTED);
+ await removeAllRecords();
+ }
+});
+
+add_task(async function test_save_doorhanger_tel_concatenated() {
+ const EXPECTED = [
+ {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ tel: "+15202486621",
+ },
+ ];
+
+ const MARKUP = `<form id="form">
+ <input id="given-name" autocomplete="given-name">
+ <input id="family-name" autocomplete="family-name">
+ <input id="organization" autocomplete="organization">
+ <input id="tel-country-code" autocomplete="tel-country-code">
+ <input id="tel-national" autocomplete="tel-national">
+ <input type="submit">
+ </form>`;
+
+ await expectSavedAddresses([]);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: EMPTY_URL },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [MARKUP], doc => {
+ content.document.body.innerHTML = doc;
+ });
+
+ let onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#tel-country-code": "+1",
+ "#tel-national": "5202486621",
+ },
+ });
+
+ await onPopupShown;
+ await clickDoorhangerButton(MAIN_BUTTON, 0);
+ }
+ );
+
+ await expectSavedAddresses(EXPECTED);
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_ui.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_ui.js
new file mode 100644
index 0000000000..9451054de9
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_ui.js
@@ -0,0 +1,277 @@
+"use strict";
+
+const { FormAutofill } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofill.sys.mjs"
+);
+
+async function expectSavedAddresses(expectedCount) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedCount,
+ `${addresses.length} address in the storage`
+ );
+ return addresses;
+}
+
+function verifyDoorhangerContent(saved, removed = {}) {
+ const rows = [
+ ...getNotification().querySelectorAll(`.address-save-update-row-container`),
+ ];
+
+ let texts = rows.reduce((acc, cur) => acc + cur.textContent, "");
+ for (const text of Object.values(saved)) {
+ ok(texts.includes(text), `Show ${text} in the doorhanger`);
+ texts = texts.replace(text, "");
+ }
+ for (const text of Object.values(removed)) {
+ ok(texts.includes(text), `Show ${text} in the doorhanger (removed)`);
+ texts = texts.replace(text, "");
+ }
+ is(texts.trim(), "", `Doorhanger shows all the submitted data`);
+}
+
+function checkVisibility(element) {
+ return element.checkVisibility({
+ checkOpacity: true,
+ checkVisibilityCSS: true,
+ });
+}
+
+function recordToFormSelector(record) {
+ let obj = {};
+ for (const [key, value] of Object.entries(record)) {
+ obj[`#${key}`] = value;
+ }
+ return obj;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+// Save address doorhanger should show description when users has no saved address
+add_task(async function test_save_doorhanger_show_description() {
+ await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser);
+
+ const header = AutofillDoorhanger.header(getNotification());
+ is(checkVisibility(header), true, "Should always show header");
+
+ const description = AutofillDoorhanger.description(getNotification());
+ is(
+ checkVisibility(description),
+ true,
+ "Should show description when this is the first address saved"
+ );
+ }
+ );
+});
+
+// Save address doorhanger should not show description when users has at least one saved address
+add_task(async function test_save_doorhanger_hide_description() {
+ await setStorage(TEST_ADDRESS_1);
+ await expectSavedAddresses(1);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser);
+
+ const header = AutofillDoorhanger.header(getNotification());
+ is(checkVisibility(header), true, "Should always show header");
+
+ const description = AutofillDoorhanger.description(getNotification());
+ is(
+ checkVisibility(description),
+ false,
+ "Should not show description when there is at least one saved address"
+ );
+ }
+ );
+
+ await removeAllRecords();
+});
+
+// Test open edit address popup and then click "learn more" button
+add_task(async function test_click_learn_more_button_in_edit_doorhanger() {
+ await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser);
+
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(gBrowser, url =>
+ url.endsWith(AddressSaveDoorhanger.learnMoreURL)
+ );
+ await clickAddressDoorhangerButton(
+ ADDRESS_MENU_BUTTON,
+ ADDRESS_MENU_LEARN_MORE
+ );
+ const tab = await tabOpenPromise;
+ gBrowser.removeTab(tab);
+ }
+ );
+});
+
+add_task(async function test_click_address_setting_button_in_edit_doorhanger() {
+ await expectSavedAddresses(0);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser);
+
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ `about:preferences#${AddressSaveDoorhanger.preferenceURL}`
+ );
+ await clickAddressDoorhangerButton(
+ ADDRESS_MENU_BUTTON,
+ ADDRESS_MENU_PREFENCE
+ );
+ const tab = await tabOpenPromise;
+ gBrowser.removeTab(tab);
+ }
+ );
+});
+
+add_task(async function test_address_display_in_save_doorhanger() {
+ await expectSavedAddresses(0);
+
+ const TESTS = [
+ {
+ description: "Test submit a form without email and tel fields",
+ form: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ },
+ expectedSectionCount: 1,
+ },
+ {
+ description: "Test submit a form with email field",
+ form: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ "#email": "test@mozilla.org",
+ },
+ expectedSectionCount: 2,
+ },
+ {
+ description: "Test submit a form with tel field",
+ form: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ "#tel": "+13453453456",
+ },
+ expectedSectionCount: 2,
+ },
+ ];
+
+ for (const TEST of TESTS) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ info(TEST.description);
+ await showAddressDoorhanger(browser, TEST.form);
+
+ is(
+ getNotification().querySelectorAll(
+ `.address-save-update-row-container`
+ ).length,
+ TEST.expectedSectionCount,
+ `Should have ${TEST.expectedSectionCount} address section`
+ );
+
+ // When the form has no country field, doorhanger shows the default region
+ verifyDoorhangerContent({
+ ...TEST.form,
+ country: FormAutofill.DEFAULT_REGION,
+ });
+
+ await clickAddressDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+ }
+
+ await removeAllRecords();
+});
+
+add_task(async function test_show_added_text_in_update_doorhanger() {
+ await setStorage(TEST_ADDRESS_2);
+ await expectSavedAddresses(1);
+
+ const form = {
+ ...TEST_ADDRESS_2,
+
+ email: "test@mozilla.org", // Add email field
+ "given-name": TEST_ADDRESS_2["given-name"] + " Doe", // Append
+ "street-address": TEST_ADDRESS_2["street-address"] + " 4F", // Append
+ };
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser, recordToFormSelector(form));
+
+ // When the form has no country field, doorhanger shows the default region
+ verifyDoorhangerContent({
+ ...form,
+ country: FormAutofill.DEFAULT_REGION,
+ });
+
+ await clickAddressDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+
+ await removeAllRecords();
+});
+
+add_task(async function test_show_removed_text_in_update_doorhanger() {
+ const SAVED_ADDRESS = {
+ ...TEST_ADDRESS_2,
+ organization: "Mozilla",
+ };
+ await setStorage(SAVED_ADDRESS);
+ await expectSavedAddresses(1);
+
+ // We will ask whether users would like to update "Mozilla" to "mozilla"
+ const form = {
+ ...SAVED_ADDRESS,
+
+ organization: SAVED_ADDRESS.organization.toLowerCase(),
+ };
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser, recordToFormSelector(form));
+
+ // When the form has no country field, doorhanger shows the default region
+ verifyDoorhangerContent(
+ { ...form, country: FormAutofill.DEFAULT_REGION },
+ { organization: SAVED_ADDRESS.organization }
+ );
+
+ await clickAddressDoorhangerButton(SECONDARY_BUTTON);
+ }
+ );
+
+ await removeAllRecords();
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_unsupported_region.js b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_unsupported_region.js
new file mode 100644
index 0000000000..fc3d1c7cf4
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_doorhanger_unsupported_region.js
@@ -0,0 +1,88 @@
+"use strict";
+
+const { Region } = ChromeUtils.importESModule(
+ "resource://gre/modules/Region.sys.mjs"
+);
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "detect"],
+ ["extensions.formautofill.addresses.supportedCountries", "US,CA"],
+ ],
+ });
+});
+
+add_task(async function test_save_doorhanger_supported_region() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ const onPopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ "#country": "US",
+ },
+ });
+ await onPopupShown;
+ }
+ );
+});
+
+/**
+ * Do not display the address capture doorhanger if the country field of the
+ * submitted form is not on the supported list."
+ */
+add_task(async function test_save_doorhanger_unsupported_region_from_record() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ "#country": "DE",
+ },
+ });
+
+ await ensureNoDoorhanger(browser);
+ }
+ );
+});
+
+add_task(async function test_save_doorhanger_unsupported_region_from_pref() {
+ const initialHomeRegion = Region._home;
+ const initialCurrentRegion = Region._current;
+
+ const region = "FR";
+ Region._setCurrentRegion(region);
+ Region._setHomeRegion(region);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": "John",
+ "#family-name": "Doe",
+ "#organization": "Mozilla",
+ "#street-address": "123 Sesame Street",
+ },
+ });
+
+ await ensureNoDoorhanger(browser);
+ }
+ );
+
+ Region._setCurrentRegion(initialHomeRegion);
+ Region._setHomeRegion(initialCurrentRegion);
+});
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..acd7a7c364
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_address_telemetry.js
@@ -0,0 +1,741 @@
+"use strict";
+
+requestLongerTimeout(3);
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const { AddressTelemetry } = ChromeUtils.importESModule(
+ "resource://autofill/AutofillTelemetry.sys.mjs"
+);
+
+// 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_AUTOFILL_ADDRESSES_PREF, true],
+ [AUTOFILL_ADDRESSES_AVAILABLE_PREF, "on"],
+ ["extensions.formautofill.addresses.capture.enabled", 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() {
+ 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"),
+ ];
+
+ await test_per_command(MAIN_BUTTON, undefined, { 1: 1 }, 1);
+ await assertTelemetry(expected_content, [
+ [EVENT_CATEGORY, "show", "capture_doorhanger"],
+ [EVENT_CATEGORY, "save", "capture_doorhanger"],
+ ]);
+
+ 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_AUTOFILL_ADDRESSES_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 });
+ await assertTelemetry(expected_content, [
+ [EVENT_CATEGORY, "show", "update_doorhanger"],
+ [EVENT_CATEGORY, "cancel", "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_click_doorhanger_menuitems() {
+ const TESTS = [
+ {
+ button: ADDRESS_MENU_BUTTON,
+ menuItem: ADDRESS_MENU_LEARN_MORE,
+ expectedEvt: "learn_more",
+ },
+ {
+ button: ADDRESS_MENU_BUTTON,
+ menuItem: ADDRESS_MENU_PREFENCE,
+ expectedEvt: "pref",
+ },
+ ];
+ for (const TEST of TESTS) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_BASIC_ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser);
+
+ const tabOpenPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ await clickAddressDoorhangerButton(TEST.button, TEST.menuItem);
+ gBrowser.removeTab(await tabOpenPromise);
+ }
+ );
+
+ await assertTelemetry(undefined, [
+ [EVENT_CATEGORY, "show", "capture_doorhanger"],
+ [EVENT_CATEGORY, TEST.expectedEvt, "capture_doorhanger"],
+ ]);
+
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+ }
+});
+
+add_task(async function test_show_edit_doorhanger() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_BASIC_ADDRESS_FORM_URL },
+ async function (browser) {
+ await showAddressDoorhanger(browser);
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ await assertTelemetry(undefined, [
+ [EVENT_CATEGORY, "show", "capture_doorhanger"],
+ [EVENT_CATEGORY, "show", "edit_doorhanger"],
+ [EVENT_CATEGORY, "save", "edit_doorhanger"],
+ ]);
+
+ await removeAllRecords();
+ 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/browser_edit_address_doorhanger_display.js b/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display.js
new file mode 100644
index 0000000000..9f82824746
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display.js
@@ -0,0 +1,216 @@
+"use strict";
+
+async function expectSavedAddresses(expectedCount) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedCount,
+ `${addresses.length} address in the storage`
+ );
+ return addresses;
+}
+
+function recordToFormSelector(record) {
+ let obj = {};
+ for (const [key, value] of Object.entries(record)) {
+ obj[`#${key}`] = value;
+ }
+ return obj;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+// Test different scenarios when we change something in the edit address dorhanger
+add_task(async function test_save_edited_fields() {
+ await expectSavedAddresses(0);
+
+ const initRecord = {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ tel: "+13453453456",
+ };
+
+ const TESTS = [
+ {
+ description: "adding the email field",
+ editedFields: {
+ email: "test@mozilla.org",
+ },
+ },
+ {
+ description: "changing the given-name field",
+ editedFields: {
+ name: "Jane",
+ },
+ },
+ {
+ description: "appending the street-address field",
+ editedFields: {
+ "street-address": initRecord["street-address"] + " 4F",
+ },
+ },
+ {
+ description: "removing some fields",
+ editedFields: {
+ name: "",
+ tel: "",
+ },
+ },
+ {
+ description: "doing all kinds of stuff",
+ editedFields: {
+ organization: initRecord.organization.toLowerCase(),
+ "address-level1": "California",
+ tel: "",
+ "street-address": initRecord["street-address"] + " Apt.6",
+ name: "Jane Doe",
+ },
+ },
+ ];
+
+ for (const TEST of TESTS) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ info(`Test ${TEST.description}`);
+
+ const onSavePopupShown = waitForPopupShown();
+
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: recordToFormSelector(initRecord),
+ });
+
+ await onSavePopupShown;
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+ fillEditDoorhanger(TEST.editedFields);
+
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ const expectedRecord = normalizeAddressFields({
+ ...initRecord,
+ ...TEST.editedFields,
+ });
+
+ const addresses = await expectSavedAddresses(1);
+ for (const [key, value] of Object.entries(expectedRecord)) {
+ is(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+
+ await removeAllRecords();
+ }
+});
+
+// This test tests edit doorhanger "save" & "cancel" buttons work correctly
+// when the edit doorhanger is triggered in an save doorhanger
+add_task(async function test_edit_doorhanger_triggered_by_save_doorhanger() {
+ for (const CLICKED_BUTTON of [MAIN_BUTTON, SECONDARY_BUTTON]) {
+ await expectSavedAddresses(0);
+
+ const initRecord = {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ tel: "+13453453456",
+ };
+
+ const editRecord = {
+ email: "test@mozilla.org",
+ };
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ info(
+ `Test clicking ${CLICKED_BUTTON == MAIN_BUTTON ? "save" : "cancel"}`
+ );
+ const onSavePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: recordToFormSelector(initRecord),
+ });
+ await onSavePopupShown;
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+ fillEditDoorhanger(editRecord);
+
+ await clickAddressDoorhangerButton(CLICKED_BUTTON);
+ }
+ );
+
+ await expectSavedAddresses(CLICKED_BUTTON == MAIN_BUTTON ? 1 : 0);
+ await removeAllRecords();
+ }
+});
+
+// This test tests edit doorhanger "save" & "cancel" buttons work correctly
+// when the edit doorhanger is triggered in an update doorhnager
+add_task(async function test_edit_doorhanger_triggered_by_update_doorhanger() {
+ for (const CLICKED_BUTTON of [MAIN_BUTTON, SECONDARY_BUTTON]) {
+ // TEST_ADDRESS_2 doesn't contain email field
+ await setStorage(TEST_ADDRESS_2);
+ await expectSavedAddresses(1);
+
+ const initRecord = {
+ "given-name": TEST_ADDRESS_2["given-name"],
+ "street-address": TEST_ADDRESS_2["street-address"],
+ country: TEST_ADDRESS_2.country,
+ email: "test@mozilla.org",
+ };
+
+ const editRecord = {
+ email: "test@mozilla.org",
+ };
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async browser => {
+ info(
+ `Test clicking ${CLICKED_BUTTON == MAIN_BUTTON ? "save" : "cancel"}`
+ );
+ const onUpdatePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: recordToFormSelector(initRecord),
+ });
+ await onUpdatePopupShown;
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+ fillEditDoorhanger(editRecord);
+
+ await clickAddressDoorhangerButton(CLICKED_BUTTON);
+ }
+ );
+
+ const addresses = await expectSavedAddresses(1);
+ const expectedRecord =
+ CLICKED_BUTTON == MAIN_BUTTON
+ ? { ...initRecord, ...editRecord }
+ : TEST_ADDRESS_2;
+
+ for (const [key, value] of Object.entries(expectedRecord)) {
+ is(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+
+ await removeAllRecords();
+ }
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display_state.js b/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display_state.js
new file mode 100644
index 0000000000..1d8933ad31
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_display_state.js
@@ -0,0 +1,77 @@
+"use strict";
+requestLongerTimeout(2);
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.formautofill.addresses.capture.enabled", true]],
+ });
+});
+
+add_task(async function test_edit_doorhanger_display_state() {
+ const DEFAULT = {
+ "given-name": "Test User",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ country: "US",
+ };
+
+ const TEST_CASES = [
+ {
+ filled: { "address-level1": "floridaa" }, // typo
+ expected: { label: "" },
+ },
+ {
+ filled: { "address-level1": "AB" }, // non-exist region code
+ expected: { label: "" },
+ },
+ {
+ filled: { "address-level1": "CA" },
+ expected: { label: "CA" },
+ },
+ {
+ filled: { "address-level1": "fl" },
+ expected: { label: "FL" },
+ },
+ {
+ filled: { "address-level1": "New York" },
+ expected: { label: "NY" },
+ },
+ {
+ filled: { "address-level1": "Washington" },
+ expected: { label: "WA" },
+ },
+ ];
+
+ for (const TEST of TEST_CASES) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ const onSavePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: {
+ "#given-name": DEFAULT["given-name"],
+ "#organization": DEFAULT.organization,
+ "#street-address": DEFAULT["street-address"],
+ "#address-level1": TEST.filled["address-level1"],
+ },
+ });
+ await onSavePopupShown;
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+
+ const notification = getNotification();
+ const id = AddressEditDoorhanger.getInputId("address-level1");
+ const element = notification.querySelector(`#${id}`);
+
+ is(
+ element.label,
+ TEST.expected.label,
+ "Edit address doorhanger shows the expected address-level1 select option"
+ );
+ }
+ );
+ }
+});
diff --git a/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_save_edited_fields.js b/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_save_edited_fields.js
new file mode 100644
index 0000000000..d146a3b722
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/address/browser_edit_address_doorhanger_save_edited_fields.js
@@ -0,0 +1,114 @@
+"use strict";
+
+const SUBMIT_RECORD = {
+ "given-name": "John",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ "street-address": "123 Sesame Street",
+ tel: "+13453453456",
+};
+
+const TEST_CASE = [
+ {
+ description: "adding the email field",
+ editedFields: {
+ email: "test@mozilla.org",
+ },
+ },
+ {
+ description: "changing the given-name field",
+ editedFields: {
+ name: "Jane",
+ },
+ },
+ {
+ description: "appending the street-address field",
+ editedFields: {
+ "street-address": SUBMIT_RECORD["street-address"] + " 4F",
+ },
+ },
+ {
+ description: "removing some fields",
+ editedFields: {
+ name: "",
+ tel: "",
+ },
+ },
+ {
+ description: "doing all kinds of stuff",
+ editedFields: {
+ organization: SUBMIT_RECORD.organization.toLowerCase(),
+ "address-level1": "California",
+ tel: "",
+ "street-address": SUBMIT_RECORD["street-address"] + " Apt.6",
+ name: "Jane Doe",
+ },
+ },
+];
+
+async function expectSavedAddresses(expectedCount) {
+ const addresses = await getAddresses();
+ is(
+ addresses.length,
+ expectedCount,
+ `${addresses.length} address in the storage`
+ );
+ return addresses;
+}
+
+function recordToFormSelector(record) {
+ let obj = {};
+ for (const [key, value] of Object.entries(record)) {
+ obj[`#${key}`] = value;
+ }
+ return obj;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.formautofill.addresses.capture.enabled", true],
+ ["extensions.formautofill.addresses.supported", "on"],
+ ],
+ });
+});
+
+// Test different scenarios when we change something in the edit address dorhanger
+add_task(async function test_save_edited_fields() {
+ await expectSavedAddresses(0);
+
+ for (const TEST of TEST_CASE) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: ADDRESS_FORM_URL },
+ async function (browser) {
+ info(`Test ${TEST.description}`);
+
+ const onSavePopupShown = waitForPopupShown();
+ await focusUpdateSubmitForm(browser, {
+ focusSelector: "#given-name",
+ newValues: recordToFormSelector(SUBMIT_RECORD),
+ });
+ await onSavePopupShown;
+
+ const onEditPopupShown = waitForPopupShown();
+ await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
+ await onEditPopupShown;
+
+ fillEditDoorhanger(TEST.editedFields);
+ await clickAddressDoorhangerButton(MAIN_BUTTON);
+ }
+ );
+
+ const expectedRecord = normalizeAddressFields({
+ ...SUBMIT_RECORD,
+ ...TEST.editedFields,
+ });
+
+ const addresses = await expectSavedAddresses(1);
+ for (const [key, value] of Object.entries(expectedRecord)) {
+ is(addresses[0][key] ?? "", value, `${key} field is saved`);
+ }
+
+ await removeAllRecords();
+ }
+});
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 */