fix(web): read aria-disabled as disabled

the enabled fact came off the disabled property, which only real form
controls have. it reads undefined on the role-based controls the
tappable set now covers, so every one of them looked enabled however
plainly it was marked otherwise, and the fuzzer would spend actions on
inert ones.

both producers answer the same two ways, and the parity fixture carries
a disabled row so the comparison covers it: reverting one side alone
names the element and the fact.
This commit is contained in:
pj committed 2026-08-15 12:46:24 +05:30
1 parent f3096d195b
commit f09c6b5d4d
3 files changed
+22 -3

No files matched your search

+9 -1
View File
@@ -393,6 +393,14 @@ func (d *Driver) Hierarchy(ctx context.Context) (string, error) {
// root a full-viewport tap target here and nowhere else. // root a full-viewport tap target here and nowhere else.
const NON_TEXT_INPUT_TYPES = const NON_TEXT_INPUT_TYPES =
['button','submit','checkbox','radio','range','color','file','image','reset']; ['button','submit','checkbox','radio','range','color','file','image','reset'];
// The disabled property belongs to real form controls only, so it reads
// undefined on the role-based controls the tappable set now covers, and every
// one of them looked enabled however plainly it was marked otherwise.
// isEnabled in pkg/spec/src/web-runtime.ts answers the same two ways.
function isEnabled(el) {
if (el.disabled) return false;
return el.getAttribute('aria-disabled') !== 'true';
}
function isEditableElement(el) { function isEditableElement(el) {
if (el.isContentEditable) return true; if (el.isContentEditable) return true;
const tag = el.tagName.toLowerCase(); const tag = el.tagName.toLowerCase();
@@ -465,7 +473,7 @@ func (d *Driver) Hierarchy(ctx context.Context) (string, error) {
attributes: attrs, attributes: attrs,
children: children, children: children,
clickable: isClickable || null, clickable: isClickable || null,
enabled: (!el.disabled) || null, enabled: isEnabled(el) || null,
focused: document.activeElement === el || null, focused: document.activeElement === el || null,
checked: el.checked || null, checked: el.checked || null,
selected: el.selected || null, selected: el.selected || null,
+1
View File
@@ -71,6 +71,7 @@
<div id="role-treeitem" role="treeitem">tree item</div> <div id="role-treeitem" role="treeitem">tree item</div>
<ul id="role-listbox" role="listbox"> <ul id="role-listbox" role="listbox">
<li id="role-option" role="option">option</li> <li id="role-option" role="option">option</li>
<li id="role-option-disabled" role="option" aria-disabled="true">disabled option</li>
</ul> </ul>
<div id="attribute-click" onclick="void 0">attribute click</div> <div id="attribute-click" onclick="void 0">attribute click</div>
<div id="delegating-root">delegating root</div> <div id="delegating-root">delegating root</div>
+12 -2
View File
@@ -328,6 +328,16 @@ function selectorTag(selector: unknown): string {
return ""; return "";
} }
// isEnabled answers the `enabled` fact. `.disabled` is a property only real form
// controls have, so it reads undefined on the role-based controls the tappable
// set now covers, and every one of them looked enabled however plainly it was
// marked otherwise. internal/driver/chrome/driver.go answers the same two ways
// for the dump the goja host reads.
function isEnabled(element: Element): boolean {
if ((element as HTMLButtonElement).disabled) return false;
return element.getAttribute("aria-disabled") !== "true";
}
function elementHandle(element: Element, selector: unknown): Record<string, unknown> { function elementHandle(element: Element, selector: unknown): Record<string, unknown> {
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
const x = Math.round(rect.left + rect.width / 2); const x = Math.round(rect.left + rect.width / 2);
@@ -346,7 +356,7 @@ function elementHandle(element: Element, selector: unknown): Record<string, unkn
desc: ariaLabel, desc: ariaLabel,
class: (element as HTMLElement).className ?? "", class: (element as HTMLElement).className ?? "",
clickable: true, clickable: true,
enabled: !(element as HTMLButtonElement).disabled, enabled: isEnabled(element),
focused: document.activeElement === element, focused: document.activeElement === element,
x, x,
y, y,
@@ -716,7 +726,7 @@ function collectTargets(): TargetElement[] {
...pointOf(element), ...pointOf(element),
selector: selectors[index], selector: selectors[index],
clickable: clickable.has(element), clickable: clickable.has(element),
enabled: !(element as HTMLButtonElement).disabled, enabled: isEnabled(element),
editable: editable.has(element), editable: editable.has(element),
scrollable: isScrollable(element), scrollable: isScrollable(element),
})); }));