From b94052dfa7725dbc5d8074927c734bf12bb3262a Mon Sep 17 00:00:00 2001 From: Boyd Ebsworthy <5266759+bebsworthy@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:51:54 +0200 Subject: [PATCH] webapi: Fix cross-origin MessageEvent.source WindowProxy identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MessageEvent.getSource returned the bare *Window, the only window accessor that did not go through Window.Access.init. Every other accessor (iframe.contentWindow, window.parent, window.top) wraps a cross-origin target as *CrossOriginWindow. JS object identity is keyed by Zig pointer (identity_map), so cross-origin event.source (the *Window) and iframe.contentWindow (&window._cross_origin_wrapper) resolved to different JS objects: `event.source === iframe.contentWindow` was false cross-origin while true same-origin. The WHATWG HTML spec requires these to be the same object regardless of origin (one WindowProxy per browsing context; MessageEvent.source is that WindowProxy). The mismatch breaks postMessage handshakes that authenticate the sender by reference identity, e.g. keycloak-js's 3rd-party-cookie check (`if (iframe.contentWindow !== event.source) return;`), which then times out and fails OIDC init. Route getSource through Window.Access.init(frame.window, source) — exactly like IFrame.getContentWindow — so both paths resolve to the same per-frame proxy. Same-origin behaviour is unchanged. Verified against Chrome 149 (identity holds cross- and same-origin). Adds a regression test, cross_origin_message_source.html. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../frames/cross_origin_message_source.html | 49 +++++++++++++++++++ .../tests/frames/support/post_to_parent.html | 4 ++ src/browser/webapi/event/MessageEvent.zig | 6 ++- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/browser/tests/frames/cross_origin_message_source.html create mode 100644 src/browser/tests/frames/support/post_to_parent.html diff --git a/src/browser/tests/frames/cross_origin_message_source.html b/src/browser/tests/frames/cross_origin_message_source.html new file mode 100644 index 000000000..87f553bf6 --- /dev/null +++ b/src/browser/tests/frames/cross_origin_message_source.html @@ -0,0 +1,49 @@ + + + + + + diff --git a/src/browser/tests/frames/support/post_to_parent.html b/src/browser/tests/frames/support/post_to_parent.html new file mode 100644 index 000000000..8de6a0c43 --- /dev/null +++ b/src/browser/tests/frames/support/post_to_parent.html @@ -0,0 +1,4 @@ + + diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig index 2bd071472..47188f3b6 100644 --- a/src/browser/webapi/event/MessageEvent.zig +++ b/src/browser/webapi/event/MessageEvent.zig @@ -21,6 +21,7 @@ const lp = @import("lightpanda"); const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); +const Frame = @import("../../Frame.zig"); const Event = @import("../Event.zig"); const MessagePort = @import("../MessagePort.zig"); @@ -116,8 +117,9 @@ pub fn getOrigin(self: *const MessageEvent) []const u8 { return self._origin; } -pub fn getSource(self: *const MessageEvent) ?*Window { - return self._source; +pub fn getSource(self: *const MessageEvent, frame: *Frame) ?Window.Access { + const source = self._source orelse return null; + return Window.Access.init(frame.window, source); } pub fn getPorts(self: *const MessageEvent) []const *MessagePort {