From 90e5ae01ad65094b20b2ad95c267e2513f7b10ac Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Fri, 23 Jan 2026 13:25:48 +0300 Subject: [PATCH] prefer `attributeChange` to run side-effects This should give more consistent results than using `setSrc`. --- src/browser/webapi/element/html/Image.zig | 104 +++++++++++++++------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index f75e200a..fc3426be 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -162,15 +162,50 @@ pub const JsApi = struct { pub const loading = bridge.accessor(Image.getLoading, Image.setLoading, .{}); }; -/// Parameters passed to scheduler for "load" events. +/// Argument passed to `dispatchLoadEvent`. const CallbackParams = struct { page: *Page, element: *Element }; +/// 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._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; +} + 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; + const src = image.getAttributeSafe("src") orelse return; + // We can at least check if this is a valid URL. + if (!URL.isCompleteHTTPUrl(src)) { + return; + } // Set `onload` if provided. blk: { @@ -202,37 +237,7 @@ pub const Build = struct { // 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 provided, 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, + dispatchLoadEvent, 25, .{ .low_priority = false, @@ -240,6 +245,39 @@ pub const Build = struct { }, ); } + + pub fn attributeChange( + element: *Element, + attr_name: []const u8, + attr_value: []const u8, + page: *Page, + ) !void { + const self = element.as(Image); + const image = self.asElement(); + + const src_changed_and_valid = std.mem.eql(u8, attr_name, "src") and + URL.isCompleteHTTPUrl(attr_value); + + if (src_changed_and_valid) { + // Have to do this since `Scheduler` only allow passing a single arg. + const args = try page._factory.create(CallbackParams{ + .page = page, + .element = image, + }); + errdefer page._factory.destroy(args); + + // We don't actually fetch the media, here we fake the load call. + try page.scheduler.add( + args, + dispatchLoadEvent, + 25, + .{ + .low_priority = false, + .name = "Image.Build.attributeChange", + }, + ); + } + } }; const testing = @import("../../../../testing.zig");