<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