webapi: prevent click() from triggering click()

/html/editing/activation/click_checkbox.html is a DCHECK crash (debug only) and
just a failure in ReleaseFast.

click() handler shouldn't trigger its own click(). Click is singled out for this
behavior, which is 'do nothing':
https://html.spec.whatwg.org/multipage/interaction.html#click-in-progress-flag

Used the new Element flags. There's a simple way to implement this in
EventManager, but the element flag is cheaper and it's currently not used. If
someone ever needs that bit, they can implement the stack-based solution.
This commit is contained in:
Karl Seguin committed 2026-08-17 11:36:46 +08:00
1 parent 2724e1704c
commit e2e8d4c730
3 files changed
+42 -1

No files matched your search

@@ -281,3 +281,30 @@
testing.expectEqual(false, r3.checked);
}
</script>
<script id="nested_click_is_noop">
{
// click() while a click() on the same element is in progress is a no-op
// (the "click in progress" flag); a re-entrant handler must not recurse.
const cb = document.createElement('input');
cb.type = 'checkbox';
document.body.appendChild(cb);
let clicks = 0;
cb.addEventListener('click', () => {
clicks++;
cb.click();
});
cb.click();
testing.expectEqual(1, clicks, 'nested click() should not dispatch');
testing.expectEqual(true, cb.checked);
// The flag is cleared afterwards, so a fresh click() works again.
cb.click();
testing.expectEqual(2, clicks);
testing.expectEqual(false, cb.checked);
document.body.removeChild(cb);
}
</script>
+8 -1
View File
@@ -139,7 +139,14 @@ pub const Namespace = enum(u8) {
pub const Flags = packed struct(u8) {
shadow_host: bool = false,
customized_builtin: bool = false,
_unused: u6 = 0,
// Prevents nested clicks (which have a specific spec-compliant behavior
// compared to other events). If this bit can be more useful for something
// else, a stack in EventManager (for click-specifically) is an alterantive
// approach
click_in_progress: bool = false,
_unused: u5 = 0,
};
_type: Type,
+7
View File
@@ -402,6 +402,13 @@ pub fn click(self: *HtmlElement, frame: *Frame) !void {
else => {},
}
const flags = &self.asElement()._flags;
if (flags.click_in_progress) {
return;
}
flags.click_in_progress = true;
defer flags.click_in_progress = false;
const event = (try @import("../event/MouseEvent.zig").init("click", .{
.bubbles = true,
.cancelable = true,