Merge pull request #1981 from lightpanda-io/window_cross_origin

Window cross origin
This commit is contained in:
Karl Seguin
2026-03-26 07:41:22 +08:00
committed by GitHub
6 changed files with 135 additions and 7 deletions

View File

@@ -300,7 +300,9 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
._performance = Performance.init(),
._screen = screen,
._visual_viewport = visual_viewport,
._cross_origin_wrapper = undefined,
});
self.window._cross_origin_wrapper = .{ .window = self.window };
self._style_manager = try StyleManager.init(self);
errdefer self._style_manager.deinit();

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<iframe src=support/frame1.html></iframe>
<script id=post_message type=module>
const state = await testing.async();
{
const ALT_BASE = testing.BASE_URL.replace('127.0.0.1', 'localhost');
{
let iframe2 = document.createElement('iframe');
iframe2.src = ALT_BASE + 'window/support/frame1.html';
document.documentElement.appendChild(iframe2);
}
{
let iframe3 = document.createElement('iframe');
iframe3.src = ALT_BASE + 'window/support/frame2.html';
document.documentElement.appendChild(iframe3);
}
let captures = [];
window.addEventListener('message', (e) => {
captures.push(e.data);
if (captures.length == 3) {
state.resolve();
}
});
await state.done(() => {
const expected_urls = [
testing.BASE_URL + 'window/support/frame1.html',
ALT_BASE + 'window/support/frame1.html',
ALT_BASE + 'window/support/frame2.html',
];
// No strong order guarantee for messaages, and we don't care about the order
// so long as it's the correct data.
testing.expectEqual(expected_urls.sort(), captures.map((c) => {return c.url}).sort());
captures.forEach((c) => {
if (c.url.includes(testing.BASE_URL)) {
testing.expectEqual(false, c.document_is_undefined);
} else {
testing.expectEqual(true, c.document_is_undefined);
}
});
});
}
</script>

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<script>
window.parent.postMessage({
url: location.toString(),
document_is_undefined: window.parent.document === undefined,
}, '*')
</script>

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<script>
window.top.postMessage({
url: location.toString(),
document_is_undefined: window.top.document === undefined,
}, '*')
</script>

View File

@@ -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", .{});

View File

@@ -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 {