mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
postMessage / MessageEvent now allow / track MessagePost
Navigator properties moved from data properties to real accessors. This impact how they're seen (and potentially treated) by JS code. These change result in https://www.browserscan.net/bot-detection correctly rendering. When paired with https://github.com/lightpanda-io/browser/pull/2561 we now render that page correctly (still detected as a bot, but we no longer fail to render)
This commit is contained in:
@@ -161,6 +161,32 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=postMessageTransfersPorts>
|
||||
{
|
||||
// window.postMessage(data, targetOrigin, [port]) must deliver the transferred
|
||||
// MessagePort as event.ports[0] (regression: ports used to always be empty,
|
||||
// so receivers doing `e.ports[0].onmessage = ...` threw on undefined).
|
||||
const channel = new MessageChannel();
|
||||
let received = null;
|
||||
let echoed = null;
|
||||
|
||||
channel.port1.onmessage = (e) => { echoed = e.data; };
|
||||
|
||||
const handler = (e) => {
|
||||
received = e.ports[0];
|
||||
received.postMessage('pong');
|
||||
};
|
||||
window.addEventListener('message', handler, { once: true });
|
||||
|
||||
window.postMessage('ping', '*', [channel.port2]);
|
||||
|
||||
testing.onload(() => {
|
||||
testing.expectEqual(true, received instanceof MessagePort);
|
||||
testing.expectEqual('pong', echoed);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=messageEventOriginFromLocation>
|
||||
{
|
||||
let receivedOrigin = null;
|
||||
|
||||
@@ -27,6 +27,36 @@
|
||||
testing.expectEqual('Gecko', navigator.product);
|
||||
testing.expectEqual(false, navigator.javaEnabled());
|
||||
testing.expectEqual(false, navigator.webdriver);
|
||||
|
||||
testing.expectEqual(null, navigator.doNotTrack);
|
||||
|
||||
// Every Navigator attribute must be a native accessor on the prototype
|
||||
for (const name of [
|
||||
'userAgent', 'appName', 'appCodeName', 'appVersion', 'platform',
|
||||
'language', 'languages', 'onLine', 'cookieEnabled', 'hardwareConcurrency',
|
||||
'deviceMemory', 'maxTouchPoints', 'vendor', 'product', 'webdriver',
|
||||
'plugins', 'doNotTrack', 'globalPrivacyControl',
|
||||
]) {
|
||||
const desc = Object.getOwnPropertyDescriptor(Navigator.prototype, name);
|
||||
testing.expectEqual('function', typeof desc.get); // accessor on prototype
|
||||
testing.expectEqual(undefined, desc.value); // not a data property
|
||||
testing.expectEqual(undefined, Object.getOwnPropertyDescriptor(navigator, name)); // not own
|
||||
testing.expectEqual(true, desc.get.toString().includes('[native code]'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=navigator_native_descriptor_walk>
|
||||
// Mirror the bot-detection fingerprint: read every navigator property's
|
||||
// descriptor and call toString() on its value/getter. This must not throw.
|
||||
let obj = navigator;
|
||||
do {
|
||||
for (const name of Object.getOwnPropertyNames(obj)) {
|
||||
const d = Object.getOwnPropertyDescriptor(obj, name);
|
||||
if (d.value !== undefined) { d.value.toString(); }
|
||||
else if (d.get !== undefined) { d.get.toString(); }
|
||||
}
|
||||
} while (obj = Object.getPrototypeOf(obj));
|
||||
testing.expectEqual(null, navigator.doNotTrack);
|
||||
</script>
|
||||
|
||||
<script id=permission_query type=module>
|
||||
|
||||
@@ -48,6 +48,62 @@ pub fn getLanguages(_: *const Navigator) [2][]const u8 {
|
||||
return .{ "en-US", "en" };
|
||||
}
|
||||
|
||||
pub fn getDoNotTrack(_: *const Navigator) ?[]const u8 {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getAppName(_: *const Navigator) []const u8 {
|
||||
return "Netscape";
|
||||
}
|
||||
|
||||
pub fn getAppCodeName(_: *const Navigator) []const u8 {
|
||||
return "Mozilla";
|
||||
}
|
||||
|
||||
pub fn getAppVersion(_: *const Navigator) []const u8 {
|
||||
return "1.0";
|
||||
}
|
||||
|
||||
pub fn getLanguage(_: *const Navigator) []const u8 {
|
||||
return "en-US";
|
||||
}
|
||||
|
||||
pub fn getOnLine(_: *const Navigator) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getCookieEnabled(_: *const Navigator) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getHardwareConcurrency(_: *const Navigator) u32 {
|
||||
return 4;
|
||||
}
|
||||
|
||||
pub fn getDeviceMemory(_: *const Navigator) f64 {
|
||||
return 8.0;
|
||||
}
|
||||
|
||||
pub fn getMaxTouchPoints(_: *const Navigator) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn getVendor(_: *const Navigator) []const u8 {
|
||||
return "";
|
||||
}
|
||||
|
||||
pub fn getProduct(_: *const Navigator) []const u8 {
|
||||
return "Gecko";
|
||||
}
|
||||
|
||||
pub fn getWebdriver(_: *const Navigator) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn getGlobalPrivacyControl(_: *const Navigator) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getPlatform(_: *const Navigator) []const u8 {
|
||||
return switch (builtin.os.tag) {
|
||||
.macos => "MacIntel",
|
||||
@@ -166,25 +222,27 @@ pub const JsApi = struct {
|
||||
pub const empty_with_no_proto = true;
|
||||
};
|
||||
|
||||
// Read-only properties
|
||||
// Read-only properties. All are accessors (not data properties) so they
|
||||
// present as native getters on Navigator.prototype, matching real browsers
|
||||
// — see the getter definitions above for why.
|
||||
pub const userAgent = bridge.accessor(Navigator.getUserAgent, null, .{});
|
||||
pub const appName = bridge.property("Netscape", .{ .template = false });
|
||||
pub const appCodeName = bridge.property("Mozilla", .{ .template = false });
|
||||
pub const appVersion = bridge.property("1.0", .{ .template = false });
|
||||
pub const appName = bridge.accessor(Navigator.getAppName, null, .{});
|
||||
pub const appCodeName = bridge.accessor(Navigator.getAppCodeName, null, .{});
|
||||
pub const appVersion = bridge.accessor(Navigator.getAppVersion, null, .{});
|
||||
pub const platform = bridge.accessor(Navigator.getPlatform, null, .{});
|
||||
pub const language = bridge.property("en-US", .{ .template = false });
|
||||
pub const language = bridge.accessor(Navigator.getLanguage, null, .{});
|
||||
pub const languages = bridge.accessor(Navigator.getLanguages, null, .{});
|
||||
pub const onLine = bridge.property(true, .{ .template = false });
|
||||
pub const cookieEnabled = bridge.property(true, .{ .template = false });
|
||||
pub const hardwareConcurrency = bridge.property(4, .{ .template = false });
|
||||
pub const deviceMemory = bridge.property(@as(f64, 8.0), .{ .template = false });
|
||||
pub const maxTouchPoints = bridge.property(0, .{ .template = false });
|
||||
pub const vendor = bridge.property("", .{ .template = false });
|
||||
pub const product = bridge.property("Gecko", .{ .template = false });
|
||||
pub const webdriver = bridge.property(false, .{ .template = false });
|
||||
pub const onLine = bridge.accessor(Navigator.getOnLine, null, .{});
|
||||
pub const cookieEnabled = bridge.accessor(Navigator.getCookieEnabled, null, .{});
|
||||
pub const hardwareConcurrency = bridge.accessor(Navigator.getHardwareConcurrency, null, .{});
|
||||
pub const deviceMemory = bridge.accessor(Navigator.getDeviceMemory, null, .{});
|
||||
pub const maxTouchPoints = bridge.accessor(Navigator.getMaxTouchPoints, null, .{});
|
||||
pub const vendor = bridge.accessor(Navigator.getVendor, null, .{});
|
||||
pub const product = bridge.accessor(Navigator.getProduct, null, .{});
|
||||
pub const webdriver = bridge.accessor(Navigator.getWebdriver, null, .{});
|
||||
pub const plugins = bridge.accessor(Navigator.getPlugins, null, .{});
|
||||
pub const doNotTrack = bridge.property(null, .{ .template = false });
|
||||
pub const globalPrivacyControl = bridge.property(true, .{ .template = false });
|
||||
pub const doNotTrack = bridge.accessor(Navigator.getDoNotTrack, null, .{});
|
||||
pub const globalPrivacyControl = bridge.accessor(Navigator.getGlobalPrivacyControl, null, .{});
|
||||
pub const registerProtocolHandler = bridge.function(Navigator.registerProtocolHandler, .{ .dom_exception = true });
|
||||
pub const unregisterProtocolHandler = bridge.function(Navigator.unregisterProtocolHandler, .{ .dom_exception = true });
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const Event = @import("Event.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
const ErrorEvent = @import("event/ErrorEvent.zig");
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
const MessagePort = @import("MessagePort.zig");
|
||||
const MediaQueryList = @import("css/MediaQueryList.zig");
|
||||
const storage = @import("storage/storage.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -561,7 +562,7 @@ pub fn close(self: *Window) void {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn postMessage(self: *Window, message: js.Value.Temp, target_origin: ?[]const u8, frame: *Frame) !void {
|
||||
pub fn postMessage(self: *Window, message: js.Value.Temp, target_origin: ?[]const u8, transfer: ?[]const *MessagePort, frame: *Frame) !void {
|
||||
// For now, we ignore targetOrigin checking and just dispatch the message
|
||||
// In a full implementation, we would validate the origin
|
||||
_ = target_origin;
|
||||
@@ -581,6 +582,7 @@ pub fn postMessage(self: *Window, message: js.Value.Temp, target_origin: ?[]cons
|
||||
.frame = target_frame,
|
||||
.source = source_window,
|
||||
.origin = try arena.dupe(u8, origin),
|
||||
.ports = if (transfer) |t| try arena.dupe(*MessagePort, t) else &.{},
|
||||
};
|
||||
|
||||
try target_frame.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||
@@ -788,6 +790,7 @@ const PostMessageCallback = struct {
|
||||
arena: Allocator,
|
||||
origin: []const u8,
|
||||
message: js.Value.Temp,
|
||||
ports: []const *MessagePort,
|
||||
|
||||
fn deinit(self: *PostMessageCallback) void {
|
||||
self.frame.releaseArena(self.arena);
|
||||
@@ -811,6 +814,7 @@ const PostMessageCallback = struct {
|
||||
.data = .{ .value = self.message },
|
||||
.origin = self.origin,
|
||||
.source = self.source,
|
||||
.ports = self.ports,
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, frame._page)).asEvent();
|
||||
@@ -987,8 +991,8 @@ pub const JsApi = struct {
|
||||
const CrossOriginWindow = struct {
|
||||
window: *Window,
|
||||
|
||||
pub fn postMessage(self: *CrossOriginWindow, message: js.Value.Temp, target_origin: ?[]const u8, frame: *Frame) !void {
|
||||
return self.window.postMessage(message, target_origin, frame);
|
||||
pub fn postMessage(self: *CrossOriginWindow, message: js.Value.Temp, target_origin: ?[]const u8, transfer: ?[]const *MessagePort, frame: *Frame) !void {
|
||||
return self.window.postMessage(message, target_origin, transfer, frame);
|
||||
}
|
||||
|
||||
pub fn getTop(self: *CrossOriginWindow, frame: *Frame) Access {
|
||||
|
||||
@@ -35,11 +35,13 @@ _proto: *Event,
|
||||
_data: ?Data = null,
|
||||
_origin: []const u8 = "",
|
||||
_source: ?*Window = null,
|
||||
_ports: []const *MessagePort = &.{},
|
||||
|
||||
const MessageEventOptions = struct {
|
||||
data: ?Data = null,
|
||||
origin: ?[]const u8 = null,
|
||||
source: ?*Window = null,
|
||||
ports: []const *MessagePort = &.{},
|
||||
};
|
||||
|
||||
pub const Data = union(enum) {
|
||||
@@ -75,6 +77,7 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
|
||||
._data = opts.data,
|
||||
._origin = if (opts.origin) |str| try arena.dupe(u8, str) else "",
|
||||
._source = opts.source,
|
||||
._ports = if (opts.ports.len == 0) &.{} else try arena.dupe(*MessagePort, opts.ports),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -117,8 +120,8 @@ pub fn getSource(self: *const MessageEvent) ?*Window {
|
||||
return self._source;
|
||||
}
|
||||
|
||||
pub fn getPorts(_: *const MessageEvent) []*MessagePort {
|
||||
return &.{};
|
||||
pub fn getPorts(self: *const MessageEvent) []const *MessagePort {
|
||||
return self._ports;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
Reference in New Issue
Block a user