diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 1cf82bfc..aeeb2248 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -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; diff --git a/src/browser/Page.zig b/src/browser/Page.zig index e2ec7885..c1622140 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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; diff --git a/src/browser/webapi/AbortController.zig b/src/browser/webapi/AbortController.zig index 0c22bd4d..9e26c4ad 100644 --- a/src/browser/webapi/AbortController.zig +++ b/src/browser/webapi/AbortController.zig @@ -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 { diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index 186a6cad..de685efc 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -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; diff --git a/src/browser/webapi/FileReader.zig b/src/browser/webapi/FileReader.zig index 3d189089..90e26aa0 100644 --- a/src/browser/webapi/FileReader.zig +++ b/src/browser/webapi/FileReader.zig @@ -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 }, ); } diff --git a/src/browser/webapi/History.zig b/src/browser/webapi/History.zig index 4e6bb348..b8819708 100644 --- a/src/browser/webapi/History.zig +++ b/src/browser/webapi/History.zig @@ -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" }, ); } diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index ffddf30c..dfe031f7 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -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 }); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 394aa004..91f55b60 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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" }, ); } diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index 07187384..a4e6484d 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -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, }); diff --git a/src/browser/webapi/navigation/Navigation.zig b/src/browser/webapi/navigation/Navigation.zig index 6facfee9..e81ff46e 100644 --- a/src/browser/webapi/navigation/Navigation.zig +++ b/src/browser/webapi/navigation/Navigation.zig @@ -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" }, ); } diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 8e839ff6..0eb32cad 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -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" }, ); } diff --git a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig index ae268a59..ad51c10e 100644 --- a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig +++ b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig @@ -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 }, ); }