More Worker APIs

Enable URL, AbortController and AbortSignal for Workers. Had a large impact
because Event.initTrusted was changed from taking a *Frame to taking a *Session.

Also, make WGS and Frame have a matching `dispatch` method so that it's easier
to dispatch events against an executor (inline else =>). Similarly, added
dupeString directly to executor to make migrations easier.
This commit is contained in:
Karl Seguin
2026-04-22 12:47:44 +08:00
parent 00ca20b485
commit 4ff55a8cff
18 changed files with 167 additions and 76 deletions

View File

@@ -124,7 +124,7 @@ pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, co
// property is just a shortcut for calling addEventListener, but they are distinct.
// An event set via property cannot be removed by removeEventListener. If you
// set both the property and add a listener, they both execute.
const DispatchDirectOptions = EventManagerBase.DispatchDirectOptions;
pub const DispatchDirectOptions = EventManagerBase.DispatchDirectOptions;
// Direct dispatch for non-DOM targets (Window, XHR, AbortSignal) or DOM nodes with
// property handlers. No propagation - just calls the handler and registered listeners.
@@ -617,7 +617,7 @@ const ActivationState = struct {
const event = try Event.initTrusted(comptime .wrap(typ), .{
.bubbles = true,
.cancelable = false,
}, frame);
}, frame._session);
const target = input.asElement().asEventTarget();
try frame._event_manager.dispatch(target, event);

View File

@@ -35,6 +35,7 @@ const URL = @import("URL.zig");
const Blob = @import("webapi/Blob.zig");
const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.zig");
const EventTarget = @import("webapi/EventTarget.zig");
const CData = @import("webapi/CData.zig");
const Element = @import("webapi/Element.zig");
const HtmlElement = @import("webapi/element/Html.zig");
@@ -806,7 +807,7 @@ pub fn documentIsLoaded(self: *Frame) void {
}
pub fn _documentIsLoaded(self: *Frame) !void {
const event = try Event.initTrusted(.wrap("DOMContentLoaded"), .{ .bubbles = true }, self);
const event = try Event.initTrusted(.wrap("DOMContentLoaded"), .{ .bubbles = true }, self._session);
try self._event_manager.dispatch(
self.document.asEventTarget(),
event,
@@ -833,7 +834,7 @@ pub fn iframeCompletedLoading(self: *Frame, iframe: *IFrame) void {
defer entered.exit();
blk: {
const event = Event.initTrusted(comptime .wrap("load"), .{}, self) catch |err| {
const event = Event.initTrusted(comptime .wrap("load"), .{}, self._session) catch |err| {
log.err(.frame, "iframe event init", .{ .err = err, .url = iframe._src });
break :blk;
};
@@ -887,7 +888,7 @@ fn _documentIsComplete(self: *Frame) !void {
// Dispatch window.load event.
const window_target = self.window.asEventTarget();
if (self._event_manager.hasDirectListeners(window_target, "load", self.window._on_load)) {
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self._session);
// This event is weird, it's dispatched directly on the window, but
// with the document as the target.
event._target = self.document.asEventTarget();
@@ -1467,7 +1468,7 @@ pub fn dispatchLoad(self: *Frame) !void {
for (to_process.items) |html_element| {
if (has_dom_load_listener or html_element.hasAttributeFunction(.onload, self)) {
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self._session);
try self._event_manager.dispatch(html_element.asEventTarget(), event);
}
}
@@ -1578,7 +1579,7 @@ pub fn deliverSlotchangeEvents(self: *Frame) void {
self._slots_pending_slotchange.clearRetainingCapacity();
for (slots) |slot| {
const event = Event.initTrusted(comptime .wrap("slotchange"), .{ .bubbles = true }, self) catch |err| {
const event = Event.initTrusted(comptime .wrap("slotchange"), .{ .bubbles = true }, self._session) catch |err| {
log.err(.frame, "deliverSlotchange.init", .{ .err = err, .type = self._type, .url = self.url });
continue;
};
@@ -2604,6 +2605,19 @@ pub fn dupeString(self: *Frame, value: []const u8) ![]const u8 {
return self.arena.dupe(u8, value);
}
// Direct (non-propagating) dispatch of an event. Mirrors WorkerGlobalScope.dispatch
// so worker-compatible APIs can uniformly call `global.dispatch(...)` across both
// Frame and Worker contexts.
pub fn dispatch(
self: *Frame,
target: *EventTarget,
event: *Event,
handler: anytype,
comptime opts: EventManager.DispatchDirectOptions,
) !void {
return self._event_manager.dispatchDirect(target, event, handler, opts);
}
pub fn dupeSSO(self: *Frame, value: []const u8) !String {
return String.init(self.arena, value, .{ .dupe = true });
}

View File

@@ -949,7 +949,7 @@ pub const Script = struct {
fn executeCallback(self: *const Script, typ: String, frame: *Frame) void {
const Event = @import("webapi/Event.zig");
const event = Event.initTrusted(typ, .{}, frame) catch |err| {
const event = Event.initTrusted(typ, .{}, frame._session) catch |err| {
log.warn(.js, "script internal callback", .{
.url = self.url,
.type = typ,

View File

@@ -28,12 +28,12 @@ const Session = @import("Session.zig");
const Selector = @import("webapi/selector/Selector.zig");
fn dispatchInputAndChangeEvents(el: *Element, frame: *Frame) !void {
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, frame);
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, frame._session);
frame._event_manager.dispatch(el.asEventTarget(), input_evt) catch |err| {
lp.log.err(.app, "dispatch input event failed", .{ .err = err });
};
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, frame);
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, frame._session);
frame._event_manager.dispatch(el.asEventTarget(), change_evt) catch |err| {
lp.log.err(.app, "dispatch change event failed", .{ .err = err });
};
@@ -196,7 +196,7 @@ pub fn scroll(node: ?*DOMNode, x: ?i32, y: ?i32, frame: *Frame) !void {
};
}
const scroll_evt: *Event = try .initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, frame);
const scroll_evt: *Event = try .initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, frame._session);
frame._event_manager.dispatch(el.asEventTarget(), scroll_evt) catch |err| {
lp.log.err(.app, "dispatch scroll event failed", .{ .err = err });
};

View File

@@ -26,10 +26,13 @@
//! whether it's a Page context or a Worker context.
const std = @import("std");
const lp = @import("lightpanda");
const Context = @import("Context.zig");
const Scheduler = @import("Scheduler.zig");
const Factory = @import("../Factory.zig");
const String = lp.String;
const Allocator = std.mem.Allocator;
const Execution = @This();
@@ -53,3 +56,10 @@ charset: *[]const u8,
pub fn base(self: *const Execution) [:0]const u8 {
return self.context.global.base();
}
pub fn dupeString(self: *const Execution, value: []const u8) ![]const u8 {
if (String.intern(value)) |v| {
return v;
}
return self.arena.dupe(u8, value);
}

View File

@@ -933,10 +933,10 @@ pub const WorkerJsApis = flattenTypes(&.{
@import("../webapi/streams/WritableStreamDefaultController.zig"),
@import("../webapi/encoding/TextEncoderStream.zig"),
@import("../webapi/encoding/TextDecoderStream.zig"),
// @import("../webapi/URL.zig"),
@import("../webapi/AbortSignal.zig"),
@import("../webapi/AbortController.zig"),
@import("../webapi/URL.zig"),
// @import("../webapi/Performance.zig"),
// @import("../webapi/AbortSignal.zig"),
// @import("../webapi/AbortController.zig"),
});
// Master list of ALL JS APIs across all contexts.

View File

@@ -29,6 +29,27 @@
const response_body_text = await response.text();
const response_clone_text = await response.clone().text();
// AbortController + AbortSignal dispatch (exercises the inline-else dispatch path)
const controller = new AbortController();
const ac_aborted_before = controller.signal.aborted;
let ac_listener_fired = false;
controller.signal.addEventListener('abort', () => { ac_listener_fired = true; });
controller.abort('cancelled');
const ac_aborted_after = controller.signal.aborted;
const ac_reason = String(controller.signal.reason);
// Pre-aborted static constructor + throwIfAborted
const pre = AbortSignal.abort('already-gone');
const pre_aborted = pre.aborted;
let pre_threw = false;
try { pre.throwIfAborted(); } catch (_) { pre_threw = true; }
// URL.createObjectURL / revokeObjectURL from a worker
const blob = new Blob(['hello worker'], { type: 'text/plain' });
const blob_url = URL.createObjectURL(blob);
const blob_url_is_blob = blob_url.startsWith('blob:');
URL.revokeObjectURL(blob_url);
postMessage({
ok: true,
results: {
@@ -47,6 +68,13 @@
response_headers_content_type: response.headers.get('content-type'),
response_body_text,
response_clone_text,
ac_aborted_before,
ac_aborted_after,
ac_listener_fired,
ac_reason,
pre_aborted,
pre_threw,
blob_url_is_blob,
},
});
} catch (e) {

View File

@@ -209,6 +209,17 @@
testing.expectEqual('text/plain', r.response_headers_content_type);
testing.expectEqual('response body', r.response_body_text);
testing.expectEqual('response body', r.response_clone_text);
// AbortController / AbortSignal
testing.expectEqual(false, r.ac_aborted_before);
testing.expectEqual(true, r.ac_aborted_after);
testing.expectEqual(true, r.ac_listener_fired);
testing.expectEqual('cancelled', r.ac_reason);
testing.expectEqual(true, r.pre_aborted);
testing.expectEqual(true, r.pre_threw);
// URL.createObjectURL / revokeObjectURL
testing.expectEqual(true, r.blob_url_is_blob);
});
}
</script>

View File

@@ -19,16 +19,17 @@
const std = @import("std");
const js = @import("../js/js.zig");
const Frame = @import("../Frame.zig");
const AbortSignal = @import("AbortSignal.zig");
const Execution = js.Execution;
const AbortController = @This();
_signal: *AbortSignal,
pub fn init(frame: *Frame) !*AbortController {
const signal = try AbortSignal.init(frame);
return frame._factory.create(AbortController{
pub fn init(exec: *const Execution) !*AbortController {
const signal = try AbortSignal.init(exec);
return exec._factory.create(AbortController{
._signal = signal,
});
}
@@ -37,8 +38,8 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
return self._signal;
}
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, frame: *Frame) !void {
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, frame);
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, exec: *const Execution) !void {
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, exec);
}
pub const JsApi = struct {

View File

@@ -20,12 +20,12 @@ const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const Frame = @import("../Frame.zig");
const Event = @import("Event.zig");
const EventTarget = @import("EventTarget.zig");
const log = lp.log;
const Execution = js.Execution;
const AbortSignal = @This();
@@ -34,8 +34,8 @@ _aborted: bool = false,
_reason: Reason = .undefined,
_on_abort: ?js.Function.Global = null,
pub fn init(frame: *Frame) !*AbortSignal {
return frame._factory.eventTarget(AbortSignal{
pub fn init(exec: *const Execution) !*AbortSignal {
return exec._factory.eventTarget(AbortSignal{
._proto = undefined,
});
}
@@ -60,7 +60,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
return self._proto;
}
pub fn abort(self: *AbortSignal, reason_: ?Reason, frame: *Frame) !void {
pub fn abort(self: *AbortSignal, reason_: ?Reason, exec: *const Execution) !void {
if (self._aborted) {
return;
}
@@ -71,36 +71,40 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, frame: *Frame) !void {
if (reason_) |reason| {
switch (reason) {
.js_val => |js_val| self._reason = .{ .js_val = js_val },
.string => |str| self._reason = .{ .string = try frame.dupeString(str) },
.string => |str| self._reason = .{ .string = try exec.dupeString(str) },
.undefined => self._reason = reason,
}
} else {
self._reason = .{ .string = "AbortError" };
}
// Dispatch abort event
const target = self.asEventTarget();
if (frame._event_manager.hasDirectListeners(target, "abort", self._on_abort)) {
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, frame);
try frame._event_manager.dispatchDirect(target, event, self._on_abort, .{ .context = "abort signal" });
const on_abort = self._on_abort;
switch (exec.context.global) {
inline else => |g| {
if (g._event_manager.hasDirectListeners(target, "abort", on_abort)) {
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, g._session);
try g.dispatch(target, event, on_abort, .{ .context = "abort signal" });
}
},
}
}
// Static method to create an already-aborted signal
pub fn createAborted(reason_: ?js.Value.Global, frame: *Frame) !*AbortSignal {
const signal = try init(frame);
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, frame);
pub fn createAborted(reason_: ?js.Value.Global, exec: *const Execution) !*AbortSignal {
const signal = try init(exec);
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, exec);
return signal;
}
pub fn createTimeout(delay: u32, frame: *Frame) !*AbortSignal {
const callback = try frame.arena.create(TimeoutCallback);
pub fn createTimeout(delay: u32, exec: *const Execution) !*AbortSignal {
const callback = try exec.arena.create(TimeoutCallback);
callback.* = .{
.frame = frame,
.signal = try init(frame),
.exec = exec,
.signal = try init(exec),
};
try frame.js.scheduler.add(callback, TimeoutCallback.run, delay, .{
try exec._scheduler.add(callback, TimeoutCallback.run, delay, .{
.name = "AbortSignal.timeout",
});
@@ -111,8 +115,8 @@ const ThrowIfAborted = union(enum) {
exception: js.Exception,
undefined: void,
};
pub fn throwIfAborted(self: *const AbortSignal, frame: *Frame) !ThrowIfAborted {
const local = frame.js.local.?;
pub fn throwIfAborted(self: *const AbortSignal, exec: *const Execution) !ThrowIfAborted {
const local = exec.context.local.?;
if (self._aborted) {
const exception = switch (self._reason) {
@@ -132,12 +136,12 @@ const Reason = union(enum) {
};
const TimeoutCallback = struct {
frame: *Frame,
exec: *const Execution,
signal: *AbortSignal,
fn run(ctx: *anyopaque) !?u32 {
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
self.signal.abort(.{ .string = "TimeoutError" }, self.frame) catch |err| {
self.signal.abort(.{ .string = "TimeoutError" }, self.exec) catch |err| {
log.warn(.app, "abort signal timeout", .{ .err = err });
};
return null;

View File

@@ -96,9 +96,9 @@ pub fn init(typ: []const u8, opts_: ?Options, frame: *Frame) !*Event {
return initWithTrusted(arena, str, opts_, false);
}
pub fn initTrusted(typ: String, opts_: ?Options, frame: *Frame) !*Event {
const arena = try frame.getArena(.tiny, "Event.trusted");
errdefer frame.releaseArena(arena);
pub fn initTrusted(typ: String, opts_: ?Options, session: *Session) !*Event {
const arena = try session.getArena(.tiny, "Event.trusted");
errdefer session.releaseArena(arena);
return initWithTrusted(arena, typ, opts_, true);
}

View File

@@ -70,7 +70,7 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, exec: *js.Execution) !bo
defer _ = event.releaseRef(frame._session);
try frame._event_manager.dispatch(self, event);
},
.worker => |wgs| try wgs.dispatch(self, event, null),
.worker => |wgs| try wgs.dispatch(self, event, null, .{}),
}
return !event._cancelable or !event._prevent_default;
}
@@ -101,8 +101,7 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi
};
switch (exec.context.global) {
.frame => |frame| _ = try frame._event_manager.register(self, typ, em_callback, options),
.worker => |wgs| _ = try wgs._event_manager.register(self, typ, em_callback, options),
inline else => |g| _ = try g._event_manager.register(self, typ, em_callback, options),
}
}
@@ -138,8 +137,7 @@ pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?Even
};
switch (exec.context.global) {
.frame => |frame| frame._event_manager.remove(self, typ, em_callback, use_capture),
.worker => |wgs| wgs._event_manager.remove(self, typ, em_callback, use_capture),
inline else => |g| g._event_manager.remove(self, typ, em_callback, use_capture),
}
}

View File

@@ -20,7 +20,6 @@ const std = @import("std");
const js = @import("../js/js.zig");
const U = @import("../URL.zig");
const Frame = @import("../Frame.zig");
const URLSearchParams = @import("net/URLSearchParams.zig");
const Blob = @import("Blob.zig");
const Execution = js.Execution;
@@ -248,28 +247,36 @@ pub fn canParse(url: []const u8, base_: ?[]const u8) bool {
return U.isCompleteHTTPUrl(url);
}
pub fn createObjectURL(blob: *Blob, frame: *Frame) ![]const u8 {
pub fn createObjectURL(blob: *Blob, exec: *const Execution) ![]const u8 {
var uuid_buf: [36]u8 = undefined;
@import("../../id.zig").uuidv4(&uuid_buf);
const blob_url = try std.fmt.allocPrint(
frame.arena,
"blob:{s}/{s}",
.{ frame.origin orelse "null", uuid_buf },
);
try frame._blob_urls.put(frame.arena, blob_url, blob);
blob.acquireRef();
return blob_url;
switch (exec.context.global) {
inline else => |g| {
const blob_url = try std.fmt.allocPrint(
g.arena,
"blob:{s}/{s}",
.{ g.origin orelse "null", uuid_buf },
);
try g._blob_urls.put(g.arena, blob_url, blob);
blob.acquireRef();
return blob_url;
},
}
}
pub fn revokeObjectURL(url: []const u8, frame: *Frame) void {
pub fn revokeObjectURL(url: []const u8, exec: *const Execution) void {
// Per spec: silently ignore non-blob URLs
if (!std.mem.startsWith(u8, url, "blob:")) {
return;
}
if (frame._blob_urls.fetchRemove(url)) |entry| {
entry.value.releaseRef(frame._session);
switch (exec.context.global) {
inline else => |g| {
if (g._blob_urls.fetchRemove(url)) |entry| {
entry.value.releaseRef(g._session);
}
},
}
}

View File

@@ -538,7 +538,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void
return null;
}
const event = try Event.initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, p);
const event = try Event.initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, p._session);
try p._event_manager.dispatch(p.document.asEventTarget(), event);
pos.state = .end;
@@ -565,7 +565,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void
.end => {},
.done => return null,
}
const event = try Event.initTrusted(comptime .wrap("scrollend"), .{ .bubbles = true }, p);
const event = try Event.initTrusted(comptime .wrap("scrollend"), .{ .bubbles = true }, p._session);
try p._event_manager.dispatch(p.document.asEventTarget(), event);
pos.state = .done;

View File

@@ -28,6 +28,7 @@ const Factory = @import("../Factory.zig");
const Session = @import("../Session.zig");
const EventManagerBase = @import("../EventManagerBase.zig");
const Blob = @import("Blob.zig");
const Worker = @import("Worker.zig");
const Crypto = @import("Crypto.zig");
const Console = @import("Console.zig");
@@ -52,11 +53,16 @@ _identity: JS.Identity = .{},
arena: Allocator,
call_arena: Allocator,
url: [:0]const u8,
// Same-origin constraint: a worker's origin is inherited from its parent frame.
origin: ?[]const u8 = null,
buf: [1024]u8 = undefined, // same size as frame.buf
// Document charset (matches Page.charset). Workers default to UTF-8.
charset: []const u8 = "UTF-8",
js: *JS.Context,
// Blob URL registry for URL.createObjectURL/revokeObjectURL.
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
// Reference back to the Worker object (for postMessage to frame)
_worker: *Worker,
@@ -76,7 +82,8 @@ _on_messageerror: ?JS.Function.Global = null,
pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
const arena = worker._arena;
const session = worker._frame._session;
const parent = worker._frame;
const session = parent._session;
const factory = &session.factory;
const call_arena = try session.getArena(.small, "WorkerGlobalScope.call_arena");
@@ -85,6 +92,7 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
const self = try factory.eventTargetWithAllocator(arena, WorkerGlobalScope{
.url = url,
.arena = arena,
.origin = parent.origin,
.js = undefined,
.call_arena = call_arena,
._session = session,
@@ -108,6 +116,10 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
pub fn deinit(self: *WorkerGlobalScope) void {
self._identity.deinit();
const session = self._session;
var it = self._blob_urls.valueIterator();
while (it.next()) |blob| {
blob.*.releaseRef(session);
}
session.browser.env.destroyContext(self.js);
session.releaseArena(self.call_arena);
}
@@ -123,7 +135,13 @@ pub fn asEventTarget(self: *WorkerGlobalScope) *EventTarget {
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 {
pub fn dispatch(
self: *WorkerGlobalScope,
target: *EventTarget,
event: *Event,
handler: anytype,
comptime opts: EventManagerBase.DispatchDirectOptions,
) !void {
try self._event_manager.dispatchDirect(
self.call_arena,
self.js,
@@ -131,7 +149,7 @@ pub fn dispatch(self: *WorkerGlobalScope, target: *EventTarget, event: *Event, h
event,
handler,
self._session,
.{},
opts,
);
}
@@ -265,7 +283,7 @@ pub fn unhandledPromiseRejection(self: *WorkerGlobalScope, no_handler: bool, rej
.reason = if (rejection.reason()) |r| try r.temp() else null,
.promise = try rejection.promise().temp(),
}, self._session)).asEvent();
try self.dispatch(target, event, attribute_callback);
try self.dispatch(target, event, attribute_callback, .{});
}
}
@@ -311,7 +329,7 @@ pub fn reportError(self: *WorkerGlobalScope, err: JS.Value) !void {
event._prevent_default = prevent_default;
// Pass null as handler: onerror was already called above with 5 args.
// We still dispatch so that addEventListener('error', ...) listeners fire.
try self.dispatch(self.asEventTarget(), event, null);
try self.dispatch(self.asEventTarget(), event, null, .{});
if (comptime builtin.is_test == false) {
if (!event._prevent_default) {
@@ -375,7 +393,7 @@ const ReceiveMessageCallback = struct {
.bubbles = false,
.cancelable = false,
}, worker_scope._session)).asEvent();
try worker_scope.dispatch(target, event, on_messageerror);
try worker_scope.dispatch(target, event, on_messageerror, .{});
return null;
}
@@ -392,7 +410,7 @@ const ReceiveMessageCallback = struct {
.bubbles = false,
.cancelable = false,
}, worker_scope._session)).asEvent();
try worker_scope.dispatch(target, event, on_message);
try worker_scope.dispatch(target, event, on_message, .{});
return null;
}
};

View File

@@ -80,13 +80,13 @@ pub fn load(self: *FontFaceSet, font: []const u8, frame: *Frame) !js.Promise {
// Dispatch loading event
const target = self.asEventTarget();
if (frame._event_manager.hasDirectListeners(target, "loading", null)) {
const event = try Event.initTrusted(comptime .wrap("loading"), .{}, frame);
const event = try Event.initTrusted(comptime .wrap("loading"), .{}, frame._session);
try frame._event_manager.dispatchDirect(target, event, null, .{ .context = "load font face set" });
}
// Dispatch loadingdone event
if (frame._event_manager.hasDirectListeners(target, "loadingdone", null)) {
const event = try Event.initTrusted(comptime .wrap("loadingdone"), .{}, frame);
const event = try Event.initTrusted(comptime .wrap("loadingdone"), .{}, frame._session);
try frame._event_manager.dispatchDirect(target, event, null, .{ .context = "load font face set" });
}

View File

@@ -452,7 +452,7 @@ fn dispatchOpenEvent(self: *WebSocket) !void {
const target = self.asEventTarget();
if (frame._event_manager.hasDirectListeners(target, "open", self._on_open)) {
const event = try Event.initTrusted(comptime .wrap("open"), .{}, frame);
const event = try Event.initTrusted(comptime .wrap("open"), .{}, frame._session);
try frame._event_manager.dispatchDirect(target, event, self._on_open, .{ .context = "WebSocket open" });
}
}
@@ -487,7 +487,7 @@ fn dispatchErrorEvent(self: *WebSocket) !void {
const target = self.asEventTarget();
if (frame._event_manager.hasDirectListeners(target, "error", self._on_error)) {
const event = try Event.initTrusted(comptime .wrap("error"), .{}, frame);
const event = try Event.initTrusted(comptime .wrap("error"), .{}, frame._session);
try frame._event_manager.dispatchDirect(target, event, self._on_error, .{ .context = "WebSocket error" });
}
}

View File

@@ -588,7 +588,7 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, frame: *Frame) !void {
const target = self.asEventTarget();
if (frame._event_manager.hasDirectListeners(target, "readystatechange", self._on_ready_state_change)) {
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, frame);
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, frame._session);
try frame._event_manager.dispatchDirect(target, event, self._on_ready_state_change, .{ .context = "XHR state change" });
}
}