Skip to content

Commit 8a7fc8e

Browse files
committed
Keep a control clickable when it contains something
Making containers click-through stopped them swallowing clicks aimed at the leaves inside them, but it treated every parent as scenery. A control that happens to contain something else is still a control: the cart button became unclickable the moment it grew a badge, because the badge and the icon are nodes of their own. An agent driving the app could only reach it through the child image — the button itself was no longer among the things a snapshot offers as actionable. Only tappable roles are exempted. A role-bearing container such as a list or a navigation bar spans its children by definition and would go straight back to swallowing their clicks, so those stay click-through. A control's own children remain clickable and, sitting on top, still win a hit test aimed at them. The roles that must not be exempted are asserted alongside the ones that must, because the failure of adding one is silent: clicks land on the wrong element rather than failing.
1 parent 1fd3152 commit 8a7fc8e

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

‎internal/web/static/device.html‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@
3838
pointer-events: none;
3939
}
4040
#mirror [data-dd-node]:not(:has([data-dd-node])) { pointer-events: auto; }
41+
/* A control that happens to contain something else is still a control.
42+
Culling every parent made the cart button unclickable the moment it
43+
grew a badge — an agent could only reach it through the child image,
44+
and the button itself vanished from what a snapshot offers as
45+
actionable. Only the roles that represent something a person taps
46+
are listed: a role-bearing *container* like a list or a navigation
47+
bar spans its children and would go back to swallowing their
48+
clicks. A control's own children stay clickable and, being on top,
49+
still win a hit test aimed at them. */
50+
#mirror [data-dd-node][role="button"],
51+
#mirror [data-dd-node][role="link"],
52+
#mirror [data-dd-node][role="textbox"],
53+
#mirror [data-dd-node][role="searchbox"],
54+
#mirror [data-dd-node][role="checkbox"],
55+
#mirror [data-dd-node][role="radio"],
56+
#mirror [data-dd-node][role="switch"],
57+
#mirror [data-dd-node][role="tab"] { pointer-events: auto; }
4158
/* A div paints nothing by default; an <input> paints a border, an
4259
opaque background and a caret. Mirrored fields are inputs so that
4360
fill() works, which made two white boxes float over the video and

‎internal/web/web_test.go‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,30 @@ func TestMirroredFieldsPaintNothing(t *testing.T) {
190190
}
191191
}
192192
}
193+
194+
// Container nodes are click-through so they stop swallowing clicks aimed
195+
// at the leaves inside them — but a control that happens to contain
196+
// something else is still a control. Without the exception the cart
197+
// button became unclickable the moment it grew a badge, and an agent
198+
// could only reach it through the child image. Only tappable roles are
199+
// exempt: a list or a navigation bar spans its children and would go
200+
// straight back to swallowing them.
201+
func TestControlsStayClickableEvenWithChildren(t *testing.T) {
202+
body := get(t, "/device/EB69B42A-4763-4A33-AF0F-CD233F721951").Body.String()
203+
if !strings.Contains(body, `:not(:has([data-dd-node])) { pointer-events: auto; }`) {
204+
t.Error("leaf nodes are no longer clickable; every click would hit a container")
205+
}
206+
for _, role := range []string{"button", "link", "textbox", "searchbox"} {
207+
want := `[role="` + role + `"]`
208+
if !strings.Contains(body, want) {
209+
t.Errorf("role %q is not exempted from click-through, so such a control "+
210+
"becomes unclickable as soon as it contains anything", role)
211+
}
212+
}
213+
// The roles that must NOT be exempt, because they span their children.
214+
for _, role := range []string{"list", "navigation", "listitem"} {
215+
if strings.Contains(body, `[role="`+role+`"] { pointer-events: auto`) {
216+
t.Errorf("role %q is exempted; it spans its children and will swallow their clicks", role)
217+
}
218+
}
219+
}

0 commit comments

Comments
 (0)