diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 97c0a9cc..3c9b8797 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -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, diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 43430ccb..66523574 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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 }); }; } diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 08ab845f..8c272cfe 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -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 = .{ diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 8e1b6d0e..575e2f3a 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -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, }; } diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index 23f6c445..b63e26cb 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -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)); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index fad648ec..0485d037 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -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| { diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig index 9bb0a039..d72908ba 100644 --- a/src/browser/webapi/EventTarget.zig +++ b/src/browser/webapi/EventTarget.zig @@ -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 { diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index a7bb9bfc..9164f377 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -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(); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index c6f63daa..f8ea6145 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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" }); } diff --git a/src/browser/webapi/Worker.zig b/src/browser/webapi/Worker.zig index d3557b11..6c7a0d5a 100644 --- a/src/browser/webapi/Worker.zig +++ b/src/browser/webapi/Worker.zig @@ -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); diff --git a/src/browser/webapi/WorkerGlobalScope.zig b/src/browser/webapi/WorkerGlobalScope.zig index af9652e2..41454b39 100644 --- a/src/browser/webapi/WorkerGlobalScope.zig +++ b/src/browser/webapi/WorkerGlobalScope.zig @@ -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); diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig index 27fdfb23..8673e450 100644 --- a/src/browser/webapi/event/MessageEvent.zig +++ b/src/browser/webapi/event/MessageEvent.zig @@ -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{ diff --git a/src/browser/webapi/net/WebSocket.zig b/src/browser/webapi/net/WebSocket.zig index 5f0c09ac..32be22fb 100644 --- a/src/browser/webapi/net/WebSocket.zig +++ b/src/browser/webapi/net/WebSocket.zig @@ -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" }); } }