mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
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:
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user