prefer attributeChange to run side-effects

This should give more consistent results than using `setSrc`.
This commit is contained in:
Halil Durak
2026-01-23 13:25:48 +03:00
parent 5a44182d36
commit 90e5ae01ad

View File

@@ -162,15 +162,50 @@ pub const JsApi = struct {
pub const loading = bridge.accessor(Image.getLoading, Image.setLoading, .{}); 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 }; 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 const Build = struct {
pub fn created(node: *Node, page: *Page) !void { pub fn created(node: *Node, page: *Page) !void {
const self = node.as(Image); const self = node.as(Image);
const image = self.asElement(); const image = self.asElement();
// Exit if src not set. We might want to check if src point to valid image. // 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. // Set `onload` if provided.
blk: { blk: {
@@ -202,37 +237,7 @@ pub const Build = struct {
// quite easier, which might be the reason. // quite easier, which might be the reason.
return page.scheduler.add( return page.scheduler.add(
args, args,
struct { dispatchLoadEvent,
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,
25, 25,
.{ .{
.low_priority = false, .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"); const testing = @import("../../../../testing.zig");