Improve event dispatching between/from workers

This commit is contained in:
Karl Seguin
2026-04-07 16:27:16 +08:00
parent ce6b01f7f6
commit 0247b21483
13 changed files with 221 additions and 197 deletions

View File

@@ -24,25 +24,24 @@ const String = @import("../string.zig").String;
const js = @import("js/js.zig");
const Page = @import("Page.zig");
const EventManagerBase = @import("EventManagerBase.zig");
const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.zig");
const EventTarget = @import("webapi/EventTarget.zig");
const Element = @import("webapi/Element.zig");
const EventManagerBase = @import("EventManagerBase.zig");
const Allocator = std.mem.Allocator;
const IS_DEBUG = builtin.mode == .Debug;
pub const EventManager = @This();
// Re-export types from EventManagerBase for API compatibility
pub const RegisterOptions = EventManagerBase.RegisterOptions;
pub const Callback = EventManagerBase.Callback;
pub const Listener = EventManagerBase.Listener;
const IS_DEBUG = builtin.mode == .Debug;
pub const EventManager = @This();
page: *Page,
base: EventManagerBase,

View File

@@ -1565,7 +1565,7 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
continue;
};
const target = slot.asNode().asEventTarget();
_ = target.dispatchEvent(event, self) catch |err| {
self._event_manager.dispatch(target, event) catch |err| {
log.err(.page, "deliverSlotchange.dispatch", .{ .err = err, .type = self._type, .url = self.url });
};
}

View File

@@ -58,7 +58,7 @@ fn throwDetachedError(isolate: *v8.Isolate) void {
_ = v8.v8__Isolate__ThrowException(isolate, js_exception);
}
fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context) void {
pub fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context) void {
ctx.call_depth += 1;
self.* = Caller{
.local = .{

View File

@@ -294,12 +294,7 @@ pub fn getIncumbent(self: *Context) *Page {
const ctx = fromC(v8.v8__Isolate__GetIncumbentContext(self.env.isolate.handle).?).?;
return switch (ctx.global) {
.page => |page| page,
.worker => {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
unreachable;
},
.worker => unreachable,
};
}

View File

@@ -178,7 +178,7 @@ pub fn create() !Snapshot {
// V8 requires a default context. We could probably make this our
// Page context, but having both the Page and Worker context be
// indexed via addContext makes things a little more consistent.
// added via addContext makes things a little more consistent.
v8.v8__SnapshotCreator__setDefaultContext(snapshot_creator, temp_context);
}
@@ -456,10 +456,6 @@ fn collectExternalReferences() [countExternalReferences()]isize {
return references;
}
fn protoIndexLookup(comptime JsApi: type) ?u16 {
return protoIndexLookupFor(&JsApis, JsApi);
}
fn countInternalFields(comptime JsApi: type) u8 {
var last_used_id = 0;
var cache_count: u8 = 0;
@@ -526,7 +522,7 @@ fn hasNamedIndexedGetter(comptime JsApi: type) bool {
}
// Generic prototype index lookup for a given API list
fn protoIndexLookupFor(comptime ApiList: []const type, comptime JsApi: type) ?u16 {
fn protoIndexLookup(comptime JsApi: type) ?u16 {
@setEvalBranchQuota(100_000);
comptime {
const T = JsApi.bridge.type;
@@ -536,7 +532,7 @@ fn protoIndexLookupFor(comptime ApiList: []const type, comptime JsApi: type) ?u1
const Ptr = std.meta.fieldInfo(T, ._proto).type;
const F = @typeInfo(Ptr).pointer.child;
// Look up in the provided API list
for (ApiList, 0..) |Api, i| {
for (JsApis, 0..) |Api, i| {
if (Api == F.JsApi) {
return i;
}
@@ -618,6 +614,7 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.F
}
},
bridge.Function => {
// For non-static functions, use the signature to validate the receiver
const func_signature = if (value.static) null else signature;
const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{
.callback = value.func,
@@ -671,7 +668,7 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.F
bridge.Property => {
const js_value = switch (value.value) {
.null => js.simpleZigValueToJs(.{ .handle = isolate }, null, true, false),
inline .bool, .int, .float, .string => |pv| js.simpleZigValueToJs(.{ .handle = isolate }, pv, true, false),
inline .bool, .int, .float, .string => |v| js.simpleZigValueToJs(.{ .handle = isolate }, v, true, false),
};
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));

View File

@@ -400,14 +400,14 @@ pub const Property = struct {
pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
// During snapshot creation, there's no Context in embedder data yet
// During snapshot creation, there's no Context in embedder data yet.
// I hate this check, but there doesn't seem to be a way to add this method
// to the global, without triggering it during snapshot creation.
const v8_context = v8.v8__Isolate__GetCurrentContext(v8_isolate) orelse return 0;
if (v8.v8__Context__GetAlignedPointerFromEmbedderData(v8_context, 1) == null) return 0;
const ctx: *Context = @ptrCast(@alignCast(v8.v8__Context__GetAlignedPointerFromEmbedderData(v8_context, 1) orelse return 0));
var caller: Caller = undefined;
if (!caller.init(v8_isolate)) {
return 0;
}
caller.initWithContext(ctx, v8_context);
defer caller.deinit();
const local = &caller.local;
@@ -578,7 +578,7 @@ fn PrototypeType(comptime T: type) ?type {
return Struct(std.meta.fieldInfo(T, ._proto).type);
}
pub fn flattenTypes(comptime Types: []const type) [countFlattenedTypes(Types)]type {
fn flattenTypes(comptime Types: []const type) [countFlattenedTypes(Types)]type {
var index: usize = 0;
var flat: [countFlattenedTypes(Types)]type = undefined;
for (Types) |T| {

View File

@@ -21,9 +21,11 @@ const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const EventManager = @import("../EventManager.zig");
const RegisterOptions = EventManager.RegisterOptions;
const Event = @import("Event.zig");
const WorkerGlobalScope = @import("WorkerGlobalScope.zig");
const RegisterOptions = EventManager.RegisterOptions;
const EventTarget = @This();
@@ -56,15 +58,20 @@ pub fn init(page: *Page) !*EventTarget {
});
}
pub fn dispatchEvent(self: *EventTarget, event: *Event, page: *Page) !bool {
pub fn dispatchEvent(self: *EventTarget, event: *Event, exec: *js.Execution) !bool {
if (event._event_phase != .none) {
return error.InvalidStateError;
}
event._is_trusted = false;
event.acquireRef();
defer _ = event.releaseRef(page._session);
try page._event_manager.dispatch(self, event);
switch (exec.context.global) {
.page => |page| {
event.acquireRef();
defer _ = event.releaseRef(page._session);
try page._event_manager.dispatch(self, event);
},
.worker => |wgs| try wgs.dispatch(self, event, null),
}
return !event._cancelable or !event._prevent_default;
}
@@ -77,12 +84,12 @@ pub const EventListenerCallback = union(enum) {
function: js.Function,
object: js.Object,
};
pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?AddEventListenerOptions, page: *Page) !void {
pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?AddEventListenerOptions, exec: *js.Execution) !void {
const callback = callback_ orelse return;
const em_callback = switch (callback) {
.object => |obj| EventManager.Callback{ .object = obj },
.function => |func| EventManager.Callback{ .function = func },
const em_callback: EventManager.Callback = switch (callback) {
.object => |obj| .{ .object = obj },
.function => |func| .{ .function = func },
};
const options = blk: {
@@ -92,7 +99,11 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi
.capture => |capture| RegisterOptions{ .capture = capture },
};
};
return page._event_manager.register(self, typ, em_callback, options);
switch (exec.context.global) {
.page => |page| _ = try page._event_manager.register(self, typ, em_callback, options),
.worker => |wgs| _ = try wgs._event_manager.register(self, typ, em_callback, options),
}
}
const RemoveEventListenerOptions = union(enum) {
@@ -103,7 +114,7 @@ const RemoveEventListenerOptions = union(enum) {
capture: bool = false,
};
};
pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?RemoveEventListenerOptions, page: *Page) !void {
pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?RemoveEventListenerOptions, exec: *js.Execution) !void {
const callback = callback_ orelse return;
// For object callbacks, check if handleEvent exists
@@ -113,9 +124,9 @@ pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?Even
}
}
const em_callback = switch (callback) {
.function => |func| EventManager.Callback{ .function = func },
.object => |obj| EventManager.Callback{ .object = obj },
const em_callback: EventManager.Callback = switch (callback) {
.function => |func| .{ .function = func },
.object => |obj| .{ .object = obj },
};
const use_capture = blk: {
@@ -125,7 +136,11 @@ pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?Even
.options => |opts| opts.capture,
};
};
return page._event_manager.remove(self, typ, em_callback, use_capture);
switch (exec.context.global) {
.page => |page| page._event_manager.remove(self, typ, em_callback, use_capture),
.worker => |wgs| wgs._event_manager.remove(self, typ, em_callback, use_capture),
}
}
pub fn format(self: *EventTarget, writer: *std.Io.Writer) !void {

View File

@@ -128,7 +128,7 @@ const PostMessageCallback = struct {
.data = .{ .value = self.message },
.origin = "",
.source = null,
}, page) catch |err| {
}, page._session) catch |err| {
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
return null;
}).asEvent();

View File

@@ -780,7 +780,7 @@ const PostMessageCallback = struct {
.source = self.source,
.bubbles = false,
.cancelable = false,
}, page)).asEvent();
}, page._session)).asEvent();
try page._event_manager.dispatchDirect(event_target, event, window._on_message, .{ .context = "window.postMessage" });
}

View File

@@ -28,6 +28,7 @@ const Session = @import("../Session.zig");
const HttpClient = @import("../HttpClient.zig");
const EventTarget = @import("EventTarget.zig");
const MessageEvent = @import("event/MessageEvent.zig");
const WorkerGlobalScope = @import("WorkerGlobalScope.zig");
const Execution = js.Execution;
@@ -190,82 +191,40 @@ pub fn terminate(self: *Worker) void {
}
// Posts a message from the page to the worker.
pub fn postMessage(self: *Worker, message: js.Value) !void {
const worker_scope = self._worker_scope;
// Enter worker context to clone the message
var ls: js.Local.Scope = undefined;
worker_scope.js.localScope(&ls);
defer ls.deinit();
// Clone message from page context to worker context
const cloned = try message.structuredCloneTo(&ls.local);
const data = try cloned.temp();
errdefer data.release();
const session = worker_scope._session;
const message_arena = try session.getArena(.{ .debug = "Worker.postMessage" });
errdefer session.releaseArena(message_arena);
const callback = try message_arena.create(PostMessageToWorkerCallback);
callback.* = .{
.data = data,
.arena = message_arena,
.worker_scope = worker_scope,
};
try worker_scope.js.scheduler.add(callback, PostMessageToWorkerCallback.run, 0, .{
.name = "Worker.postMessage",
.low_priority = false,
.finalizer = PostMessageToWorkerCallback.cancelled,
});
pub fn postMessage(self: *Worker, data: js.Value) !void {
try self._worker_scope.receiveMessage(data);
}
const PostMessageToWorkerCallback = struct {
data: js.Value.Temp,
arena: Allocator,
worker_scope: *WorkerGlobalScope,
fn cancelled(ctx: *anyopaque) void {
const self: *PostMessageToWorkerCallback = @ptrCast(@alignCast(ctx));
self.deinit();
}
fn deinit(self: *PostMessageToWorkerCallback) void {
self.data.release();
self.worker_scope._session.releaseArena(self.arena);
}
fn run(ctx: *anyopaque) !?u32 {
const self: *PostMessageToWorkerCallback = @ptrCast(@alignCast(ctx));
defer self.deinit();
const worker_scope = self.worker_scope;
const on_message = worker_scope._on_message orelse return null;
// Called internally by WorkerGlobalScope when it wants to post a message to use
pub fn receiveMessage(self: *Worker, data: js.Value) !void {
const page = self._page;
const cloned_data = blk: {
var ls: js.Local.Scope = undefined;
worker_scope.js.localScope(&ls);
page.js.localScope(&ls);
defer ls.deinit();
// Get the cloned message data in worker context
const data = self.data.local(&ls.local);
// clones from where it currently is (the Worker context) to our Page's context
const cloned = try data.structuredCloneTo(&ls.local);
break :blk try cloned.temp();
};
errdefer cloned_data.release();
// Call the onmessage handler with a simple object {data: value}
// TODO: Create proper MessageEvent
const message_obj = ls.local.newObject();
_ = message_obj.set("data", data, .{}) catch |err| {
log.err(.browser, "message data set fail", .{ .err = err });
return null;
};
const message_arena = try page.getArena(.{ .debug = "Worker.receiveMessage" });
errdefer page.releaseArena(message_arena);
const func = on_message.local(&ls.local);
_ = func.call(void, .{message_obj.toValue()}) catch |err| {
log.err(.browser, "worker onmessage fail", .{ .err = err });
};
const callback = try message_arena.create(ReceiveMessageCallback);
callback.* = .{
.worker = self,
.data = cloned_data,
.arena = message_arena,
};
return null;
}
};
try page.js.scheduler.add(callback, ReceiveMessageCallback.run, 0, .{
.name = "Worker.receiveMessage",
.low_priority = false,
.finalizer = ReceiveMessageCallback.cancelled,
});
}
pub fn getOnMessage(self: *const Worker) ?js.Function.Global {
return self._on_message;
@@ -304,6 +263,48 @@ fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
};
}
const ReceiveMessageCallback = struct {
data: js.Value.Temp,
arena: Allocator,
worker: *Worker,
fn cancelled(ctx: *anyopaque) void {
const self: *ReceiveMessageCallback = @ptrCast(@alignCast(ctx));
self.data.release();
self.deinit();
}
fn deinit(self: *ReceiveMessageCallback) void {
self.worker._page._session.releaseArena(self.arena);
}
fn run(ctx: *anyopaque) !?u32 {
const self: *ReceiveMessageCallback = @ptrCast(@alignCast(ctx));
defer self.deinit();
const worker = self.worker;
const page = worker._page;
const target = worker.asEventTarget();
const on_message = worker._on_message;
// Check if there are any listeners before creating the event
if (!page._event_manager.hasDirectListeners(target, "message", on_message)) {
self.data.release();
return null;
}
const event = (try MessageEvent.initTrusted(comptime .wrap("message"), .{
.data = .{ .value = self.data },
.bubbles = false,
.cancelable = false,
}, page._session)).asEvent();
try page._event_manager.dispatchDirect(target, event, on_message, .{ .context = "Worker.receiveMessage" });
return null;
}
};
pub const JsApi = struct {
pub const bridge = js.Bridge(Worker);

View File

@@ -23,8 +23,10 @@ const log = @import("../../log.zig");
const Console = @import("Console.zig");
const Crypto = @import("Crypto.zig");
const EventManagerBase = @import("../EventManagerBase.zig");
const EventTarget = @import("EventTarget.zig");
const Factory = @import("../Factory.zig");
const MessageEvent = @import("event/MessageEvent.zig");
const Performance = @import("Performance.zig");
const Session = @import("../Session.zig");
const Worker = @import("Worker.zig");
@@ -48,6 +50,9 @@ js: *JS.Context,
// Reference back to the Worker object (for postMessage to page)
_worker: *Worker,
// Event management for non-DOM targets in worker context
_event_manager: EventManagerBase,
// These fields represent the "Window"-like component of the WGS
_proto: *EventTarget,
_console: Console = .init,
@@ -76,6 +81,7 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
._proto = undefined,
._factory = factory,
._worker = worker,
._event_manager = EventManagerBase.init(arena),
._performance = .init(),
});
errdefer factory.destroy(self);
@@ -104,6 +110,21 @@ pub fn asEventTarget(self: *WorkerGlobalScope) *EventTarget {
return self._proto;
}
const Event = @import("Event.zig");
/// Dispatch an event to listeners on the given target within this worker context.
pub fn dispatch(self: *WorkerGlobalScope, target: *EventTarget, event: *Event, handler: anytype) !void {
try self._event_manager.dispatchDirect(
self.call_arena,
self.js,
target,
event,
handler,
self._session,
.{},
);
}
pub fn getSelf(self: *WorkerGlobalScope) *WorkerGlobalScope {
return self;
}
@@ -152,89 +173,44 @@ pub fn setOnMessage(self: *WorkerGlobalScope, setter: ?FunctionSetter) void {
self._on_message = getFunctionFromSetter(setter);
}
/// Posts a message from the worker back to the page.
/// The message is cloned via structured clone and dispatched on the Worker object.
pub fn postMessage(self: *WorkerGlobalScope, message: JS.Value, exec: *JS.Execution) !void {
_ = exec;
const worker = self._worker;
const page = worker._page;
const session = self._session;
const message_arena = try session.getArena(.{ .debug = "WorkerGlobalScope.postMessage" });
errdefer session.releaseArena(message_arena);
// Enter page context to clone the message
var ls: JS.Local.Scope = undefined;
page.js.localScope(&ls);
defer ls.deinit();
// Clone message from worker context to page context
const cloned = try message.structuredCloneTo(&ls.local);
const data = try cloned.temp();
// Create callback to deliver message to Worker
const callback = try message_arena.create(PostMessageToPageCallback);
callback.* = .{
.data = data,
.arena = message_arena,
.worker = worker,
};
try page.js.scheduler.add(callback, PostMessageToPageCallback.run, 0, .{
.name = "WorkerGlobalScope.postMessage",
.low_priority = false,
.finalizer = PostMessageToPageCallback.cancelled,
});
// Posts a message from the worker back to the page.
// The message is cloned via structured clone and dispatched on the Worker object.
pub fn postMessage(self: *WorkerGlobalScope, data: JS.Value) !void {
try self._worker.receiveMessage(data);
}
const PostMessageToPageCallback = struct {
data: JS.Value.Temp,
arena: Allocator,
worker: *Worker,
fn cancelled(ctx: *anyopaque) void {
const self: *PostMessageToPageCallback = @ptrCast(@alignCast(ctx));
self.deinit();
}
fn deinit(self: *PostMessageToPageCallback) void {
self.data.release();
self.worker._page._session.releaseArena(self.arena);
}
fn run(ctx: *anyopaque) !?u32 {
const self: *PostMessageToPageCallback = @ptrCast(@alignCast(ctx));
defer self.deinit();
const worker = self.worker;
const on_message = worker._on_message orelse return null;
const page = worker._page;
// Called internally by Worker when it wants to post a message to us
pub fn receiveMessage(self: *WorkerGlobalScope, data: JS.Value) !void {
const cloned_data = blk: {
// Enter our context to clone the message
var ls: JS.Local.Scope = undefined;
page.js.localScope(&ls);
self.js.localScope(&ls);
defer ls.deinit();
// Get the cloned message data in page context
const data = self.data.local(&ls.local);
// clones from where it currently is (the Worker's Page context) to our Context
const cloned = try data.structuredCloneTo(&ls.local);
break :blk try cloned.temp();
};
errdefer cloned_data.release();
// Call the onmessage handler with a simple object {data: value}
// TODO: Create proper MessageEvent
const message_obj = ls.local.newObject();
_ = message_obj.set("data", data, .{}) catch |err| {
log.err(.browser, "message data set fail", .{ .err = err });
return null;
};
const session = self._session;
const func = on_message.local(&ls.local);
_ = func.call(void, .{message_obj.toValue()}) catch |err| {
log.err(.browser, "page onmessage fail", .{ .err = err });
};
const message_arena = try session.getArena(.{ .debug = "WorkerGlobalScope.receiveMessage" });
errdefer session.releaseArena(message_arena);
return null;
}
};
const callback = try message_arena.create(ReceiveMessageCallback);
callback.* = .{
.data = cloned_data,
.worker_scope = self,
.arena = message_arena,
};
try self.js.scheduler.add(callback, ReceiveMessageCallback.run, 0, .{
.name = "WorkerGlobalScope.receiveMessage",
.low_priority = false,
.finalizer = ReceiveMessageCallback.cancelled,
});
}
pub fn btoa(_: *const WorkerGlobalScope, input: []const u8, exec: *JS.Execution) ![]const u8 {
const base64 = @import("encoding/base64.zig");
@@ -268,6 +244,45 @@ fn getFunctionFromSetter(setter_: ?FunctionSetter) ?JS.Function.Global {
};
}
const ReceiveMessageCallback = struct {
data: JS.Value.Temp,
arena: Allocator,
worker_scope: *WorkerGlobalScope,
fn cancelled(ctx: *anyopaque) void {
const self: *ReceiveMessageCallback = @ptrCast(@alignCast(ctx));
self.data.release();
self.deinit();
}
fn deinit(self: *ReceiveMessageCallback) void {
self.worker_scope._session.releaseArena(self.arena);
}
fn run(ctx: *anyopaque) !?u32 {
const self: *ReceiveMessageCallback = @ptrCast(@alignCast(ctx));
defer self.deinit();
const worker_scope = self.worker_scope;
const target = worker_scope.asEventTarget();
const on_message = worker_scope._on_message;
// Check if there are any listeners before creating the event
if (!worker_scope._event_manager.hasDirectListeners(target, "message", on_message)) {
self.data.release();
return null;
}
const event = (try MessageEvent.initTrusted(comptime .wrap("message"), .{
.data = .{ .value = self.data },
.bubbles = false,
.cancelable = false,
}, worker_scope._session)).asEvent();
try worker_scope.dispatch(target, event, on_message);
return null;
}
};
pub const JsApi = struct {
pub const bridge = JS.Bridge(WorkerGlobalScope);

View File

@@ -20,11 +20,13 @@ const std = @import("std");
const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Factory = @import("../../Factory.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Window = @import("../Window.zig");
const Allocator = std.mem.Allocator;
const MessageEvent = @This();
@@ -53,19 +55,19 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent {
const arena = try page.getArena(.small, "MessageEvent");
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, opts_, false, page);
return initWithTrusted(arena, type_string, opts_, false, page._factory);
}
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*MessageEvent {
const arena = try page.getArena(.small, "MessageEvent.trusted");
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, opts_, true, page);
pub fn initTrusted(typ: String, opts_: ?Options, session: *Session) !*MessageEvent {
const arena = try session.getArena(.small, "MessageEvent.trusted");
errdefer session.releaseArena(arena);
return initWithTrusted(arena, typ, opts_, true, &session.factory);
}
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool, page: *Page) !*MessageEvent {
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool, factory: *Factory) !*MessageEvent {
const opts = opts_ orelse Options{};
const event = try page._factory.event(
const event = try factory.event(
arena,
typ,
MessageEvent{

View File

@@ -475,7 +475,7 @@ fn dispatchMessageEvent(self: *WebSocket, data: []const u8, frame_type: http.WsF
const event = try MessageEvent.initTrusted(comptime .wrap("message"), .{
.data = msg_data,
.origin = "",
}, page);
}, page._session);
try page._event_manager.dispatchDirect(target, event.asEvent(), self._on_message, .{ .context = "WebSocket message" });
}
}