diff --git a/mobile/android/app/geckoview-prefs.js b/mobile/android/app/geckoview-prefs.js index d96fb2fc5910..3ac2de98ed56 100644 --- a/mobile/android/app/geckoview-prefs.js +++ b/mobile/android/app/geckoview-prefs.js @@ -84,3 +84,5 @@ pref("toolkit.autocomplete.delegate", true); // Android doesn't support the new sync storage yet, we will have our own in // Bug 1625257. pref("webextensions.storage.sync.kinto", true); + +pref("browser.formfill.enable", true); \ No newline at end of file diff --git a/mobile/android/chrome/geckoview/geckoview.js b/mobile/android/chrome/geckoview/geckoview.js index 5479a7bb150b..95b2f69443dc 100644 --- a/mobile/android/chrome/geckoview/geckoview.js +++ b/mobile/android/chrome/geckoview/geckoview.js @@ -681,6 +681,26 @@ function startup() { frameScript: "chrome://geckoview/content/GeckoViewMediaControlChild.js", }, }, + { + name: "GeckoViewAutocomplete", + onInit: { + actors: { + FormAutofill: { + parent: { + moduleURI: "resource://autofill/FormAutofillParent.jsm", + }, + child: { + moduleURI: "resource://autofill/FormAutofillChild.jsm", + events: { + focusin: {}, + DOMFormBeforeSubmit: {}, + }, + }, + allFrames: true, + }, + }, + }, + }, ]); if (!Services.appinfo.sessionHistoryInParent) { diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AutocompleteTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AutocompleteTest.kt index 03a7926f4e88..7f625b816379 100644 --- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AutocompleteTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AutocompleteTest.kt @@ -24,7 +24,8 @@ import org.mozilla.geckoview.Autocomplete import org.mozilla.geckoview.Autocomplete.LoginEntry import org.mozilla.geckoview.Autocomplete.LoginSaveOption import org.mozilla.geckoview.Autocomplete.LoginSelectOption -import org.mozilla.geckoview.Autocomplete.LoginStorageDelegate +import org.mozilla.geckoview.Autocomplete.SelectOption +import org.mozilla.geckoview.Autocomplete.StorageDelegate import org.mozilla.geckoview.test.rule.GeckoSessionTestRule import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled import org.mozilla.geckoview.test.util.Callbacks @@ -43,18 +44,18 @@ class AutocompleteTest : BaseSessionTest() { "signon.autofillForms.http" to true)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val fetchHandled = GeckoResult() sessionRule.addExternalDelegateDuringNextWait( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled(count = 1) override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -81,16 +82,16 @@ class AutocompleteTest : BaseSessionTest() { "signon.userInputRequiredToCapture.enabled" to false)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } sessionRule.addExternalDelegateDuringNextWait( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled(count = 1) override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -104,8 +105,8 @@ class AutocompleteTest : BaseSessionTest() { mainSession.waitForPageStop() sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled(count = 0) override fun onLoginSave(login: LoginEntry) {} }) @@ -152,11 +153,11 @@ class AutocompleteTest : BaseSessionTest() { "signon.userInputRequiredToCapture.enabled" to false)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } mainSession.loadTestPath(FORMS3_HTML_PATH) @@ -165,8 +166,8 @@ class AutocompleteTest : BaseSessionTest() { val saveHandled = GeckoResult() sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginSave(login: LoginEntry) { assertThat( @@ -229,11 +230,11 @@ class AutocompleteTest : BaseSessionTest() { "signon.userInputRequiredToCapture.enabled" to false)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } mainSession.loadTestPath(FORMS3_HTML_PATH) @@ -242,8 +243,8 @@ class AutocompleteTest : BaseSessionTest() { val saveHandled = GeckoResult() sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginSave(login: LoginEntry) { assertThat( @@ -314,11 +315,11 @@ class AutocompleteTest : BaseSessionTest() { "signon.userInputRequiredToCapture.enabled" to false)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val saveHandled = GeckoResult() @@ -331,8 +332,8 @@ class AutocompleteTest : BaseSessionTest() { val savedLogins = mutableListOf() sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -430,11 +431,11 @@ class AutocompleteTest : BaseSessionTest() { "signon.userInputRequiredToCapture.enabled" to false)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val usedHandled = GeckoResult() @@ -454,8 +455,8 @@ class AutocompleteTest : BaseSessionTest() { if (autofillEnabled) { sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -491,8 +492,8 @@ class AutocompleteTest : BaseSessionTest() { }) } else { sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -547,11 +548,11 @@ class AutocompleteTest : BaseSessionTest() { "signon.userInputRequiredToCapture.enabled" to false)) val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val user1 = "user1x" @@ -568,8 +569,8 @@ class AutocompleteTest : BaseSessionTest() { val savedLogins = mutableListOf(savedLogin) sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -657,11 +658,11 @@ class AutocompleteTest : BaseSessionTest() { // f. Ensure that onLoginUsed is called. val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val user1 = "user1x" @@ -676,8 +677,8 @@ class AutocompleteTest : BaseSessionTest() { val usedHandled = GeckoResult() sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -929,11 +930,11 @@ class AutocompleteTest : BaseSessionTest() { // f. Ensure that onLoginUsed is not called. val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val user1 = "user1x" @@ -949,8 +950,8 @@ class AutocompleteTest : BaseSessionTest() { val selectHandled = GeckoResult() sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -1178,11 +1179,11 @@ class AutocompleteTest : BaseSessionTest() { // a. Ensure onLoginSave is called with accordingly. val runtime = sessionRule.runtime - val register = { delegate: LoginStorageDelegate -> - runtime.loginStorageDelegate = delegate + val register = { delegate: StorageDelegate -> + runtime.autocompleteStorageDelegate = delegate } - val unregister = { _: LoginStorageDelegate -> - runtime.loginStorageDelegate = null + val unregister = { _: StorageDelegate -> + runtime.autocompleteStorageDelegate = null } val user1 = "user1x" @@ -1193,8 +1194,8 @@ class AutocompleteTest : BaseSessionTest() { var numSelects = 0 sessionRule.addExternalDelegateUntilTestEnd( - LoginStorageDelegate::class, register, unregister, - object : LoginStorageDelegate { + StorageDelegate::class, register, unregister, + object : StorageDelegate { @AssertCalled override fun onLoginFetch(domain: String) : GeckoResult>? { @@ -1244,7 +1245,7 @@ class AutocompleteTest : BaseSessionTest() { assertThat( "Hint should match", option.hint, - equalTo(LoginSelectOption.Hint.GENERATED)) + equalTo(SelectOption.Hint.GENERATED)) assertThat("Login should not be null", login, notNullValue()) assertThat( diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java index ff4f6060fcd5..012f8ca32ded 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java @@ -62,7 +62,7 @@ import org.mozilla.gecko.util.GeckoBundle; *

* With the document parsed and the login input fields identified, GeckoView * dispatches a - * LoginStorageDelegate.onLoginFetch("example.com") + * StorageDelegate.onLoginFetch("example.com") * request to fetch logins for the given domain. *

*

@@ -82,14 +82,14 @@ import org.mozilla.gecko.util.GeckoBundle; *

Update API

*

* When the user submits some login input fields, GeckoView dispatches another - * LoginStorageDelegate.onLoginFetch("example.com") + * StorageDelegate.onLoginFetch("example.com") * request to check whether the submitted login exists or whether it's a new or * updated login entry. *

*

* If the submitted login is already contained as-is in the collection returned * by onLoginFetch, then GeckoView dispatches - * LoginStorageDelegate.onLoginUsed with the submitted login + * StorageDelegate.onLoginUsed with the submitted login * entry. *

*

@@ -120,12 +120,12 @@ import org.mozilla.gecko.util.GeckoBundle; *

* The login entry returned in a confirmed save prompt is used to request for * saving in the runtime delegate via - * LoginStorageDelegate.onLoginSave(login). + * StorageDelegate.onLoginSave(login). * If the app has already stored the entry during the prompt request handling, * it may ignore this storage saving request. *

* - *
@see GeckoRuntime#setLoginStorageDelegate + *
@see GeckoRuntime#setAutocompleteStorageDelegate *
@see GeckoSession#setPromptDelegate *
@see GeckoSession.PromptDelegate#onLoginSave *
@see GeckoSession.PromptDelegate#onLoginSelect @@ -136,6 +136,172 @@ public class Autocomplete { protected Autocomplete() {} + /** + * Holds credit card information for a specific entry. + */ + public static class CreditCard { + private static final String GUID_KEY = "guid"; + private static final String NAME_KEY = "name"; + private static final String NUMBER_KEY = "number"; + private static final String EXP_MONTH_KEY = "expMonth"; + private static final String EXP_YEAR_KEY = "expYear"; + + /** + * The unique identifier for this login entry. + */ + public final @Nullable String guid; + + /** + * The full name as it appears on the credit card. + */ + public final @NonNull String name; + + /** + * The credit card number. + */ + public final @NonNull String number; + + /** + * The expiration month. + */ + public final @NonNull String expirationMonth; + + /** + * The expiration year. + */ + public final @NonNull String expirationYear; + + // For tests only. + @AnyThread + protected CreditCard() { + guid = null; + name = ""; + number = ""; + expirationMonth = ""; + expirationYear = ""; + } + + @AnyThread + /* package */ CreditCard(final @NonNull GeckoBundle bundle) { + guid = bundle.getString(GUID_KEY); + name = bundle.getString(NAME_KEY, ""); + number = bundle.getString(NUMBER_KEY, ""); + expirationMonth = bundle.getString(EXP_MONTH_KEY, ""); + expirationYear = bundle.getString(EXP_YEAR_KEY, ""); + } + + @Override + @AnyThread + public String toString() { + final StringBuilder builder = new StringBuilder("CreditCard {"); + builder + .append("guid=").append(guid) + .append(", name=").append(name) + .append(", number=").append(number) + .append(", expirationMonth=").append(expirationMonth) + .append(", expirationYear=").append(expirationYear) + .append("}"); + return builder.toString(); + } + + @AnyThread + /* package */ @NonNull GeckoBundle toBundle() { + final GeckoBundle bundle = new GeckoBundle(7); + bundle.putString(GUID_KEY, guid); + bundle.putString(NAME_KEY, name); + bundle.putString(NUMBER_KEY, number); + bundle.putString(EXP_MONTH_KEY, expirationMonth); + bundle.putString(EXP_YEAR_KEY, expirationYear); + + 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(7); + } + + /** + * Finalize the {@link CreditCard} instance. + * + * @return The {@link CreditCard} instance. + */ + @AnyThread + public @NonNull CreditCard build() { + return new CreditCard(mBundle); + } + + /** + * Set the unique identifier for this credit card 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 name for this credit card entry. + * + * @param name The full name as it appears on the credit card. + * @return This {@link Builder} instance. + */ + @AnyThread + public @NonNull Builder name(final @Nullable String name) { + mBundle.putString(NAME_KEY, name); + return this; + } + + /** + * Set the number for this credit card entry. + * + * @param number The credit card number string. + * @return This {@link Builder} instance. + */ + @AnyThread + public @NonNull Builder number(final @Nullable String number) { + mBundle.putString(NUMBER_KEY, number); + return this; + } + + /** + * Set the expiration month for this credit card entry. + * + * @param expMonth The expiration month string. + * @return This {@link Builder} instance. + */ + @AnyThread + public @NonNull Builder expirationMonth(final @Nullable String expMonth) { + mBundle.putString(EXP_MONTH_KEY, expMonth); + return this; + } + + /** + * Set the expiration year for this credit card entry. + * + * @param expYear The expiration year string. + * @return This {@link Builder} instance. + */ + @AnyThread + public @NonNull Builder expirationYear(final @Nullable String expYear) { + mBundle.putString(EXP_YEAR_KEY, expYear); + return this; + } + } + } + /** * Holds login information for a specific entry. */ @@ -337,7 +503,7 @@ public class Autocomplete { // Sync with UsedField in GeckoViewAutocomplete.jsm. /** - * Possible login entry field types for {@link LoginStorageDelegate#onLoginUsed}. + * Possible login entry field types for {@link StorageDelegate#onLoginUsed}. */ public static class UsedField { /** @@ -353,9 +519,9 @@ public class Autocomplete { * Login storage events include login entry requests for autofill and * autocompletion of login input fields. * This delegate is attached to the runtime via - * {@link GeckoRuntime#setLoginStorageDelegate}. + * {@link GeckoRuntime#setAutocompleteStorageDelegate}. */ - public interface LoginStorageDelegate { + public interface StorageDelegate { /** * Request login entries for a given domain. * While processing the web document, we have identified elements @@ -374,6 +540,21 @@ public class Autocomplete { return null; } + /** + * Request credit card entries. + * While processing the web document, we have identified elements + * resembling credit card input fields suitable for autofill. + * We will attempt to match the provided credit card information to the + * identified input fields. + * + * @return A {@link GeckoResult} that completes with an array of + * {@link CreditCard} containing the existing credit cards. + */ + @UiThread + default @Nullable GeckoResult onCreditCardFetch() { + return null; + } + /** * Request saving or updating of the given login entry. * This is triggered by confirming a @@ -401,6 +582,13 @@ public class Autocomplete { @LSUsedField final int usedFields) {} } + /** + * @deprecated This API has been replaced by {@link StorageDelegate} and + * will be removed in GeckoView 93. + */ + @Deprecated @DeprecationSchedule(version = 93, id = "login-storage") + public interface LoginStorageDelegate extends StorageDelegate {} + /** * Abstract base class for Autocomplete options. * Extended by {@link Autocomplete.SaveOption} and @@ -428,44 +616,10 @@ public class Autocomplete { * Extended by {@link Autocomplete.LoginSaveOption}. */ public abstract static class SaveOption extends Option { - - @SuppressWarnings("checkstyle:javadocmethod") - public SaveOption(final @NonNull T value, final int hint) { - super(value, hint); - } - } - - /** - * Abstract base class for saving options. - * Extended by {@link Autocomplete.LoginSelectOption}. - */ - public abstract static class SelectOption extends Option { - @SuppressWarnings("checkstyle:javadocmethod") - public SelectOption( - final @NonNull T value, - final int hint) { - super(value, hint); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder("SelectOption {"); - builder - .append("value=").append(value).append(", ") - .append("hint=").append(hint) - .append("}"); - return builder.toString(); - } - } - - /** - * Holds information required to process login saving requests. - */ - public static class LoginSaveOption extends SaveOption { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { Hint.NONE, Hint.GENERATED, Hint.LOW_CONFIDENCE }) - /* package */ @interface LoginSaveHint {} + /* package */ @interface SaveOptionHint {} /** * Hint types for login saving requests. @@ -491,6 +645,84 @@ public class Autocomplete { protected Hint() {} } + @SuppressWarnings("checkstyle:javadocmethod") + public SaveOption( + final @NonNull T value, + final @SaveOptionHint int hint) { + super(value, hint); + } + } + + /** + * Abstract base class for saving options. + * Extended by {@link Autocomplete.LoginSelectOption}. + */ + public abstract static class SelectOption extends Option { + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = { Hint.NONE, Hint.GENERATED, Hint.INSECURE_FORM, + Hint.DUPLICATE_USERNAME, Hint.MATCHING_ORIGIN }) + /* package */ @interface SelectOptionHint {} + + /** + * Hint types for selection requests. + */ + public static class Hint { + public static final int NONE = 0; + + /** + * Auto-generated password. + * A new password-only login entry containing a secure generated + * password. + */ + public static final int GENERATED = 1 << 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; + + /** + * The username is shared with another login entry. + * There are multiple login entries in the options that share the + * same username. You may have to disambiguate the login entry, + * e.g., using the last date of modification and its origin. + */ + public static final int DUPLICATE_USERNAME = 1 << 2; + + /** + * The login entry's origin matches the login form origin. + * The login was saved from the same origin it is being requested + * for, rather than for a subdomain. + */ + public static final int MATCHING_ORIGIN = 1 << 3; + } + + @SuppressWarnings("checkstyle:javadocmethod") + public SelectOption( + final @NonNull T value, + final @SelectOptionHint int hint) { + super(value, hint); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder("SelectOption {"); + builder + .append("value=").append(value).append(", ") + .append("hint=").append(hint) + .append("}"); + return builder.toString(); + } + } + + /** + * Holds information required to process login saving requests. + */ + public static class LoginSaveOption extends SaveOption { /** * Construct a login save option. * @@ -499,7 +731,7 @@ public class Autocomplete { */ /* package */ LoginSaveOption( final @NonNull LoginEntry value, - final @LoginSaveHint int hint) { + final @SaveOptionHint int hint) { super(value, hint); } @@ -525,49 +757,6 @@ public class Autocomplete { * Holds information required to process login selection requests. */ public static class LoginSelectOption extends SelectOption { - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = { Hint.NONE, Hint.GENERATED, Hint.INSECURE_FORM, - Hint.DUPLICATE_USERNAME, Hint.MATCHING_ORIGIN }) - /* package */ @interface LoginSelectHint {} - - /** - * Hint types for login selection requests. - */ - public static class Hint { - public static final int NONE = 0; - - /** - * Auto-generated password. - * A new password-only login entry containing a secure generated - * password. - */ - public static final int GENERATED = 1 << 0; - - /** - * Insecure login. - * The login 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; - - /** - * The username is shared with another login entry. - * There are multiple login entries in the options that share the - * same username. You may have to disambiguate the login entry, - * e.g., using the last date of modification and its origin. - */ - public static final int DUPLICATE_USERNAME = 1 << 2; - - /** - * The login entry's origin matches the login form origin. - * The login was saved from the same origin it is being requested - * for, rather than for a subdomain. - */ - public static final int MATCHING_ORIGIN = 1 << 3; - } - /** * Construct a login select option. * @@ -576,7 +765,7 @@ public class Autocomplete { */ /* package */ LoginSelectOption( final @NonNull LoginEntry value, - final @LoginSelectHint int hint) { + final @SelectOptionHint int hint) { super(value, hint); } @@ -606,24 +795,87 @@ public class Autocomplete { } } - /* package */ final static class LoginStorageProxy implements BundleEventListener { - private static final String LOGTAG = "LoginStorageProxy"; + /** + * Holds information required to process credit card selection requests. + */ + public static class CreditCardSelectOption extends SelectOption { + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = { Hint.NONE, Hint.INSECURE_FORM }) + /* package */ @interface CreditCardSelectHint {} + /** + * 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 */ CreditCardSelectOption( + final @NonNull CreditCard value, + final @CreditCardSelectHint int hint) { + super(value, hint); + } + + /** + * Construct a credit card select option. + * + * @param value The {@link CreditCard} credit card entry selection option. + */ + public CreditCardSelectOption(final @NonNull CreditCard value) { + this(value, Hint.NONE); + } + + /* package */ static @NonNull CreditCardSelectOption fromBundle( + final @NonNull GeckoBundle bundle) { + final int hint = bundle.getInt("hint"); + final CreditCard value = new CreditCard(bundle.getBundle("value")); + + return new CreditCardSelectOption(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 SAVE_LOGIN_EVENT = "GeckoView:Autocomplete:Save:Login"; private static final String USED_LOGIN_EVENT = "GeckoView:Autocomplete:Used:Login"; - private @Nullable LoginStorageDelegate mDelegate; + private @Nullable StorageDelegate mDelegate; - public LoginStorageProxy() {} + public StorageProxy() {} private void registerListener() { EventDispatcher.getInstance().registerUiThreadListener( this, FETCH_LOGIN_EVENT, + FETCH_CREDIT_CARD_EVENT, SAVE_LOGIN_EVENT, USED_LOGIN_EVENT); } @@ -632,22 +884,28 @@ public class Autocomplete { EventDispatcher.getInstance().unregisterUiThreadListener( this, FETCH_LOGIN_EVENT, + FETCH_CREDIT_CARD_EVENT, SAVE_LOGIN_EVENT, USED_LOGIN_EVENT); } public synchronized void setDelegate( - final @Nullable LoginStorageDelegate delegate) { - if (mDelegate == null && delegate != null) { - registerListener(); - } else if (mDelegate != null && delegate == null) { + final @Nullable StorageDelegate delegate) { + if (mDelegate == delegate) { + return; + } + if (mDelegate != null) { unregisterListener(); } mDelegate = delegate; + + if (mDelegate != null) { + registerListener(); + } } - public synchronized @Nullable LoginStorageDelegate getDelegate() { + public synchronized @Nullable StorageDelegate getDelegate() { return mDelegate; } @@ -662,7 +920,7 @@ public class Autocomplete { if (mDelegate == null) { if (callback != null) { - callback.sendError("No LoginStorageDelegate attached"); + callback.sendError("No StorageDelegate attached"); } return; } @@ -691,6 +949,29 @@ public class Autocomplete { return loginBundles; })); + } else if (FETCH_CREDIT_CARD_EVENT.equals(event)) { + final GeckoResult result = + mDelegate.onCreditCardFetch(); + + if (result == null) { + callback.sendSuccess(new GeckoBundle[0]); + return; + } + + callback.resolveTo(result.map(creditCards -> { + if (creditCards == null) { + return new GeckoBundle[0]; + } + + // This is a one-liner with streams (API level 24). + final GeckoBundle[] creditCardBundles = + new GeckoBundle[creditCards.length]; + for (int i = 0; i < creditCards.length; ++i) { + creditCardBundles[i] = creditCards[i].toBundle(); + } + + return creditCardBundles; + })); } else if (SAVE_LOGIN_EVENT.equals(event)) { final GeckoBundle loginBundle = message.getBundle("login"); final LoginEntry login = new LoginEntry(loginBundle); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index 1c26eab0d939..dd11307320a5 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -173,13 +173,13 @@ public final class GeckoRuntime implements Parcelable { private final WebExtensionController mWebExtensionController; private WebPushController mPushController; private final ContentBlockingController mContentBlockingController; - private final Autocomplete.LoginStorageProxy mLoginStorageProxy; + private final Autocomplete.StorageProxy mAutocompleteStorageProxy; private final ProfilerController mProfilerController; private GeckoRuntime() { mWebExtensionController = new WebExtensionController(this); mContentBlockingController = new ContentBlockingController(); - mLoginStorageProxy = new Autocomplete.LoginStorageProxy(); + mAutocompleteStorageProxy = new Autocomplete.StorageProxy(); mProfilerController = new ProfilerController(); if (sRuntime != null) { @@ -566,28 +566,63 @@ public final class GeckoRuntime implements Parcelable { } /** - * Set the {@link Autocomplete.LoginStorageDelegate} instance on this runtime. - * This delegate is required for handling login storage requests. + * Set the {@link Autocomplete.StorageDelegate} instance on this runtime. + * This delegate is required for handling autocomplete storage requests. * - * @param delegate The {@link Autocomplete.LoginStorageDelegate} handling login storage - * requests. + * @param delegate The {@link Autocomplete.StorageDelegate} handling + * autocomplete storage requests. */ @UiThread + public void setAutocompleteStorageDelegate( + final @Nullable Autocomplete.StorageDelegate delegate) { + ThreadUtils.assertOnUiThread(); + mAutocompleteStorageProxy.setDelegate(delegate); + } + + /** + * Set the {@link Autocomplete.LoginStorageDelegate} instance on this runtime. + * This delegate is required for handling autocomplete storage requests. + * + * @param delegate The {@link Autocomplete.LoginStorageDelegate} handling + * autocomplete storage requests. + * + * @deprecated This API has been replaced by + * {@link #setAutocompleteStorageDelegate} and + * will be removed in GeckoView 93. + */ + @Deprecated @DeprecationSchedule(version = 93, id = "login-storage") + @UiThread public void setLoginStorageDelegate( final @Nullable Autocomplete.LoginStorageDelegate delegate) { ThreadUtils.assertOnUiThread(); - mLoginStorageProxy.setDelegate(delegate); + mAutocompleteStorageProxy.setDelegate(delegate); + } + + /** + * Get the {@link Autocomplete.StorageDelegate} instance set on this runtime. + * + * @return The {@link Autocomplete.StorageDelegate} set on this runtime. + */ + @UiThread + public @Nullable Autocomplete.StorageDelegate getAutocompleteStorageDelegate() { + ThreadUtils.assertOnUiThread(); + return mAutocompleteStorageProxy.getDelegate(); } /** * Get the {@link Autocomplete.LoginStorageDelegate} instance set on this runtime. * * @return The {@link Autocomplete.LoginStorageDelegate} set on this runtime. + * + * @deprecated This API has been replaced by + * {@link #getAutocompleteStorageDelegate} and + * will be removed in GeckoView 93. */ + @Deprecated @DeprecationSchedule(version = 93, id = "login-storage") @UiThread public @Nullable Autocomplete.LoginStorageDelegate getLoginStorageDelegate() { ThreadUtils.assertOnUiThread(); - return mLoginStorageProxy.getDelegate(); + return (Autocomplete.LoginStorageDelegate)mAutocompleteStorageProxy.getDelegate(); } @UiThread diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java index 0c1077241cce..7da945684ed6 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java @@ -294,7 +294,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { /** * Set whether login forms should be filled automatically if only one * viable candidate is provided via - * {@link Autocomplete.LoginStorageDelegate#onLoginFetch onLoginFetch}. + * {@link Autocomplete.StorageDelegate#onLoginFetch onLoginFetch}. * * @param enabled A flag determining whether login autofill should be * enabled. @@ -1181,7 +1181,7 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { /** * Set whether login forms should be filled automatically if only one * viable candidate is provided via - * {@link Autocomplete.LoginStorageDelegate#onLoginFetch onLoginFetch}. + * {@link Autocomplete.StorageDelegate#onLoginFetch onLoginFetch}. * * @param enabled A flag determining whether login autofill should be * enabled. diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 9d008ea0e5ef..72050bb0c89a 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -2860,7 +2860,29 @@ public class GeckoSession { new PromptDelegate.AutocompleteRequest<>(options); res = delegate.onLoginSelect(session, request); + break; + } + case "Autocomplete:Select:CreditCard": { + final GeckoBundle[] optionBundles = + message.getBundleArray("options"); + if (optionBundles == null) { + break; + } + + final Autocomplete.CreditCardSelectOption[] options = + new Autocomplete.CreditCardSelectOption[optionBundles.length]; + + for (int i = 0; i < options.length; ++i) { + options[i] = Autocomplete.CreditCardSelectOption.fromBundle( + optionBundles[i]); + } + + final PromptDelegate.AutocompleteRequest + request = + new PromptDelegate.AutocompleteRequest<>(options); + + res = delegate.onCreditCardSelect(session, request); break; } default: { @@ -5143,7 +5165,7 @@ public class GeckoSession { * * Confirm the request with an {@link Autocomplete.Option} * to trigger a - * {@link Autocomplete.LoginStorageDelegate#onLoginSave} request + * {@link Autocomplete.StorageDelegate#onLoginSave} 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 login entry. @@ -5184,6 +5206,34 @@ public class GeckoSession { request) { return null; } + + /** + * Handle a credit card selection prompt request. + * This is triggered by the user focusing on a credit card input 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 credit card 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 credit + * card entry. + * + * Dismiss the request to deny autocompletion for the detected + * form. + */ + @UiThread + default @Nullable GeckoResult onCreditCardSelect( + @NonNull final GeckoSession session, + @NonNull final AutocompleteRequest + request) { + return null; + } } /** diff --git a/mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm b/mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm index de74c2f06d3a..9ed22e7695d4 100644 --- a/mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm +++ b/mobile/android/modules/geckoview/GeckoViewAutocomplete.jsm @@ -7,6 +7,8 @@ const EXPORTED_SYMBOLS = [ "GeckoViewAutocomplete", "LoginEntry", + "CreditCard", + "Address", "SelectOption", ]; @@ -106,8 +108,189 @@ class LoginEntry { } } +class Address { + constructor({ + name, + givenName, + additionalName, + familyName, + organization, + streetAddress, + addressLevel1, + addressLevel2, + addressLevel3, + postalCode, + country, + tel, + email, + guid, + timeCreated, + timeLastUsed, + timeLastModified, + timesUsed, + version, + }) { + this.name = name ?? null; + this.givenName = givenName ?? null; + this.additionalName = additionalName ?? null; + this.familyName = familyName ?? null; + this.organization = organization ?? null; + this.streetAddress = streetAddress ?? null; + this.addressLevel1 = addressLevel1 ?? null; + this.addressLevel2 = addressLevel2 ?? null; + this.addressLevel3 = addressLevel3 ?? null; + this.postalCode = postalCode ?? null; + this.country = country ?? null; + this.tel = tel ?? null; + this.email = email ?? null; + + // Metadata. + this.guid = guid ?? null; + // TODO: Not supported by GV. + this.timeCreated = timeCreated ?? null; + this.timeLastUsed = timeLastUsed ?? null; + this.timeLastModified = timeLastModified ?? null; + this.timesUsed = timesUsed ?? null; + this.version = version ?? null; + } + + isValid() { + return ( + (this.name ?? this.givenName ?? this.familyName) !== null && + this.streetAddress !== null && + this.postalCode !== null + ); + } + + static fromGecko(aObj) { + return new Address({ + version: aObj.version, + name: aObj.name, + givenName: aObj["given-name"], + additionalName: aObj["additional-name"], + familyName: aObj["family-name"], + organization: aObj.organization, + streetAddress: aObj["street-address"], + addressLevel1: aObj["address-level1"], + addressLevel2: aObj["address-level2"], + addressLevel3: aObj["address-level3"], + postalCode: aObj["postal-code"], + country: aObj.country, + tel: aObj.tel, + email: aObj.email, + guid: aObj.guid, + timeCreated: aObj.timeCreated, + timeLastUsed: aObj.timeLastUsed, + timeLastModified: aObj.timeLastModified, + timesUsed: aObj.timesUsed, + }); + } + + static parse(aObj) { + const entry = new Address({}); + Object.assign(entry, aObj); + + return entry; + } + + toGecko() { + return { + version: this.version, + name: this.name, + "given-name": this.givenName, + "additional-name": this.additionalName, + "family-name": this.familyName, + organization: this.organization, + "street-address": this.streetAddress, + "address-level1": this.addressLevel1, + "address-level2": this.addressLevel2, + "address-level3": this.addressLevel3, + "postal-code": this.postalCode, + country: this.country, + tel: this.tel, + email: this.email, + guid: this.guid, + }; + } +} + +class CreditCard { + constructor({ + name, + number, + expMonth, + expYear, + type, + guid, + timeCreated, + timeLastUsed, + timeLastModified, + timesUsed, + version, + }) { + this.name = name ?? null; + this.number = number ?? null; + this.expMonth = expMonth ?? null; + this.expYear = expYear ?? null; + this.type = type ?? null; + + // Metadata. + this.guid = guid ?? null; + // TODO: Not supported by GV. + this.timeCreated = timeCreated ?? null; + this.timeLastUsed = timeLastUsed ?? null; + this.timeLastModified = timeLastModified ?? null; + this.timesUsed = timesUsed ?? null; + this.version = version ?? null; + } + + isValid() { + return ( + this.name !== null && + this.number !== null && + this.expMonth !== null && + this.expYear !== null + ); + } + + static fromGecko(aObj) { + return new CreditCard({ + version: aObj.version, + name: aObj["cc-name"], + number: aObj["cc-number"], + expMonth: aObj["cc-exp-month"], + expYear: aObj["cc-exp-year"], + type: aObj["cc-type"], + guid: aObj.guid, + timeCreated: aObj.timeCreated, + timeLastUsed: aObj.timeLastUsed, + timeLastModified: aObj.timeLastModified, + timesUsed: aObj.timesUsed, + }); + } + + static parse(aObj) { + const entry = new CreditCard({}); + Object.assign(entry, aObj); + + return entry; + } + + toGecko() { + return { + version: this.version, + "cc-name": this.name, + "cc-number": this.number, + "cc-exp-month": this.expMonth, + "cc-exp-year": this.expYear, + "cc-type": this.type, + guid: this.guid, + }; + } +} + class SelectOption { - // Sync with Autocomplete.LoginSelectOption.Hint in Autocomplete.java. + // Sync with Autocomplete.SelectOption.Hint in Autocomplete.java. static Hint = { NONE: 0, GENERATED: 1 << 0, @@ -160,7 +343,9 @@ const GeckoViewAutocomplete = { fetchCreditCards() { debug`fetchCreditCards`; - return Promise.resolve(null); + return EventDispatcher.instance.sendRequestForResult({ + type: "GeckoView:Autocomplete:Fetch:CreditCard", + }); }, /** @@ -189,6 +374,11 @@ const GeckoViewAutocomplete = { */ onCreditCardSave(aCreditCard) { debug`onLoginSave ${aCreditCard}`; + + EventDispatcher.instance.sendRequest({ + type: "GeckoView:Autocomplete:Save:CreditCard", + creditCard: aCreditCard, + }); }, /** @@ -235,7 +425,8 @@ const GeckoViewAutocomplete = { }); }, - _numActiveOnLoginSelect: 0, + _numActiveSelections: 0, + /** * Delegates login entry selection. * Call this when there are multiple login entry option for a form to delegate @@ -276,6 +467,60 @@ const GeckoViewAutocomplete = { }); }, + /** + * Delegates credit card entry selection. + * Call this when there are multiple credit card entry option for a form to delegate + * the selection. + * + * @param aBrowser The browser instance the triggered the selection. + * @param aOptions The list of {SelectOption} depicting viable options. + */ + onCreditCardSelect(aBrowser, aOptions) { + debug`onCreditCardSelect ${aOptions}`; + + return new Promise((resolve, reject) => { + if (!aBrowser || !aOptions) { + debug`onCreditCardSelect Rejecting - no browser or options provided`; + reject(); + return; + } + + const prompt = new GeckoViewPrompter(aBrowser.ownerGlobal); + prompt.asyncShowPrompt( + { + type: "Autocomplete:Select:CreditCard", + options: aOptions, + }, + result => { + if (!result || !result.selection) { + reject(); + return; + } + + const option = new SelectOption({ + value: CreditCard.parse(result.selection.value), + hint: result.selection.hint, + }); + resolve(option); + } + ); + }); + }, + + /** + * Delegates address entry selection. + * Call this when there are multiple address entry option for a form to delegate + * 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); + }, + async delegateSelection({ browsingContext, options, @@ -291,6 +536,8 @@ const GeckoViewAutocomplete = { let insecureHint = SelectOption.Hint.NONE; let loginStyle = null; + // TODO: Replace this string with more robust mechanics. + let selectionType = null; const selectOptions = []; for (const option of options) { @@ -301,6 +548,7 @@ const GeckoViewAutocomplete = { break; } case "generatedPassword": { + selectionType = "login"; const comment = JSON.parse(option.comment); selectOptions.push( new SelectOption({ @@ -315,6 +563,7 @@ const GeckoViewAutocomplete = { case "login": // Fallthrough. case "loginWithOrigin": { + selectionType = "login"; loginStyle = option.style; const comment = JSON.parse(option.comment); @@ -334,6 +583,32 @@ const GeckoViewAutocomplete = { ); break; } + case "autofill-profile": { + const comment = JSON.parse(option.comment); + debug`delegateSelection ${comment}`; + const creditCard = CreditCard.fromGecko(comment); + const address = Address.fromGecko(comment); + if (creditCard.isValid()) { + selectionType = "creditCard"; + selectOptions.push( + new SelectOption({ + value: creditCard, + hint: insecureHint, + }) + ); + } else if (address.isValid()) { + selectionType = "address"; + selectOptions.push( + new SelectOption({ + value: address, + hint: insecureHint, + }) + ); + } + break; + } + default: + debug`delegateSelection - ignoring unknown option style ${option.style}`; } } @@ -342,45 +617,79 @@ const GeckoViewAutocomplete = { return; } - if (this._numActiveOnLoginSelect > 0) { + if (this._numActiveSelections > 0) { debug`Abort delegateSelection - there is already one delegation active`; return; } - ++this._numActiveOnLoginSelect; + ++this._numActiveSelections; + let selectedOption = null; const browser = browsingContext.top.embedderElement; - const selectedOption = await this.onLoginSelect( - browser, - selectOptions - ).catch(_ => { - debug`No GV delegate attached`; - }); - - --this._numActiveOnLoginSelect; - - debug`delegateSelection selected option: ${selectedOption}`; - const selectedLogin = selectedOption?.value?.toLoginInfo(); - - if (!selectedLogin) { - debug`Abort delegateSelection - no login entry selected`; - return; + if (selectionType === "login") { + selectedOption = await this.onLoginSelect(browser, selectOptions).catch( + _ => { + debug`No GV delegate attached`; + } + ); + } else if (selectionType === "creditCard") { + selectedOption = await this.onCreditCardSelect( + browser, + selectOptions + ).catch(_ => { + debug`No GV delegate attached`; + }); + } else if (selectionType === "address") { + selectedOption = await this.onAddressSelect(browser, selectOptions).catch( + _ => { + debug`No GV delegate attached`; + } + ); } - debug`delegateSelection - filling form`; + --this._numActiveSelections; - const actor = browsingContext.currentWindowGlobal.getActor("LoginManager"); + debug`delegateSelection selected option: ${selectedOption}`; - await actor.fillForm({ - browser, - inputElementIdentifier, - loginFormOrigin: formOrigin, - login: selectedLogin, - style: - selectedOption.hint & SelectOption.Hint.GENERATED - ? "generatedPassword" - : loginStyle, - }); + if (selectionType === "login") { + const selectedLogin = selectedOption?.value?.toLoginInfo(); + + if (!selectedLogin) { + debug`Abort delegateSelection - no login entry selected`; + return; + } + + debug`delegateSelection - filling form`; + + const actor = browsingContext.currentWindowGlobal.getActor( + "LoginManager" + ); + + await actor.fillForm({ + browser, + inputElementIdentifier, + loginFormOrigin: formOrigin, + login: selectedLogin, + style: + selectedOption.hint & SelectOption.Hint.GENERATED + ? "generatedPassword" + : loginStyle, + }); + } else if (selectionType === "creditCard") { + const selectedCreditCard = selectedOption?.value?.toGecko(); + const actor = browsingContext.currentWindowGlobal.getActor( + "FormAutofill" + ); + + actor.sendAsyncMessage("FormAutofill:FillForm", selectedCreditCard); + } else if (selectionType === "address") { + const selectedAddress = selectedOption?.value?.toGecko(); + const actor = browsingContext.currentWindowGlobal.getActor( + "FormAutofill" + ); + + actor.sendAsyncMessage("FormAutofill:FillForm", selectedAddress); + } debug`delegateSelection - form filled`; }, diff --git a/mobile/android/modules/geckoview/GeckoViewLoginStorage.jsm b/mobile/android/modules/geckoview/GeckoViewLoginStorage.jsm deleted file mode 100644 index 1440cdcf76a4..000000000000 --- a/mobile/android/modules/geckoview/GeckoViewLoginStorage.jsm +++ /dev/null @@ -1,153 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const EXPORTED_SYMBOLS = ["GeckoViewLoginStorage", "LoginEntry"]; - -const { XPCOMUtils } = ChromeUtils.import( - "resource://gre/modules/XPCOMUtils.jsm" -); - -const { GeckoViewUtils } = ChromeUtils.import( - "resource://gre/modules/GeckoViewUtils.jsm" -); - -XPCOMUtils.defineLazyModuleGetters(this, { - EventDispatcher: "resource://gre/modules/Messaging.jsm", -}); - -XPCOMUtils.defineLazyGetter(this, "LoginInfo", () => - Components.Constructor( - "@mozilla.org/login-manager/loginInfo;1", - "nsILoginInfo", - "init" - ) -); - -class LoginEntry { - constructor() { - this.origin = null; - this.formActionOrigin = null; - this.httpRealm = null; - this.username = null; - this.password = null; - - // Metadata. - this.guid = null; - // TODO: Not supported by GV. - this.timeCreated = null; - this.timeLastUsed = null; - this.timePasswordChanged = null; - this.timesUsed = null; - } - - toLoginInfo() { - const info = new LoginInfo( - this.origin, - this.formActionOrigin, - this.httpRealm, - this.username, - this.password - ); - - // Metadata. - info.QueryInterface(Ci.nsILoginMetaInfo); - info.guid = this.guid; - info.timeCreated = this.timeCreated; - info.timeLastUsed = this.timeLastUsed; - info.timePasswordChanged = this.timePasswordChanged; - info.timesUsed = this.timesUsed; - - return info; - } - - static fromBundle(aObj) { - const entry = new LoginEntry(); - Object.assign(entry, aObj); - - return entry; - } - - static fromLoginInfo(aInfo) { - const entry = new LoginEntry(); - entry.origin = aInfo.origin; - entry.formActionOrigin = aInfo.formActionOrigin; - entry.httpRealm = aInfo.httpRealm; - entry.username = aInfo.username; - entry.password = aInfo.password; - - // Metadata. - aInfo.QueryInterface(Ci.nsILoginMetaInfo); - entry.guid = aInfo.guid; - entry.timeCreated = aInfo.timeCreated; - entry.timeLastUsed = aInfo.timeLastUsed; - entry.timePasswordChanged = aInfo.timePasswordChanged; - entry.timesUsed = aInfo.timesUsed; - - return entry; - } -} - -// Sync with LoginStorage.Delegate.UsedField in LoginStorage.java. -const UsedField = { PASSWORD: 1 }; - -const GeckoViewLoginStorage = { - /** - * Delegates login entry fetching for the given domain to the attached - * LoginStorage GeckoView delegate. - * - * @param aDomain - * The domain string to fetch login entries for. - * @return {Promise} - * Resolves with an array of login objects or null. - * Rejected if no delegate is attached. - * Login object string properties: - * { guid, origin, formActionOrigin, httpRealm, username, password } - */ - fetchLogins(aDomain) { - debug`fetchLogins for ${aDomain}`; - - return EventDispatcher.instance.sendRequestForResult({ - type: "GeckoView:LoginStorage:Fetch", - domain: aDomain, - }); - }, - - /** - * 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. - */ - onLoginSave(aLogin) { - debug`onLoginSave ${aLogin}`; - - EventDispatcher.instance.sendRequest({ - type: "GeckoView:LoginStorage:Save", - login: aLogin, - }); - }, - - /** - * Delegates login entry password usage to the attached LoginStorage GeckoView - * delegate. - * Call this when the password of an existing login entry, as returned by - * fetchLogins, has been used for autofill. - * - * @param aLogin The {LoginEntry} whose password was used. - */ - onLoginPasswordUsed(aLogin) { - debug`onLoginUsed ${aLogin}`; - - EventDispatcher.instance.sendRequest({ - type: "GeckoView:LoginStorage:Used", - usedFields: UsedField.PASSWORD, - login: aLogin, - }); - }, -}; - -const { debug } = GeckoViewUtils.initLogging("GeckoViewLoginStorage");