Commit de738da4 authored by owlishDeveloper's avatar owlishDeveloper
Browse files

Bug 1703976 - [1.0] Extend Autocomplete API to support credit card saving r=geckoview-reviewers,agi

parent 56ed4f6c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ class LoginStorageDelegate {
      hint |= LoginStorageHint.GENERATED;
    }
    return {
      // Sync with GeckoSession.handlePromptEvent.
      // Sync with PromptController
      type: "Autocomplete:Save:Login",
      hint,
      logins: aLogins,
+339 −2
Original line number Diff line number Diff line
@@ -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
@@ -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()
@@ -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 {
@@ -341,7 +345,6 @@ class AutocompleteTest : BaseSessionTest() {
        checkAddressesForCorrectness(savedAddresses.toTypedArray(), savedAddress)
    }


    @Test
    fun addressSelectAndFillMultipleAddresses() {
        val givenNames = arrayOf("Peter", "Wade")
@@ -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.
+60 −2
Original line number Diff line number Diff line
@@ -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;
        }
@@ -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
@@ -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.
     */
@@ -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 =
@@ -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);
        }
@@ -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);
        }
@@ -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);
+29 −0
Original line number Diff line number Diff line
@@ -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.
+37 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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