mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Attempt to improve non-DOM EventTarget dispatching
There are two main ways to dispatch events, both via the EventManager: dispatch and dispatchWithFunction. dispatchWithFunction came about from having to dispatch to function callbacks in addition to event listeners. Specifically, firing the window.onload callback. Since that original design, much has changed. Most significantly, with https://github.com/lightpanda-io/browser/pull/1524 callbacks defined via attributes became properly (I hope) integrated with the event dispatching. Furthermore, the number of non-tree event targets (e.g. AbortSignal) has grown significantly. Finally, dispatching an event is DOM-based event is pretty complex, involving multiple phases and capturing the path. The current design is largely correct, but non-obvious. This commit attempts to improve the ergonomics of event dispatching. `dispatchWithFunction` has been renamed to `dispatchDirect`. This function is meant to be used with non-DOM event targets. It is optimized for having an event path with a single target andh no bubbling/capture phase. In addition to being a little more streamlined, `dispatchDirect` will internally turn a `js.Function.Global` or `js.Function.Temp` into a local. This makes the callsite simpler, but also provides optimization opportunity - not having to create a new scope for the common case of having no callback/listener. This lays the groundwork for having a `hasDirect` guard clause at the callsite to avoid unnecessary event creation (todo in a follow up commit). `dispatch` remains unchanged. While `dispatch` is primarily meant to handle the DOM-based EventTarget, it will forward non-DOM EventTargets to `dispatchDirect`. This is necessary since JS code can call `signal.dispatchEvent(....)`. Two notes: 1 - The flow of dispatchDirect is an optimization. The spec makes no distinction between DOM and non-DOM based EventTargets. 2 - While the window (as an EventTarget) should probably be thought of as a DOM-based EventTarget, we use `dispatchDirect with it. This is because it sits at the root and thus can safely go through the faster `dispatchDirect`.
This commit is contained in:
@@ -211,38 +211,9 @@ pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, co
|
||||
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
|
||||
}
|
||||
|
||||
event._target = target;
|
||||
event._dispatch_target = target; // Store original target for composedPath()
|
||||
var was_handled = false;
|
||||
|
||||
defer if (was_handled) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self.page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
ls.local.runMicrotasks();
|
||||
};
|
||||
|
||||
switch (target._type) {
|
||||
.node => |node| try self.dispatchNode(node, event, &was_handled, opts),
|
||||
.xhr,
|
||||
.window,
|
||||
.abort_signal,
|
||||
.media_query_list,
|
||||
.message_port,
|
||||
.text_track_cue,
|
||||
.navigation,
|
||||
.screen,
|
||||
.screen_orientation,
|
||||
.visual_viewport,
|
||||
.file_reader,
|
||||
.generic,
|
||||
=> {
|
||||
const list = self.lookup.get(.{
|
||||
.event_target = @intFromPtr(target),
|
||||
.type_string = event._type_string,
|
||||
}) orelse return;
|
||||
try self.dispatchAll(list, target, event, &was_handled, opts);
|
||||
},
|
||||
.node => |node| try self.dispatchNode(node, event, opts),
|
||||
else => try self.dispatchDirect(target, event, null, .{ .context = "dispatch" }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,16 +222,22 @@ 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 DispatchWithFunctionOptions = struct {
|
||||
const DispatchDirectOptions = struct {
|
||||
context: []const u8,
|
||||
inject_target: bool = true,
|
||||
};
|
||||
pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *Event, function_: ?js.Function, comptime opts: DispatchWithFunctionOptions) !void {
|
||||
|
||||
// Direct dispatch for non-DOM targets (Window, XHR, AbortSignal) or DOM nodes with
|
||||
// property handlers. No propagation - just calls the handler and registered listeners.
|
||||
// Handler can be: null, ?js.Function.Global, ?js.Function.Temp, or js.Function
|
||||
pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, handler: anytype, comptime opts: DispatchDirectOptions) !void {
|
||||
const page = self.page;
|
||||
|
||||
event.acquireRef();
|
||||
defer event.deinit(false, self.page);
|
||||
defer event.deinit(false, page);
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.event, "dispatchWithFunction", .{ .type = event._type_string.str(), .context = opts.context, .has_function = function_ != null });
|
||||
log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context });
|
||||
}
|
||||
|
||||
if (comptime opts.inject_target) {
|
||||
@@ -269,14 +246,15 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
||||
}
|
||||
|
||||
var was_dispatched = false;
|
||||
defer if (was_dispatched) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self.page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
ls.local.runMicrotasks();
|
||||
};
|
||||
|
||||
if (function_) |func| {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer {
|
||||
ls.local.runMicrotasks();
|
||||
ls.deinit();
|
||||
}
|
||||
|
||||
if (getFunction(handler, &ls.local)) |func| {
|
||||
event._current_target = target;
|
||||
if (func.callWithThis(void, target, .{event})) {
|
||||
was_dispatched = true;
|
||||
@@ -286,17 +264,126 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
||||
}
|
||||
}
|
||||
|
||||
// listeners reigstered via addEventListener
|
||||
const list = self.lookup.get(.{
|
||||
.event_target = @intFromPtr(target),
|
||||
.type_string = event._type_string,
|
||||
}) orelse return;
|
||||
try self.dispatchAll(list, target, event, &was_dispatched, .{});
|
||||
|
||||
// This is a slightly simplified version of what you'll find in dispatchPhase
|
||||
// It is simpler because, for direct dispatching, we know there's no ancestors
|
||||
// and only the single target phase.
|
||||
|
||||
// Track dispatch depth for deferred removal
|
||||
self.dispatch_depth += 1;
|
||||
defer {
|
||||
const dispatch_depth = self.dispatch_depth;
|
||||
// Only destroy deferred listeners when we exit the outermost dispatch
|
||||
if (dispatch_depth == 1) {
|
||||
for (self.deferred_removals.items) |removal| {
|
||||
removal.list.remove(&removal.listener.node);
|
||||
self.listener_pool.destroy(removal.listener);
|
||||
}
|
||||
self.deferred_removals.clearRetainingCapacity();
|
||||
} else {
|
||||
self.dispatch_depth = dispatch_depth - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the last listener in the list as sentinel - listeners added during dispatch will be after it
|
||||
const last_node = list.last orelse return;
|
||||
const last_listener: *Listener = @alignCast(@fieldParentPtr("node", last_node));
|
||||
|
||||
// Iterate through the list, stopping after we've encountered the last_listener
|
||||
var node = list.first;
|
||||
var is_done = false;
|
||||
while (node) |n| {
|
||||
if (is_done) {
|
||||
break;
|
||||
}
|
||||
|
||||
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
|
||||
is_done = (listener == last_listener);
|
||||
node = n.next;
|
||||
|
||||
// Skip removed listeners
|
||||
if (listener.removed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the listener has an aborted signal, remove it and skip
|
||||
if (listener.signal) |signal| {
|
||||
if (signal.getAborted()) {
|
||||
self.removeListener(list, listener);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove "once" listeners BEFORE calling them so nested dispatches don't see them
|
||||
if (listener.once) {
|
||||
self.removeListener(list, listener);
|
||||
}
|
||||
|
||||
was_dispatched = true;
|
||||
event._current_target = target;
|
||||
|
||||
switch (listener.function) {
|
||||
.value => |value| try ls.toLocal(value).callWithThis(void, target, .{event}),
|
||||
.string => |string| {
|
||||
const str = try page.call_arena.dupeZ(u8, string.str());
|
||||
try ls.local.eval(str, null);
|
||||
},
|
||||
.object => |obj_global| {
|
||||
const obj = ls.toLocal(obj_global);
|
||||
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
||||
try handleEvent.callWithThis(void, obj, .{event});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if (event._stop_immediate_propagation) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled: *bool, comptime opts: DispatchOpts) !void {
|
||||
fn getFunction(handler: anytype, local: *const js.Local) ?js.Function {
|
||||
const T = @TypeOf(handler);
|
||||
const ti = @typeInfo(T);
|
||||
|
||||
if (ti == .null) {
|
||||
return null;
|
||||
}
|
||||
if (ti == .optional) {
|
||||
return getFunction(handler orelse return null, local);
|
||||
}
|
||||
return switch (T) {
|
||||
js.Function => handler,
|
||||
js.Function.Temp => local.toLocal(handler),
|
||||
js.Function.Global => local.toLocal(handler),
|
||||
else => @compileError("handler must be null or \\??js.Function(\\.(Temp|Global))?"),
|
||||
};
|
||||
}
|
||||
|
||||
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts: DispatchOpts) !void {
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
|
||||
{
|
||||
const et = target.asEventTarget();
|
||||
event._target = et;
|
||||
event._dispatch_target = et; // Store original target for composedPath()
|
||||
}
|
||||
|
||||
const page = self.page;
|
||||
var was_handled = false;
|
||||
|
||||
defer if (was_handled) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
ls.local.runMicrotasks();
|
||||
};
|
||||
|
||||
const activation_state = ActivationState.create(event, target, page);
|
||||
|
||||
// Defer runs even on early return - ensures event phase is reset
|
||||
@@ -374,7 +461,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
.event_target = @intFromPtr(current_target),
|
||||
.type_string = event._type_string,
|
||||
})) |list| {
|
||||
try self.dispatchPhase(list, current_target, event, was_handled, comptime .init(true, opts));
|
||||
try self.dispatchPhase(list, current_target, event, &was_handled, comptime .init(true, opts));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +473,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
blk: {
|
||||
// Get inline handler (e.g., onclick property) for this target
|
||||
if (self.getInlineHandler(target_et, event)) |inline_handler| {
|
||||
was_handled.* = true;
|
||||
was_handled = true;
|
||||
event._current_target = target_et;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
@@ -408,7 +495,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
.type_string = event._type_string,
|
||||
.event_target = @intFromPtr(target_et),
|
||||
})) |list| {
|
||||
try self.dispatchPhase(list, target_et, event, was_handled, comptime .init(null, opts));
|
||||
try self.dispatchPhase(list, target_et, event, &was_handled, comptime .init(null, opts));
|
||||
if (event._stop_propagation) {
|
||||
return;
|
||||
}
|
||||
@@ -425,7 +512,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
.type_string = event._type_string,
|
||||
.event_target = @intFromPtr(current_target),
|
||||
})) |list| {
|
||||
try self.dispatchPhase(list, current_target, event, was_handled, comptime .init(false, opts));
|
||||
try self.dispatchPhase(list, current_target, event, &was_handled, comptime .init(false, opts));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,11 +636,6 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
||||
}
|
||||
}
|
||||
|
||||
// Non-Node dispatching (XHR, Window without propagation)
|
||||
fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool, comptime opts: DispatchOpts) !void {
|
||||
return self.dispatchPhase(list, current_target, event, was_handled, comptime .init(null, opts));
|
||||
}
|
||||
|
||||
fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?js.Function.Global {
|
||||
const global_event_handlers = @import("webapi/global_event_handlers.zig");
|
||||
const handler_type = global_event_handlers.fromEventType(event._type_string.str()) orelse return null;
|
||||
|
||||
@@ -131,7 +131,7 @@ _element_namespace_uris: Element.NamespaceUriLookup = .empty,
|
||||
/// ```js
|
||||
/// img.setAttribute("onload", "(() => { ... })()");
|
||||
/// ```
|
||||
_element_attr_listeners: GlobalEventHandlersLookup = .empty,
|
||||
_event_target_attr_listeners: GlobalEventHandlersLookup = .empty,
|
||||
|
||||
// Blob URL registry for URL.createObjectURL/revokeObjectURL
|
||||
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
|
||||
@@ -726,27 +726,23 @@ fn _documentIsComplete(self: *Page) !void {
|
||||
// Run load events before window.load.
|
||||
try self.dispatchLoad();
|
||||
|
||||
var ls: JS.Local.Scope = undefined;
|
||||
self.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
// Dispatch window.load event.
|
||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
|
||||
// This event is weird, it's dispatched directly on the window, but
|
||||
// with the document as the target.
|
||||
event._target = self.document.asEventTarget();
|
||||
try self._event_manager.dispatchWithFunction(
|
||||
try self._event_manager.dispatchDirect(
|
||||
self.window.asEventTarget(),
|
||||
event,
|
||||
ls.toLocal(self.window._on_load),
|
||||
self.window._on_load,
|
||||
.{ .inject_target = false, .context = "page load" },
|
||||
);
|
||||
|
||||
const pageshow_event = (try PageTransitionEvent.initTrusted(comptime .wrap("pageshow"), .{}, self)).asEvent();
|
||||
try self._event_manager.dispatchWithFunction(
|
||||
try self._event_manager.dispatchDirect(
|
||||
self.window.asEventTarget(),
|
||||
pageshow_event,
|
||||
ls.toLocal(self.window._on_pageshow),
|
||||
self.window._on_pageshow,
|
||||
.{ .context = "page show" },
|
||||
);
|
||||
|
||||
@@ -3127,20 +3123,13 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
|
||||
const form_element = form.asElement();
|
||||
|
||||
if (submit_opts.fire_event) {
|
||||
const onsubmit_handler = try form.asHtmlElement().getOnSubmit(self);
|
||||
const submit_event = try Event.initTrusted(comptime .wrap("submit"), .{ .bubbles = true, .cancelable = true }, self);
|
||||
|
||||
var ls: JS.Local.Scope = undefined;
|
||||
self.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
try self._event_manager.dispatchWithFunction(
|
||||
form_element.asEventTarget(),
|
||||
submit_event,
|
||||
ls.toLocal(onsubmit_handler),
|
||||
.{ .context = "form submit" },
|
||||
);
|
||||
// so submit_event is still valid when we check _prevent_default
|
||||
submit_event.acquireRef();
|
||||
defer submit_event.deinit(false, self);
|
||||
|
||||
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
|
||||
// If the submit event was prevented, don't submit the form
|
||||
if (submit_event._prevent_default) {
|
||||
return;
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
|
||||
}
|
||||
|
||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page: *Page) !void {
|
||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -77,10 +77,10 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page:
|
||||
|
||||
// Dispatch abort event
|
||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, page);
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
try page._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
local.toLocal(self._on_abort),
|
||||
self._on_abort,
|
||||
.{ .context = "abort signal" },
|
||||
);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page:
|
||||
// Static method to create an already-aborted signal
|
||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||
const signal = try init(page);
|
||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||
return signal;
|
||||
}
|
||||
|
||||
@@ -136,11 +136,7 @@ const TimeoutCallback = struct {
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self.page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
self.signal.abort(.{ .string = "TimeoutError" }, &ls.local, self.page) catch |err| {
|
||||
self.signal.abort(.{ .string = "TimeoutError" }, self.page) catch |err| {
|
||||
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||
};
|
||||
return null;
|
||||
|
||||
@@ -183,12 +183,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, local, page);
|
||||
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, page);
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -196,7 +191,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
// Perform the read (synchronous since data is in memory)
|
||||
const data = blob._slice;
|
||||
const size = data.len;
|
||||
try self.dispatch(.progress, .{ .loaded = size, .total = size }, local, page);
|
||||
try self.dispatch(.progress, .{ .loaded = size, .total = size }, page);
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -216,8 +211,8 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
|
||||
self._ready_state = .done;
|
||||
|
||||
try self.dispatch(.load, .{ .loaded = size, .total = size }, local, page);
|
||||
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, local, page);
|
||||
try self.dispatch(.load, .{ .loaded = size, .total = size }, page);
|
||||
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, page);
|
||||
}
|
||||
|
||||
pub fn abort(self: *FileReader) !void {
|
||||
@@ -231,17 +226,12 @@ pub fn abort(self: *FileReader) !void {
|
||||
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
try self.dispatch(.abort, null, page);
|
||||
|
||||
try self.dispatch(.abort, null, local, page);
|
||||
|
||||
try self.dispatch(.load_end, null, local, page);
|
||||
try self.dispatch(.load_end, null, page);
|
||||
}
|
||||
|
||||
fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Progress, local: *const js.Local, page: *Page) !void {
|
||||
fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Progress, page: *Page) !void {
|
||||
const field, const typ = comptime blk: {
|
||||
break :blk switch (event_type) {
|
||||
.abort => .{ "_on_abort", "abort" },
|
||||
@@ -260,10 +250,10 @@ fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Pr
|
||||
page,
|
||||
)).asEvent();
|
||||
|
||||
return page._event_manager.dispatchWithFunction(
|
||||
return page._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
local.toLocal(@field(self, field)),
|
||||
@field(self, field),
|
||||
.{ .context = "FileReader " ++ typ },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,10 +80,10 @@ fn goInner(delta: i32, page: *Page) !void {
|
||||
if (entry._url) |url| {
|
||||
if (try page.isSameOrigin(url)) {
|
||||
const event = (try PopStateEvent.initTrusted(comptime .wrap("popstate"), .{ .state = entry._state.value }, page)).asEvent();
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
try page._event_manager.dispatchDirect(
|
||||
page.window.asEventTarget(),
|
||||
event,
|
||||
page.js.toLocal(page.window._on_popstate),
|
||||
page.window._on_popstate,
|
||||
.{ .context = "Pop State" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,14 +131,10 @@ const PostMessageCallback = struct {
|
||||
return null;
|
||||
}).asEvent();
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
page._event_manager.dispatchWithFunction(
|
||||
page._event_manager.dispatchDirect(
|
||||
self.port.asEventTarget(),
|
||||
event,
|
||||
ls.toLocal(self.port._on_message),
|
||||
self.port._on_message,
|
||||
.{ .context = "MessagePort message" },
|
||||
) catch |err| {
|
||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||
|
||||
@@ -556,10 +556,10 @@ pub fn unhandledPromiseRejection(self: *Window, rejection: js.PromiseRejection,
|
||||
.promise = try rejection.promise().temp(),
|
||||
}, page)).asEvent();
|
||||
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
try page._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
rejection.local.toLocal(self._on_unhandled_rejection),
|
||||
self._on_unhandled_rejection,
|
||||
.{ .inject_target = true, .context = "window.unhandledrejection" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ pub fn getAttributeFunction(
|
||||
page: *Page,
|
||||
) !?js.Function.Global {
|
||||
const element = self.asElement();
|
||||
if (page._element_attr_listeners.get(.{ .target = element.asEventTarget(), .handler = listener_type })) |cached| {
|
||||
if (page._event_target_attr_listeners.get(.{ .target = element.asEventTarget(), .handler = listener_type })) |cached| {
|
||||
return cached;
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ pub fn getAttributeFunction(
|
||||
}
|
||||
|
||||
pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool {
|
||||
return page._element_attr_listeners.contains(.{ .target = self.asEventTarget(), .handler = listener_type });
|
||||
return page._event_target_attr_listeners.contains(.{ .target = self.asEventTarget(), .handler = listener_type });
|
||||
}
|
||||
|
||||
fn setAttributeListener(
|
||||
@@ -421,7 +421,7 @@ fn setAttributeListener(
|
||||
}
|
||||
|
||||
if (listener_callback) |cb| {
|
||||
try page._element_attr_listeners.put(page.arena, .{
|
||||
try page._event_target_attr_listeners.put(page.arena, .{
|
||||
.target = self.asEventTarget(),
|
||||
.handler = listener_type,
|
||||
}, cb);
|
||||
@@ -429,7 +429,7 @@ fn setAttributeListener(
|
||||
}
|
||||
|
||||
// The listener is null, remove existing listener.
|
||||
_ = page._element_attr_listeners.remove(.{
|
||||
_ = page._event_target_attr_listeners.remove(.{
|
||||
.target = self.asEventTarget(),
|
||||
.handler = listener_type,
|
||||
});
|
||||
|
||||
@@ -440,14 +440,10 @@ pub fn updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions,
|
||||
}
|
||||
|
||||
pub fn dispatch(self: *Navigation, func: js.Function.Global, event: *Event, page: *Page) !void {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
return page._event_manager.dispatchWithFunction(
|
||||
return page._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
ls.toLocal(func),
|
||||
func,
|
||||
.{ .context = "Navigation" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void
|
||||
const page = self._page;
|
||||
self._method = try parseMethod(method_);
|
||||
self._url = try URL.resolve(self._arena, page.base(), url, .{ .always_dupe = true, .encode = true });
|
||||
try self.stateChanged(.opened, page.js.local.?, page);
|
||||
try self.stateChanged(.opened, page);
|
||||
}
|
||||
|
||||
pub fn setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const u8, page: *Page) !void {
|
||||
@@ -397,11 +397,10 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !bool {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.stateChanged(.headers_received, local, page);
|
||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, local, page);
|
||||
try self.stateChanged(.loading, local, page);
|
||||
try self.stateChanged(.headers_received, page);
|
||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, page);
|
||||
try self.stateChanged(.loading, page);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -412,14 +411,10 @@ fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
try self._proto.dispatch(.progress, .{
|
||||
.total = self._response_len orelse 0,
|
||||
.loaded = self._response_data.items.len,
|
||||
}, &ls.local, page);
|
||||
}, page);
|
||||
}
|
||||
|
||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
@@ -438,22 +433,17 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.stateChanged(.done, local, page);
|
||||
try self.stateChanged(.done, page);
|
||||
|
||||
const loaded = self._response_data.items.len;
|
||||
try self._proto.dispatch(.load, .{
|
||||
.total = loaded,
|
||||
.loaded = loaded,
|
||||
}, local, page);
|
||||
}, page);
|
||||
try self._proto.dispatch(.load_end, .{
|
||||
.total = loaded,
|
||||
.loaded = loaded,
|
||||
}, local, page);
|
||||
}, page);
|
||||
|
||||
page.js.weakRef(self);
|
||||
}
|
||||
@@ -495,17 +485,12 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
||||
if (new_state != self._ready_state) {
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.stateChanged(new_state, local, page);
|
||||
try self.stateChanged(new_state, page);
|
||||
if (is_abort) {
|
||||
try self._proto.dispatch(.abort, null, local, page);
|
||||
try self._proto.dispatch(.abort, null, page);
|
||||
}
|
||||
try self._proto.dispatch(.err, null, local, page);
|
||||
try self._proto.dispatch(.load_end, null, local, page);
|
||||
try self._proto.dispatch(.err, null, page);
|
||||
try self._proto.dispatch(.load_end, null, page);
|
||||
}
|
||||
|
||||
const level: log.Level = if (err == error.Abort) .debug else .err;
|
||||
@@ -516,7 +501,7 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
||||
});
|
||||
}
|
||||
|
||||
fn stateChanged(self: *XMLHttpRequest, state: ReadyState, local: *const js.Local, page: *Page) !void {
|
||||
fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
|
||||
if (state == self._ready_state) {
|
||||
return;
|
||||
}
|
||||
@@ -524,10 +509,10 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, local: *const js.Local
|
||||
self._ready_state = state;
|
||||
|
||||
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, page);
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
try page._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
local.toLocal(self._on_ready_state_change),
|
||||
self._on_ready_state_change,
|
||||
.{ .context = "XHR state change" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn asEventTarget(self: *XMLHttpRequestEventTarget) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, local: *const js.Local, page: *Page) !void {
|
||||
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, page: *Page) !void {
|
||||
const field, const typ = comptime blk: {
|
||||
break :blk switch (event_type) {
|
||||
.abort => .{ "_on_abort", "abort" },
|
||||
@@ -63,10 +63,10 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
|
||||
page,
|
||||
)).asEvent();
|
||||
|
||||
return page._event_manager.dispatchWithFunction(
|
||||
return page._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
local.toLocal(@field(self, field)),
|
||||
@field(self, field),
|
||||
.{ .context = "XHR " ++ typ },
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user