mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +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 });
|
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) {
|
switch (target._type) {
|
||||||
.node => |node| try self.dispatchNode(node, event, &was_handled, opts),
|
.node => |node| try self.dispatchNode(node, event, opts),
|
||||||
.xhr,
|
else => try self.dispatchDirect(target, event, null, .{ .context = "dispatch" }),
|
||||||
.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);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.
|
// property is just a shortcut for calling addEventListener, but they are distinct.
|
||||||
// An event set via property cannot be removed by removeEventListener. If you
|
// An event set via property cannot be removed by removeEventListener. If you
|
||||||
// set both the property and add a listener, they both execute.
|
// set both the property and add a listener, they both execute.
|
||||||
const DispatchWithFunctionOptions = struct {
|
const DispatchDirectOptions = struct {
|
||||||
context: []const u8,
|
context: []const u8,
|
||||||
inject_target: bool = true,
|
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();
|
event.acquireRef();
|
||||||
defer event.deinit(false, self.page);
|
defer event.deinit(false, page);
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
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) {
|
if (comptime opts.inject_target) {
|
||||||
@@ -269,14 +246,15 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
|||||||
}
|
}
|
||||||
|
|
||||||
var was_dispatched = false;
|
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;
|
event._current_target = target;
|
||||||
if (func.callWithThis(void, target, .{event})) {
|
if (func.callWithThis(void, target, .{event})) {
|
||||||
was_dispatched = true;
|
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(.{
|
const list = self.lookup.get(.{
|
||||||
.event_target = @intFromPtr(target),
|
.event_target = @intFromPtr(target),
|
||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
}) orelse return;
|
}) 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 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;
|
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);
|
const activation_state = ActivationState.create(event, target, page);
|
||||||
|
|
||||||
// Defer runs even on early return - ensures event phase is reset
|
// 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),
|
.event_target = @intFromPtr(current_target),
|
||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
})) |list| {
|
})) |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: {
|
blk: {
|
||||||
// Get inline handler (e.g., onclick property) for this target
|
// Get inline handler (e.g., onclick property) for this target
|
||||||
if (self.getInlineHandler(target_et, event)) |inline_handler| {
|
if (self.getInlineHandler(target_et, event)) |inline_handler| {
|
||||||
was_handled.* = true;
|
was_handled = true;
|
||||||
event._current_target = target_et;
|
event._current_target = target_et;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
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,
|
.type_string = event._type_string,
|
||||||
.event_target = @intFromPtr(target_et),
|
.event_target = @intFromPtr(target_et),
|
||||||
})) |list| {
|
})) |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) {
|
if (event._stop_propagation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -425,7 +512,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
.event_target = @intFromPtr(current_target),
|
.event_target = @intFromPtr(current_target),
|
||||||
})) |list| {
|
})) |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 {
|
fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?js.Function.Global {
|
||||||
const global_event_handlers = @import("webapi/global_event_handlers.zig");
|
const global_event_handlers = @import("webapi/global_event_handlers.zig");
|
||||||
const handler_type = global_event_handlers.fromEventType(event._type_string.str()) orelse return null;
|
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
|
/// ```js
|
||||||
/// img.setAttribute("onload", "(() => { ... })()");
|
/// img.setAttribute("onload", "(() => { ... })()");
|
||||||
/// ```
|
/// ```
|
||||||
_element_attr_listeners: GlobalEventHandlersLookup = .empty,
|
_event_target_attr_listeners: GlobalEventHandlersLookup = .empty,
|
||||||
|
|
||||||
// Blob URL registry for URL.createObjectURL/revokeObjectURL
|
// Blob URL registry for URL.createObjectURL/revokeObjectURL
|
||||||
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
|
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
|
||||||
@@ -726,27 +726,23 @@ fn _documentIsComplete(self: *Page) !void {
|
|||||||
// Run load events before window.load.
|
// Run load events before window.load.
|
||||||
try self.dispatchLoad();
|
try self.dispatchLoad();
|
||||||
|
|
||||||
var ls: JS.Local.Scope = undefined;
|
|
||||||
self.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
// Dispatch window.load event.
|
// Dispatch window.load event.
|
||||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
|
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
|
||||||
// This event is weird, it's dispatched directly on the window, but
|
// This event is weird, it's dispatched directly on the window, but
|
||||||
// with the document as the target.
|
// with the document as the target.
|
||||||
event._target = self.document.asEventTarget();
|
event._target = self.document.asEventTarget();
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchDirect(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
ls.toLocal(self.window._on_load),
|
self.window._on_load,
|
||||||
.{ .inject_target = false, .context = "page load" },
|
.{ .inject_target = false, .context = "page load" },
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageshow_event = (try PageTransitionEvent.initTrusted(comptime .wrap("pageshow"), .{}, self)).asEvent();
|
const pageshow_event = (try PageTransitionEvent.initTrusted(comptime .wrap("pageshow"), .{}, self)).asEvent();
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchDirect(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
pageshow_event,
|
pageshow_event,
|
||||||
ls.toLocal(self.window._on_pageshow),
|
self.window._on_pageshow,
|
||||||
.{ .context = "page show" },
|
.{ .context = "page show" },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3127,20 +3123,13 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
|
|||||||
const form_element = form.asElement();
|
const form_element = form.asElement();
|
||||||
|
|
||||||
if (submit_opts.fire_event) {
|
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);
|
const submit_event = try Event.initTrusted(comptime .wrap("submit"), .{ .bubbles = true, .cancelable = true }, self);
|
||||||
|
|
||||||
var ls: JS.Local.Scope = undefined;
|
// so submit_event is still valid when we check _prevent_default
|
||||||
self.js.localScope(&ls);
|
submit_event.acquireRef();
|
||||||
defer ls.deinit();
|
defer submit_event.deinit(false, self);
|
||||||
|
|
||||||
try self._event_manager.dispatchWithFunction(
|
|
||||||
form_element.asEventTarget(),
|
|
||||||
submit_event,
|
|
||||||
ls.toLocal(onsubmit_handler),
|
|
||||||
.{ .context = "form submit" },
|
|
||||||
);
|
|
||||||
|
|
||||||
|
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
|
||||||
// If the submit event was prevented, don't submit the form
|
// If the submit event was prevented, don't submit the form
|
||||||
if (submit_event._prevent_default) {
|
if (submit_event._prevent_default) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
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 {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
|||||||
return self._proto;
|
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) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,10 +77,10 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page:
|
|||||||
|
|
||||||
// Dispatch abort event
|
// Dispatch abort event
|
||||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, page);
|
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, page);
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(self._on_abort),
|
self._on_abort,
|
||||||
.{ .context = "abort signal" },
|
.{ .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
|
// Static method to create an already-aborted signal
|
||||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||||
const signal = try init(page);
|
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;
|
return signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +136,7 @@ const TimeoutCallback = struct {
|
|||||||
|
|
||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
||||||
var ls: js.Local.Scope = undefined;
|
self.signal.abort(.{ .string = "TimeoutError" }, self.page) catch |err| {
|
||||||
self.page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
self.signal.abort(.{ .string = "TimeoutError" }, &ls.local, self.page) catch |err| {
|
|
||||||
log.warn(.app, "abort signal timeout", .{ .err = err });
|
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||||
};
|
};
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -183,12 +183,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, local, page);
|
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -196,7 +191,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
|||||||
// Perform the read (synchronous since data is in memory)
|
// Perform the read (synchronous since data is in memory)
|
||||||
const data = blob._slice;
|
const data = blob._slice;
|
||||||
const size = data.len;
|
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) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -216,8 +211,8 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
|||||||
|
|
||||||
self._ready_state = .done;
|
self._ready_state = .done;
|
||||||
|
|
||||||
try self.dispatch(.load, .{ .loaded = size, .total = size }, local, page);
|
try self.dispatch(.load, .{ .loaded = size, .total = size }, page);
|
||||||
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, local, page);
|
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *FileReader) !void {
|
pub fn abort(self: *FileReader) !void {
|
||||||
@@ -231,17 +226,12 @@ pub fn abort(self: *FileReader) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.dispatch(.abort, null, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.dispatch(.abort, null, local, page);
|
try self.dispatch(.load_end, null, page);
|
||||||
|
|
||||||
try self.dispatch(.load_end, null, local, 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: {
|
const field, const typ = comptime blk: {
|
||||||
break :blk switch (event_type) {
|
break :blk switch (event_type) {
|
||||||
.abort => .{ "_on_abort", "abort" },
|
.abort => .{ "_on_abort", "abort" },
|
||||||
@@ -260,10 +250,10 @@ fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Pr
|
|||||||
page,
|
page,
|
||||||
)).asEvent();
|
)).asEvent();
|
||||||
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
return page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(@field(self, field)),
|
@field(self, field),
|
||||||
.{ .context = "FileReader " ++ typ },
|
.{ .context = "FileReader " ++ typ },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,10 +80,10 @@ fn goInner(delta: i32, page: *Page) !void {
|
|||||||
if (entry._url) |url| {
|
if (entry._url) |url| {
|
||||||
if (try page.isSameOrigin(url)) {
|
if (try page.isSameOrigin(url)) {
|
||||||
const event = (try PopStateEvent.initTrusted(comptime .wrap("popstate"), .{ .state = entry._state.value }, page)).asEvent();
|
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(),
|
page.window.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
page.js.toLocal(page.window._on_popstate),
|
page.window._on_popstate,
|
||||||
.{ .context = "Pop State" },
|
.{ .context = "Pop State" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,14 +131,10 @@ const PostMessageCallback = struct {
|
|||||||
return null;
|
return null;
|
||||||
}).asEvent();
|
}).asEvent();
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
page._event_manager.dispatchDirect(
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
page._event_manager.dispatchWithFunction(
|
|
||||||
self.port.asEventTarget(),
|
self.port.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
ls.toLocal(self.port._on_message),
|
self.port._on_message,
|
||||||
.{ .context = "MessagePort message" },
|
.{ .context = "MessagePort message" },
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err(.dom, "MessagePort.postMessage", .{ .err = 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(),
|
.promise = try rejection.promise().temp(),
|
||||||
}, page)).asEvent();
|
}, page)).asEvent();
|
||||||
|
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
rejection.local.toLocal(self._on_unhandled_rejection),
|
self._on_unhandled_rejection,
|
||||||
.{ .inject_target = true, .context = "window.unhandledrejection" },
|
.{ .inject_target = true, .context = "window.unhandledrejection" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ pub fn getAttributeFunction(
|
|||||||
page: *Page,
|
page: *Page,
|
||||||
) !?js.Function.Global {
|
) !?js.Function.Global {
|
||||||
const element = self.asElement();
|
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;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +404,7 @@ pub fn getAttributeFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool {
|
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(
|
fn setAttributeListener(
|
||||||
@@ -421,7 +421,7 @@ fn setAttributeListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (listener_callback) |cb| {
|
if (listener_callback) |cb| {
|
||||||
try page._element_attr_listeners.put(page.arena, .{
|
try page._event_target_attr_listeners.put(page.arena, .{
|
||||||
.target = self.asEventTarget(),
|
.target = self.asEventTarget(),
|
||||||
.handler = listener_type,
|
.handler = listener_type,
|
||||||
}, cb);
|
}, cb);
|
||||||
@@ -429,7 +429,7 @@ fn setAttributeListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The listener is null, remove existing listener.
|
// The listener is null, remove existing listener.
|
||||||
_ = page._element_attr_listeners.remove(.{
|
_ = page._event_target_attr_listeners.remove(.{
|
||||||
.target = self.asEventTarget(),
|
.target = self.asEventTarget(),
|
||||||
.handler = listener_type,
|
.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 {
|
pub fn dispatch(self: *Navigation, func: js.Function.Global, event: *Event, page: *Page) !void {
|
||||||
var ls: js.Local.Scope = undefined;
|
return page._event_manager.dispatchDirect(
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
ls.toLocal(func),
|
func,
|
||||||
.{ .context = "Navigation" },
|
.{ .context = "Navigation" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void
|
|||||||
const page = self._page;
|
const page = self._page;
|
||||||
self._method = try parseMethod(method_);
|
self._method = try parseMethod(method_);
|
||||||
self._url = try URL.resolve(self._arena, page.base(), url, .{ .always_dupe = true, .encode = true });
|
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 {
|
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;
|
var ls: js.Local.Scope = undefined;
|
||||||
page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
defer ls.deinit();
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.stateChanged(.headers_received, local, page);
|
try self.stateChanged(.headers_received, page);
|
||||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, local, page);
|
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, page);
|
||||||
try self.stateChanged(.loading, local, page);
|
try self.stateChanged(.loading, page);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -412,14 +411,10 @@ fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
try self._proto.dispatch(.progress, .{
|
try self._proto.dispatch(.progress, .{
|
||||||
.total = self._response_len orelse 0,
|
.total = self._response_len orelse 0,
|
||||||
.loaded = self._response_data.items.len,
|
.loaded = self._response_data.items.len,
|
||||||
}, &ls.local, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||||
@@ -438,22 +433,17 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.stateChanged(.done, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.stateChanged(.done, local, page);
|
|
||||||
|
|
||||||
const loaded = self._response_data.items.len;
|
const loaded = self._response_data.items.len;
|
||||||
try self._proto.dispatch(.load, .{
|
try self._proto.dispatch(.load, .{
|
||||||
.total = loaded,
|
.total = loaded,
|
||||||
.loaded = loaded,
|
.loaded = loaded,
|
||||||
}, local, page);
|
}, page);
|
||||||
try self._proto.dispatch(.load_end, .{
|
try self._proto.dispatch(.load_end, .{
|
||||||
.total = loaded,
|
.total = loaded,
|
||||||
.loaded = loaded,
|
.loaded = loaded,
|
||||||
}, local, page);
|
}, page);
|
||||||
|
|
||||||
page.js.weakRef(self);
|
page.js.weakRef(self);
|
||||||
}
|
}
|
||||||
@@ -495,17 +485,12 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
|||||||
if (new_state != self._ready_state) {
|
if (new_state != self._ready_state) {
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.stateChanged(new_state, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.stateChanged(new_state, local, page);
|
|
||||||
if (is_abort) {
|
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(.err, null, page);
|
||||||
try self._proto.dispatch(.load_end, null, local, page);
|
try self._proto.dispatch(.load_end, null, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const level: log.Level = if (err == error.Abort) .debug else .err;
|
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) {
|
if (state == self._ready_state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -524,10 +509,10 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, local: *const js.Local
|
|||||||
self._ready_state = state;
|
self._ready_state = state;
|
||||||
|
|
||||||
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, page);
|
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, page);
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(self._on_ready_state_change),
|
self._on_ready_state_change,
|
||||||
.{ .context = "XHR state change" },
|
.{ .context = "XHR state change" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn asEventTarget(self: *XMLHttpRequestEventTarget) *EventTarget {
|
|||||||
return self._proto;
|
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: {
|
const field, const typ = comptime blk: {
|
||||||
break :blk switch (event_type) {
|
break :blk switch (event_type) {
|
||||||
.abort => .{ "_on_abort", "abort" },
|
.abort => .{ "_on_abort", "abort" },
|
||||||
@@ -63,10 +63,10 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
|
|||||||
page,
|
page,
|
||||||
)).asEvent();
|
)).asEvent();
|
||||||
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
return page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(@field(self, field)),
|
@field(self, field),
|
||||||
.{ .context = "XHR " ++ typ },
|
.{ .context = "XHR " ++ typ },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user