Bug 1699794 - [1.0] Extend GV Autocomplete API with address support r=geckoview-reviewers,agi,aklotz
authorowlishDeveloper <bugzeeeeee@gmail.com>
Thu, 10 Jun 2021 19:17:11 +0000
changeset 582742 bfa7384a88748e7f70eb7f673e88bfd0c31e3516
parent 582741 3fa7dc1e1303eb7f1b61b9cbfb4eacd1d376f05a
child 582743 10c4df33bc803ff408ba1f40e03ee1e7448b5eb0
push id144761
push useristorozhko@mozilla.com
push dateThu, 10 Jun 2021 19:18:45 +0000
treeherderautoland@bfa7384a8874 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, agi, aklotz
bugs1699794
milestone91.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1699794 - [1.0] Extend GV Autocomplete API with address support r=geckoview-reviewers,agi,aklotz Differential Revision: https://phabricator.services.mozilla.com/D109139
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java
@@ -298,16 +298,384 @@ public class Autocomplete {
             public @NonNull Builder expirationYear(final @Nullable String expYear) {
                 mBundle.putString(EXP_YEAR_KEY, expYear);
                 return this;
             }
         }
     }
 
     /**
+     * Holds address information for a specific entry.
+     */
+    public static class Address {
+        private static final String GUID_KEY = "guid";
+        private static final String NAME_KEY = "name";
+        private static final String GIVEN_NAME_KEY = "givenName";
+        private static final String ADDITIONAL_NAME_KEY = "additionalName";
+        private static final String FAMILY_NAME_KEY = "familyName";
+        private static final String ORGANIZATION_KEY = "organization";
+        private static final String STREET_ADDRESS_KEY = "streetAddress";
+        private static final String ADDRESS_LEVEL1_KEY = "addressLevel1";
+        private static final String ADDRESS_LEVEL2_KEY = "addressLevel2";
+        private static final String ADDRESS_LEVEL3_KEY = "addressLevel3";
+        private static final String POSTAL_CODE_KEY = "postalCode";
+        private static final String COUNTRY_KEY = "country";
+        private static final String TEL_KEY = "tel";
+        private static final String EMAIL_KEY = "email";
+        private static final byte bundleCapacity = 14;
+
+
+        /**
+         * The unique identifier for this address entry.
+         */
+        public final @Nullable String guid;
+
+        /**
+         * The full name.
+         */
+        public final @NonNull String name;
+
+        /**
+         * The given (first) name.
+         */
+        public final @NonNull String givenName;
+
+        /**
+         * An additional name, if available.
+         */
+        public final @NonNull String additionalName;
+
+        /**
+         * The family name.
+         */
+        public final @NonNull String familyName;
+
+        /**
+         * The name of the company, if applicable.
+         */
+        public final @NonNull String organization;
+
+        /**
+         * The (multiline) street address.
+         */
+        public final @NonNull String streetAddress;
+
+        /**
+         * The level 1 (province) address.
+         * Note: Only use if streetAddress is not provided.
+         */
+        public final @NonNull String addressLevel1;
+
+        /**
+         * The level 2 (city/town) address.
+         * Note: Only use if streetAddress is not provided.
+         */
+        public final @NonNull String addressLevel2;
+
+        /**
+         * The level 3 (suburb/sublocality) address.
+         * Note: Only use if streetAddress is not provided.
+         */
+        public final @NonNull String addressLevel3;
+
+        /**
+         * The postal code.
+         */
+        public final @NonNull String postalCode;
+
+        /**
+         * The country string in ISO 3166.
+         */
+        public final @NonNull String country;
+
+        /**
+         * The telephone number string.
+         */
+        public final @NonNull String tel;
+
+        /**
+         * The email address.
+         */
+        public final @NonNull String email;
+
+        // For tests only.
+        @AnyThread
+        protected Address() {
+            guid = null;
+            name = "";
+            givenName = "";
+            additionalName = "";
+            familyName = "";
+            organization = "";
+            streetAddress = "";
+            addressLevel1 = "";
+            addressLevel2 = "";
+            addressLevel3 = "";
+            postalCode = "";
+            country = "";
+            tel = "";
+            email = "";
+        }
+
+        @AnyThread
+            /* package */ Address(final @NonNull GeckoBundle bundle) {
+            guid = bundle.getString(GUID_KEY);
+            name = bundle.getString(NAME_KEY);
+            givenName = bundle.getString(GIVEN_NAME_KEY);
+            additionalName = bundle.getString(ADDITIONAL_NAME_KEY);
+            familyName = bundle.getString(FAMILY_NAME_KEY);
+            organization = bundle.getString(ORGANIZATION_KEY);
+            streetAddress = bundle.getString(STREET_ADDRESS_KEY);
+            addressLevel1 = bundle.getString(ADDRESS_LEVEL1_KEY);
+            addressLevel2 = bundle.getString(ADDRESS_LEVEL2_KEY);
+            addressLevel3 = bundle.getString(ADDRESS_LEVEL3_KEY);
+            postalCode = bundle.getString(POSTAL_CODE_KEY);
+            country = bundle.getString(COUNTRY_KEY);
+            tel = bundle.getString(TEL_KEY);
+            email = bundle.getString(EMAIL_KEY);
+        }
+
+        @Override
+        @AnyThread
+        public String toString() {
+            final StringBuilder builder = new StringBuilder("Address {");
+            builder
+                    .append("guid=").append(guid)
+                    .append(", givenName=").append(givenName)
+                    .append(", additionalName=").append(additionalName)
+                    .append(", familyName=").append(familyName)
+                    .append(", organization=").append(organization)
+                    .append(", streetAddress=").append(streetAddress)
+                    .append(", addressLevel1=").append(addressLevel1)
+                    .append(", addressLevel2=").append(addressLevel2)
+                    .append(", addressLevel3=").append(addressLevel3)
+                    .append(", postalCode=").append(postalCode)
+                    .append(", country=").append(country)
+                    .append(", tel=").append(tel)
+                    .append(", email=").append(email)
+                    .append("}");
+            return builder.toString();
+        }
+
+        @AnyThread
+        /* package */ @NonNull GeckoBundle toBundle() {
+            final GeckoBundle bundle = new GeckoBundle(bundleCapacity);
+            bundle.putString(GUID_KEY, guid);
+            bundle.putString(NAME_KEY, name);
+            bundle.putString(GIVEN_NAME_KEY, givenName);
+            bundle.putString(ADDITIONAL_NAME_KEY, additionalName);
+            bundle.putString(FAMILY_NAME_KEY, familyName);
+            bundle.putString(ORGANIZATION_KEY, organization);
+            bundle.putString(STREET_ADDRESS_KEY, streetAddress);
+            bundle.putString(ADDRESS_LEVEL1_KEY, addressLevel1);
+            bundle.putString(ADDRESS_LEVEL2_KEY, addressLevel2);
+            bundle.putString(ADDRESS_LEVEL3_KEY, addressLevel3);
+            bundle.putString(POSTAL_CODE_KEY, postalCode);
+            bundle.putString(COUNTRY_KEY, country);
+            bundle.putString(TEL_KEY, tel);
+            bundle.putString(EMAIL_KEY, email);
+
+            return bundle;
+        }
+
+        public static class Builder {
+            private final GeckoBundle mBundle;
+
+            @AnyThread
+                /* package */ Builder(final @NonNull GeckoBundle bundle) {
+                mBundle = new GeckoBundle(bundle);
+            }
+
+            @AnyThread
+            @SuppressWarnings("checkstyle:javadocmethod")
+            public Builder() {
+                mBundle = new GeckoBundle(bundleCapacity);
+            }
+
+            /**
+             * Finalize the {@link Address} instance.
+             *
+             * @return The {@link Address} instance.
+             */
+            @AnyThread
+            public @NonNull Address build() {
+                return new Address(mBundle);
+            }
+
+            /**
+             * Set the unique identifier for this address entry.
+             *
+             * @param guid The unique identifier string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder guid(final @Nullable String guid) {
+                mBundle.putString(GUID_KEY, guid);
+                return this;
+            }
+
+            /**
+             * Set the full name for this address entry.
+             *
+             * @param name The full name string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder name(final @Nullable String name) {
+                mBundle.putString(NAME_KEY, name);
+                return this;
+            }
+
+            /**
+             * Set the given name for this address entry.
+             *
+             * @param givenName The given name string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder givenName(final @Nullable String givenName) {
+                mBundle.putString(GIVEN_NAME_KEY, givenName);
+                return this;
+            }
+
+            /**
+             * Set the additional name for this address entry.
+             *
+             * @param additionalName The additional name string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder additionalName(final @Nullable String additionalName) {
+                mBundle.putString(ADDITIONAL_NAME_KEY, additionalName);
+                return this;
+            }
+
+            /**
+             * Set the family name for this address entry.
+             *
+             * @param familyName The family name string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder familyName(final @Nullable String familyName) {
+                mBundle.putString(FAMILY_NAME_KEY, familyName);
+                return this;
+            }
+
+            /**
+             * Set the company name for this address entry.
+             *
+             * @param organization The company name string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder organization(final @Nullable String organization) {
+                mBundle.putString(ORGANIZATION_KEY, organization);
+                return this;
+            }
+
+            /**
+             * Set the street address for this address entry.
+             *
+             * @param streetAddress The street address string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder streetAddress(final @Nullable String streetAddress) {
+                mBundle.putString(STREET_ADDRESS_KEY, streetAddress);
+                return this;
+            }
+
+            /**
+             * Set the level 1 address for this address entry.
+             *
+             * @param addressLevel1 The level 1 address string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder addressLevel1(final @Nullable String addressLevel1) {
+                mBundle.putString(ADDRESS_LEVEL1_KEY, addressLevel1);
+                return this;
+            }
+
+            /**
+             * Set the level 2 address for this address entry.
+             *
+             * @param addressLevel2 The level 2 address string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder addressLevel2(final @Nullable String addressLevel2) {
+                mBundle.putString(ADDRESS_LEVEL2_KEY, addressLevel2);
+                return this;
+            }
+
+            /**
+             * Set the level 3 address for this address entry.
+             *
+             * @param addressLevel3 The level 3 address string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder addressLevel3(final @Nullable String addressLevel3) {
+                mBundle.putString(ADDRESS_LEVEL3_KEY, addressLevel3);
+                return this;
+            }
+
+            /**
+             * Set the postal code for this address entry.
+             *
+             * @param postalCode The postal code string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder postalCode(final @Nullable String postalCode) {
+                mBundle.putString(POSTAL_CODE_KEY, postalCode);
+                return this;
+            }
+
+            /**
+             * Set the country code for this address entry.
+             *
+             * @param country The country string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder country(final @Nullable String country) {
+                mBundle.putString(COUNTRY_KEY, country);
+                return this;
+            }
+
+            /**
+             * Set the telephone number for this address entry.
+             *
+             * @param tel The telephone number string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder tel(final @Nullable String tel) {
+                mBundle.putString(TEL_KEY, tel);
+                return this;
+            }
+
+            /**
+             * Set the email address for this address entry.
+             *
+             * @param email The email address string.
+             * @return This {@link Builder} instance.
+             */
+            @AnyThread
+            public @NonNull Builder email(final @Nullable String email) {
+                mBundle.putString(EMAIL_KEY, email);
+                return this;
+            }
+        }
+    }
+
+    /**
      * Holds login information for a specific entry.
      */
     public static class LoginEntry {
         private static final String GUID_KEY = "guid";
         private static final String ORIGIN_KEY = "origin";
         private static final String FORM_ACTION_ORIGIN_KEY = "formActionOrigin";
         private static final String HTTP_REALM_KEY = "httpRealm";
         private static final String USERNAME_KEY = "username";
@@ -551,27 +919,53 @@ public class Autocomplete {
          *         {@link CreditCard} containing the existing credit cards.
          */
         @UiThread
         default @Nullable GeckoResult<CreditCard[]> onCreditCardFetch() {
             return null;
         }
 
         /**
+         * Request address entries.
+         * While processing the web document, we have identified elements
+         * resembling address input fields suitable for autofill.
+         * We will attempt to match the provided address information to the
+         * identified input fields.
+         *
+         * @return A {@link GeckoResult} that completes with an array of
+         *         {@link Address} containing the existing addresses.
+         */
+        @UiThread
+        default @Nullable GeckoResult<Address[]> onAddressFetch() {
+            return null;
+        }
+
+        /**
          * Request saving or updating of the given login entry.
          * This is triggered by confirming a
          * {@link GeckoSession.PromptDelegate#onLoginSave onLoginSave} request.
          *
          * @param login The {@link LoginEntry} as confirmed by the prompt
          *              request.
          */
         @UiThread
         default void onLoginSave(@NonNull final LoginEntry login) {}
 
         /**
+         * Request saving or updating of the given address entry.
+         * This is triggered by confirming a
+         * {@link GeckoSession.PromptDelegate#onAddressSave onAddressSave} request.
+         *
+         * @param address The {@link Address} as confirmed by the prompt
+         *              request.
+         */
+        @UiThread
+        default void onAddressSave(@NonNull Address address) {}
+
+        /**
          * Notify that the given login was used to autofill login input fields.
          * This is triggered by autofilling elements with unmodified login
          * entries as provided via {@link #onLoginFetch}.
          *
          * @param login The {@link LoginEntry} that was used for the
          *              autofilling.
          * @param usedFields The login entry fields used for autofilling.
          *                   A combination of {@link UsedField}.
@@ -749,16 +1143,50 @@ public class Autocomplete {
             final GeckoBundle bundle = new GeckoBundle(2);
             bundle.putBundle(VALUE_KEY, value.toBundle());
             bundle.putInt(HINT_KEY, hint);
             return bundle;
         }
     }
 
     /**
+     * Holds information required to process address saving requests.
+     */
+    public static class AddressSaveOption extends SaveOption<Address> {
+        /**
+         * Construct a address save option.
+         *
+         * @param value The {@link Address} address entry to be saved.
+         * @param hint The {@link Hint} detailing the type of the option.
+         */
+        /* package */ AddressSaveOption(
+                final @NonNull Address value,
+                final @SaveOptionHint int hint) {
+            super(value, hint);
+        }
+
+        /**
+         * Construct an address save option.
+         *
+         * @param value The {@link Address} address entry to be saved.
+         */
+        public AddressSaveOption(final @NonNull Address value) {
+            this(value, Hint.NONE);
+        }
+
+        @Override
+        /* package */ @NonNull GeckoBundle toBundle() {
+            final GeckoBundle bundle = new GeckoBundle(2);
+            bundle.putBundle(VALUE_KEY, value.toBundle());
+            bundle.putInt(HINT_KEY, hint);
+            return bundle;
+        }
+    }
+
+    /**
      * Holds information required to process login selection requests.
      */
     public static class LoginSelectOption extends SelectOption<LoginEntry> {
         /**
          * Construct a login select option.
          *
          * @param value The {@link LoginEntry} login entry selection option.
          * @param hint The {@link Hint} detailing the type of the option.
@@ -852,45 +1280,115 @@ public class Autocomplete {
         /* package */ @NonNull GeckoBundle toBundle() {
             final GeckoBundle bundle = new GeckoBundle(2);
             bundle.putBundle(VALUE_KEY, value.toBundle());
             bundle.putInt(HINT_KEY, hint);
             return bundle;
         }
     }
 
+    /**
+     * Holds information required to process address selection requests.
+     */
+    public static class AddressSelectOption extends SelectOption<Address> {
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true,
+                value = { Hint.NONE, Hint.INSECURE_FORM })
+                /* package */ @interface AddressSelectHint {}
+
+        /**
+         * Hint types for credit card selection requests.
+         */
+        public static class Hint {
+            public static final int NONE = 0;
+
+            /**
+             * Insecure context.
+             * The form or transmission mechanics are considered insecure.
+             * This is the case when the form is served via http or submitted
+             * insecurely.
+             */
+            public static final int INSECURE_FORM = 1 << 1;
+        }
+
+        /**
+         * Construct a credit card select option.
+         *
+         * @param value The {@link LoginEntry} credit card entry selection option.
+         * @param hint The {@link Hint} detailing the type of the option.
+         */
+        /* package */ AddressSelectOption(
+                final @NonNull Address value,
+                final @AddressSelectHint int hint) {
+            super(value, hint);
+        }
+
+        /**
+         * Construct a address select option.
+         *
+         * @param value The {@link Address} address entry selection option.
+         */
+        public AddressSelectOption(final @NonNull Address value) {
+            this(value, Hint.NONE);
+        }
+
+        /* package */ static @NonNull AddressSelectOption fromBundle(
+                final @NonNull GeckoBundle bundle) {
+            final int hint = bundle.getInt("hint");
+            final Address value = new Address(bundle.getBundle("value"));
+
+            return new AddressSelectOption(value, hint);
+        }
+
+        @Override
+        /* package */ @NonNull GeckoBundle toBundle() {
+            final GeckoBundle bundle = new GeckoBundle(2);
+            bundle.putBundle(VALUE_KEY, value.toBundle());
+            bundle.putInt(HINT_KEY, hint);
+            return bundle;
+        }
+    }
+
     /* package */ final static class StorageProxy implements BundleEventListener {
         private static final String FETCH_LOGIN_EVENT =
             "GeckoView:Autocomplete:Fetch:Login";
         private static final String FETCH_CREDIT_CARD_EVENT =
             "GeckoView:Autocomplete:Fetch:CreditCard";
+        private static final String FETCH_ADDRESS_EVENT =
+                "GeckoView:Autocomplete:Fetch:Address";
         private static final String SAVE_LOGIN_EVENT =
             "GeckoView:Autocomplete:Save:Login";
+        private static final String SAVE_ADDRESS_EVENT =
+                "GeckoView:Autocomplete:Save:Address";
         private static final String USED_LOGIN_EVENT =
             "GeckoView:Autocomplete:Used:Login";
 
         private @Nullable StorageDelegate mDelegate;
 
         public StorageProxy() {}
 
         private void registerListener() {
             EventDispatcher.getInstance().registerUiThreadListener(
                     this,
                     FETCH_LOGIN_EVENT,
                     FETCH_CREDIT_CARD_EVENT,
+                    FETCH_ADDRESS_EVENT,
                     SAVE_LOGIN_EVENT,
+                    SAVE_ADDRESS_EVENT,
                     USED_LOGIN_EVENT);
         }
 
         private void unregisterListener() {
             EventDispatcher.getInstance().unregisterUiThreadListener(
                     this,
                     FETCH_LOGIN_EVENT,
                     FETCH_CREDIT_CARD_EVENT,
+                    FETCH_ADDRESS_EVENT,
                     SAVE_LOGIN_EVENT,
+                    SAVE_ADDRESS_EVENT,
                     USED_LOGIN_EVENT);
         }
 
         public synchronized void setDelegate(
                 final @Nullable StorageDelegate delegate) {
             if (mDelegate == delegate) {
                 return;
             }
@@ -967,21 +1465,49 @@ public class Autocomplete {
                     final GeckoBundle[] creditCardBundles =
                             new GeckoBundle[creditCards.length];
                     for (int i = 0; i < creditCards.length; ++i) {
                         creditCardBundles[i] = creditCards[i].toBundle();
                     }
 
                     return creditCardBundles;
                 }));
+            } else if (FETCH_ADDRESS_EVENT.equals(event)) {
+                final GeckoResult<Autocomplete.Address[]> result =
+                        mDelegate.onAddressFetch();
+
+                if (result == null) {
+                    callback.sendSuccess(new GeckoBundle[0]);
+                    return;
+                }
+
+                callback.resolveTo(result.map(addresses -> {
+                    if (addresses == null) {
+                        return new GeckoBundle[0];
+                    }
+
+                    // This is a one-liner with streams (API level 24).
+                    final GeckoBundle[] addressBundles =
+                            new GeckoBundle[addresses.length];
+                    for (int i = 0; i < addresses.length; ++i) {
+                        addressBundles[i] = addresses[i].toBundle();
+                    }
+
+                    return addressBundles;
+                }));
             } else if (SAVE_LOGIN_EVENT.equals(event)) {
                 final GeckoBundle loginBundle = message.getBundle("login");
                 final LoginEntry login = new LoginEntry(loginBundle);
 
                 mDelegate.onLoginSave(login);
+            } else if (SAVE_ADDRESS_EVENT.equals(event)) {
+                final GeckoBundle addressBundle = message.getBundle("address");
+                final Address address = new Address(addressBundle);
+
+                mDelegate.onAddressSave(address);
             } else if (USED_LOGIN_EVENT.equals(event)) {
                 final GeckoBundle loginBundle = message.getBundle("login");
                 final LoginEntry login = new LoginEntry(loginBundle);
                 final int fields = message.getInt("usedFields");
 
                 mDelegate.onLoginUsed(login, fields);
             }
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -2814,16 +2814,41 @@ public class GeckoSession {
 
                 final PromptDelegate.AutocompleteRequest
                     <Autocomplete.LoginSaveOption> request =
                     new PromptDelegate.AutocompleteRequest<>(options);
 
                 res = delegate.onLoginSave(session, request);
                 break;
             }
+            case "Autocomplete:Save:Address": {
+                final int hint = message.getInt("hint");
+                final GeckoBundle[] addressBundles =
+                        message.getBundleArray("addresses");
+
+                if (addressBundles == null) {
+                    break;
+                }
+
+                final Autocomplete.AddressSaveOption[] options =
+                        new Autocomplete.AddressSaveOption[addressBundles.length];
+
+                for (int i = 0; i < options.length; ++i) {
+                    options[i] = new Autocomplete.AddressSaveOption(
+                            new Autocomplete.Address(addressBundles[i]),
+                            hint);
+                }
+
+                final PromptDelegate.AutocompleteRequest
+                        <Autocomplete.AddressSaveOption> request =
+                        new PromptDelegate.AutocompleteRequest<>(options);
+
+                res = delegate.onAddressSave(session, request);
+                break;
+            }
             case "Autocomplete:Select:Login": {
                 final GeckoBundle[] optionBundles =
                     message.getBundleArray("options");
 
                 if (optionBundles == null) {
                     break;
                 }
 
@@ -2860,16 +2885,39 @@ public class GeckoSession {
 
                 final PromptDelegate.AutocompleteRequest
                     <Autocomplete.CreditCardSelectOption> request =
                     new PromptDelegate.AutocompleteRequest<>(options);
 
                 res = delegate.onCreditCardSelect(session, request);
                 break;
             }
+            case "Autocomplete:Select:Address": {
+                final GeckoBundle[] optionBundles =
+                        message.getBundleArray("options");
+
+                if (optionBundles == null) {
+                    break;
+                }
+
+                final Autocomplete.AddressSelectOption[] options =
+                        new Autocomplete.AddressSelectOption[optionBundles.length];
+
+                for (int i = 0; i < options.length; ++i) {
+                    options[i] = Autocomplete.AddressSelectOption.fromBundle(
+                            optionBundles[i]);
+                }
+
+                final PromptDelegate.AutocompleteRequest
+                        <Autocomplete.AddressSelectOption> request =
+                        new PromptDelegate.AutocompleteRequest<>(options);
+
+                res = delegate.onAddressSelect(session, request);
+                break;
+            }
             default: {
                 callback.sendError("Invalid type");
                 return;
             }
         }
 
         if (res == null) {
             // Adhere to default behavior if the delegate returns null.
@@ -5156,16 +5204,44 @@ public class GeckoSession {
         default @Nullable GeckoResult<PromptResponse> onLoginSave(
                 @NonNull final GeckoSession session,
                 @NonNull final AutocompleteRequest<Autocomplete.LoginSaveOption>
                     request) {
             return null;
         }
 
         /**
+         * Handle a address save prompt request.
+         * This is triggered by the user entering new or modified address
+         * credentials into a address form.
+         *
+         * @param session The {@link GeckoSession} that triggered the request.
+         * @param request The {@link AutocompleteRequest} containing the request
+         *                details.
+         *
+         * @return A {@link GeckoResult} resolving to a {@link PromptResponse}.
+         *
+         *         Confirm the request with an {@link Autocomplete.Option}
+         *         to trigger a
+         *         {@link Autocomplete.StorageDelegate#onAddressSave} request
+         *         to save the given selection.
+         *         The confirmed selection may be an entry out of the request's
+         *         options, a modified option, or a freshly created address entry.
+         *
+         *         Dismiss the request to deny the saving request.
+         */
+        @UiThread
+        default @Nullable GeckoResult<PromptResponse> onAddressSave(
+                @NonNull final GeckoSession session,
+                @NonNull final AutocompleteRequest<Autocomplete.AddressSaveOption>
+                        request) {
+            return null;
+        }
+
+        /**
          * Handle a login selection prompt request.
          * This is triggered by the user focusing on a login username field.
          *
          * @param session The {@link GeckoSession} that triggered the request.
          * @param request The {@link AutocompleteRequest} containing the request
          *                details.
          *
          * @return A {@link GeckoResult} resolving to a {@link PromptResponse}
@@ -5209,16 +5285,44 @@ public class GeckoSession {
          */
         @UiThread
         default @Nullable GeckoResult<PromptResponse> onCreditCardSelect(
                 @NonNull final GeckoSession session,
                 @NonNull final AutocompleteRequest<Autocomplete.CreditCardSelectOption>
                     request) {
             return null;
         }
+
+        /**
+         * Handle a address selection prompt request.
+         * This is triggered by the user focusing on a address field.
+         *
+         * @param session The {@link GeckoSession} that triggered the request.
+         * @param request The {@link AutocompleteRequest} containing the request
+         *                details.
+         *
+         * @return A {@link GeckoResult} resolving to a {@link PromptResponse}
+         *
+         *         Confirm the request with an {@link Autocomplete.Option}
+         *         to let GeckoView fill out the address forms with the given
+         *         selection details.
+         *         The confirmed selection may be an entry out of the request's
+         *         options, a modified option, or a freshly created address entry.
+         *
+         *         Dismiss the request to deny autocompletion for the detected
+         *         form.
+         */
+        @UiThread
+        default @Nullable GeckoResult<PromptResponse> onAddressSelect(
+                @NonNull final GeckoSession session,
+                @NonNull final AutocompleteRequest<Autocomplete.AddressSelectOption>
+                        request) {
+            return null;
+        }
+
     }
 
     /**
      * GeckoSession applications implement this interface to handle content scroll
      * events.
      **/
     public interface ScrollDelegate {
         /**
--- a/mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm
@@ -358,17 +358,19 @@ const GeckoViewAutocomplete = {
    *         Login object string properties:
    *         { guid, name, givenName, additionalName, familyName,
    *           organization, streetAddress, addressLevel1, addressLevel2,
    *           addressLevel3, postalCode, country, tel, email }
    */
   fetchAddresses() {
     debug`fetchAddresses`;
 
-    return Promise.resolve(null);
+    return EventDispatcher.instance.sendRequestForResult({
+      type: "GeckoView:Autocomplete:Fetch:Address",
+    });
   },
 
   /**
    * Delegates credit card entry saving to the attached LoginStorage GeckoView delegate.
    * Call this when a new or modified credit card entry has been submitted.
    *
    * @param aCreditCard The {CreditCard} to be saved.
    */
@@ -384,16 +386,21 @@ const GeckoViewAutocomplete = {
   /**
    * Delegates address entry saving to the attached LoginStorage GeckoView delegate.
    * Call this when a new or modified address entry has been submitted.
    *
    * @param aAddress The {Address} to be saved.
    */
   onAddressSave(aAddress) {
     debug`onLoginSave ${aAddress}`;
+
+    EventDispatcher.instance.sendRequest({
+      type: "GeckoView:Autocomplete:Save:Address",
+      address: aAddress,
+    });
   },
 
   /**
    * Delegates login entry saving to the attached LoginStorage GeckoView delegate.
    * Call this when a new login entry or a new password for an existing login
    * entry has been submitted.
    *
    * @param aLogin The {LoginEntry} to be saved.
@@ -513,17 +520,43 @@ const GeckoViewAutocomplete = {
    * the selection.
    *
    * @param aBrowser The browser instance the triggered the selection.
    * @param aOptions The list of {SelectOption} depicting viable options.
    */
   onAddressSelect(aBrowser, aOptions) {
     debug`onAddressSelect ${aOptions}`;
 
-    return Promise.resolve(null);
+    return new Promise((resolve, reject) => {
+      if (!aBrowser || !aOptions) {
+        debug`onAddressSelect Rejecting - no browser or options provided`;
+        reject();
+        return;
+      }
+
+      const prompt = new GeckoViewPrompter(aBrowser.ownerGlobal);
+      prompt.asyncShowPrompt(
+        {
+          type: "Autocomplete:Select:Address",
+          options: aOptions,
+        },
+        result => {
+          if (!result || !result.selection) {
+            reject();
+            return;
+          }
+
+          const option = new SelectOption({
+            value: Address.parse(result.selection.value),
+            hint: result.selection.hint,
+          });
+          resolve(option);
+        }
+      );
+    });
   },
 
   async delegateSelection({
     browsingContext,
     options,
     inputElementIdentifier,
     formOrigin,
   }) {