diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index 93c909a5..d126e1c8 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -6,9 +6,11 @@ const Node = @import("../../Node.zig"); const Element = @import("../../Element.zig"); const HtmlElement = @import("../Html.zig"); const Event = @import("../../Event.zig"); +const log = @import("../../../../log.zig"); const Image = @This(); _proto: *HtmlElement, +_on_load: ?js.Function.Global = null, pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image { const node = try page.createElementNS(.html, "img", null); @@ -151,6 +153,82 @@ pub const JsApi = struct { pub const loading = bridge.accessor(Image.getLoading, Image.setLoading, .{}); }; +/// Parameters passed to scheduler for "load" events. +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. We might want to check if src point to valid image. + _ = image.getAttributeSafe("src") orelse return; + // Exit if there's no `onload`. + const on_load_str = image.getAttributeSafe("onload") orelse return; + + // Derive function from string. + const on_load_func = page.js.stringToPersistedFunction(on_load_str) catch |err| { + log.err(.js, "Image.onload", .{ .err = err, .str = on_load_str }); + return; + }; + // Set onload. + self._on_load = on_load_func; + + const args = try page._factory.create(CallbackParams{ + .page = page, + .element = image, + }); + errdefer page._factory.destroy(args); + + // Execute it asynchronously; Chrome and FF seem to do like this. + // We may change the behavior if its inconsistent. + // + // I'm well aware that such scenario is possible: + // imageElement.onload = () => console.warn("HA HA HA overwritten!"); + // + // this is allowed both in Chrome and FF. Honestly it makes the logic + // quite easier, which might be the reason. + return page.scheduler.add( + args, + struct { + fn wrap(raw: *anyopaque) anyerror!?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 still there, dispatch with it. + if (_img._on_load) |_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; + } + + // Dispatch to addEventListener listeners. + try _page._event_manager.dispatch(event_target, event); + return null; + } + }.wrap, + 25, + .{ + .low_priority = false, + .name = "Image.Build.created", + }, + ); + } +}; + const testing = @import("../../../../testing.zig"); test "WebApi: HTML.Image" { try testing.htmlRunner("element/html/image.html", .{});