Loading mobile/android/components/geckoview/LoginStorageDelegate.jsm +1 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ class LoginStorageDelegate { hint |= LoginStorageHint.GENERATED; } return { // Sync with GeckoSession.handlePromptEvent. // Sync with PromptController type: "Autocomplete:Save:Login", hint, logins: aLogins, Loading mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AutocompleteTest.kt +339 −2 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest import org.mozilla.geckoview.Autocomplete.Address import org.mozilla.geckoview.Autocomplete.AddressSelectOption import org.mozilla.geckoview.Autocomplete.CreditCard import org.mozilla.geckoview.Autocomplete.CreditCardSaveOption import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption import org.mozilla.geckoview.Autocomplete.LoginEntry import org.mozilla.geckoview.Autocomplete.LoginSaveOption Loading Loading @@ -103,7 +104,7 @@ class AutocompleteTest : BaseSessionTest() { val name = arrayOf("Peter Parker", "John Doe") val number = arrayOf("1234-1234-1234-1234", "2345-2345-2345-2345") val guid = arrayOf("test-guid1", "test-guid2") val expMonth = arrayOf("Apr", "Aug") val expMonth = arrayOf("04", "08") val expYear = arrayOf("22", "23") val savedCC = arrayOf( CreditCard.Builder() Loading Loading @@ -132,6 +133,9 @@ class AutocompleteTest : BaseSessionTest() { : GeckoResult<Array<CreditCard>>? { return GeckoResult.fromValue(savedCC) } @AssertCalled(false) override fun onCreditCardSave(creditCard: CreditCard) {} }) mainSession.delegateUntilTestEnd(object : PromptDelegate { Loading Loading @@ -341,7 +345,6 @@ class AutocompleteTest : BaseSessionTest() { checkAddressesForCorrectness(savedAddresses.toTypedArray(), savedAddress) } @Test fun addressSelectAndFillMultipleAddresses() { val givenNames = arrayOf("Peter", "Wade") Loading Loading @@ -690,6 +693,340 @@ class AutocompleteTest : BaseSessionTest() { sessionRule.waitForResult(saveHandled2) } @Test fun creditCardSaveAccept() { val ccName = "MyCard" val ccNumber = "5105-1051-0510-5100" val ccExpMonth = "06" val ccExpYear = "24" mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() val saveHandled = GeckoResult<Void>() sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled override fun onCreditCardSave(creditCard: CreditCard) { assertThat("Credit card name should match", creditCard.name, equalTo(ccName)) assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber)) assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth)) assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYear)) saveHandled.complete(null) } }) sessionRule.delegateDuringNextWait(object : PromptDelegate { @AssertCalled override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(ccNumber)) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(ccExpMonth)) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(ccExpYear)) return GeckoResult.fromValue(request.confirm(option)) } }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled) } @Test fun creditCardSaveDismiss() { val ccName = "MyCard" val ccNumber = "5105-1051-0510-5100" val ccExpMonth = "06" val ccExpYear = "24" mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() sessionRule.delegateDuringNextWait(object : StorageDelegate { @AssertCalled override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? { return null } }) sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled(count = 0) override fun onCreditCardSave(creditCard: CreditCard) {} }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitUntilCalled(object : PromptDelegate { @AssertCalled override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(ccNumber)) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(ccExpMonth)) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(ccExpYear)) return GeckoResult.fromValue(request.dismiss()) } }) } @Test fun creditCardSaveModifyAccept() { val ccName = "MyCard" val ccNumber = "5105-1051-0510-5100" val ccExpMonth = "06" val ccExpYearNew = "26" val ccExpYear = "24" mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() val saveHandled = GeckoResult<Void>() sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled override fun onCreditCardSave(creditCard: CreditCard) { assertThat("Credit card name should match", creditCard.name, equalTo(ccName)) assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber)) assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth)) assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYearNew)) saveHandled.complete(null) } }) sessionRule.delegateDuringNextWait(object : PromptDelegate { @AssertCalled override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(ccNumber)) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(ccExpMonth)) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(ccExpYear)) val modifiedCreditCard = CreditCard.Builder() .name(cc.name) .number(cc.number) .expirationMonth(cc.expirationMonth) .expirationYear(ccExpYearNew) .build() return GeckoResult.fromValue(request.confirm(CreditCardSaveOption(modifiedCreditCard))) } }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled) } @Test fun creditCardUpdateAccept() { val ccName = "MyCard" val ccNumber1 = "5105-1051-0510-5100" val ccExpMonth1 = "06" val ccExpYear1 = "24" val ccNumber2 = "4111-1111-1111-1111" val ccExpMonth2 = "11" val ccExpYear2 = "21" val savedCreditCards = mutableListOf<CreditCard>() mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() val saveHandled1 = GeckoResult<Void>() val saveHandled2 = GeckoResult<Void>() sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? { return GeckoResult.fromValue(savedCreditCards.toTypedArray()) } @AssertCalled(count = 2) override fun onCreditCardSave(creditCard: CreditCard) { assertThat( "Credit card name should match", creditCard.name, equalTo(ccName)) assertThat( "Credit card number should match", creditCard.number, equalTo(forEachCall(ccNumber1, ccNumber2))) assertThat( "Credit card expiration month should match", creditCard.expirationMonth, equalTo(forEachCall(ccExpMonth1, ccExpMonth2))) assertThat( "Credit card expiration year should match", creditCard.expirationYear, equalTo(forEachCall(ccExpYear1, ccExpYear2))) val savedCC = CreditCard.Builder() .guid("test1") .name(creditCard.name) .number(creditCard.number) .expirationMonth(creditCard.expirationMonth) .expirationYear(creditCard.expirationYear) .build() savedCreditCards.add(savedCC) if (sessionRule.currentCall.counter == 1) { saveHandled1.complete(null) } else if (sessionRule.currentCall.counter == 2) { saveHandled2.complete(null) } } }) sessionRule.delegateUntilTestEnd(object : PromptDelegate { @AssertCalled(count = 2) override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(forEachCall(ccNumber1, ccNumber2))) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(forEachCall(ccExpMonth1, ccExpMonth2))) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(forEachCall(ccExpYear1, ccExpYear2))) return GeckoResult.fromValue(request.confirm(option)) } }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber1}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth1}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear1}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled1) // Update credit card val session2 = sessionRule.createOpenSession() session2.loadTestPath(CC_FORM_HTML_PATH) session2.waitForPageStop() session2.evaluateJS("document.querySelector('#name').focus()") session2.evaluateJS("document.querySelector('#name').value = '${ccName}'") session2.evaluateJS("document.querySelector('#number').value = '${ccNumber2}'") session2.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth2}'") session2.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear2}'") session2.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled2) } fun testLoginUsed(autofillEnabled: Boolean) { sessionRule.setPrefsUntilTestEnd(mapOf( // Enable login management since it's disabled in automation. Loading mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java +60 −2 Original line number Diff line number Diff line Loading @@ -210,8 +210,12 @@ public class Autocomplete { bundle.putString(GUID_KEY, guid); bundle.putString(NAME_KEY, name); bundle.putString(NUMBER_KEY, number); if (expirationMonth != null) { bundle.putString(EXP_MONTH_KEY, expirationMonth); } if (expirationYear != null) { bundle.putString(EXP_YEAR_KEY, expirationYear); } return bundle; } Loading Loading @@ -949,6 +953,17 @@ public class Autocomplete { @UiThread default void onLoginSave(@NonNull final LoginEntry login) {} /** * Request saving or updating of the given credit card entry. * This is triggered by confirming a * {@link GeckoSession.PromptDelegate#onCreditCardSave onCreditCardSave} request. * * @param creditCard The {@link CreditCard} as confirmed by the prompt * request. */ @UiThread default void onCreditCardSave(@NonNull CreditCard creditCard) {} /** * Request saving or updating of the given address entry. * This is triggered by confirming a Loading Loading @@ -1174,6 +1189,40 @@ public class Autocomplete { } } /** * Holds information required to process credit card saving requests. */ public static class CreditCardSaveOption extends SaveOption<CreditCard> { /** * Construct a credit card save option. * * @param value The {@link CreditCard} credit card entry to be saved. * @param hint The {@link Hint} detailing the type of the option. */ /* package */ CreditCardSaveOption( final @NonNull CreditCard value, final @SaveOptionHint int hint) { super(value, hint); } /** * Construct a credit card save option. * * @param value The {@link CreditCard} credit card entry to be saved. */ public CreditCardSaveOption(final @NonNull CreditCard value) { this(value, Hint.NONE); } @Override /* package */ @NonNull GeckoBundle toBundle() { final GeckoBundle bundle = new GeckoBundle(2); bundle.putBundle(VALUE_KEY, value.toBundle()); bundle.putInt(HINT_KEY, hint); return bundle; } } /** * Holds information required to process login selection requests. */ Loading Loading @@ -1349,6 +1398,8 @@ public class Autocomplete { "GeckoView:Autocomplete:Fetch:Address"; private static final String SAVE_LOGIN_EVENT = "GeckoView:Autocomplete:Save:Login"; private static final String SAVE_CREDIT_CARD_EVENT = "GeckoView:Autocomplete:Save:CreditCard"; private static final String SAVE_ADDRESS_EVENT = "GeckoView:Autocomplete:Save:Address"; private static final String USED_LOGIN_EVENT = Loading @@ -1365,6 +1416,7 @@ public class Autocomplete { FETCH_CREDIT_CARD_EVENT, FETCH_ADDRESS_EVENT, SAVE_LOGIN_EVENT, SAVE_CREDIT_CARD_EVENT, SAVE_ADDRESS_EVENT, USED_LOGIN_EVENT); } Loading @@ -1376,6 +1428,7 @@ public class Autocomplete { FETCH_CREDIT_CARD_EVENT, FETCH_ADDRESS_EVENT, SAVE_LOGIN_EVENT, SAVE_CREDIT_CARD_EVENT, SAVE_ADDRESS_EVENT, USED_LOGIN_EVENT); } Loading Loading @@ -1491,6 +1544,11 @@ public class Autocomplete { final LoginEntry login = new LoginEntry(loginBundle); mDelegate.onLoginSave(login); } else if (SAVE_CREDIT_CARD_EVENT.equals(event)) { final GeckoBundle creditCardBundle = message.getBundle("creditCard"); final CreditCard creditCard = new CreditCard(creditCardBundle); mDelegate.onCreditCardSave(creditCard); } else if (SAVE_ADDRESS_EVENT.equals(event)) { final GeckoBundle addressBundle = message.getBundle("address"); final Address address = new Address(addressBundle); Loading mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +29 −0 Original line number Diff line number Diff line Loading @@ -4994,6 +4994,35 @@ public class GeckoSession { return null; } /** * Handle a credit card save prompt request. * This is triggered by the user entering new or modified credit card * credentials into a form. * * @param session The {@link GeckoSession} that triggered the request. * @param request The {@link AutocompleteRequest} containing the request * details. * * @return A {@link GeckoResult} resolving to a {@link PromptResponse}. * * Confirm the request with an {@link Autocomplete.Option} * to trigger a * {@link Autocomplete.StorageDelegate#onCreditCardSave} 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 credit * card entry. * * Dismiss the request to deny the saving request. */ @UiThread default @Nullable GeckoResult<PromptResponse> onCreditCardSave( @NonNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.CreditCardSaveOption> request) { return null; } /** * Handle a login selection prompt request. * This is triggered by the user focusing on a login username field. Loading mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PromptController.java +37 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import org.mozilla.gecko.util.GeckoBundle; import org.mozilla.geckoview.Autocomplete.AddressSaveOption; import org.mozilla.geckoview.Autocomplete.AddressSelectOption; import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption; import org.mozilla.geckoview.Autocomplete.CreditCardSaveOption; import org.mozilla.geckoview.Autocomplete.LoginSaveOption; import org.mozilla.geckoview.Autocomplete.LoginSelectOption; import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest; Loading Loading @@ -451,6 +452,41 @@ import java.util.Map; } } private static final class CreditCardSaveHandler implements PromptHandler<AutocompleteRequest<CreditCardSaveOption>> { @Override public AutocompleteRequest<CreditCardSaveOption> newPrompt( final GeckoBundle info, final Observer observer) { final int hint = info.getInt("hint"); final GeckoBundle[] creditCardBundles = info.getBundleArray("creditCards"); if (creditCardBundles == null) { return null; } final Autocomplete.CreditCardSaveOption[] options = new Autocomplete.CreditCardSaveOption[creditCardBundles.length]; for (int i = 0; i < options.length; ++i) { options[i] = new Autocomplete.CreditCardSaveOption( new Autocomplete.CreditCard(creditCardBundles[i]), hint); } return new PromptDelegate.AutocompleteRequest<>(info.getString("id"), options, observer); } @Override public GeckoResult<PromptResponse> callDelegate( final AutocompleteRequest<CreditCardSaveOption> prompt, final GeckoSession session, final PromptDelegate delegate) { return delegate.onCreditCardSave(session, prompt); } } private static final class AddressSaveHandler implements PromptHandler<AutocompleteRequest<AddressSaveOption>> { @Override Loading Loading @@ -612,6 +648,7 @@ import java.util.Map; sPromptHandlers.register(new RepostHandler(), "repost"); sPromptHandlers.register(new ShareHandler(), "share"); sPromptHandlers.register(new LoginSaveHandler(), "Autocomplete:Save:Login"); sPromptHandlers.register(new CreditCardSaveHandler(), "Autocomplete:Save:CreditCard"); sPromptHandlers.register(new AddressSaveHandler(), "Autocomplete:Save:Address"); sPromptHandlers.register(new LoginSelectHandler(), Loading Loading
mobile/android/components/geckoview/LoginStorageDelegate.jsm +1 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ class LoginStorageDelegate { hint |= LoginStorageHint.GENERATED; } return { // Sync with GeckoSession.handlePromptEvent. // Sync with PromptController type: "Autocomplete:Save:Login", hint, logins: aLogins, Loading
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AutocompleteTest.kt +339 −2 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest import org.mozilla.geckoview.Autocomplete.Address import org.mozilla.geckoview.Autocomplete.AddressSelectOption import org.mozilla.geckoview.Autocomplete.CreditCard import org.mozilla.geckoview.Autocomplete.CreditCardSaveOption import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption import org.mozilla.geckoview.Autocomplete.LoginEntry import org.mozilla.geckoview.Autocomplete.LoginSaveOption Loading Loading @@ -103,7 +104,7 @@ class AutocompleteTest : BaseSessionTest() { val name = arrayOf("Peter Parker", "John Doe") val number = arrayOf("1234-1234-1234-1234", "2345-2345-2345-2345") val guid = arrayOf("test-guid1", "test-guid2") val expMonth = arrayOf("Apr", "Aug") val expMonth = arrayOf("04", "08") val expYear = arrayOf("22", "23") val savedCC = arrayOf( CreditCard.Builder() Loading Loading @@ -132,6 +133,9 @@ class AutocompleteTest : BaseSessionTest() { : GeckoResult<Array<CreditCard>>? { return GeckoResult.fromValue(savedCC) } @AssertCalled(false) override fun onCreditCardSave(creditCard: CreditCard) {} }) mainSession.delegateUntilTestEnd(object : PromptDelegate { Loading Loading @@ -341,7 +345,6 @@ class AutocompleteTest : BaseSessionTest() { checkAddressesForCorrectness(savedAddresses.toTypedArray(), savedAddress) } @Test fun addressSelectAndFillMultipleAddresses() { val givenNames = arrayOf("Peter", "Wade") Loading Loading @@ -690,6 +693,340 @@ class AutocompleteTest : BaseSessionTest() { sessionRule.waitForResult(saveHandled2) } @Test fun creditCardSaveAccept() { val ccName = "MyCard" val ccNumber = "5105-1051-0510-5100" val ccExpMonth = "06" val ccExpYear = "24" mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() val saveHandled = GeckoResult<Void>() sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled override fun onCreditCardSave(creditCard: CreditCard) { assertThat("Credit card name should match", creditCard.name, equalTo(ccName)) assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber)) assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth)) assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYear)) saveHandled.complete(null) } }) sessionRule.delegateDuringNextWait(object : PromptDelegate { @AssertCalled override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(ccNumber)) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(ccExpMonth)) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(ccExpYear)) return GeckoResult.fromValue(request.confirm(option)) } }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled) } @Test fun creditCardSaveDismiss() { val ccName = "MyCard" val ccNumber = "5105-1051-0510-5100" val ccExpMonth = "06" val ccExpYear = "24" mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() sessionRule.delegateDuringNextWait(object : StorageDelegate { @AssertCalled override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? { return null } }) sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled(count = 0) override fun onCreditCardSave(creditCard: CreditCard) {} }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitUntilCalled(object : PromptDelegate { @AssertCalled override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(ccNumber)) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(ccExpMonth)) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(ccExpYear)) return GeckoResult.fromValue(request.dismiss()) } }) } @Test fun creditCardSaveModifyAccept() { val ccName = "MyCard" val ccNumber = "5105-1051-0510-5100" val ccExpMonth = "06" val ccExpYearNew = "26" val ccExpYear = "24" mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() val saveHandled = GeckoResult<Void>() sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled override fun onCreditCardSave(creditCard: CreditCard) { assertThat("Credit card name should match", creditCard.name, equalTo(ccName)) assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber)) assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth)) assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYearNew)) saveHandled.complete(null) } }) sessionRule.delegateDuringNextWait(object : PromptDelegate { @AssertCalled override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(ccNumber)) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(ccExpMonth)) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(ccExpYear)) val modifiedCreditCard = CreditCard.Builder() .name(cc.name) .number(cc.number) .expirationMonth(cc.expirationMonth) .expirationYear(ccExpYearNew) .build() return GeckoResult.fromValue(request.confirm(CreditCardSaveOption(modifiedCreditCard))) } }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled) } @Test fun creditCardUpdateAccept() { val ccName = "MyCard" val ccNumber1 = "5105-1051-0510-5100" val ccExpMonth1 = "06" val ccExpYear1 = "24" val ccNumber2 = "4111-1111-1111-1111" val ccExpMonth2 = "11" val ccExpYear2 = "21" val savedCreditCards = mutableListOf<CreditCard>() mainSession.loadTestPath(CC_FORM_HTML_PATH) mainSession.waitForPageStop() val saveHandled1 = GeckoResult<Void>() val saveHandled2 = GeckoResult<Void>() sessionRule.delegateUntilTestEnd(object : StorageDelegate { @AssertCalled override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? { return GeckoResult.fromValue(savedCreditCards.toTypedArray()) } @AssertCalled(count = 2) override fun onCreditCardSave(creditCard: CreditCard) { assertThat( "Credit card name should match", creditCard.name, equalTo(ccName)) assertThat( "Credit card number should match", creditCard.number, equalTo(forEachCall(ccNumber1, ccNumber2))) assertThat( "Credit card expiration month should match", creditCard.expirationMonth, equalTo(forEachCall(ccExpMonth1, ccExpMonth2))) assertThat( "Credit card expiration year should match", creditCard.expirationYear, equalTo(forEachCall(ccExpYear1, ccExpYear2))) val savedCC = CreditCard.Builder() .guid("test1") .name(creditCard.name) .number(creditCard.number) .expirationMonth(creditCard.expirationMonth) .expirationYear(creditCard.expirationYear) .build() savedCreditCards.add(savedCC) if (sessionRule.currentCall.counter == 1) { saveHandled1.complete(null) } else if (sessionRule.currentCall.counter == 2) { saveHandled2.complete(null) } } }) sessionRule.delegateUntilTestEnd(object : PromptDelegate { @AssertCalled(count = 2) override fun onCreditCardSave( session: GeckoSession, request: AutocompleteRequest<CreditCardSaveOption>) : GeckoResult<PromptDelegate.PromptResponse>? { assertThat("Session should not be null", session, notNullValue()) val option = request.options[0] val cc = option.value assertThat("Credit card should not be null", cc, notNullValue()) assertThat( "Credit card name should match", cc.name, equalTo(ccName)) assertThat( "Credit card number should match", cc.number, equalTo(forEachCall(ccNumber1, ccNumber2))) assertThat( "Credit card expiration month should match", cc.expirationMonth, equalTo(forEachCall(ccExpMonth1, ccExpMonth2))) assertThat( "Credit card expiration year should match", cc.expirationYear, equalTo(forEachCall(ccExpYear1, ccExpYear2))) return GeckoResult.fromValue(request.confirm(option)) } }) // Enter the card values mainSession.evaluateJS("document.querySelector('#name').focus()") mainSession.evaluateJS("document.querySelector('#name').value = '${ccName}'") mainSession.evaluateJS("document.querySelector('#number').value = '${ccNumber1}'") mainSession.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth1}'") mainSession.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear1}'") // Submit the form mainSession.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled1) // Update credit card val session2 = sessionRule.createOpenSession() session2.loadTestPath(CC_FORM_HTML_PATH) session2.waitForPageStop() session2.evaluateJS("document.querySelector('#name').focus()") session2.evaluateJS("document.querySelector('#name').value = '${ccName}'") session2.evaluateJS("document.querySelector('#number').value = '${ccNumber2}'") session2.evaluateJS("document.querySelector('#expMonth').value = '${ccExpMonth2}'") session2.evaluateJS("document.querySelector('#expYear').value = '${ccExpYear2}'") session2.evaluateJS("document.querySelector('form').submit()") sessionRule.waitForResult(saveHandled2) } fun testLoginUsed(autofillEnabled: Boolean) { sessionRule.setPrefsUntilTestEnd(mapOf( // Enable login management since it's disabled in automation. Loading
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/Autocomplete.java +60 −2 Original line number Diff line number Diff line Loading @@ -210,8 +210,12 @@ public class Autocomplete { bundle.putString(GUID_KEY, guid); bundle.putString(NAME_KEY, name); bundle.putString(NUMBER_KEY, number); if (expirationMonth != null) { bundle.putString(EXP_MONTH_KEY, expirationMonth); } if (expirationYear != null) { bundle.putString(EXP_YEAR_KEY, expirationYear); } return bundle; } Loading Loading @@ -949,6 +953,17 @@ public class Autocomplete { @UiThread default void onLoginSave(@NonNull final LoginEntry login) {} /** * Request saving or updating of the given credit card entry. * This is triggered by confirming a * {@link GeckoSession.PromptDelegate#onCreditCardSave onCreditCardSave} request. * * @param creditCard The {@link CreditCard} as confirmed by the prompt * request. */ @UiThread default void onCreditCardSave(@NonNull CreditCard creditCard) {} /** * Request saving or updating of the given address entry. * This is triggered by confirming a Loading Loading @@ -1174,6 +1189,40 @@ public class Autocomplete { } } /** * Holds information required to process credit card saving requests. */ public static class CreditCardSaveOption extends SaveOption<CreditCard> { /** * Construct a credit card save option. * * @param value The {@link CreditCard} credit card entry to be saved. * @param hint The {@link Hint} detailing the type of the option. */ /* package */ CreditCardSaveOption( final @NonNull CreditCard value, final @SaveOptionHint int hint) { super(value, hint); } /** * Construct a credit card save option. * * @param value The {@link CreditCard} credit card entry to be saved. */ public CreditCardSaveOption(final @NonNull CreditCard value) { this(value, Hint.NONE); } @Override /* package */ @NonNull GeckoBundle toBundle() { final GeckoBundle bundle = new GeckoBundle(2); bundle.putBundle(VALUE_KEY, value.toBundle()); bundle.putInt(HINT_KEY, hint); return bundle; } } /** * Holds information required to process login selection requests. */ Loading Loading @@ -1349,6 +1398,8 @@ public class Autocomplete { "GeckoView:Autocomplete:Fetch:Address"; private static final String SAVE_LOGIN_EVENT = "GeckoView:Autocomplete:Save:Login"; private static final String SAVE_CREDIT_CARD_EVENT = "GeckoView:Autocomplete:Save:CreditCard"; private static final String SAVE_ADDRESS_EVENT = "GeckoView:Autocomplete:Save:Address"; private static final String USED_LOGIN_EVENT = Loading @@ -1365,6 +1416,7 @@ public class Autocomplete { FETCH_CREDIT_CARD_EVENT, FETCH_ADDRESS_EVENT, SAVE_LOGIN_EVENT, SAVE_CREDIT_CARD_EVENT, SAVE_ADDRESS_EVENT, USED_LOGIN_EVENT); } Loading @@ -1376,6 +1428,7 @@ public class Autocomplete { FETCH_CREDIT_CARD_EVENT, FETCH_ADDRESS_EVENT, SAVE_LOGIN_EVENT, SAVE_CREDIT_CARD_EVENT, SAVE_ADDRESS_EVENT, USED_LOGIN_EVENT); } Loading Loading @@ -1491,6 +1544,11 @@ public class Autocomplete { final LoginEntry login = new LoginEntry(loginBundle); mDelegate.onLoginSave(login); } else if (SAVE_CREDIT_CARD_EVENT.equals(event)) { final GeckoBundle creditCardBundle = message.getBundle("creditCard"); final CreditCard creditCard = new CreditCard(creditCardBundle); mDelegate.onCreditCardSave(creditCard); } else if (SAVE_ADDRESS_EVENT.equals(event)) { final GeckoBundle addressBundle = message.getBundle("address"); final Address address = new Address(addressBundle); Loading
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +29 −0 Original line number Diff line number Diff line Loading @@ -4994,6 +4994,35 @@ public class GeckoSession { return null; } /** * Handle a credit card save prompt request. * This is triggered by the user entering new or modified credit card * credentials into a form. * * @param session The {@link GeckoSession} that triggered the request. * @param request The {@link AutocompleteRequest} containing the request * details. * * @return A {@link GeckoResult} resolving to a {@link PromptResponse}. * * Confirm the request with an {@link Autocomplete.Option} * to trigger a * {@link Autocomplete.StorageDelegate#onCreditCardSave} 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 credit * card entry. * * Dismiss the request to deny the saving request. */ @UiThread default @Nullable GeckoResult<PromptResponse> onCreditCardSave( @NonNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.CreditCardSaveOption> request) { return null; } /** * Handle a login selection prompt request. * This is triggered by the user focusing on a login username field. Loading
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PromptController.java +37 −0 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ import org.mozilla.gecko.util.GeckoBundle; import org.mozilla.geckoview.Autocomplete.AddressSaveOption; import org.mozilla.geckoview.Autocomplete.AddressSelectOption; import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption; import org.mozilla.geckoview.Autocomplete.CreditCardSaveOption; import org.mozilla.geckoview.Autocomplete.LoginSaveOption; import org.mozilla.geckoview.Autocomplete.LoginSelectOption; import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest; Loading Loading @@ -451,6 +452,41 @@ import java.util.Map; } } private static final class CreditCardSaveHandler implements PromptHandler<AutocompleteRequest<CreditCardSaveOption>> { @Override public AutocompleteRequest<CreditCardSaveOption> newPrompt( final GeckoBundle info, final Observer observer) { final int hint = info.getInt("hint"); final GeckoBundle[] creditCardBundles = info.getBundleArray("creditCards"); if (creditCardBundles == null) { return null; } final Autocomplete.CreditCardSaveOption[] options = new Autocomplete.CreditCardSaveOption[creditCardBundles.length]; for (int i = 0; i < options.length; ++i) { options[i] = new Autocomplete.CreditCardSaveOption( new Autocomplete.CreditCard(creditCardBundles[i]), hint); } return new PromptDelegate.AutocompleteRequest<>(info.getString("id"), options, observer); } @Override public GeckoResult<PromptResponse> callDelegate( final AutocompleteRequest<CreditCardSaveOption> prompt, final GeckoSession session, final PromptDelegate delegate) { return delegate.onCreditCardSave(session, prompt); } } private static final class AddressSaveHandler implements PromptHandler<AutocompleteRequest<AddressSaveOption>> { @Override Loading Loading @@ -612,6 +648,7 @@ import java.util.Map; sPromptHandlers.register(new RepostHandler(), "repost"); sPromptHandlers.register(new ShareHandler(), "share"); sPromptHandlers.register(new LoginSaveHandler(), "Autocomplete:Save:Login"); sPromptHandlers.register(new CreditCardSaveHandler(), "Autocomplete:Save:CreditCard"); sPromptHandlers.register(new AddressSaveHandler(), "Autocomplete:Save:Address"); sPromptHandlers.register(new LoginSelectHandler(), Loading