<lambda>null1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2  * Any copyright is dedicated to the Public Domain.
3    http://creativecommons.org/publicdomain/zero/1.0/ */
4 
5 package org.mozilla.geckoview.test
6 
7 import androidx.test.filters.MediumTest
8 import androidx.test.ext.junit.runners.AndroidJUnit4
9 
10 import android.os.Handler
11 import android.os.Looper
12 import android.view.KeyEvent
13 
14 import org.hamcrest.Matchers.*
15 
16 import org.junit.Test
17 import org.junit.runner.RunWith
18 
19 import org.mozilla.geckoview.GeckoResult
20 import org.mozilla.geckoview.GeckoSession
21 import org.mozilla.geckoview.GeckoSession.PromptDelegate
22 import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest
23 import org.mozilla.geckoview.Autocomplete.Address
24 import org.mozilla.geckoview.Autocomplete.AddressSelectOption
25 import org.mozilla.geckoview.Autocomplete.CreditCard
26 import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption
27 import org.mozilla.geckoview.Autocomplete.LoginEntry
28 import org.mozilla.geckoview.Autocomplete.LoginSaveOption
29 import org.mozilla.geckoview.Autocomplete.LoginSelectOption
30 import org.mozilla.geckoview.Autocomplete.SelectOption
31 import org.mozilla.geckoview.Autocomplete.StorageDelegate
32 import org.mozilla.geckoview.Autocomplete.UsedField
33 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
34 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
35 import org.mozilla.geckoview.test.util.Callbacks
36 
37 
38 @RunWith(AndroidJUnit4::class)
39 @MediumTest
40 class AutocompleteTest : BaseSessionTest() {
41     val acceptDelay: Long = 100
42 
43     @Test
44     fun fetchLogins() {
45         sessionRule.setPrefsUntilTestEnd(mapOf(
46                 // Enable login management since it's disabled in automation.
47                 "signon.rememberSignons" to true,
48                 "signon.autofillForms.http" to true))
49 
50         val runtime = sessionRule.runtime
51         val register = { delegate: StorageDelegate ->
52             runtime.autocompleteStorageDelegate = delegate
53         }
54         val unregister = { _: StorageDelegate ->
55             runtime.autocompleteStorageDelegate = null
56         }
57 
58         val fetchHandled = GeckoResult<Void>()
59 
60         sessionRule.addExternalDelegateDuringNextWait(
61                 StorageDelegate::class, register, unregister,
62                 object : StorageDelegate {
63             @AssertCalled(count = 1)
64             override fun onLoginFetch(domain: String)
65                     : GeckoResult<Array<LoginEntry>>? {
66                 assertThat("Domain should match", domain, equalTo("localhost"))
67 
68                 Handler(Looper.getMainLooper()).postDelayed({
69                     fetchHandled.complete(null)
70                 }, acceptDelay)
71 
72                 return null
73             }
74         })
75 
76         mainSession.loadTestPath(FORMS3_HTML_PATH)
77         sessionRule.waitForResult(fetchHandled)
78     }
79 
80     @Test
81     fun fetchCreditCards() {
82         val runtime = sessionRule.runtime
83         val register = { delegate: StorageDelegate ->
84             runtime.autocompleteStorageDelegate = delegate
85         }
86         val unregister = { _: StorageDelegate ->
87             runtime.autocompleteStorageDelegate = null
88         }
89 
90         val fetchHandled = GeckoResult<Void>()
91 
92         mainSession.loadTestPath(CC_FORM_HTML_PATH)
93         mainSession.waitForPageStop()
94 
95         sessionRule.addExternalDelegateDuringNextWait(
96                 StorageDelegate::class, register, unregister,
97                 object : StorageDelegate {
98             @AssertCalled(count = 1)
99             override fun onCreditCardFetch()
100                     : GeckoResult<Array<CreditCard>>? {
101                 Handler(Looper.getMainLooper()).postDelayed({
102                     fetchHandled.complete(null)
103                 }, acceptDelay)
104 
105                 return null
106             }
107         })
108 
109         mainSession.evaluateJS("document.querySelector('#name').focus()")
110         sessionRule.waitForResult(fetchHandled)
111     }
112 
113     @Test
114     fun creditCardSelectAndFill() {
115         // Test:
116         // 1. Load a credit card form page.
117         // 2. Focus on the name input field.
118         //    a. Ensure onCreditCardFetch is called.
119         //    b. Return the saved entries.
120         //    c. Ensure onCreditCardSelect is called.
121         //    d. Select and return one of the options.
122         //    e. Ensure the form is filled accordingly.
123 
124         val runtime = sessionRule.runtime
125         val register = { delegate: StorageDelegate ->
126             runtime.autocompleteStorageDelegate = delegate
127         }
128         val unregister = { _: StorageDelegate ->
129             runtime.autocompleteStorageDelegate = null
130         }
131 
132         val name = arrayOf("Peter Parker", "John Doe")
133         val number = arrayOf("1234-1234-1234-1234", "2345-2345-2345-2345")
134         val guid = arrayOf("test-guid1", "test-guid2")
135         val expMonth = arrayOf("Apr", "Aug")
136         val expYear = arrayOf("22", "23")
137         val savedCC = arrayOf(
138           CreditCard.Builder()
139                 .guid(guid[0])
140                 .name(name[0])
141                 .number(number[0])
142                 .expirationMonth(expMonth[0])
143                 .expirationYear(expYear[0])
144                 .build(),
145           CreditCard.Builder()
146                 .guid(guid[1])
147                 .name(name[1])
148                 .number(number[1])
149                 .expirationMonth(expMonth[1])
150                 .expirationYear(expYear[1])
151                 .build())
152 
153         val selectHandled = GeckoResult<Void>()
154 
155         mainSession.loadTestPath(CC_FORM_HTML_PATH)
156         mainSession.waitForPageStop()
157 
158         sessionRule.addExternalDelegateDuringNextWait(
159                 StorageDelegate::class, register, unregister,
160                 object : StorageDelegate {
161             @AssertCalled
162             override fun onCreditCardFetch()
163                     : GeckoResult<Array<CreditCard>>? {
164                 return GeckoResult.fromValue(savedCC)
165             }
166         })
167 
168         mainSession.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
169             @AssertCalled(count = 1)
170             override fun onCreditCardSelect(
171                     session: GeckoSession,
172                     prompt: AutocompleteRequest<CreditCardSelectOption>)
173                     : GeckoResult<PromptDelegate.PromptResponse>? {
174                 assertThat("Session should not be null", session, notNullValue())
175 
176                 assertThat(
177                     "There should be two options",
178                     prompt.options.size,
179                     equalTo(2))
180 
181                 for (i in 0..1) {
182                     val creditCard = prompt.options[i].value
183 
184                     assertThat("Credit card should not be null", creditCard, notNullValue())
185                     assertThat(
186                         "Name should match",
187                         creditCard.name,
188                         equalTo(name[i]))
189                     assertThat(
190                         "Number should match",
191                         creditCard.number,
192                         equalTo(number[i]))
193                 }
194                 Handler(Looper.getMainLooper()).postDelayed({
195                     selectHandled.complete(null)
196                 }, acceptDelay)
197 
198                 return GeckoResult.fromValue(prompt.confirm(prompt.options[0]))
199             }
200         })
201 
202         // Focus on the name input field.
203         mainSession.evaluateJS("document.querySelector('#name').focus()")
204         sessionRule.waitForResult(selectHandled)
205 
206         assertThat(
207             "Filled name should match",
208             mainSession.evaluateJS("document.querySelector('#name').value") as String,
209             equalTo(name[0]))
210         assertThat(
211             "Filled number should match",
212             mainSession.evaluateJS("document.querySelector('#number').value") as String,
213             equalTo(number[0]))
214         assertThat(
215             "Filled expiration month should match",
216             mainSession.evaluateJS("document.querySelector('#expMonth').value") as String,
217             equalTo(expMonth[0]))
218         assertThat(
219             "Filled expiration year should match",
220             mainSession.evaluateJS("document.querySelector('#expYear').value") as String,
221             equalTo(expYear[0]))
222     }
223 
224     @Test
225     fun fetchAddresses() {
226         val runtime = sessionRule.runtime
227         val register = { delegate: StorageDelegate ->
228             runtime.autocompleteStorageDelegate = delegate
229         }
230         val unregister = { _: StorageDelegate ->
231             runtime.autocompleteStorageDelegate = null
232         }
233 
234         val fetchHandled = GeckoResult<Void>()
235 
236         sessionRule.addExternalDelegateUntilTestEnd(
237                 StorageDelegate::class, register, unregister,
238                 object : StorageDelegate {
239                     @AssertCalled(count = 1)
240                     override fun onAddressFetch()
241                             : GeckoResult<Array<Address>>? {
242                         Handler(Looper.getMainLooper()).postDelayed({
243                             fetchHandled.complete(null)
244                         }, acceptDelay)
245 
246                         return null
247                     }
248                 })
249 
250         mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
251         mainSession.waitForPageStop()
252         mainSession.evaluateJS("document.querySelector('#name').focus()")
253         sessionRule.waitForResult(fetchHandled)
254     }
255 
256     fun checkAddressesForCorrectness(savedAddresses: Array<Address>, selectedAddress: Address) {
257         // Test:
258         // 1. Load an address form page.
259         // 2. Focus on the given name input field.
260         //    a. Ensure onAddressFetch is called.
261         //    b. Return the saved entries.
262         //    c. Ensure onAddressSelect is called.
263         //    d. Select and return one of the options.
264         //    e. Ensure the form is filled accordingly.
265 
266         val runtime = sessionRule.runtime
267         val register = { delegate: StorageDelegate ->
268             runtime.autocompleteStorageDelegate = delegate
269         }
270         val unregister = { _: StorageDelegate ->
271             runtime.autocompleteStorageDelegate = null
272         }
273 
274         val selectHandled = GeckoResult<Void>()
275 
276         sessionRule.addExternalDelegateUntilTestEnd(
277                 StorageDelegate::class, register, unregister,
278                 object : StorageDelegate {
279                     @AssertCalled
280                     override fun onAddressFetch()
281                             : GeckoResult<Array<Address>>? {
282                         return GeckoResult.fromValue(savedAddresses)
283                     }
284 
285                     @AssertCalled(false)
286                     override fun onAddressSave(address: Address) {}
287                 })
288 
289         mainSession.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
290             @AssertCalled(count = 1)
291             override fun onAddressSelect(
292                     session: GeckoSession,
293                     prompt: AutocompleteRequest<AddressSelectOption>)
294                     : GeckoResult<PromptDelegate.PromptResponse>? {
295                 assertThat("Session should not be null", session, notNullValue())
296 
297                 assertThat(
298                         "There should be one option",
299                         prompt.options.size,
300                         equalTo(savedAddresses.size))
301 
302                 val addressOption = prompt.options.find { it.value.familyName == selectedAddress.familyName }
303                 val address = addressOption?.value
304 
305                 assertThat("Address should not be null", address, notNullValue())
306                 assertThat(
307                         "Given name should match",
308                         address?.givenName,
309                         equalTo(selectedAddress.givenName))
310                 assertThat(
311                         "Family name should match",
312                         address?.familyName,
313                         equalTo(selectedAddress.familyName))
314                 assertThat(
315                         "Street address should match",
316                         address?.streetAddress,
317                         equalTo(selectedAddress.streetAddress))
318 
319                 Handler(Looper.getMainLooper()).postDelayed({
320                     selectHandled.complete(null)
321                 }, acceptDelay)
322 
323                 return GeckoResult.fromValue(prompt.confirm(addressOption!!))
324             }
325         })
326 
327         mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
328         mainSession.waitForPageStop()
329 
330         // Focus on the given name input field.
331         mainSession.evaluateJS("document.querySelector('#givenName').focus()")
332         sessionRule.waitForResult(selectHandled)
333 
334         assertThat(
335                 "Filled given name should match",
336                 mainSession.evaluateJS("document.querySelector('#givenName').value") as String,
337                 equalTo(selectedAddress.givenName))
338         assertThat(
339                 "Filled family name should match",
340                 mainSession.evaluateJS("document.querySelector('#familyName').value") as String,
341                 equalTo(selectedAddress.familyName))
342         assertThat(
343                 "Filled street address should match",
344                 mainSession.evaluateJS("document.querySelector('#streetAddress').value") as String,
345                 equalTo(selectedAddress.streetAddress))
346         assertThat(
347                 "Filled country should match",
348                 mainSession.evaluateJS("document.querySelector('#country').value") as String,
349                 equalTo(selectedAddress.country))
350         assertThat(
351                 "Filled postal code should match",
352                 mainSession.evaluateJS("document.querySelector('#postalCode').value") as String,
353                 equalTo(selectedAddress.postalCode))
354         assertThat(
355                 "Filled email should match",
356                 mainSession.evaluateJS("document.querySelector('#email').value") as String,
357                 equalTo(selectedAddress.email))
358         assertThat(
359                 "Filled telephone number should match",
360                 mainSession.evaluateJS("document.querySelector('#tel').value") as String,
361                 equalTo(selectedAddress.tel))
362         assertThat(
363                 "Filled organization should match",
364                 mainSession.evaluateJS("document.querySelector('#organization').value") as String,
365                 equalTo(selectedAddress.organization))
366     }
367 
368     @Test
369     fun addressSelectAndFill() {
370         val givenName = "Peter"
371         val familyName = "Parker"
372         val streetAddress = "20 Ingram Street, Forest Hills Gardens, Queens"
373         val postalCode = "11375"
374         val country = "US"
375         val email = "spiderman@newyork.com"
376         val tel = "+1 180090021"
377         val organization = ""
378         val guid = "test-guid"
379         val savedAddress = Address.Builder()
380                 .guid(guid)
381                 .givenName(givenName)
382                 .familyName(familyName)
383                 .streetAddress(streetAddress)
384                 .postalCode(postalCode)
385                 .country(country)
386                 .email(email)
387                 .tel(tel)
388                 .organization(organization)
389                 .build()
390         val savedAddresses = mutableListOf<Address>(savedAddress)
391 
392         checkAddressesForCorrectness(savedAddresses.toTypedArray(), savedAddress)
393     }
394 
395 
396     @Test
397     fun addressSelectAndFillMultipleAddresses() {
398         val givenNames = arrayOf("Peter", "Wade")
399         val familyNames = arrayOf("Parker", "Wilson")
400         val streetAddresses = arrayOf("20 Ingram Street, Forest Hills Gardens, Queens", "890 Fifth Avenue, Manhattan")
401         val postalCodes = arrayOf("11375", "10110")
402         val countries = arrayOf("US", "US")
403         val emails = arrayOf("spiderman@newyork.com", "deadpool@newyork.com")
404         val tels = arrayOf("+1 180090021", "+1 180055555")
405         val organizations = arrayOf("", "")
406         val guids = arrayOf("test-guid-1", "test-guid-2")
407         val selectedAddress = Address.Builder()
408                 .guid(guids[1])
409                 .givenName(givenNames[1])
410                 .familyName(familyNames[1])
411                 .streetAddress(streetAddresses[1])
412                 .postalCode(postalCodes[1])
413                 .country(countries[1])
414                 .email(emails[1])
415                 .tel(tels[1])
416                 .organization(organizations[1])
417                 .build()
418         val savedAddresses = mutableListOf<Address>(
419                 Address.Builder()
420                         .guid(guids[0])
421                         .givenName(givenNames[0])
422                         .familyName(familyNames[0])
423                         .streetAddress(streetAddresses[0])
424                         .postalCode(postalCodes[0])
425                         .country(countries[0])
426                         .email(emails[0])
427                         .tel(tels[0])
428                         .organization(organizations[0])
429                         .build(),
430                 selectedAddress
431         )
432 
433         checkAddressesForCorrectness(savedAddresses.toTypedArray(), selectedAddress)
434     }
435 
436     @Test
437     fun loginSaveDismiss() {
438         sessionRule.setPrefsUntilTestEnd(mapOf(
439                 // Enable login management since it's disabled in automation.
440                 "signon.rememberSignons" to true,
441                 "signon.autofillForms.http" to true,
442                 "signon.userInputRequiredToCapture.enabled" to false))
443 
444         val runtime = sessionRule.runtime
445         val register = { delegate: StorageDelegate ->
446             runtime.autocompleteStorageDelegate = delegate
447         }
448         val unregister = { _: StorageDelegate ->
449             runtime.autocompleteStorageDelegate = null
450         }
451 
452         sessionRule.addExternalDelegateDuringNextWait(
453                 StorageDelegate::class, register, unregister,
454                 object : StorageDelegate {
455             @AssertCalled(count = 1)
456             override fun onLoginFetch(domain: String)
457                     : GeckoResult<Array<LoginEntry>>? {
458                 assertThat("Domain should match", domain, equalTo("localhost"))
459 
460                 return null
461             }
462         })
463 
464         mainSession.loadTestPath(FORMS3_HTML_PATH)
465         mainSession.waitForPageStop()
466 
467         sessionRule.addExternalDelegateUntilTestEnd(
468                 StorageDelegate::class, register, unregister,
469                 object : StorageDelegate {
470             @AssertCalled(count = 0)
471             override fun onLoginSave(login: LoginEntry) {}
472         })
473 
474         // Assign login credentials.
475         mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
476         mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
477 
478         // Submit the form.
479         mainSession.evaluateJS("document.querySelector('#form1').submit()")
480 
481         sessionRule.waitUntilCalled(object : Callbacks.PromptDelegate {
482             @AssertCalled(count = 1)
483             override fun onLoginSave(
484                     session: GeckoSession,
485                     prompt: AutocompleteRequest<LoginSaveOption>)
486                     : GeckoResult<PromptDelegate.PromptResponse>? {
487                 val option = prompt.options[0]
488                 val login = option.value
489 
490                 assertThat("Session should not be null", session, notNullValue())
491                 assertThat("Login should not be null", login, notNullValue())
492                 assertThat(
493                     "Username should match",
494                     login.username,
495                     equalTo("user1x"))
496 
497                 assertThat(
498                     "Password should match",
499                     login.password,
500                     equalTo("pass1x"))
501 
502                 return GeckoResult.fromValue(prompt.dismiss())
503             }
504         })
505     }
506 
507     @Test
508     fun loginSaveAccept() {
509         sessionRule.setPrefsUntilTestEnd(mapOf(
510                 // Enable login management since it's disabled in automation.
511                 "signon.rememberSignons" to true,
512                 "signon.autofillForms.http" to true,
513                 "signon.userInputRequiredToCapture.enabled" to false))
514 
515         val runtime = sessionRule.runtime
516         val register = { delegate: StorageDelegate ->
517             runtime.autocompleteStorageDelegate = delegate
518         }
519         val unregister = { _: StorageDelegate ->
520             runtime.autocompleteStorageDelegate = null
521         }
522 
523         mainSession.loadTestPath(FORMS3_HTML_PATH)
524         mainSession.waitForPageStop()
525 
526         val saveHandled = GeckoResult<Void>()
527 
528         sessionRule.addExternalDelegateUntilTestEnd(
529                 StorageDelegate::class, register, unregister,
530                 object : StorageDelegate {
531             @AssertCalled
532             override fun onLoginSave(login: LoginEntry) {
533                 assertThat(
534                     "Username should match",
535                     login.username,
536                     equalTo("user1x"))
537 
538                 assertThat(
539                     "Password should match",
540                     login.password,
541                     equalTo("pass1x"))
542 
543                 saveHandled.complete(null)
544             }
545         })
546 
547         sessionRule.delegateDuringNextWait(object : Callbacks.PromptDelegate {
548             @AssertCalled(count = 1)
549             override fun onLoginSave(
550                     session: GeckoSession,
551                     prompt: AutocompleteRequest<LoginSaveOption>)
552                     : GeckoResult<PromptDelegate.PromptResponse>? {
553                 assertThat("Session should not be null", session, notNullValue())
554 
555                 val option = prompt.options[0]
556                 val login = option.value
557 
558                 assertThat("Login should not be null", login, notNullValue())
559 
560                 assertThat(
561                     "Username should match",
562                     login.username,
563                     equalTo("user1x"))
564 
565                 assertThat(
566                     "Password should match",
567                     login.password,
568                     equalTo("pass1x"))
569 
570                 return GeckoResult.fromValue(prompt.confirm(option))
571             }
572         })
573 
574         // Assign login credentials.
575         mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
576         mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
577 
578         // Submit the form.
579         mainSession.evaluateJS("document.querySelector('#form1').submit()")
580 
581         sessionRule.waitForResult(saveHandled)
582     }
583 
584     @Test
585     fun loginSaveModifyAccept() {
586         sessionRule.setPrefsUntilTestEnd(mapOf(
587                 // Enable login management since it's disabled in automation.
588                 "signon.rememberSignons" to true,
589                 "signon.autofillForms.http" to true,
590                 "signon.userInputRequiredToCapture.enabled" to false))
591 
592         val runtime = sessionRule.runtime
593         val register = { delegate: StorageDelegate ->
594             runtime.autocompleteStorageDelegate = delegate
595         }
596         val unregister = { _: StorageDelegate ->
597             runtime.autocompleteStorageDelegate = null
598         }
599 
600         mainSession.loadTestPath(FORMS3_HTML_PATH)
601         mainSession.waitForPageStop()
602 
603         val saveHandled = GeckoResult<Void>()
604 
605         sessionRule.addExternalDelegateUntilTestEnd(
606                 StorageDelegate::class, register, unregister,
607                 object : StorageDelegate {
608             @AssertCalled
609             override fun onLoginSave(login: LoginEntry) {
610                 assertThat(
611                     "Username should match",
612                     login.username,
613                     equalTo("user1x"))
614 
615                 assertThat(
616                     "Password should match",
617                     login.password,
618                     equalTo("pass1xmod"))
619 
620                 saveHandled.complete(null)
621             }
622         })
623 
624         sessionRule.delegateDuringNextWait(object : Callbacks.PromptDelegate {
625             @AssertCalled(count = 1)
626             override fun onLoginSave(
627                     session: GeckoSession,
628                     prompt: AutocompleteRequest<LoginSaveOption>)
629                     : GeckoResult<PromptDelegate.PromptResponse>? {
630                 assertThat("Session should not be null", session, notNullValue())
631 
632                 val option = prompt.options[0]
633                 val login = option.value
634 
635                 assertThat("Login should not be null", login, notNullValue())
636 
637                 assertThat(
638                     "Username should match",
639                     login.username,
640                     equalTo("user1x"))
641 
642                 assertThat(
643                     "Password should match",
644                     login.password,
645                     equalTo("pass1x"))
646 
647                 val modLogin = LoginEntry.Builder()
648                         .origin(login.origin)
649                         .formActionOrigin(login.origin)
650                         .httpRealm(login.httpRealm)
651                         .username(login.username)
652                         .password("pass1xmod")
653                         .build()
654 
655                 return GeckoResult.fromValue(prompt.confirm(LoginSaveOption(modLogin)))
656             }
657         })
658 
659         // Assign login credentials.
660         mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
661         mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
662 
663         // Submit the form.
664         mainSession.evaluateJS("document.querySelector('#form1').submit()")
665 
666         sessionRule.waitForResult(saveHandled)
667     }
668 
669     @Test
670     fun loginUpdateAccept() {
671         sessionRule.setPrefsUntilTestEnd(mapOf(
672                 // Enable login management since it's disabled in automation.
673                 "signon.rememberSignons" to true,
674                 "signon.autofillForms.http" to true,
675                 "signon.userInputRequiredToCapture.enabled" to false))
676 
677         val runtime = sessionRule.runtime
678         val register = { delegate: StorageDelegate ->
679             runtime.autocompleteStorageDelegate = delegate
680         }
681         val unregister = { _: StorageDelegate ->
682             runtime.autocompleteStorageDelegate = null
683         }
684 
685         val saveHandled = GeckoResult<Void>()
686         val saveHandled2 = GeckoResult<Void>()
687 
688         val user1 = "user1x"
689         val pass1 = "pass1x"
690         val pass2 = "pass1up"
691         val guid = "test-guid"
692         val savedLogins = mutableListOf<LoginEntry>()
693 
694         sessionRule.addExternalDelegateUntilTestEnd(
695                 StorageDelegate::class, register, unregister,
696                 object : StorageDelegate {
697             @AssertCalled
698             override fun onLoginFetch(domain: String)
699                     : GeckoResult<Array<LoginEntry>>? {
700                 assertThat("Domain should match", domain, equalTo("localhost"))
701 
702                 return GeckoResult.fromValue(savedLogins.toTypedArray())
703             }
704 
705             @AssertCalled(count = 2)
706             override fun onLoginSave(login: LoginEntry) {
707                 assertThat(
708                     "Username should match",
709                     login.username,
710                     equalTo(user1))
711 
712                 assertThat(
713                     "Password should match",
714                     login.password,
715                     equalTo(forEachCall(pass1, pass2)))
716 
717                 assertThat(
718                     "GUID should match",
719                     login.guid,
720                     equalTo(forEachCall(null, guid)))
721 
722                 val savedLogin = LoginEntry.Builder()
723                         .guid(guid)
724                         .origin(login.origin)
725                         .formActionOrigin(login.formActionOrigin)
726                         .username(login.username)
727                         .password(login.password)
728                         .build()
729 
730                 savedLogins.add(savedLogin)
731 
732                 if (sessionRule.currentCall.counter == 1) {
733                     saveHandled.complete(null)
734                 } else if (sessionRule.currentCall.counter == 2) {
735                     saveHandled2.complete(null)
736                 }
737             }
738         })
739 
740         sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
741             @AssertCalled(count = 2)
742             override fun onLoginSave(
743                     session: GeckoSession,
744                     prompt: AutocompleteRequest<LoginSaveOption>)
745                     : GeckoResult<PromptDelegate.PromptResponse>? {
746                 assertThat("Session should not be null", session, notNullValue())
747 
748                 val option = prompt.options[0]
749                 val login = option.value
750 
751                 assertThat("Login should not be null", login, notNullValue())
752 
753                 assertThat(
754                     "Username should match",
755                     login.username,
756                     equalTo(user1))
757 
758                 assertThat(
759                     "Password should match",
760                     login.password,
761                     equalTo(forEachCall(pass1, pass2)))
762 
763                 return GeckoResult.fromValue(prompt.confirm(option))
764             }
765         })
766 
767         // Assign login credentials.
768         mainSession.loadTestPath(FORMS3_HTML_PATH)
769         mainSession.waitForPageStop()
770         mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
771         mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
772         mainSession.evaluateJS("document.querySelector('#form1').submit()")
773 
774         sessionRule.waitForResult(saveHandled)
775 
776         // Update login credentials.
777         val session2 = sessionRule.createOpenSession()
778         session2.loadTestPath(FORMS3_HTML_PATH)
779         session2.waitForPageStop()
780         session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
781         session2.evaluateJS("document.querySelector('#form1').submit()")
782 
783         sessionRule.waitForResult(saveHandled2)
784     }
785 
786     fun testLoginUsed(autofillEnabled: Boolean) {
787         sessionRule.setPrefsUntilTestEnd(mapOf(
788                 // Enable login management since it's disabled in automation.
789                 "signon.rememberSignons" to true,
790                 "signon.autofillForms.http" to true,
791                 "signon.userInputRequiredToCapture.enabled" to false))
792 
793         val runtime = sessionRule.runtime
794         val register = { delegate: StorageDelegate ->
795             runtime.autocompleteStorageDelegate = delegate
796         }
797         val unregister = { _: StorageDelegate ->
798             runtime.autocompleteStorageDelegate = null
799         }
800 
801         val usedHandled = GeckoResult<Void>()
802 
803         val user1 = "user1x"
804         val pass1 = "pass1x"
805         val guid = "test-guid"
806         val origin = GeckoSessionTestRule.TEST_ENDPOINT
807         val savedLogin = LoginEntry.Builder()
808                 .guid(guid)
809                 .origin(origin)
810                 .formActionOrigin(origin)
811                 .username(user1)
812                 .password(pass1)
813                 .build()
814         val savedLogins = mutableListOf<LoginEntry>(savedLogin)
815 
816         if (autofillEnabled) {
817             sessionRule.addExternalDelegateUntilTestEnd(
818                     StorageDelegate::class, register, unregister,
819                     object : StorageDelegate {
820                 @AssertCalled
821                 override fun onLoginFetch(domain: String)
822                         : GeckoResult<Array<LoginEntry>>? {
823                     assertThat("Domain should match", domain, equalTo("localhost"))
824 
825                     return GeckoResult.fromValue(savedLogins.toTypedArray())
826                 }
827 
828                 @AssertCalled(count = 1)
829                 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {
830                     assertThat(
831                         "Used fields should match",
832                         usedFields,
833                         equalTo(UsedField.PASSWORD))
834 
835                     assertThat(
836                         "Username should match",
837                         login.username,
838                         equalTo(user1))
839 
840                     assertThat(
841                         "Password should match",
842                         login.password,
843                         equalTo(pass1))
844 
845                     assertThat(
846                         "GUID should match",
847                         login.guid,
848                         equalTo(guid))
849 
850                     usedHandled.complete(null)
851                 }
852             })
853         } else {
854             sessionRule.addExternalDelegateUntilTestEnd(
855                     StorageDelegate::class, register, unregister,
856                     object : StorageDelegate {
857                 @AssertCalled
858                 override fun onLoginFetch(domain: String)
859                         : GeckoResult<Array<LoginEntry>>? {
860                     assertThat("Domain should match", domain, equalTo("localhost"))
861 
862                     return GeckoResult.fromValue(savedLogins.toTypedArray())
863                 }
864 
865                 @AssertCalled(false)
866                 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
867             })
868         }
869 
870         sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
871             @AssertCalled(false)
872             override fun onLoginSave(
873                     session: GeckoSession,
874                     prompt: AutocompleteRequest<LoginSaveOption>)
875                     : GeckoResult<PromptDelegate.PromptResponse>? {
876                 return null
877             }
878         })
879 
880         mainSession.loadTestPath(FORMS3_HTML_PATH)
881         mainSession.waitForPageStop()
882         mainSession.evaluateJS("document.querySelector('#form1').submit()")
883 
884         if (autofillEnabled) {
885             sessionRule.waitForResult(usedHandled)
886         } else {
887             mainSession.waitForPageStop()
888         }
889     }
890 
891     @Test
892     fun loginUsed() {
893         testLoginUsed(true)
894     }
895 
896     @Test
897     fun loginAutofillDisabled() {
898         sessionRule.runtime.settings.loginAutofillEnabled = false
899         testLoginUsed(false)
900         sessionRule.runtime.settings.loginAutofillEnabled = true
901     }
902 
903     fun testPasswordAutofill(autofillEnabled: Boolean) {
904         sessionRule.setPrefsUntilTestEnd(mapOf(
905                 // Enable login management since it's disabled in automation.
906                 "signon.rememberSignons" to true,
907                 "signon.autofillForms.http" to true,
908                 "signon.userInputRequiredToCapture.enabled" to false))
909 
910         val runtime = sessionRule.runtime
911         val register = { delegate: StorageDelegate ->
912             runtime.autocompleteStorageDelegate = delegate
913         }
914         val unregister = { _: StorageDelegate ->
915             runtime.autocompleteStorageDelegate = null
916         }
917 
918         val user1 = "user1x"
919         val pass1 = "pass1x"
920         val guid = "test-guid"
921         val origin = GeckoSessionTestRule.TEST_ENDPOINT
922         val savedLogin = LoginEntry.Builder()
923                 .guid(guid)
924                 .origin(origin)
925                 .formActionOrigin(origin)
926                 .username(user1)
927                 .password(pass1)
928                 .build()
929         val savedLogins = mutableListOf<LoginEntry>(savedLogin)
930 
931         sessionRule.addExternalDelegateUntilTestEnd(
932                 StorageDelegate::class, register, unregister,
933                 object : StorageDelegate {
934             @AssertCalled
935             override fun onLoginFetch(domain: String)
936                     : GeckoResult<Array<LoginEntry>>? {
937                 assertThat("Domain should match", domain, equalTo("localhost"))
938 
939                 return GeckoResult.fromValue(savedLogins.toTypedArray())
940             }
941 
942             @AssertCalled(false)
943             override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
944         })
945 
946         sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
947             @AssertCalled(false)
948             override fun onLoginSave(
949                     session: GeckoSession,
950                     prompt: AutocompleteRequest<LoginSaveOption>)
951                     : GeckoResult<PromptDelegate.PromptResponse>? {
952                 return null
953             }
954         })
955 
956         mainSession.loadTestPath(FORMS3_HTML_PATH)
957         mainSession.waitForPageStop()
958         mainSession.evaluateJS("document.querySelector('#user1').focus()")
959         mainSession.evaluateJS(
960                 "document.querySelector('#user1').value = '$user1'")
961         mainSession.pressKey(KeyEvent.KEYCODE_TAB)
962 
963         val pass = mainSession.evaluateJS(
964             "document.querySelector('#pass1').value") as String
965 
966         if (autofillEnabled) {
967             assertThat(
968                 "Password should match",
969                 pass,
970                 equalTo(pass1))
971         } else {
972             assertThat(
973                 "Password should not be filled",
974                 pass,
975                 equalTo(""))
976         }
977     }
978 
979     @Test
980     fun loginAutofillDisabledPasswordAutofill() {
981         sessionRule.runtime.settings.loginAutofillEnabled = false
982         testPasswordAutofill(false)
983         sessionRule.runtime.settings.loginAutofillEnabled = true
984     }
985 
986     @Test
987     fun loginAutofillEnabledPasswordAutofill() {
988         testPasswordAutofill(true)
989     }
990 
991     @Test
992     fun loginSelectAccept() {
993         sessionRule.setPrefsUntilTestEnd(mapOf(
994                 // Enable login management since it's disabled in automation.
995                 "signon.rememberSignons" to true,
996                 "signon.autofillForms.http" to true,
997                 "dom.disable_open_during_load" to false,
998                 "signon.userInputRequiredToCapture.enabled" to false))
999 
1000         // Test:
1001         // 1. Load a login form page.
1002         // 2. Input un/pw and submit.
1003         //    a. Ensure onLoginSave is called accordingly.
1004         //    b. Save the submitted login entry.
1005         // 3. Reload the login form page.
1006         //    a. Ensure onLoginFetch is called.
1007         //    b. Return empty login entry list to avoid autofilling.
1008         // 4. Input a new set of un/pw and submit.
1009         //    a. Ensure onLoginSave is called again.
1010         //    b. Save the submitted login entry.
1011         // 5. Reload the login form page.
1012         // 6. Focus on the username input field.
1013         //    a. Ensure onLoginFetch is called.
1014         //    b. Return the saved login entries.
1015         //    c. Ensure onLoginSelect is called.
1016         //    d. Select and return one of the options.
1017         //    e. Submit the form.
1018         //    f. Ensure that onLoginUsed is called.
1019 
1020         val runtime = sessionRule.runtime
1021         val register = { delegate: StorageDelegate ->
1022             runtime.autocompleteStorageDelegate = delegate
1023         }
1024         val unregister = { _: StorageDelegate ->
1025             runtime.autocompleteStorageDelegate = null
1026         }
1027 
1028         val user1 = "user1x"
1029         val user2 = "user2x"
1030         val pass1 = "pass1x"
1031         val pass2 = "pass2x"
1032         val savedLogins = mutableListOf<LoginEntry>()
1033 
1034         val saveHandled1 = GeckoResult<Void>()
1035         val saveHandled2 = GeckoResult<Void>()
1036         val selectHandled = GeckoResult<Void>()
1037         val usedHandled = GeckoResult<Void>()
1038 
1039         sessionRule.addExternalDelegateUntilTestEnd(
1040                 StorageDelegate::class, register, unregister,
1041                 object : StorageDelegate {
1042             @AssertCalled
1043             override fun onLoginFetch(domain: String)
1044                     : GeckoResult<Array<LoginEntry>>? {
1045                 assertThat("Domain should match", domain, equalTo("localhost"))
1046 
1047                 var logins = mutableListOf<LoginEntry>()
1048 
1049                 if (savedLogins.size == 2) {
1050                     logins = savedLogins
1051                 }
1052 
1053                 return GeckoResult.fromValue(logins.toTypedArray())
1054             }
1055 
1056             @AssertCalled(count = 2)
1057             override fun onLoginSave(login: LoginEntry) {
1058                 var username = ""
1059                 var password = ""
1060                 var handle = GeckoResult<Void>()
1061 
1062                 if (sessionRule.currentCall.counter == 1) {
1063                     username = user1
1064                     password = pass1
1065                     handle = saveHandled1
1066                 } else if (sessionRule.currentCall.counter == 2) {
1067                     username = user2
1068                     password = pass2
1069                     handle = saveHandled2
1070                 }
1071 
1072                 val savedLogin = LoginEntry.Builder()
1073                         .guid(login.username)
1074                         .origin(login.origin)
1075                         .formActionOrigin(login.formActionOrigin)
1076                         .username(login.username)
1077                         .password(login.password)
1078                         .build()
1079 
1080                 savedLogins.add(savedLogin)
1081 
1082                 assertThat(
1083                     "Username should match",
1084                     login.username,
1085                     equalTo(username))
1086 
1087                 assertThat(
1088                     "Password should match",
1089                     login.password,
1090                     equalTo(password))
1091 
1092                 handle.complete(null)
1093             }
1094 
1095             @AssertCalled(count = 1)
1096             override fun onLoginUsed(login: LoginEntry, usedFields: Int) {
1097                 assertThat(
1098                     "Used fields should match",
1099                     usedFields,
1100                     equalTo(UsedField.PASSWORD))
1101 
1102                 assertThat(
1103                     "Username should match",
1104                     login.username,
1105                     equalTo(user1))
1106 
1107                 assertThat(
1108                     "Password should match",
1109                     login.password,
1110                     equalTo(pass1))
1111 
1112                 assertThat(
1113                     "GUID should match",
1114                     login.guid,
1115                     equalTo(user1))
1116 
1117                 usedHandled.complete(null)
1118             }
1119         })
1120 
1121         mainSession.loadTestPath(FORMS3_HTML_PATH)
1122         mainSession.waitForPageStop()
1123 
1124         mainSession.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1125             @AssertCalled(count = 1)
1126             override fun onLoginSave(
1127                     session: GeckoSession,
1128                     prompt: AutocompleteRequest<LoginSaveOption>)
1129                     : GeckoResult<PromptDelegate.PromptResponse>? {
1130                 assertThat("Session should not be null", session, notNullValue())
1131 
1132                 val option = prompt.options[0]
1133                 val login = option.value
1134 
1135                 assertThat("Login should not be null", login, notNullValue())
1136 
1137                 assertThat(
1138                     "Username should match",
1139                     login.username,
1140                     equalTo(user1))
1141 
1142                 assertThat(
1143                     "Password should match",
1144                     login.password,
1145                     equalTo(pass1))
1146 
1147                 return GeckoResult.fromValue(prompt.confirm(option))
1148             }
1149         })
1150 
1151         // Assign login credentials.
1152         mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
1153         mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
1154 
1155         // Submit the form.
1156         mainSession.evaluateJS("document.querySelector('#form1').submit()")
1157         sessionRule.waitForResult(saveHandled1)
1158 
1159         // Reload.
1160         val session2 = sessionRule.createOpenSession()
1161         session2.loadTestPath(FORMS3_HTML_PATH)
1162         session2.waitForPageStop()
1163 
1164         session2.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1165             @AssertCalled(count = 1)
1166             override fun onLoginSave(
1167                     session: GeckoSession,
1168                     prompt: AutocompleteRequest<LoginSaveOption>)
1169                     : GeckoResult<PromptDelegate.PromptResponse>? {
1170                 assertThat("Session should not be null", session, notNullValue())
1171 
1172                 val option = prompt.options[0]
1173                 val login = option.value
1174 
1175                 assertThat("Login should not be null", login, notNullValue())
1176 
1177                 assertThat(
1178                     "Username should match",
1179                     login.username,
1180                     equalTo(user2))
1181 
1182                 assertThat(
1183                     "Password should match",
1184                     login.password,
1185                     equalTo(pass2))
1186 
1187                 return GeckoResult.fromValue(prompt.confirm(option))
1188             }
1189         })
1190 
1191         // Assign alternative login credentials.
1192         session2.evaluateJS("document.querySelector('#user1').value = '$user2'")
1193         session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
1194 
1195         // Submit the form.
1196         session2.evaluateJS("document.querySelector('#form1').submit()")
1197         sessionRule.waitForResult(saveHandled2)
1198 
1199         // Reload for the last time.
1200         val session3 = sessionRule.createOpenSession()
1201 
1202         session3.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
1203             @AssertCalled(count = 1)
1204             override fun onLoginSelect(
1205                     session: GeckoSession,
1206                     prompt: AutocompleteRequest<LoginSelectOption>)
1207                     : GeckoResult<PromptDelegate.PromptResponse>? {
1208                 assertThat("Session should not be null", session, notNullValue())
1209 
1210                 assertThat(
1211                     "There should be two options",
1212                     prompt.options.size,
1213                     equalTo(2))
1214 
1215                 var usernames = arrayOf(user1, user2)
1216                 var passwords = arrayOf(pass1, pass2)
1217 
1218                 for (i in 0..1) {
1219                     val login = prompt.options[i].value
1220 
1221                     assertThat("Login should not be null", login, notNullValue())
1222                     assertThat(
1223                         "Username should match",
1224                         login.username,
1225                         equalTo(usernames[i]))
1226                     assertThat(
1227                         "Password should match",
1228                         login.password,
1229                         equalTo(passwords[i]))
1230                 }
1231 
1232 
1233                 Handler(Looper.getMainLooper()).postDelayed({
1234                     selectHandled.complete(null)
1235                 }, acceptDelay)
1236 
1237                 return GeckoResult.fromValue(prompt.confirm(prompt.options[0]))
1238             }
1239         })
1240 
1241         session3.loadTestPath(FORMS3_HTML_PATH)
1242         session3.waitForPageStop()
1243 
1244         // Focus on the username input field.
1245         session3.evaluateJS("document.querySelector('#user1').focus()")
1246         sessionRule.waitForResult(selectHandled)
1247 
1248         assertThat(
1249             "Filled username should match",
1250             session3.evaluateJS("document.querySelector('#user1').value") as String,
1251             equalTo(user1))
1252 
1253         assertThat(
1254             "Filled password should match",
1255             session3.evaluateJS("document.querySelector('#pass1').value") as String,
1256             equalTo(pass1))
1257 
1258         // Submit the selection.
1259         session3.evaluateJS("document.querySelector('#form1').submit()")
1260         sessionRule.waitForResult(usedHandled)
1261     }
1262 
1263     @Test
1264     fun loginSelectModifyAccept() {
1265         sessionRule.setPrefsUntilTestEnd(mapOf(
1266                 // Enable login management since it's disabled in automation.
1267                 "signon.rememberSignons" to true,
1268                 "signon.autofillForms.http" to true,
1269                 "dom.disable_open_during_load" to false,
1270                 "signon.userInputRequiredToCapture.enabled" to false))
1271 
1272         // Test:
1273         // 1. Load a login form page.
1274         // 2. Input un/pw and submit.
1275         //    a. Ensure onLoginSave is called accordingly.
1276         //    b. Save the submitted login entry.
1277         // 3. Reload the login form page.
1278         //    a. Ensure onLoginFetch is called.
1279         //    b. Return empty login entry list to avoid autofilling.
1280         // 4. Input a new set of un/pw and submit.
1281         //    a. Ensure onLoginSave is called again.
1282         //    b. Save the submitted login entry.
1283         // 5. Reload the login form page.
1284         // 6. Focus on the username input field.
1285         //    a. Ensure onLoginFetch is called.
1286         //    b. Return the saved login entries.
1287         //    c. Ensure onLoginSelect is called.
1288         //    d. Select and return a new login entry.
1289         //    e. Submit the form.
1290         //    f. Ensure that onLoginUsed is not called.
1291 
1292         val runtime = sessionRule.runtime
1293         val register = { delegate: StorageDelegate ->
1294             runtime.autocompleteStorageDelegate = delegate
1295         }
1296         val unregister = { _: StorageDelegate ->
1297             runtime.autocompleteStorageDelegate = null
1298         }
1299 
1300         val user1 = "user1x"
1301         val user2 = "user2x"
1302         val pass1 = "pass1x"
1303         val pass2 = "pass2x"
1304         val userMod = "user1xmod"
1305         val passMod = "pass1xmod"
1306         val savedLogins = mutableListOf<LoginEntry>()
1307 
1308         val saveHandled1 = GeckoResult<Void>()
1309         val saveHandled2 = GeckoResult<Void>()
1310         val selectHandled = GeckoResult<Void>()
1311 
1312         sessionRule.addExternalDelegateUntilTestEnd(
1313                 StorageDelegate::class, register, unregister,
1314                 object : StorageDelegate {
1315             @AssertCalled
1316             override fun onLoginFetch(domain: String)
1317                     : GeckoResult<Array<LoginEntry>>? {
1318                 assertThat("Domain should match", domain, equalTo("localhost"))
1319 
1320                 var logins = mutableListOf<LoginEntry>()
1321 
1322                 if (savedLogins.size == 2) {
1323                     logins = savedLogins
1324                 }
1325 
1326                 return GeckoResult.fromValue(logins.toTypedArray())
1327             }
1328 
1329             @AssertCalled(count = 2)
1330             override fun onLoginSave(login: LoginEntry) {
1331                 var username = ""
1332                 var password = ""
1333                 var handle = GeckoResult<Void>()
1334 
1335                 if (sessionRule.currentCall.counter == 1) {
1336                     username = user1
1337                     password = pass1
1338                     handle = saveHandled1
1339                 } else if (sessionRule.currentCall.counter == 2) {
1340                     username = user2
1341                     password = pass2
1342                     handle = saveHandled2
1343                 }
1344 
1345                 val savedLogin = LoginEntry.Builder()
1346                         .guid(login.username)
1347                         .origin(login.origin)
1348                         .formActionOrigin(login.formActionOrigin)
1349                         .username(login.username)
1350                         .password(login.password)
1351                         .build()
1352 
1353                 savedLogins.add(savedLogin)
1354 
1355                 assertThat(
1356                     "Username should match",
1357                     login.username,
1358                     equalTo(username))
1359 
1360                 assertThat(
1361                     "Password should match",
1362                     login.password,
1363                     equalTo(password))
1364 
1365                 handle.complete(null)
1366             }
1367 
1368             @AssertCalled(false)
1369             override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
1370         })
1371 
1372         mainSession.loadTestPath(FORMS3_HTML_PATH)
1373         mainSession.waitForPageStop()
1374 
1375         mainSession.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1376             @AssertCalled(count = 1)
1377             override fun onLoginSave(
1378                     session: GeckoSession,
1379                     prompt: AutocompleteRequest<LoginSaveOption>)
1380                     : GeckoResult<PromptDelegate.PromptResponse>? {
1381                 assertThat("Session should not be null", session, notNullValue())
1382 
1383                 val option = prompt.options[0]
1384                 val login = option.value
1385 
1386                 assertThat("Login should not be null", login, notNullValue())
1387 
1388                 assertThat(
1389                     "Username should match",
1390                     login.username,
1391                     equalTo(user1))
1392 
1393                 assertThat(
1394                     "Password should match",
1395                     login.password,
1396                     equalTo(pass1))
1397 
1398                 return GeckoResult.fromValue(prompt.confirm(option))
1399             }
1400         })
1401 
1402         // Assign login credentials.
1403         mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
1404         mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
1405 
1406         // Submit the form.
1407         mainSession.evaluateJS("document.querySelector('#form1').submit()")
1408         sessionRule.waitForResult(saveHandled1)
1409 
1410         // Reload.
1411         val session2 = sessionRule.createOpenSession()
1412         session2.loadTestPath(FORMS3_HTML_PATH)
1413         session2.waitForPageStop()
1414 
1415         session2.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1416             @AssertCalled(count = 1)
1417             override fun onLoginSave(
1418                     session: GeckoSession,
1419                     prompt: AutocompleteRequest<LoginSaveOption>)
1420                     : GeckoResult<PromptDelegate.PromptResponse>? {
1421                 assertThat("Session should not be null", session, notNullValue())
1422 
1423                 val option = prompt.options[0]
1424                 val login = option.value
1425 
1426                 assertThat("Login should not be null", login, notNullValue())
1427 
1428                 assertThat(
1429                     "Username should match",
1430                     login.username,
1431                     equalTo(user2))
1432 
1433                 assertThat(
1434                     "Password should match",
1435                     login.password,
1436                     equalTo(pass2))
1437 
1438                 return GeckoResult.fromValue(prompt.confirm(option))
1439             }
1440         })
1441 
1442         // Assign alternative login credentials.
1443         session2.evaluateJS("document.querySelector('#user1').value = '$user2'")
1444         session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
1445 
1446         // Submit the form.
1447         session2.evaluateJS("document.querySelector('#form1').submit()")
1448         sessionRule.waitForResult(saveHandled2)
1449 
1450         // Reload for the last time.
1451         val session3 = sessionRule.createOpenSession()
1452 
1453         session3.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
1454             @AssertCalled(count = 1)
1455             override fun onLoginSelect(
1456                     session: GeckoSession,
1457                     prompt: AutocompleteRequest<LoginSelectOption>)
1458                     : GeckoResult<PromptDelegate.PromptResponse>? {
1459                 assertThat("Session should not be null", session, notNullValue())
1460 
1461                 assertThat(
1462                     "There should be two options",
1463                     prompt.options.size,
1464                     equalTo(2))
1465 
1466                 var usernames = arrayOf(user1, user2)
1467                 var passwords = arrayOf(pass1, pass2)
1468 
1469                 for (i in 0..1) {
1470                     val login = prompt.options[i].value
1471 
1472                     assertThat("Login should not be null", login, notNullValue())
1473                     assertThat(
1474                         "Username should match",
1475                         login.username,
1476                         equalTo(usernames[i]))
1477                     assertThat(
1478                         "Password should match",
1479                         login.password,
1480                         equalTo(passwords[i]))
1481                 }
1482 
1483                 val login = prompt.options[0].value
1484                 val modOption = LoginSelectOption(LoginEntry.Builder()
1485                         .origin(login.origin)
1486                         .formActionOrigin(login.formActionOrigin)
1487                         .username(userMod)
1488                         .password(passMod)
1489                         .build())
1490 
1491                 Handler(Looper.getMainLooper()).postDelayed({
1492                     selectHandled.complete(null)
1493                 }, acceptDelay)
1494 
1495                 return GeckoResult.fromValue(prompt.confirm(modOption))
1496             }
1497         })
1498 
1499         session3.loadTestPath(FORMS3_HTML_PATH)
1500         session3.waitForPageStop()
1501 
1502         // Focus on the username input field.
1503         session3.evaluateJS("document.querySelector('#user1').focus()")
1504         sessionRule.waitForResult(selectHandled)
1505 
1506         assertThat(
1507             "Filled username should match",
1508             session3.evaluateJS("document.querySelector('#user1').value") as String,
1509             equalTo(userMod))
1510 
1511         assertThat(
1512             "Filled password should match",
1513             session3.evaluateJS("document.querySelector('#pass1').value") as String,
1514             equalTo(passMod))
1515 
1516         // Submit the selection.
1517         session3.evaluateJS("document.querySelector('#form1').submit()")
1518         session3.waitForPageStop()
1519     }
1520 
1521     @Test
1522     fun loginSelectGeneratedPassword() {
1523         sessionRule.setPrefsUntilTestEnd(mapOf(
1524                 // Enable login management since it's disabled in automation.
1525                 "signon.rememberSignons" to true,
1526                 "signon.autofillForms.http" to true,
1527                 "signon.generation.enabled" to true,
1528                 "signon.generation.available" to true,
1529                 "dom.disable_open_during_load" to false,
1530                 "signon.userInputRequiredToCapture.enabled" to false))
1531 
1532         // Test:
1533         // 1. Load a login form page.
1534         // 2. Input username.
1535         // 3. Focus on the password input field.
1536         //    a. Ensure onLoginSelect is called with a generated password.
1537         //    b. Return the login entry with the generated password.
1538         // 4. Submit the login form.
1539         //    a. Ensure onLoginSave is called with accordingly.
1540 
1541         val runtime = sessionRule.runtime
1542         val register = { delegate: StorageDelegate ->
1543             runtime.autocompleteStorageDelegate = delegate
1544         }
1545         val unregister = { _: StorageDelegate ->
1546             runtime.autocompleteStorageDelegate = null
1547         }
1548 
1549         val user1 = "user1x"
1550         var genPass = ""
1551 
1552         val saveHandled1 = GeckoResult<Void>()
1553         val selectHandled = GeckoResult<Void>()
1554         var numSelects = 0
1555 
1556         sessionRule.addExternalDelegateUntilTestEnd(
1557                 StorageDelegate::class, register, unregister,
1558                 object : StorageDelegate {
1559             @AssertCalled
1560             override fun onLoginFetch(domain: String)
1561                     : GeckoResult<Array<LoginEntry>>? {
1562                 assertThat("Domain should match", domain, equalTo("localhost"))
1563 
1564                 return GeckoResult.fromValue(null)
1565             }
1566 
1567             @AssertCalled(count = 1)
1568             override fun onLoginSave(login: LoginEntry) {
1569                 assertThat(
1570                     "Username should match",
1571                     login.username,
1572                     equalTo(user1))
1573 
1574                 assertThat(
1575                     "Password should match",
1576                     login.password,
1577                     equalTo(genPass))
1578 
1579                 saveHandled1.complete(null)
1580             }
1581 
1582             @AssertCalled(false)
1583             override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
1584         })
1585 
1586         mainSession.loadTestPath(FORMS4_HTML_PATH)
1587         mainSession.waitForPageStop()
1588 
1589         mainSession.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
1590             @AssertCalled
1591             override fun onLoginSelect(
1592                     session: GeckoSession,
1593                     prompt: AutocompleteRequest<LoginSelectOption>)
1594                     : GeckoResult<PromptDelegate.PromptResponse>? {
1595                 assertThat("Session should not be null", session, notNullValue())
1596 
1597                 assertThat(
1598                     "There should be one option",
1599                     prompt.options.size,
1600                     equalTo(1))
1601 
1602                 val option = prompt.options[0]
1603                 val login = option.value
1604 
1605                 assertThat(
1606                     "Hint should match",
1607                     option.hint,
1608                     equalTo(SelectOption.Hint.GENERATED))
1609 
1610                 assertThat("Login should not be null", login, notNullValue())
1611                 assertThat(
1612                     "Password should not be empty",
1613                     login.password,
1614                     not(isEmptyOrNullString()))
1615 
1616                 genPass = login.password
1617 
1618                 if (numSelects == 0) {
1619                     Handler(Looper.getMainLooper()).postDelayed({
1620                         selectHandled.complete(null)
1621                     }, acceptDelay)
1622                 }
1623                 ++numSelects
1624 
1625                 return GeckoResult.fromValue(prompt.confirm(option))
1626             }
1627 
1628             @AssertCalled(count = 1)
1629             override fun onLoginSave(
1630                     session: GeckoSession,
1631                     prompt: AutocompleteRequest<LoginSaveOption>)
1632                     : GeckoResult<PromptDelegate.PromptResponse>? {
1633                 assertThat("Session should not be null", session, notNullValue())
1634 
1635                 val option = prompt.options[0]
1636                 val login = option.value
1637 
1638                 assertThat("Login should not be null", login, notNullValue())
1639 
1640                 assertThat(
1641                     "Username should match",
1642                     login.username,
1643                     equalTo(user1))
1644 
1645                 // TODO: The flag is only set for login entry updates yet.
1646                 /*
1647                 assertThat(
1648                     "Hint should match",
1649                     option.hint,
1650                     equalTo(LoginSaveOption.Hint.GENERATED))
1651                 */
1652 
1653                 assertThat(
1654                     "Password should not be empty",
1655                     login.password,
1656                     not(isEmptyOrNullString()))
1657 
1658                 assertThat(
1659                     "Password should match",
1660                     login.password,
1661                     equalTo(genPass))
1662 
1663                 return GeckoResult.fromValue(prompt.confirm(option))
1664             }
1665         })
1666 
1667         // Assign username and focus on password.
1668         mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
1669         mainSession.evaluateJS("document.querySelector('#pass1').focus()")
1670         sessionRule.waitForResult(selectHandled)
1671 
1672         assertThat(
1673             "Filled username should match",
1674             mainSession.evaluateJS("document.querySelector('#user1').value") as String,
1675             equalTo(user1))
1676 
1677         val filledPass = mainSession.evaluateJS(
1678             "document.querySelector('#pass1').value") as String
1679 
1680         assertThat(
1681             "Password should not be empty",
1682             filledPass,
1683             not(isEmptyOrNullString()))
1684 
1685         assertThat(
1686             "Filled password should match",
1687             filledPass,
1688             equalTo(genPass))
1689 
1690         // Submit the selection.
1691         mainSession.evaluateJS("document.querySelector('#form1').submit()")
1692         mainSession.waitForPageStop()
1693     }
1694 }
1695