<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 android.graphics.Matrix
8 import android.os.Bundle
9 import android.os.LocaleList
10 import androidx.test.filters.MediumTest
11 import androidx.test.ext.junit.runners.AndroidJUnit4
12 import android.util.Pair
13 import android.util.SparseArray
14 import android.view.View
15 import android.view.ViewStructure
16 import android.view.autofill.AutofillId
17 import android.view.autofill.AutofillValue
18 import org.hamcrest.Matchers.*
19 import org.junit.Test
20 import org.junit.runner.RunWith
21 import org.mozilla.geckoview.Autofill
22 import org.mozilla.geckoview.GeckoSession
23 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
24 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
25 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
26 import org.mozilla.geckoview.test.util.Callbacks
27 
28 
29 @RunWith(AndroidJUnit4::class)
30 @MediumTest
31 class AutofillDelegateTest : BaseSessionTest() {
32 
33     @Test fun autofillCommit() {
34         sessionRule.setPrefsUntilTestEnd(mapOf(
35                 "signon.rememberSignons" to true,
36                 "signon.userInputRequiredToCapture.enabled" to false))
37 
38         mainSession.loadTestPath(FORMS_HTML_PATH)
39         // Wait for the auto-fill nodes to populate.
40         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
41             // For the root document and the iframe document, each has a form group and
42             // a group for inputs outside of forms, so the total count is 4.
43             @AssertCalled(count = 4)
44             override fun onAutofill(session: GeckoSession,
45                                     notification: Int,
46                                     node: Autofill.Node?) {
47                 assertThat("Should be starting auto-fill",
48                            notification,
49                            equalTo(forEachCall(
50                               Autofill.Notify.SESSION_STARTED,
51                               Autofill.Notify.NODE_ADDED)))
52             }
53         })
54 
55         // Assign node values.
56         mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
57         mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
58         mainSession.evaluateJS("document.querySelector('#email1').value = 'e@mail.com'")
59         mainSession.evaluateJS("document.querySelector('#number1').value = '1'")
60 
61         // Submit the session.
62         mainSession.evaluateJS("document.querySelector('#form1').submit()")
63 
64         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
65             @AssertCalled(count = 5)
66             override fun onAutofill(session: GeckoSession,
67                                     notification: Int,
68                                     node: Autofill.Node?) {
69                 val info = sessionRule.currentCall
70 
71                 if (info.counter < 5) {
72                     assertThat("Should be an update notification",
73                                notification,
74                                equalTo(Autofill.Notify.NODE_UPDATED))
75                 } else {
76                     assertThat("Should be a commit notification",
77                                notification,
78                                equalTo(Autofill.Notify.SESSION_COMMITTED))
79 
80                     assertThat("Values should match",
81                                countAutofillNodes({ it.value == "user1x" }),
82                                equalTo(1))
83                     assertThat("Values should match",
84                                countAutofillNodes({ it.value == "pass1x" }),
85                                equalTo(1))
86                     assertThat("Values should match",
87                                countAutofillNodes({ it.value == "e@mail.com" }),
88                                equalTo(1))
89                     assertThat("Values should match",
90                                countAutofillNodes({ it.value == "1" }),
91                                equalTo(1))
92                 }
93             }
94         })
95     }
96 
97     @Test fun autofillCommitIdValue() {
98         sessionRule.setPrefsUntilTestEnd(mapOf(
99                 "signon.rememberSignons" to true,
100                 "signon.userInputRequiredToCapture.enabled" to false))
101 
102         mainSession.loadTestPath(FORMS_ID_VALUE_HTML_PATH)
103         // Wait for the auto-fill nodes to populate.
104         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
105             @AssertCalled(count = 1)
106             override fun onAutofill(session: GeckoSession,
107                                     notification: Int,
108                                     node: Autofill.Node?) {
109                 assertThat("Should be starting auto-fill",
110                            notification,
111                            equalTo(forEachCall(
112                               Autofill.Notify.SESSION_STARTED,
113                               Autofill.Notify.NODE_ADDED)))
114             }
115         })
116 
117         // Assign node values.
118         mainSession.evaluateJS("document.querySelector('#value').value = 'pass1x'")
119 
120         // Submit the session.
121         mainSession.evaluateJS("document.querySelector('#form1').submit()")
122 
123         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
124             @AssertCalled(count = 2)
125             override fun onAutofill(session: GeckoSession,
126                                     notification: Int,
127                                     node: Autofill.Node?) {
128                 val info = sessionRule.currentCall
129 
130                 if (info.counter < 2) {
131                     assertThat("Should be an update notification",
132                                notification,
133                                equalTo(Autofill.Notify.NODE_UPDATED))
134                 } else {
135                     assertThat("Should be a commit notification",
136                                notification,
137                                equalTo(Autofill.Notify.SESSION_COMMITTED))
138 
139                     assertThat("Values should match",
140                                countAutofillNodes({ it.value == "pass1x" }),
141                                equalTo(1))
142                 }
143             }
144         })
145     }
146 
147     @Test fun autofill() {
148         // Test parts of the Oreo auto-fill API; there is another autofill test in
149         // SessionAccessibility for a11y auto-fill support.
150         mainSession.loadTestPath(FORMS_HTML_PATH)
151         // Wait for the auto-fill nodes to populate.
152         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
153             // For the root document and the iframe document, each has a form group and
154             // a group for inputs outside of forms, so the total count is 4.
155             @AssertCalled(count = 4)
156             override fun onAutofill(session: GeckoSession,
157                                     notification: Int,
158                                     node: Autofill.Node?) {
159             }
160         })
161 
162         val autofills = mapOf(
163                 "#user1" to "bar", "#user2" to "bar",
164                 "#pass1" to "baz", "#pass2" to "baz", "#email1" to "a@b.c",
165                 "#number1" to "24", "#tel1" to "42")
166 
167         // Set up promises to monitor the values changing.
168         val promises = autofills.flatMap { entry ->
169             // Repeat each test with both the top document and the iframe document.
170             arrayOf("document", "document.querySelector('#iframe').contentDocument").map { doc ->
171                 mainSession.evaluatePromiseJS("""new Promise(resolve =>
172                     $doc.querySelector('${entry.key}').addEventListener(
173                       'input', event => {
174                         let eventInterface =
175                           event instanceof $doc.defaultView.InputEvent ? "InputEvent" :
176                           event instanceof $doc.defaultView.UIEvent ? "UIEvent" :
177                           event instanceof $doc.defaultView.Event ? "Event" : "Unknown";
178                         resolve([
179                           '${entry.key}',
180                           event.target.value,
181                           '${entry.value}',
182                           eventInterface
183                         ]);
184                 }, { once: true }))""")
185             }
186         }
187 
188         val autofillValues = SparseArray<CharSequence>()
189 
190         // Perform auto-fill and return number of auto-fills performed.
191         fun checkAutofillChild(child: Autofill.Node) {
192             // Seal the node info instance so we can perform actions on it.
193             if (child.children.count() > 0) {
194                 for (c in child.children) {
195                     checkAutofillChild(c!!)
196                 }
197             }
198 
199             if (child.id == View.NO_ID) {
200                 return
201             }
202 
203             assertThat("Should have HTML tag",
204                        child.tag, not(isEmptyOrNullString()))
205             assertThat("Web domain should match",
206                        child.domain, equalTo(GeckoSessionTestRule.TEST_ENDPOINT))
207 
208             if (child.inputType == Autofill.InputType.TEXT) {
209                 assertThat("Input should be enabled", child.enabled, equalTo(true))
210                 assertThat("Input should be focusable",
211                         child.focusable, equalTo(true))
212 
213                 assertThat("Should have HTML tag", child.tag, equalTo("input"))
214                 assertThat("Should have ID attribute", child.attributes.get("id"), not(isEmptyOrNullString()))
215             }
216 
217             autofillValues.append(child.id, when (child.inputType) {
218                 Autofill.InputType.NUMBER -> "24"
219                 Autofill.InputType.PHONE -> "42"
220                 Autofill.InputType.TEXT -> when (child.hint) {
221                     Autofill.Hint.PASSWORD -> "baz"
222                     Autofill.Hint.EMAIL_ADDRESS -> "a@b.c"
223                     else -> "bar"
224                 }
225                 else -> "bar"
226             })
227         }
228 
229         val nodes = mainSession.autofillSession.root
230         checkAutofillChild(nodes)
231 
232         mainSession.autofill(autofillValues)
233 
234         // Wait on the promises and check for correct values.
235         for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
236             assertThat("Auto-filled value must match ($key)", actual, equalTo(expected))
237             assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
238         }
239     }
240 
241     private fun countAutofillNodes(cond: (Autofill.Node) -> Boolean =
242                                    { it.inputType != Autofill.InputType.NONE },
243                            root: Autofill.Node? = null): Int {
244         val node = if (root !== null) root else mainSession.autofillSession.root
245         return (if (cond(node)) 1 else 0) +
246                 node.children.sumBy {
247                     countAutofillNodes(cond, it) }
248     }
249 
250     @WithDisplay(width = 100, height = 100)
251     @Test fun autofillNavigation() {
252         // Wait for the accessibility nodes to populate.
253         mainSession.loadTestPath(FORMS_HTML_PATH)
254         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
255             @AssertCalled(count = 4)
256             override fun onAutofill(session: GeckoSession,
257                                     notification: Int,
258                                     node: Autofill.Node?) {
259                 assertThat("Should be starting auto-fill",
260                            notification,
261                            equalTo(forEachCall(
262                               Autofill.Notify.SESSION_STARTED,
263                               Autofill.Notify.NODE_ADDED)))
264                 assertThat("Node should be valid", node, notNullValue())
265             }
266         })
267 
268         assertThat("Initial auto-fill count should match",
269                    countAutofillNodes(), equalTo(16))
270 
271         // Now wait for the nodes to clear.
272         mainSession.loadTestPath(HELLO_HTML_PATH)
273         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
274             @AssertCalled(count = 1)
275             override fun onAutofill(session: GeckoSession,
276                                     notification: Int,
277                                     node: Autofill.Node?) {
278                 assertThat("Should be canceling auto-fill",
279                            notification,
280                            equalTo(Autofill.Notify.SESSION_CANCELED))
281                 assertThat("Node should be null", node, nullValue())
282             }
283         })
284         assertThat("Should not have auto-fill fields",
285                    countAutofillNodes(), equalTo(0))
286 
287         // Now wait for the nodes to reappear.
288         mainSession.waitForPageStop()
289         mainSession.goBack()
290         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
291             @AssertCalled(count = 4)
292             override fun onAutofill(session: GeckoSession,
293                                     notification: Int,
294                                     node: Autofill.Node?) {
295                 assertThat("Should be starting auto-fill",
296                            notification,
297                            equalTo(forEachCall(
298                                Autofill.Notify.SESSION_STARTED,
299                                Autofill.Notify.NODE_ADDED)))
300                 assertThat("ID should be valid", node, notNullValue())
301             }
302         })
303         assertThat("Should have auto-fill fields again",
304                    countAutofillNodes(), equalTo(16))
305         assertThat("Should not have focused field",
306                    countAutofillNodes({ it.focused }), equalTo(0))
307 
308         mainSession.evaluateJS("document.querySelector('#pass2').focus()")
309 
310         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
311             @AssertCalled(count = 1)
312             override fun onAutofill(session: GeckoSession,
313                                     notification: Int,
314                                     node: Autofill.Node?) {
315                 assertThat("Should be entering auto-fill view",
316                            notification,
317                            equalTo(Autofill.Notify.NODE_FOCUSED))
318                 assertThat("ID should be valid", node, notNullValue())
319             }
320         })
321         assertThat("Should have one focused field",
322                    countAutofillNodes({ it.focused }), equalTo(1))
323         // The focused field, its siblings, its parent, and the root node should
324         // be visible.
325         // Hidden elements are ignored.
326         // TODO: Is this actually correct? Should the whole focused branch be
327         // visible or just the nodes as described above?
328         assertThat("Should have nine visible nodes",
329                    countAutofillNodes({ node -> node.visible }),
330                    equalTo(8))
331 
332         mainSession.evaluateJS("document.querySelector('#pass2').blur()")
333         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
334             @AssertCalled(count = 1)
335             override fun onAutofill(session: GeckoSession,
336                                     notification: Int,
337                                     node: Autofill.Node?) {
338                 assertThat("Should be exiting auto-fill view",
339                            notification,
340                            equalTo(Autofill.Notify.NODE_BLURRED))
341                 assertThat("ID should be valid", node, notNullValue())
342             }
343         })
344         assertThat("Should not have focused field",
345                    countAutofillNodes({ it.focused }), equalTo(0))
346     }
347 
348     @WithDisplay(height = 100, width = 100)
349     @Test fun autofillUserpass() {
350         mainSession.loadTestPath(FORMS2_HTML_PATH)
351         // Wait for the auto-fill nodes to populate.
352         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
353             @AssertCalled(count = 3)
354             override fun onAutofill(session: GeckoSession,
355                                     notification: Int,
356                                     node: Autofill.Node?) {
357                 assertThat("Autofill notification should match", notification,
358                         equalTo(forEachCall(Autofill.Notify.SESSION_STARTED,
359                                 Autofill.Notify.NODE_FOCUSED,
360                                 Autofill.Notify.NODE_ADDED)))
361             }
362         })
363 
364         // Perform auto-fill and return number of auto-fills performed.
365         fun checkAutofillChild(child: Autofill.Node): Int {
366             var sum = 0
367             // Seal the node info instance so we can perform actions on it.
368             for (c in child.children) {
369                 sum += checkAutofillChild(c!!)
370             }
371 
372             if (child.hint == Autofill.Hint.NONE) {
373                 return sum
374             }
375 
376             assertThat("ID should be valid", child.id, not(equalTo(View.NO_ID)))
377             assertThat("Should have HTML tag", child.tag, equalTo("input"))
378 
379             return sum + 1
380         }
381 
382         val root = mainSession.autofillSession.root
383 
384         // form and iframe have each have 2 nodes with hints.
385         assertThat("autofill hint count",
386                    checkAutofillChild(root), equalTo(4))
387     }
388 
389     @WithDisplay(width = 100, height = 100)
390     @Test fun autofillActiveChange() {
391         // We should blur the active autofill node if the session is set
392         // inactive. Likewise, we should focus a node once we return.
393         mainSession.loadTestPath(FORMS_HTML_PATH)
394         // Wait for the auto-fill nodes to populate.
395         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
396             // For the root document and the iframe document, each has a form group and
397             // a group for inputs outside of forms, so the total count is 4.
398             @AssertCalled(count = 4)
399             override fun onAutofill(session: GeckoSession,
400                                     notification: Int,
401                                     node: Autofill.Node?) {
402                 assertThat("Should be starting auto-fill",
403                            notification,
404                            equalTo(forEachCall(
405                               Autofill.Notify.SESSION_STARTED,
406                               Autofill.Notify.NODE_ADDED)))
407             }
408         })
409 
410         mainSession.evaluateJS("document.querySelector('#pass2').focus()")
411         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
412             @AssertCalled(count = 1)
413             override fun onAutofill(session: GeckoSession,
414                                     notification: Int,
415                                     node: Autofill.Node?) {
416                 assertThat("Should be entering auto-fill view",
417                         notification,
418                         equalTo(Autofill.Notify.NODE_FOCUSED))
419                 assertThat("ID should be valid", node, notNullValue())
420             }
421         })
422         assertThat("Should have one focused field",
423                 countAutofillNodes({ it.focused }), equalTo(1))
424 
425         // Make sure we get NODE_BLURRED when inactive
426         mainSession.setActive(false)
427         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
428             @AssertCalled(count = 1)
429             override fun onAutofill(session: GeckoSession,
430                                     notification: Int,
431                                     node: Autofill.Node?) {
432                 assertThat("Should be exiting auto-fill view",
433                         notification,
434                         equalTo(Autofill.Notify.NODE_BLURRED))
435                 assertThat("ID should be valid", node, notNullValue())
436             }
437         })
438 
439         // Make sure we get NODE_FOCUSED when active once again
440         mainSession.setActive(true)
441         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
442             @AssertCalled(count = 1)
443             override fun onAutofill(session: GeckoSession,
444                                     notification: Int,
445                                     node: Autofill.Node?) {
446                 assertThat("Should be entering auto-fill view",
447                         notification,
448                         equalTo(Autofill.Notify.NODE_FOCUSED))
449                 assertThat("ID should be valid", node, notNullValue())
450             }
451         })
452         assertThat("Should have one focused field",
453                 countAutofillNodes({ it.focused }), equalTo(1))
454     }
455 
456     @WithDisplay(width = 100, height = 100)
457     @Test fun autofillAutocompleteAttribute() {
458         mainSession.loadTestPath(FORMS_AUTOCOMPLETE_HTML_PATH)
459         sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
460             @AssertCalled(count = 3)
461             override fun onAutofill(session: GeckoSession,
462                                     notification: Int,
463                                     node: Autofill.Node?) {
464             }
465         });
466 
467         fun checkAutofillChild(child: Autofill.Node): Int {
468             var sum = 0
469             for (c in child.children) {
470                sum += checkAutofillChild(c!!)
471             }
472             if (child.hint == Autofill.Hint.NONE) {
473                 return sum
474             }
475             assertThat("Should have HTML tag", child.tag, equalTo("input"))
476             return sum + 1
477         }
478 
479         val root = mainSession.autofillSession.root
480         // Each page has 3 nodes for autofill.
481         assertThat("autofill hint count",
482                    checkAutofillChild(root), equalTo(6))
483     }
484 
485     class MockViewNode : ViewStructure() {
486         private var mClassName: String? = null
487         private var mEnabled = false
488         private var mVisibility = -1
489         private var mPackageName: String? = null
490         private var mTypeName: String? = null
491         private var mEntryName: String? = null
492         private var mAutofillType = -1
493         private var mAutofillHints: Array<String>? = null
494         private var mInputType = -1
495         private var mHtmlInfo: HtmlInfo? = null
496         private var mWebDomain: String? = null
497         private var mFocused = false
498         private var mFocusable = false
499 
500         var children = ArrayList<MockViewNode?>()
501         var id = View.NO_ID
502         var height = 0
503         var width = 0
504 
505         val className get() = mClassName
506         val htmlInfo get() = mHtmlInfo
507         val autofillHints get() = mAutofillHints
508         val autofillType get() = mAutofillType
509         val webDomain get() = mWebDomain
510         val isEnabled get() = mEnabled
511         val isFocused get() = mFocused
512         val isFocusable get() = mFocusable
513         val visibility get() = mVisibility
514         val inputType get() = mInputType
515 
516         override fun setId(id: Int, packageName: String?, typeName: String?, entryName: String?) {
517             this.id = id
518             mPackageName = packageName
519             mTypeName = typeName
520             mEntryName = entryName
521         }
522 
523         override fun setHint(hint: CharSequence?) {
524             TODO("not implemented")
525         }
526 
527         override fun setElevation(elevation: Float) {
528             TODO("not implemented")
529         }
530 
531         override fun getText(): CharSequence {
532             TODO("not implemented")
533         }
534 
535         override fun setText(text: CharSequence?) {
536             TODO("not implemented")
537         }
538 
539         override fun setText(text: CharSequence?, selectionStart: Int, selectionEnd: Int) {
540             TODO("not implemented")
541         }
542 
543         override fun asyncCommit() {
544             TODO("not implemented")
545         }
546 
547         override fun getChildCount(): Int = children.size
548 
549         override fun setEnabled(state: Boolean) {
550             mEnabled = state
551         }
552 
553         override fun setLocaleList(localeList: LocaleList?) {
554             TODO("not implemented")
555         }
556 
557         override fun setDimens(left: Int, top: Int, scrollX: Int, scrollY: Int, width: Int, height: Int) {
558             this.width = width
559             this.height = height
560         }
561 
562         override fun setChecked(state: Boolean) {
563             TODO("not implemented")
564         }
565 
566         override fun setContextClickable(state: Boolean) {
567             TODO("not implemented")
568         }
569 
570         override fun setAccessibilityFocused(state: Boolean) {
571             TODO("not implemented")
572         }
573 
574         override fun setAlpha(alpha: Float) {
575             TODO("not implemented")
576         }
577 
578         override fun setTransformation(matrix: Matrix?) {
579             TODO("not implemented")
580         }
581 
582         override fun setClassName(className: String?) {
583             mClassName = className
584         }
585 
586         override fun setLongClickable(state: Boolean) {
587             TODO("not implemented")
588         }
589 
590         override fun newChild(index: Int): ViewStructure {
591             val child = MockViewNode()
592             children[index] = child
593             return child
594         }
595 
596         override fun getHint(): CharSequence {
597             TODO("not implemented")
598         }
599 
600         override fun setInputType(inputType: Int) {
601             mInputType = inputType
602         }
603 
604         override fun setWebDomain(domain: String?) {
605             mWebDomain = domain
606         }
607 
608         override fun setAutofillOptions(options: Array<out CharSequence>?) {
609             TODO("not implemented")
610         }
611 
612         override fun setTextStyle(size: Float, fgColor: Int, bgColor: Int, style: Int) {
613             TODO("not implemented")
614         }
615 
616         override fun setVisibility(visibility: Int) {
617             mVisibility = visibility
618         }
619 
620         override fun getAutofillId(): AutofillId? {
621             TODO("not implemented")
622         }
623 
624         override fun setHtmlInfo(htmlInfo: HtmlInfo) {
625             mHtmlInfo = htmlInfo
626         }
627 
628         override fun setTextLines(charOffsets: IntArray?, baselines: IntArray?) {
629             TODO("not implemented")
630         }
631 
632         override fun getExtras(): Bundle {
633             TODO("not implemented")
634         }
635 
636         override fun setClickable(state: Boolean) {
637             TODO("not implemented")
638         }
639 
640         override fun newHtmlInfoBuilder(tagName: String): HtmlInfo.Builder {
641             return MockHtmlInfoBuilder(tagName)
642         }
643 
644         override fun getTextSelectionEnd(): Int {
645             TODO("not implemented")
646         }
647 
648         override fun setAutofillId(id: AutofillId) {
649             TODO("not implemented")
650         }
651 
652         override fun setAutofillId(parentId: AutofillId, virtualId: Int) {
653             TODO("not implemented")
654         }
655 
656         override fun hasExtras(): Boolean {
657             TODO("not implemented")
658         }
659 
660         override fun addChildCount(num: Int): Int {
661             TODO("not implemented")
662         }
663 
664         override fun setAutofillType(type: Int) {
665             mAutofillType = type
666         }
667 
668         override fun setActivated(state: Boolean) {
669             TODO("not implemented")
670         }
671 
672         override fun setFocused(state: Boolean) {
673             mFocused = state
674         }
675 
676         override fun getTextSelectionStart(): Int {
677             TODO("not implemented")
678         }
679 
680         override fun setChildCount(num: Int) {
681             children = ArrayList()
682             for (i in 0 until num) {
683                 children.add(null)
684             }
685         }
686 
687         override fun setAutofillValue(value: AutofillValue?) {
688             TODO("not implemented")
689         }
690 
691         override fun setAutofillHints(hint: Array<String>?) {
692             mAutofillHints = hint
693         }
694 
695         override fun setContentDescription(contentDescription: CharSequence?) {
696             TODO("not implemented")
697         }
698 
699         override fun setFocusable(state: Boolean) {
700             mFocusable = state
701         }
702 
703         override fun setCheckable(state: Boolean) {
704             TODO("not implemented")
705         }
706 
707         override fun asyncNewChild(index: Int): ViewStructure {
708             TODO("not implemented")
709         }
710 
711         override fun setSelected(state: Boolean) {
712             TODO("not implemented")
713         }
714 
715         override fun setDataIsSensitive(sensitive: Boolean) {
716             TODO("not implemented")
717         }
718 
719         override fun setOpaque(opaque: Boolean) {
720             TODO("not implemented")
721         }
722     }
723 
724     class MockHtmlInfoBuilder(tagName: String) : ViewStructure.HtmlInfo.Builder() {
725         val mTagName = tagName
726         val mAttributes: MutableList<Pair<String, String>> = mutableListOf()
727 
728         override fun addAttribute(name: String, value: String): ViewStructure.HtmlInfo.Builder {
729             mAttributes.add(Pair(name, value))
730             return this
731         }
732 
733         override fun build(): ViewStructure.HtmlInfo {
734             return MockHtmlInfo(mTagName, mAttributes)
735         }
736     }
737 
738     class MockHtmlInfo(tagName: String, attributes: MutableList<Pair<String, String>>)
739             : ViewStructure.HtmlInfo() {
740         private val mTagName = tagName
741         private val mAttributes = attributes
742 
743         override fun getTag() = mTagName
744         override fun getAttributes() = mAttributes
745     }
746 }
747