fix(web-runtime): a handle answers editable for itself, not its container

isContentEditable is inherited, so every span inside a contenteditable
div called itself typeable. collectTargets and the chrome dump both
require the element itself to match; the handle was the one that did not.
This commit is contained in:
pj committed 2026-08-18 00:05:16 +05:30
1 parent d5f48266e8
commit bd01a76d70
2 files changed
+26 -2

No files matched your search

+5 -1
View File
@@ -547,7 +547,11 @@ function elementHandle(
// container a spec reached through state.ax claimed to be a tap target.
clickable: element.matches(TAPPABLE_SELECTOR),
enabled: isEnabled(element),
editable: isEditableElement(element as HTMLElement),
// isContentEditable is inherited, so reading it alone made every span inside
// a contenteditable container typeable here while collectTargets and the
// hierarchy dump, which both require the element ITSELF to match
// EDITABLE_SELECTOR, called the same span inert.
editable: element.matches(EDITABLE_SELECTOR) && isEditableElement(element as HTMLElement),
focused: focusedElement === element,
// Checkbox and option state lives in the DOM PROPERTY: the markup attribute
// records only what the page started with, so a handle reading it reports a
+21 -1
View File
@@ -627,12 +627,13 @@ function domElement(spec: {
labels?: string[];
text?: string;
checked?: boolean;
contentEditable?: boolean;
}): unknown {
const attributes = spec.attributes ?? {};
return {
tagName: spec.tag.toUpperCase(),
type: spec.tag === "input" ? "text" : "",
isContentEditable: false,
isContentEditable: spec.contentEditable ?? false,
checked: spec.checked,
id: attributes.id ?? "",
className: attributes.class ?? "",
@@ -777,6 +778,25 @@ test("an element reached through ax reports the tappable selector's clickability
);
});
// isContentEditable is inherited, so the handle called every span inside a
// contenteditable container typeable while the enumeration and the hierarchy
// dump, which both ask whether the element itself matches EDITABLE_SELECTOR,
// called the same span inert.
test("an element reached through ax reports the editable selector's editability", () => {
const editabilityOf = (element: unknown) => handleOf(element).editable;
assert.equal(
editabilityOf(
domElement({ tag: "div", attributes: { id: "note", contenteditable: "" }, contentEditable: true }),
),
true,
);
assert.equal(
editabilityOf(domElement({ tag: "span", attributes: { id: "word" }, contentEditable: true })),
false,
);
assert.equal(editabilityOf(domElement({ tag: "textarea", attributes: { id: "memo" } })), true);
});
test("a non-editable element carries no hintText", () => {
const element = domElement({ tag: "button", attributes: { id: "txn-submit" }, text: "Add credit" });
assert.equal(attrsOf(element).hintText, undefined);