mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-09 13:25:00 +00:00
Bug 1691819
- [1.7] Extend the Autocomplete API to support credit cards. r=geckoview-reviewers,agi
Differential Revision: https://phabricator.services.mozilla.com/D106695
This commit is contained in:
parent
33cc29d588
commit
434f165a88
@ -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);
|
@ -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) {
|
||||
|
@ -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<Void>()
|
||||
|
||||
sessionRule.addExternalDelegateDuringNextWait(
|
||||
LoginStorageDelegate::class, register, unregister,
|
||||
object : LoginStorageDelegate {
|
||||
StorageDelegate::class, register, unregister,
|
||||
object : StorageDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onLoginFetch(domain: String)
|
||||
: GeckoResult<Array<LoginEntry>>? {
|
||||
@ -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<Array<LoginEntry>>? {
|
||||
@ -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<Void>()
|
||||
|
||||
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<Void>()
|
||||
|
||||
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<Void>()
|
||||
@ -331,8 +332,8 @@ class AutocompleteTest : BaseSessionTest() {
|
||||
val savedLogins = mutableListOf<LoginEntry>()
|
||||
|
||||
sessionRule.addExternalDelegateUntilTestEnd(
|
||||
LoginStorageDelegate::class, register, unregister,
|
||||
object : LoginStorageDelegate {
|
||||
StorageDelegate::class, register, unregister,
|
||||
object : StorageDelegate {
|
||||
@AssertCalled
|
||||
override fun onLoginFetch(domain: String)
|
||||
: GeckoResult<Array<LoginEntry>>? {
|
||||
@ -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<Void>()
|
||||
@ -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<Array<LoginEntry>>? {
|
||||
@ -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<Array<LoginEntry>>? {
|
||||
@ -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<LoginEntry>(savedLogin)
|
||||
|
||||
sessionRule.addExternalDelegateUntilTestEnd(
|
||||
LoginStorageDelegate::class, register, unregister,
|
||||
object : LoginStorageDelegate {
|
||||
StorageDelegate::class, register, unregister,
|
||||
object : StorageDelegate {
|
||||
@AssertCalled
|
||||
override fun onLoginFetch(domain: String)
|
||||
: GeckoResult<Array<LoginEntry>>? {
|
||||
@ -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<Void>()
|
||||
|
||||
sessionRule.addExternalDelegateUntilTestEnd(
|
||||
LoginStorageDelegate::class, register, unregister,
|
||||
object : LoginStorageDelegate {
|
||||
StorageDelegate::class, register, unregister,
|
||||
object : StorageDelegate {
|
||||
@AssertCalled
|
||||
override fun onLoginFetch(domain: String)
|
||||
: GeckoResult<Array<LoginEntry>>? {
|
||||
@ -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<Void>()
|
||||
|
||||
sessionRule.addExternalDelegateUntilTestEnd(
|
||||
LoginStorageDelegate::class, register, unregister,
|
||||
object : LoginStorageDelegate {
|
||||
StorageDelegate::class, register, unregister,
|
||||
object : StorageDelegate {
|
||||
@AssertCalled
|
||||
override fun onLoginFetch(domain: String)
|
||||
: GeckoResult<Array<LoginEntry>>? {
|
||||
@ -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<Array<LoginEntry>>? {
|
||||
@ -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(
|
||||
|
@ -62,7 +62,7 @@ import org.mozilla.gecko.util.GeckoBundle;
|
||||
* <p>
|
||||
* With the document parsed and the login input fields identified, GeckoView
|
||||
* dispatches a
|
||||
* <code>LoginStorageDelegate.onLoginFetch("example.com")</code>
|
||||
* <code>StorageDelegate.onLoginFetch("example.com")</code>
|
||||
* request to fetch logins for the given domain.
|
||||
* </p>
|
||||
* <p>
|
||||
@ -82,14 +82,14 @@ import org.mozilla.gecko.util.GeckoBundle;
|
||||
* <h3>Update API</h3>
|
||||
* <p>
|
||||
* When the user submits some login input fields, GeckoView dispatches another
|
||||
* <code>LoginStorageDelegate.onLoginFetch("example.com")</code>
|
||||
* <code>StorageDelegate.onLoginFetch("example.com")</code>
|
||||
* request to check whether the submitted login exists or whether it's a new or
|
||||
* updated login entry.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the submitted login is already contained as-is in the collection returned
|
||||
* by <code>onLoginFetch</code>, then GeckoView dispatches
|
||||
* <code>LoginStorageDelegate.onLoginUsed</code> with the submitted login
|
||||
* <code>StorageDelegate.onLoginUsed</code> with the submitted login
|
||||
* entry.
|
||||
* </p>
|
||||
* <p>
|
||||
@ -120,12 +120,12 @@ import org.mozilla.gecko.util.GeckoBundle;
|
||||
* <p>
|
||||
* The login entry returned in a confirmed save prompt is used to request for
|
||||
* saving in the runtime delegate via
|
||||
* <code>LoginStorageDelegate.onLoginSave(login)</code>.
|
||||
* <code>StorageDelegate.onLoginSave(login)</code>.
|
||||
* If the app has already stored the entry during the prompt request handling,
|
||||
* it may ignore this storage saving request.
|
||||
* </p>
|
||||
*
|
||||
* <br>@see GeckoRuntime#setLoginStorageDelegate
|
||||
* <br>@see GeckoRuntime#setAutocompleteStorageDelegate
|
||||
* <br>@see GeckoSession#setPromptDelegate
|
||||
* <br>@see GeckoSession.PromptDelegate#onLoginSave
|
||||
* <br>@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<CreditCard[]> 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<T> extends Option<T> {
|
||||
|
||||
@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<T> extends Option<T> {
|
||||
@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<LoginEntry> {
|
||||
@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<T> extends Option<T> {
|
||||
@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<LoginEntry> {
|
||||
/**
|
||||
* 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<LoginEntry> {
|
||||
@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<CreditCard> {
|
||||
@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<Autocomplete.CreditCard[]> 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);
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
<Autocomplete.CreditCardSelectOption> 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<PromptResponse> onCreditCardSelect(
|
||||
@NonNull final GeckoSession session,
|
||||
@NonNull final AutocompleteRequest<Autocomplete.CreditCardSelectOption>
|
||||
request) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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`;
|
||||
},
|
||||
|
@ -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");
|
Loading…
Reference in New Issue
Block a user