From 8ecbd8e71c94f67e916d76c4dbfeb52bd6cc8da7 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Wed, 4 Feb 2026 23:17:25 +0300 Subject: [PATCH] add `Page._to_load` and implement load even dispatching for `Image` --- src/browser/Page.zig | 33 +++++++++- src/browser/webapi/element/html/Image.zig | 78 ++++------------------- 2 files changed, 42 insertions(+), 69 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 8e86c47a..8dd10fde 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -41,8 +41,10 @@ const Parser = @import("parser/Parser.zig"); const URL = @import("URL.zig"); const Node = @import("webapi/Node.zig"); const Event = @import("webapi/Event.zig"); +const EventTarget = @import("webapi/EventTarget.zig"); const CData = @import("webapi/CData.zig"); const Element = @import("webapi/Element.zig"); +const HtmlElement = @import("webapi/element/Html.zig"); const Window = @import("webapi/Window.zig"); const Location = @import("webapi/Location.zig"); const Document = @import("webapi/Document.zig"); @@ -124,6 +126,10 @@ _element_assigned_slots: Element.AssignedSlotLookup = .{}, /// ``` _element_attr_listeners: GlobalEventHandlersLookup = .{}, +/// `load` events that'll be fired before window's `load` event. +/// A call to `documentIsComplete` (which calls `_documentIsComplete`) resets it. +_to_load: std.ArrayList(*Element) = .{}, + _script_manager: ScriptManager, // List of active MutationObservers @@ -344,6 +350,8 @@ fn reset(self: *Page, comptime initializing: bool) !void { self._element_attr_listeners = .{}; + self._to_load = .{}; + self._notified_network_idle = .init; self._notified_network_almost_idle = .init; @@ -699,15 +707,34 @@ pub fn documentIsComplete(self: *Page) void { fn _documentIsComplete(self: *Page) !void { self.document._ready_state = .complete; - // dispatch window.load event const event = try Event.initTrusted("load", .{}, self); - // this event is weird, it's dispatched directly on the window, but - // with the document as the target var ls: JS.Local.Scope = undefined; self.js.localScope(&ls); defer ls.deinit(); + // Dispatch `_to_load` events before window.load. + for (self._to_load.items) |element| { + const maybe_inline_listener = self.getAttrListener(element, .onload); + + try self._event_manager.dispatchWithFunction( + element.asEventTarget(), + event, + ls.toLocal(maybe_inline_listener), + .{ .context = "Page dispatch load events" }, + ); + + if (comptime IS_DEBUG) { + log.debug(.page, "load event for element", .{ .element = element }); + } + } + + // `_to_load` can be cleaned here. + self._to_load.clearAndFree(self.arena); + + // Dispatch window.load event. + // 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( self.window.asEventTarget(), diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index 4a675e6b..10a37ce7 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -8,6 +8,8 @@ const HtmlElement = @import("../Html.zig"); const Event = @import("../../Event.zig"); const log = @import("../../../../log.zig"); +const IS_DEBUG = @import("builtin").mode == .Debug; + const Image = @This(); _proto: *HtmlElement, @@ -49,41 +51,6 @@ pub fn getSrc(self: *const Image, page: *Page) ![]const u8 { pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void { try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), page); - - const event_target = self.asNode().asEventTarget(); - - // Have to do this since `Scheduler` only allow passing a single arg. - const SetSrcCallback = struct { - page: *Page, - event_target: *@import("../../EventTarget.zig"), - }; - const args = try page._factory.create(SetSrcCallback{ - .page = page, - .event_target = event_target, - }); - errdefer page._factory.destroy(args); - - // We don't actually fetch the media, here we fake the load call. - try page.scheduler.add( - args, - struct { - fn wrap(raw: *anyopaque) anyerror!?u32 { - const _args: *SetSrcCallback = @ptrCast(@alignCast(raw)); - const _page = _args.page; - defer _page._factory.destroy(_args); - // Dispatch. - const event = try Event.initTrusted("load", .{}, _page); - try _page._event_manager.dispatch(_args.event_target, event); - - return null; - } - }.wrap, - 25, - .{ - .low_priority = false, - .name = "Image.setSrc", - }, - ); } pub fn getAlt(self: *const Image) []const u8 { @@ -152,39 +119,18 @@ pub const JsApi = struct { pub const loading = bridge.accessor(Image.getLoading, Image.setLoading, .{}); }; -/// Argument passed to `dispatchLoadEvent`. -const CallbackParams = struct { page: *Page, element: *Element }; +pub const Build = struct { + pub fn created(node: *Node, page: *Page) !void { + const self = node.as(Image); + const image = self.asElement(); + // Exit if src not set. + // TODO: We might want to check if src point to valid image. + _ = image.getAttributeSafe(comptime .wrap("src")) orelse return; -/// Callback passed to `Scheduler` to execute load listeners. -fn dispatchLoadEvent(raw: *anyopaque) !?u32 { - const _args: *CallbackParams = @ptrCast(@alignCast(raw)); - const _page = _args.page; - defer _page._factory.destroy(_args); - - const _element = _args.element; - const _img = _element.as(Image); - const event_target = _element.asEventTarget(); - const event = try Event.initTrusted("load", .{}, _page); - - // If onload provided, dispatch with it. - if (_img.getOnLoad(_page)) |_on_load| { - var ls: js.Local.Scope = undefined; - _page.js.localScope(&ls); - defer ls.deinit(); - - try _page._event_manager.dispatchWithFunction( - event_target, - event, - _on_load.local(&ls.local), - .{ .context = "Image.onload" }, - ); - return null; + // Push to `_to_load` to dispatch load event just before window load event. + return page._to_load.append(page.arena, image); } - - // Dispatch to addEventListener listeners. - try _page._event_manager.dispatch(event_target, event); - return null; -} +}; const testing = @import("../../../../testing.zig"); test "WebApi: HTML.Image" {