From 76a53bedbe095a3c20110fa0ab01476997871ad4 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Wed, 28 Jan 2026 17:26:56 +0300 Subject: [PATCH] split inline event listener logic to `Page.zig` and `Element.zig` --- src/browser/EventManager.zig | 146 -------------------- src/browser/Page.zig | 238 +++++++++++++++++++-------------- src/browser/webapi/Element.zig | 122 +++++++++++++++++ 3 files changed, 263 insertions(+), 243 deletions(-) diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 90cc888c..c21792c4 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -43,26 +43,11 @@ list_pool: std.heap.MemoryPool(std.DoublyLinkedList), lookup: std.AutoHashMapUnmanaged(usize, *std.DoublyLinkedList), dispatch_depth: usize, deferred_removals: std.ArrayList(struct { list: *std.DoublyLinkedList, listener: *Listener }), -/// Use this when a listener provided like this: -/// -/// ```html -/// -/// ``` -/// -/// Or: -/// -/// ```js -/// img.onload = () => { ... }; -/// ``` -inline_lookup: std.AutoHashMapUnmanaged(usize, js.Function.Global), pub fn init(page: *Page) EventManager { - lp.assert(@alignOf(EventTarget) == 8, "EventManager.init", .{ .event_target_alignment = @alignOf(EventTarget) }); - return .{ .page = page, .lookup = .{}, - .inline_lookup = .{}, .arena = page.arena, .list_pool = std.heap.MemoryPool(std.DoublyLinkedList).init(page.arena), .listener_pool = std.heap.MemoryPool(Listener).init(page.arena), @@ -83,32 +68,6 @@ pub const Callback = union(enum) { object: js.Object, }; -/// Sets an inline event listener (`onload`, `onclick`, `onwheel` etc.); -/// overrides the listener if there's already one. -pub fn setInlineListener( - self: *EventManager, - event_target: *EventTarget, - event_type: Listener.Type, - listener_callback: js.Function.Global, -) !void { - if (comptime IS_DEBUG) { - log.debug(.event, "EventManager.setInlineListener", .{ .event_target = event_target, .event_type = event_type }); - } - - const key = createLookupKey(event_target, event_type); - const gop = try self.inline_lookup.getOrPut(self.arena, key); - gop.value_ptr.* = listener_callback; -} - -/// Returns the inline event listener by the `EventTarget` and event type. -pub fn getInlineListener( - self: *const EventManager, - event_target: *EventTarget, - event_type: Listener.Type, -) ?js.Function.Global { - return self.inline_lookup.get(createLookupKey(event_target, event_type)); -} - pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, callback: Callback, opts: RegisterOptions) !void { if (comptime IS_DEBUG) { log.debug(.event, "eventManager.register", .{ .type = typ, .capture = opts.capture, .once = opts.once, .target = target }); @@ -484,13 +443,6 @@ fn findListener(list: *const std.DoublyLinkedList, typ: []const u8, callback: Ca return null; } -/// Creates a lookup key to use with `inline_lookup`. -fn createLookupKey(event_target: *EventTarget, event_type: Listener.Type) usize { - const ptr = @intFromPtr(event_target) >> 3; - lp.assert(ptr < (1 << 57), "createLookupKey: pointer overflow", .{ .ptr = ptr }); - return ptr | (@as(u64, @intFromEnum(event_type)) << 57); -} - const Listener = struct { typ: String, once: bool, @@ -500,104 +452,6 @@ const Listener = struct { signal: ?*@import("webapi/AbortSignal.zig") = null, node: std.DoublyLinkedList.Node, removed: bool = false, - - const Type = enum(u7) { - abort, - animationcancel, - animationend, - animationiteration, - animationstart, - auxclick, - beforeinput, - beforematch, - beforetoggle, - blur, - cancel, - canplay, - canplaythrough, - change, - click, - close, - command, - contentvisibilityautostatechange, - contextlost, - contextmenu, - contextrestored, - copy, - cuechange, - cut, - dblclick, - drag, - dragend, - dragenter, - dragexit, - dragleave, - dragover, - dragstart, - drop, - durationchange, - emptied, - ended, - @"error", - focus, - formdata, - fullscreenchange, - fullscreenerror, - gotpointercapture, - input, - invalid, - keydown, - keypress, - keyup, - load, - loadeddata, - loadedmetadata, - loadstart, - lostpointercapture, - mousedown, - mousemove, - mouseout, - mouseover, - mouseup, - paste, - pause, - play, - playing, - pointercancel, - pointerdown, - pointerenter, - pointerleave, - pointermove, - pointerout, - pointerover, - pointerrawupdate, - pointerup, - progress, - ratechange, - reset, - resize, - scroll, - scrollend, - securitypolicyviolation, - seeked, - seeking, - select, - selectionchange, - selectstart, - slotchange, - stalled, - submit, - @"suspend", - timeupdate, - toggle, - transitioncancel, - transitionend, - transitionrun, - transitionstart, - volumechange, - waiting, - wheel, - }; }; const Function = union(enum) { diff --git a/src/browser/Page.zig b/src/browser/Page.zig index ae580737..5e4f1113 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -102,6 +102,20 @@ _element_shadow_roots: Element.ShadowRootLookup = .{}, _node_owner_documents: Node.OwnerDocumentLookup = .{}, _element_assigned_slots: Element.AssignedSlotLookup = .{}, +/// Lazily-created inline event listeners (or listeners provided as attributes). +/// Avoids bloating all elements with extra function fields for rare usage. +/// +/// Use this when a listener provided like these: +/// +/// ```html +/// +/// ``` +/// +/// ```js +/// img.onload = () => { ... }; +/// ``` +_element_attr_listeners: Element.AttrListenerLookup = .{}, + _script_manager: ScriptManager, // List of active MutationObservers @@ -316,6 +330,9 @@ fn reset(self: *Page, comptime initializing: bool) !void { self._element_shadow_roots = .{}; self._node_owner_documents = .{}; self._element_assigned_slots = .{}; + + self._element_attr_listeners = .{}; + self._notified_network_idle = .init; self._notified_network_almost_idle = .init; @@ -1134,6 +1151,35 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen return null; } +/// Sets an inline event listener (`onload`, `onclick`, `onwheel` etc.); +/// overrides the listener if there's already one. +pub fn setAttrListener( + self: *Page, + element: *Element, + listener_type: Element.KnownListener, + listener_callback: JS.Function.Global, +) !void { + if (comptime IS_DEBUG) { + log.debug(.event, "Page.setAttrListener", .{ + .element = element, + .listener_type = listener_type, + }); + } + + const key = element.calcAttrListenerKey(listener_type); + const gop = try self._element_attr_listeners.getOrPut(self.arena, key); + gop.value_ptr.* = listener_callback; +} + +/// Returns the inline event listener by an element and listener type. +pub fn getAttrListener( + self: *const Page, + element: *Element, + listener_type: Element.KnownListener, +) ?JS.Function.Global { + return self._element_attr_listeners.get(element.calcAttrListenerKey(listener_type)); +} + pub fn registerPerformanceObserver(self: *Page, observer: *PerformanceObserver) !void { return self._performance_observers.append(self.arena, observer); } @@ -2182,8 +2228,6 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi if (has_on_prefix) { // Must be usable as function. const func = try self.js.stringToPersistedFunction(attr.value.slice()); - const target = element.asEventTarget(); - const event_manager = &self._event_manager; // Longest known listener kind is 32 bytes long. const remaining: u6 = @truncate(name.len -| 2); @@ -2193,160 +2237,160 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi switch (remaining) { 3 => if (@as(u24, @bitCast(unsafe[0..3].*)) == asUint("cut")) { - try event_manager.setInlineListener(target, .cut, func); + try self.setAttrListener(element, .cut, func); }, 4 => switch (@as(u32, @bitCast(unsafe[0..4].*))) { - asUint("blur") => try event_manager.setInlineListener(target, .blur, func), - asUint("copy") => try event_manager.setInlineListener(target, .copy, func), - asUint("drag") => try event_manager.setInlineListener(target, .drag, func), - asUint("drop") => try event_manager.setInlineListener(target, .drop, func), - asUint("load") => try event_manager.setInlineListener(target, .load, func), - asUint("play") => try event_manager.setInlineListener(target, .play, func), + asUint("blur") => try self.setAttrListener(element, .blur, func), + asUint("copy") => try self.setAttrListener(element, .copy, func), + asUint("drag") => try self.setAttrListener(element, .drag, func), + asUint("drop") => try self.setAttrListener(element, .drop, func), + asUint("load") => try self.setAttrListener(element, .load, func), + asUint("play") => try self.setAttrListener(element, .play, func), else => {}, }, 5 => switch (@as(u40, @bitCast(unsafe[0..5].*))) { - asUint("abort") => try event_manager.setInlineListener(target, .abort, func), - asUint("click") => try event_manager.setInlineListener(target, .click, func), - asUint("close") => try event_manager.setInlineListener(target, .close, func), - asUint("ended") => try event_manager.setInlineListener(target, .ended, func), - asUint("error") => try event_manager.setInlineListener(target, .@"error", func), - asUint("focus") => try event_manager.setInlineListener(target, .focus, func), - asUint("input") => try event_manager.setInlineListener(target, .input, func), - asUint("keyup") => try event_manager.setInlineListener(target, .keyup, func), - asUint("paste") => try event_manager.setInlineListener(target, .paste, func), - asUint("pause") => try event_manager.setInlineListener(target, .pause, func), - asUint("reset") => try event_manager.setInlineListener(target, .reset, func), - asUint("wheel") => try event_manager.setInlineListener(target, .wheel, func), + asUint("abort") => try self.setAttrListener(element, .abort, func), + asUint("click") => try self.setAttrListener(element, .click, func), + asUint("close") => try self.setAttrListener(element, .close, func), + asUint("ended") => try self.setAttrListener(element, .ended, func), + asUint("error") => try self.setAttrListener(element, .@"error", func), + asUint("focus") => try self.setAttrListener(element, .focus, func), + asUint("input") => try self.setAttrListener(element, .input, func), + asUint("keyup") => try self.setAttrListener(element, .keyup, func), + asUint("paste") => try self.setAttrListener(element, .paste, func), + asUint("pause") => try self.setAttrListener(element, .pause, func), + asUint("reset") => try self.setAttrListener(element, .reset, func), + asUint("wheel") => try self.setAttrListener(element, .wheel, func), else => {}, }, 6 => switch (@as(u48, @bitCast(unsafe[0..6].*))) { - asUint("cancel") => try event_manager.setInlineListener(target, .cancel, func), - asUint("change") => try event_manager.setInlineListener(target, .change, func), - asUint("resize") => try event_manager.setInlineListener(target, .resize, func), - asUint("scroll") => try event_manager.setInlineListener(target, .scroll, func), - asUint("seeked") => try event_manager.setInlineListener(target, .seeked, func), - asUint("select") => try event_manager.setInlineListener(target, .select, func), - asUint("submit") => try event_manager.setInlineListener(target, .submit, func), - asUint("toggle") => try event_manager.setInlineListener(target, .toggle, func), + asUint("cancel") => try self.setAttrListener(element, .cancel, func), + asUint("change") => try self.setAttrListener(element, .change, func), + asUint("resize") => try self.setAttrListener(element, .resize, func), + asUint("scroll") => try self.setAttrListener(element, .scroll, func), + asUint("seeked") => try self.setAttrListener(element, .seeked, func), + asUint("select") => try self.setAttrListener(element, .select, func), + asUint("submit") => try self.setAttrListener(element, .submit, func), + asUint("toggle") => try self.setAttrListener(element, .toggle, func), else => {}, }, 7 => switch (@as(u56, @bitCast(unsafe[0..7].*))) { - asUint("canplay") => try event_manager.setInlineListener(target, .canplay, func), - asUint("command") => try event_manager.setInlineListener(target, .command, func), - asUint("dragend") => try event_manager.setInlineListener(target, .dragend, func), - asUint("emptied") => try event_manager.setInlineListener(target, .emptied, func), - asUint("invalid") => try event_manager.setInlineListener(target, .invalid, func), - asUint("keydown") => try event_manager.setInlineListener(target, .keydown, func), - asUint("mouseup") => try event_manager.setInlineListener(target, .mouseup, func), - asUint("playing") => try event_manager.setInlineListener(target, .playing, func), - asUint("seeking") => try event_manager.setInlineListener(target, .seeking, func), - asUint("stalled") => try event_manager.setInlineListener(target, .stalled, func), - asUint("suspend") => try event_manager.setInlineListener(target, .@"suspend", func), - asUint("waiting") => try event_manager.setInlineListener(target, .waiting, func), + asUint("canplay") => try self.setAttrListener(element, .canplay, func), + asUint("command") => try self.setAttrListener(element, .command, func), + asUint("dragend") => try self.setAttrListener(element, .dragend, func), + asUint("emptied") => try self.setAttrListener(element, .emptied, func), + asUint("invalid") => try self.setAttrListener(element, .invalid, func), + asUint("keydown") => try self.setAttrListener(element, .keydown, func), + asUint("mouseup") => try self.setAttrListener(element, .mouseup, func), + asUint("playing") => try self.setAttrListener(element, .playing, func), + asUint("seeking") => try self.setAttrListener(element, .seeking, func), + asUint("stalled") => try self.setAttrListener(element, .stalled, func), + asUint("suspend") => try self.setAttrListener(element, .@"suspend", func), + asUint("waiting") => try self.setAttrListener(element, .waiting, func), else => {}, }, 8 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { - asUint("auxclick") => try event_manager.setInlineListener(target, .auxclick, func), - asUint("dblclick") => try event_manager.setInlineListener(target, .dblclick, func), - asUint("dragexit") => try event_manager.setInlineListener(target, .dragexit, func), - asUint("dragover") => try event_manager.setInlineListener(target, .dragover, func), - asUint("formdata") => try event_manager.setInlineListener(target, .formdata, func), - asUint("keypress") => try event_manager.setInlineListener(target, .keypress, func), - asUint("mouseout") => try event_manager.setInlineListener(target, .mouseout, func), - asUint("progress") => try event_manager.setInlineListener(target, .progress, func), + asUint("auxclick") => try self.setAttrListener(element, .auxclick, func), + asUint("dblclick") => try self.setAttrListener(element, .dblclick, func), + asUint("dragexit") => try self.setAttrListener(element, .dragexit, func), + asUint("dragover") => try self.setAttrListener(element, .dragover, func), + asUint("formdata") => try self.setAttrListener(element, .formdata, func), + asUint("keypress") => try self.setAttrListener(element, .keypress, func), + asUint("mouseout") => try self.setAttrListener(element, .mouseout, func), + asUint("progress") => try self.setAttrListener(element, .progress, func), else => {}, }, // Won't fit to 64-bit integer; we do 2 checks. 9 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { - asUint("cuechang") => if (unsafe[8] == 'e') try event_manager.setInlineListener(target, .cuechange, func), - asUint("dragente") => if (unsafe[8] == 'r') try event_manager.setInlineListener(target, .dragenter, func), - asUint("dragleav") => if (unsafe[8] == 'e') try event_manager.setInlineListener(target, .dragleave, func), - asUint("dragstar") => if (unsafe[8] == 't') try event_manager.setInlineListener(target, .dragstart, func), - asUint("loadstar") => if (unsafe[8] == 't') try event_manager.setInlineListener(target, .loadstart, func), - asUint("mousedow") => if (unsafe[8] == 'n') try event_manager.setInlineListener(target, .mousedown, func), - asUint("mousemov") => if (unsafe[8] == 'e') try event_manager.setInlineListener(target, .mousemove, func), - asUint("mouseove") => if (unsafe[8] == 'r') try event_manager.setInlineListener(target, .mouseover, func), - asUint("pointeru") => if (unsafe[8] == 'p') try event_manager.setInlineListener(target, .pointerup, func), - asUint("scrollen") => if (unsafe[8] == 'd') try event_manager.setInlineListener(target, .scrollend, func), + asUint("cuechang") => if (unsafe[8] == 'e') try self.setAttrListener(element, .cuechange, func), + asUint("dragente") => if (unsafe[8] == 'r') try self.setAttrListener(element, .dragenter, func), + asUint("dragleav") => if (unsafe[8] == 'e') try self.setAttrListener(element, .dragleave, func), + asUint("dragstar") => if (unsafe[8] == 't') try self.setAttrListener(element, .dragstart, func), + asUint("loadstar") => if (unsafe[8] == 't') try self.setAttrListener(element, .loadstart, func), + asUint("mousedow") => if (unsafe[8] == 'n') try self.setAttrListener(element, .mousedown, func), + asUint("mousemov") => if (unsafe[8] == 'e') try self.setAttrListener(element, .mousemove, func), + asUint("mouseove") => if (unsafe[8] == 'r') try self.setAttrListener(element, .mouseover, func), + asUint("pointeru") => if (unsafe[8] == 'p') try self.setAttrListener(element, .pointerup, func), + asUint("scrollen") => if (unsafe[8] == 'd') try self.setAttrListener(element, .scrollend, func), else => {}, }, 10 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { asUint("loadedda") => if (asUint("ta") == @as(u16, @bitCast(unsafe[8..10].*))) - try event_manager.setInlineListener(target, .loadeddata, func), + try self.setAttrListener(element, .loadeddata, func), asUint("pointero") => if (asUint("ut") == @as(u16, @bitCast(unsafe[8..10].*))) - try event_manager.setInlineListener(target, .pointerout, func), + try self.setAttrListener(element, .pointerout, func), asUint("ratechan") => if (asUint("ge") == @as(u16, @bitCast(unsafe[8..10].*))) - try event_manager.setInlineListener(target, .ratechange, func), + try self.setAttrListener(element, .ratechange, func), asUint("slotchan") => if (asUint("ge") == @as(u16, @bitCast(unsafe[8..10].*))) - try event_manager.setInlineListener(target, .slotchange, func), + try self.setAttrListener(element, .slotchange, func), asUint("timeupda") => if (asUint("te") == @as(u16, @bitCast(unsafe[8..10].*))) - try event_manager.setInlineListener(target, .timeupdate, func), + try self.setAttrListener(element, .timeupdate, func), else => {}, }, 11 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { asUint("beforein") => if (asUint("put") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .beforeinput, func), + try self.setAttrListener(element, .beforeinput, func), asUint("beforema") => if (asUint("tch") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .beforematch, func), + try self.setAttrListener(element, .beforematch, func), asUint("contextl") => if (asUint("ost") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .contextlost, func), + try self.setAttrListener(element, .contextlost, func), asUint("contextm") => if (asUint("enu") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .contextmenu, func), + try self.setAttrListener(element, .contextmenu, func), asUint("pointerd") => if (asUint("own") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .pointerdown, func), + try self.setAttrListener(element, .pointerdown, func), asUint("pointerm") => if (asUint("ove") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .pointermove, func), + try self.setAttrListener(element, .pointermove, func), asUint("pointero") => if (asUint("ver") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .pointerover, func), + try self.setAttrListener(element, .pointerover, func), asUint("selectst") => if (asUint("art") == @as(u24, @bitCast(unsafe[8..11].*))) - try event_manager.setInlineListener(target, .selectstart, func), + try self.setAttrListener(element, .selectstart, func), else => {}, }, 12 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { asUint("animatio") => if (asUint("nend") == @as(u32, @bitCast(unsafe[8..12].*))) - try event_manager.setInlineListener(target, .animationend, func), + try self.setAttrListener(element, .animationend, func), asUint("beforeto") => if (asUint("ggle") == @as(u32, @bitCast(unsafe[8..12].*))) - try event_manager.setInlineListener(target, .beforetoggle, func), + try self.setAttrListener(element, .beforetoggle, func), asUint("pointere") => if (asUint("nter") == @as(u32, @bitCast(unsafe[8..12].*))) - try event_manager.setInlineListener(target, .pointerenter, func), + try self.setAttrListener(element, .pointerenter, func), asUint("pointerl") => if (asUint("eave") == @as(u32, @bitCast(unsafe[8..12].*))) - try event_manager.setInlineListener(target, .pointerleave, func), + try self.setAttrListener(element, .pointerleave, func), asUint("volumech") => if (asUint("ange") == @as(u32, @bitCast(unsafe[8..12].*))) - try event_manager.setInlineListener(target, .volumechange, func), + try self.setAttrListener(element, .volumechange, func), else => {}, }, 13 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { asUint("pointerc") => if (asUint("ancel") == @as(u40, @bitCast(unsafe[8..13].*))) - try event_manager.setInlineListener(target, .pointercancel, func), + try self.setAttrListener(element, .pointercancel, func), asUint("transiti") => switch (@as(u40, @bitCast(unsafe[8..13].*))) { - asUint("onend") => try event_manager.setInlineListener(target, .transitionend, func), - asUint("onrun") => try event_manager.setInlineListener(target, .transitionrun, func), + asUint("onend") => try self.setAttrListener(element, .transitionend, func), + asUint("onrun") => try self.setAttrListener(element, .transitionrun, func), else => {}, }, else => {}, }, 14 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { asUint("animatio") => if (asUint("nstart") == @as(u48, @bitCast(unsafe[8..14].*))) - try event_manager.setInlineListener(target, .animationstart, func), + try self.setAttrListener(element, .animationstart, func), asUint("canplayt") => if (asUint("hrough") == @as(u48, @bitCast(unsafe[8..14].*))) - try event_manager.setInlineListener(target, .canplaythrough, func), + try self.setAttrListener(element, .canplaythrough, func), asUint("duration") => if (asUint("change") == @as(u48, @bitCast(unsafe[8..14].*))) - try event_manager.setInlineListener(target, .durationchange, func), + try self.setAttrListener(element, .durationchange, func), asUint("loadedme") => if (asUint("tadata") == @as(u48, @bitCast(unsafe[8..14].*))) - try event_manager.setInlineListener(target, .loadedmetadata, func), + try self.setAttrListener(element, .loadedmetadata, func), else => {}, }, 15 => switch (@as(u64, @bitCast(unsafe[0..8].*))) { asUint("animatio") => if (asUint("ncancel") == @as(u56, @bitCast(unsafe[8..15].*))) - try event_manager.setInlineListener(target, .animationcancel, func), + try self.setAttrListener(element, .animationcancel, func), asUint("contextr") => if (asUint("estored") == @as(u56, @bitCast(unsafe[8..15].*))) - try event_manager.setInlineListener(target, .contextrestored, func), + try self.setAttrListener(element, .contextrestored, func), asUint("fullscre") => if (asUint("enerror") == @as(u56, @bitCast(unsafe[8..15].*))) - try event_manager.setInlineListener(target, .fullscreenerror, func), + try self.setAttrListener(element, .fullscreenerror, func), asUint("selectio") => if (asUint("nchange") == @as(u56, @bitCast(unsafe[8..15].*))) - try event_manager.setInlineListener(target, .selectionchange, func), + try self.setAttrListener(element, .selectionchange, func), asUint("transiti") => if (asUint("onstart") == @as(u56, @bitCast(unsafe[8..15].*))) - try event_manager.setInlineListener(target, .transitionstart, func), + try self.setAttrListener(element, .transitionstart, func), else => {}, }, // Can't switch on vector types. @@ -2354,11 +2398,11 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi const as_vector: Vec16x8 = unsafe[0..16].*; if (@reduce(.And, as_vector == @as(Vec16x8, "fullscreenchange".*))) { - try event_manager.setInlineListener(target, .fullscreenchange, func); + try self.setAttrListener(element, .fullscreenchange, func); } else if (@reduce(.And, as_vector == @as(Vec16x8, "pointerrawupdate".*))) { - try event_manager.setInlineListener(target, .pointerrawupdate, func); + try self.setAttrListener(element, .pointerrawupdate, func); } else if (@reduce(.And, as_vector == @as(Vec16x8, "transitioncancel".*))) { - try event_manager.setInlineListener(target, .transitioncancel, func); + try self.setAttrListener(element, .transitioncancel, func); } }, 17 => { @@ -2367,7 +2411,7 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi const dirty = @reduce(.And, as_vector == @as(Vec16x8, "gotpointercaptur".*)) and unsafe[16] == 'e'; if (dirty) { - try event_manager.setInlineListener(target, .gotpointercapture, func); + try self.setAttrListener(element, .gotpointercapture, func); } }, 18 => { @@ -2376,12 +2420,12 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi const is_animationiteration = @reduce(.And, as_vector == @as(Vec16x8, "animationiterati".*)) and asUint("on") == @as(u16, @bitCast(unsafe[16..18].*)); if (is_animationiteration) { - try event_manager.setInlineListener(target, .animationiteration, func); + try self.setAttrListener(element, .animationiteration, func); } else { const is_lostpointercapture = @reduce(.And, as_vector == @as(Vec16x8, "lostpointercaptu".*)) and asUint("re") == @as(u16, @bitCast(unsafe[16..18].*)); if (is_lostpointercapture) { - try event_manager.setInlineListener(target, .lostpointercapture, func); + try self.setAttrListener(element, .lostpointercapture, func); } } }, @@ -2391,14 +2435,14 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi const dirty = @reduce(.And, as_vector == @as(Vec16x8, "securitypolicyvi".*)) and asUint("olation") == @as(u56, @bitCast(unsafe[16..23].*)); if (dirty) { - try event_manager.setInlineListener(target, .securitypolicyviolation, func); + try self.setAttrListener(element, .securitypolicyviolation, func); } }, 32 => { const as_vector: Vec32x8 = unsafe[0..32].*; if (@reduce(.And, as_vector == @as(Vec32x8, "contentvisibilityautostatechange".*))) { - try event_manager.setInlineListener(target, .contentvisibilityautostatechange, func); + try self.setAttrListener(element, .contentvisibilityautostatechange, func); } }, else => {}, diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index f983b514..512f5821 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -48,6 +48,128 @@ pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMT pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList); pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot); pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot); +/// Better to discriminate it since not directly a pointer int. +/// +/// See `calcAttrListenerKey` to obtain one. +pub const AttrListenerKey = u64; +/// Use `getAttrListenerKey` to create a key. +pub const AttrListenerLookup = std.AutoHashMapUnmanaged(AttrListenerKey, js.Function.Global); + +/// Enum of known event listeners; increasing the size of it (u7) +/// can cause `AttrListenerKey` to behave incorrectly. +pub const KnownListener = enum(u7) { + abort, + animationcancel, + animationend, + animationiteration, + animationstart, + auxclick, + beforeinput, + beforematch, + beforetoggle, + blur, + cancel, + canplay, + canplaythrough, + change, + click, + close, + command, + contentvisibilityautostatechange, + contextlost, + contextmenu, + contextrestored, + copy, + cuechange, + cut, + dblclick, + drag, + dragend, + dragenter, + dragexit, + dragleave, + dragover, + dragstart, + drop, + durationchange, + emptied, + ended, + @"error", + focus, + formdata, + fullscreenchange, + fullscreenerror, + gotpointercapture, + input, + invalid, + keydown, + keypress, + keyup, + load, + loadeddata, + loadedmetadata, + loadstart, + lostpointercapture, + mousedown, + mousemove, + mouseout, + mouseover, + mouseup, + paste, + pause, + play, + playing, + pointercancel, + pointerdown, + pointerenter, + pointerleave, + pointermove, + pointerout, + pointerover, + pointerrawupdate, + pointerup, + progress, + ratechange, + reset, + resize, + scroll, + scrollend, + securitypolicyviolation, + seeked, + seeking, + select, + selectionchange, + selectstart, + slotchange, + stalled, + submit, + @"suspend", + timeupdate, + toggle, + transitioncancel, + transitionend, + transitionrun, + transitionstart, + volumechange, + waiting, + wheel, +}; + +/// Calculates a lookup key (`AttrListenerKey`) to use with `AttrListenerLookup` for an element. +/// NEVER use generated key to retrieve a pointer back. Portability is not guaranteed. +pub fn calcAttrListenerKey(self: *Element, event_type: KnownListener) AttrListenerKey { + // We can use `Element` for the key too; `EventTarget` is strict about + // its size and alignment, though. + const target = self.asEventTarget(); + // Check if we have 3 bits available from alignment of 8. + lp.assert(@alignOf(@TypeOf(target)) == 8, "createLookupKey: incorrect alignment", .{ + .event_target_alignment = @alignOf(@TypeOf(target)), + }); + + const ptr = @intFromPtr(target) >> 3; + lp.assert(ptr < (1 << 57), "createLookupKey: pointer overflow", .{ .ptr = ptr }); + return ptr | (@as(u64, @intFromEnum(event_type)) << 57); +} pub const Namespace = enum(u8) { html,