mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 17:46:32 -04:00
Improve event dispatching between/from workers
This commit is contained in:
@@ -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,
|
||||
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 = .{
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user