From 2d52bd4f8e85d97da7f5230cf42b15d12982c11a Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 14 Jul 2026 15:35:41 +0200 Subject: [PATCH] webapi: resolve the element's frame in hasNonPassiveListener Review pattern from #2944: an injected `frame` parameter is the frame of the calling realm, not the frame owning the element. The wheel/touch cancelability check consulted the caller's event manager and window; for an element living in another frame (e.g. inside an iframe) its listeners are registered in its own frame's event manager and its propagation path ends at that frame's window. Resolve the frame through Element.ownerFrame like the other cross-realm-safe paths. Co-Authored-By: Karl Seguin --- src/browser/webapi/WebDriver.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/browser/webapi/WebDriver.zig b/src/browser/webapi/WebDriver.zig index ef94f2eec..0aa6e3ae3 100644 --- a/src/browser/webapi/WebDriver.zig +++ b/src/browser/webapi/WebDriver.zig @@ -417,14 +417,18 @@ fn dispatch(target: *EventTarget, event: *Event, frame: *Frame, typ: []const u8) } fn hasNonPassiveListener(el: *Element, typ: []const u8, frame: *Frame) bool { - const base = &frame._event_manager.base; + // Listeners live in the event manager of the element's own frame (and the + // propagation path ends at that frame's window), which is not the caller's + // frame when the element belongs to e.g. an iframe's document. + const owner = el.ownerFrame(frame); + const base = &owner._event_manager.base; var current: ?*@import("Node.zig") = el.asNode(); while (current) |node| : (current = node.parentNode()) { if (anyNonPassive(base.getListeners(node.asEventTarget(), .wrap(typ)))) { return true; } } - return anyNonPassive(base.getListeners(frame.window.asEventTarget(), .wrap(typ))); + return anyNonPassive(base.getListeners(owner.window.asEventTarget(), .wrap(typ))); } fn anyNonPassive(list_: ?*std.DoublyLinkedList) bool {