mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 12:07:09 +00:00
feat(hierarchy): an element reports whether it masks what is typed into it
ios reads it off SecureTextField, which the companion already sent and nothing read; web reads input[type=password]. Android cannot: the native tree mapper drops the password attribute before the sidecar sees it, so the fact is three-valued and null there rather than a false that would read as "not secure".
This commit is contained in:
1 parent
5f1f50c2fb
commit
b1e95739ad
11 files changed
+186
-5
No files matched your search
@@ -72,10 +72,20 @@ type Element struct {
|
||||
Focused bool `json:"focused,omitempty"`
|
||||
Selected bool `json:"selected,omitempty"`
|
||||
Editable bool `json:"editable,omitempty"`
|
||||
Secure bool `json:"secure,omitempty"`
|
||||
Bounds Bounds `json:"bounds"`
|
||||
Attributes map[string]string `json:"attrs,omitempty"`
|
||||
}
|
||||
|
||||
// SecureReported reports whether the producer stated this element's secure
|
||||
// fact at all. Android never does, so an element without it is unknown rather
|
||||
// than known not to be a secure entry, and a caller deciding what may be
|
||||
// written down has to tell those two apart.
|
||||
func (e *Element) SecureReported() bool {
|
||||
_, reported := e.Attributes["secure"]
|
||||
return reported
|
||||
}
|
||||
|
||||
// Node is one node in the hierarchy tree.
|
||||
type Node struct {
|
||||
Element
|
||||
@@ -194,6 +204,7 @@ type treeNodeJSON struct {
|
||||
Checked flagJSON `json:"checked"`
|
||||
Selected flagJSON `json:"selected"`
|
||||
Editable flagJSON `json:"editable"`
|
||||
Secure flagJSON `json:"secure"`
|
||||
}
|
||||
|
||||
// flagJSON is one boolean field of a node. A value that is not a boolean
|
||||
@@ -223,7 +234,7 @@ func (f *flagJSON) UnmarshalJSON(data []byte) error {
|
||||
|
||||
func (n *treeNodeJSON) unreadableFlags() int {
|
||||
count := 0
|
||||
for _, flag := range []flagJSON{n.Clickable, n.Enabled, n.Focused, n.Checked, n.Selected, n.Editable} {
|
||||
for _, flag := range []flagJSON{n.Clickable, n.Enabled, n.Focused, n.Checked, n.Selected, n.Editable, n.Secure} {
|
||||
if flag.unreadable {
|
||||
count++
|
||||
}
|
||||
@@ -305,6 +316,7 @@ var selectorKeys = []string{
|
||||
"placeholderValue",
|
||||
"resource-id",
|
||||
"scrollable",
|
||||
"secure",
|
||||
"selected",
|
||||
"tag",
|
||||
"testID",
|
||||
@@ -583,6 +595,9 @@ func elementFromNode(node *treeNodeJSON) *Element {
|
||||
if node.Selected.set {
|
||||
element.Selected = node.Selected.value
|
||||
}
|
||||
if node.Secure.set {
|
||||
element.Secure = node.Secure.value
|
||||
}
|
||||
if node.Editable.set {
|
||||
element.Editable = node.Editable.value
|
||||
} else {
|
||||
@@ -613,6 +628,9 @@ func elementFromNode(node *treeNodeJSON) *Element {
|
||||
if node.Selected.set {
|
||||
element.Attributes["selected"] = strconv.FormatBool(node.Selected.value)
|
||||
}
|
||||
if node.Secure.set {
|
||||
element.Attributes["secure"] = strconv.FormatBool(node.Secure.value)
|
||||
}
|
||||
element.Attributes["editable"] = strconv.FormatBool(element.Editable)
|
||||
|
||||
return element
|
||||
|
||||
@@ -235,6 +235,53 @@ func TestBoolFieldsFromNode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// secure is the one state flag with three answers: a producer that reports
|
||||
// nothing leaves the element unknown rather than known-not-secure, and a
|
||||
// consumer deciding what a typed value may be written into a record reads the
|
||||
// difference.
|
||||
func TestSecureIsUnknownUntilAProducerReportsIt(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
node string
|
||||
wantReported bool
|
||||
wantSecure bool
|
||||
}{
|
||||
{"reported secure", `{"attributes": {"bounds": "[0,0,10,10]"}, "secure": true}`, true, true},
|
||||
{"reported not secure", `{"attributes": {"bounds": "[0,0,10,10]"}, "secure": false}`, true, false},
|
||||
{"never reported", `{"attributes": {"bounds": "[0,0,10,10]"}}`, false, false},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
tree, err := Parse(testCase.node)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %v", err)
|
||||
}
|
||||
element := tree.Elements[0]
|
||||
if element.SecureReported() != testCase.wantReported {
|
||||
t.Errorf("SecureReported = %v, want %v", element.SecureReported(), testCase.wantReported)
|
||||
}
|
||||
if element.Secure != testCase.wantSecure {
|
||||
t.Errorf("Secure = %v, want %v", element.Secure, testCase.wantSecure)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A selector reaches the fact by the same route every other boolean state does.
|
||||
func TestSecureIsSelectable(t *testing.T) {
|
||||
tree, err := Parse(`{"attributes": {"resource-id": "root", "bounds": "[0,0,10,10]"}, "children": [
|
||||
{"attributes": {"resource-id": "pwd", "bounds": "[0,0,10,5]"}, "secure": true, "children": []},
|
||||
{"attributes": {"resource-id": "email", "bounds": "[0,5,10,10]"}, "secure": false, "children": []}
|
||||
]}`)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %v", err)
|
||||
}
|
||||
element := tree.Find("secure:true")
|
||||
if element == nil || element.ResourceID != "pwd" {
|
||||
t.Errorf("secure:true resolved to %+v, want the pwd element", element)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditableDerivation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
Reference in new issue
Block a user