From 35be9f897f3ac58b104995a38f5cb26a05da1308 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 23 Mar 2026 17:09:37 +0800 Subject: [PATCH 1/2] Improve async tests testing.async(...) is pretty lame. It works for simple cases, where the microtask is very quickly resolved, but otherwise can't block the test from exiting. This adds an overload to testing.async and leverages the new Runner https://github.com/lightpanda-io/browser/pull/1958 to "tick" until completion (or timeout). The overloaded version of testing.async() (called without a callback) will increment a counter which is only decremented with the promise is resolved. The test runner will now `tick` until the counter == 0. --- src/browser/Page.zig | 7 +-- src/browser/tests/frames/frames.html | 35 ++++++++------- src/browser/tests/frames/post_message.html | 1 - .../frames/support/message_receiver.html | 1 - src/browser/tests/testing.js | 26 +++++++++++ src/testing.zig | 45 ++++++++++++++----- 6 files changed, 80 insertions(+), 35 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index bfff78ce..70abf189 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -3585,12 +3585,7 @@ test "WebApi: Page" { } test "WebApi: Frames" { - // TOO FLAKY, disabled for now - - // const filter: testing.LogFilter = .init(&.{.js}); - // defer filter.deinit(); - - // try testing.htmlRunner("frames", .{}); + try testing.htmlRunner("frames", .{}); } test "WebApi: Integration" { diff --git a/src/browser/tests/frames/frames.html b/src/browser/tests/frames/frames.html index 6634ccf2..0d11b1bb 100644 --- a/src/browser/tests/frames/frames.html +++ b/src/browser/tests/frames/frames.html @@ -118,23 +118,24 @@ } - diff --git a/src/browser/tests/frames/post_message.html b/src/browser/tests/frames/post_message.html index 837897c3..4899eb1e 100644 --- a/src/browser/tests/frames/post_message.html +++ b/src/browser/tests/frames/post_message.html @@ -7,7 +7,6 @@ { let reply = null; window.addEventListener('message', (e) => { - console.warn('reply') reply = e.data; }); diff --git a/src/browser/tests/frames/support/message_receiver.html b/src/browser/tests/frames/support/message_receiver.html index 55612a7c..fa9d594d 100644 --- a/src/browser/tests/frames/support/message_receiver.html +++ b/src/browser/tests/frames/support/message_receiver.html @@ -1,7 +1,6 @@ + + + + diff --git a/src/browser/tests/window/support/frame1.html b/src/browser/tests/window/support/frame1.html new file mode 100644 index 00000000..be6a714e --- /dev/null +++ b/src/browser/tests/window/support/frame1.html @@ -0,0 +1,7 @@ + + diff --git a/src/browser/tests/window/support/frame2.html b/src/browser/tests/window/support/frame2.html new file mode 100644 index 00000000..7dcce7f1 --- /dev/null +++ b/src/browser/tests/window/support/frame2.html @@ -0,0 +1,7 @@ + + diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index b89e7383..aaed5494 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -49,6 +49,10 @@ const IS_DEBUG = builtin.mode == .Debug; const Allocator = std.mem.Allocator; +pub fn registerTypes() []const type { + return &.{ Window, CrossOriginWindow }; +} + const Window = @This(); _proto: *EventTarget, @@ -87,6 +91,8 @@ _scroll_pos: struct { .y = 0, .state = .done, }, +// A cross origin wrapper for this window +_cross_origin_wrapper: CrossOriginWindow, pub fn asEventTarget(self: *Window) *EventTarget { return self._proto; @@ -104,19 +110,19 @@ pub fn getWindow(self: *Window) *Window { return self; } -pub fn getTop(self: *Window) *Window { +pub fn getTop(self: *Window, page: *Page) Access { var p = self._page; while (p.parent) |parent| { p = parent; } - return p.window; + return Access.init(page.window, p.window); } -pub fn getParent(self: *Window) *Window { +pub fn getParent(self: *Window, page: *Page) Access { if (self._page.parent) |p| { - return p.window; + return Access.init(page.window, p.window); } - return self; + return .{ .window = self }; } pub fn getDocument(self: *Window) *Document { @@ -606,6 +612,25 @@ pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js. } } +pub const Access = union(enum) { + window: *Window, + cross_origin: *CrossOriginWindow, + + pub fn init(callee: *Window, accessing: *Window) Access { + if (callee == accessing) { + // common enough that it's worth the check + return .{ .window = accessing }; + } + + if (callee._page.js.origin == accessing._page.js.origin) { + // two different windows, but same origin, return the full window + return .{ .window = accessing }; + } + + return .{ .cross_origin = &accessing._cross_origin_wrapper }; + } +}; + const ScheduleOpts = struct { repeat: bool, params: []js.Value.Temp, @@ -892,6 +917,41 @@ pub const JsApi = struct { }.prompt, .{}); }; +const CrossOriginWindow = struct { + window: *Window, + + pub fn postMessage(self: *CrossOriginWindow, message: js.Value.Temp, target_origin: ?[]const u8, page: *Page) !void { + return self.window.postMessage(message, target_origin, page); + } + + pub fn getTop(self: *CrossOriginWindow, page: *Page) Access { + return self.window.getParent(page); + } + + pub fn getParent(self: *CrossOriginWindow, page: *Page) Access { + return self.window.getParent(page); + } + + pub fn getFramesLength(self: *const CrossOriginWindow) u32 { + return self.window.getFramesLength(); + } + + pub const JsApi = struct { + pub const bridge = js.Bridge(CrossOriginWindow); + + pub const Meta = struct { + pub const name = "CrossOriginWindow"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const postMessage = bridge.function(CrossOriginWindow.postMessage, .{}); + pub const top = bridge.accessor(CrossOriginWindow.getTop, null, .{}); + pub const parent = bridge.accessor(CrossOriginWindow.getParent, null, .{}); + pub const length = bridge.accessor(CrossOriginWindow.getFramesLength, null, .{}); + }; +}; + const testing = @import("../../testing.zig"); test "WebApi: Window" { try testing.htmlRunner("window", .{}); diff --git a/src/browser/webapi/element/html/IFrame.zig b/src/browser/webapi/element/html/IFrame.zig index 79bf3f7f..8aa24814 100644 --- a/src/browser/webapi/element/html/IFrame.zig +++ b/src/browser/webapi/element/html/IFrame.zig @@ -39,8 +39,9 @@ pub fn asNode(self: *IFrame) *Node { return self.asElement().asNode(); } -pub fn getContentWindow(self: *const IFrame) ?*Window { - return self._window; +pub fn getContentWindow(self: *const IFrame, page: *Page) ?Window.Access { + const frame_window = self._window orelse return null; + return Window.Access.init(page.window, frame_window); } pub fn getContentDocument(self: *const IFrame) ?*Document {