mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-21 20:24:42 +00:00
Merge pull request #1690 from lightpanda-io/event_dispatch_cleanup
Attempt to improve non-DOM EventTarget dispatching
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