diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig
index 21211752..845c7da9 100644
--- a/src/browser/html/elements.zig
+++ b/src/browser/html/elements.zig
@@ -19,6 +19,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig");
+const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const URL = @import("../url/url.zig").URL;
@@ -746,6 +747,9 @@ pub const HTMLScriptElement = struct {
pub const prototype = *HTMLElement;
pub const subtype = .node;
+ onload: ?Env.Function = null,
+ onerror: ?Env.Function = null,
+
pub fn get_src(self: *parser.Script) !?[]const u8 {
return try parser.elementGetAttribute(
parser.scriptToElt(self),
@@ -854,6 +858,26 @@ pub const HTMLScriptElement = struct {
return try parser.elementRemoveAttribute(parser.scriptToElt(self), "nomodule");
}
+
+ pub fn get_onload(script: *parser.Script, page: *Page) !?Env.Function {
+ const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null;
+ return self.onload;
+ }
+
+ pub fn set_onload(script: *parser.Script, function: ?Env.Function, page: *Page) !void {
+ const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script));
+ self.onload = function;
+ }
+
+ pub fn get_onerror(script: *parser.Script, page: *Page) !?Env.Function {
+ const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null;
+ return self.onerror;
+ }
+
+ pub fn set_onerror(script: *parser.Script, function: ?Env.Function, page: *Page) !void {
+ const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script));
+ self.onerror = function;
+ }
};
pub const HTMLSourceElement = struct {
diff --git a/src/browser/page.zig b/src/browser/page.zig
index 42b7b527..726cf7d9 100644
--- a/src/browser/page.zig
+++ b/src/browser/page.zig
@@ -327,7 +327,7 @@ pub const Page = struct {
continue;
}
- const script = try Script.init(e) orelse continue;
+ const script = try Script.init(e, null) orelse continue;
// TODO use fetchpriority
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#fetchpriority
@@ -568,7 +568,7 @@ pub const Page = struct {
}
pub fn getOrCreateNodeWrapper(self: *Page, comptime T: type, node: *parser.Node) !*T {
- if (try self.getNodeWrapper(T, node)) |wrap| {
+ if (self.getNodeWrapper(T, node)) |wrap| {
return wrap;
}
@@ -579,7 +579,7 @@ pub const Page = struct {
return wrap;
}
- pub fn getNodeWrapper(_: *Page, comptime T: type, node: *parser.Node) !?*T {
+ pub fn getNodeWrapper(_: *const Page, comptime T: type, node: *parser.Node) ?*T {
if (parser.nodeGetEmbedderData(node)) |wrap| {
return @alignCast(@ptrCast(wrap));
}
@@ -608,8 +608,9 @@ const Script = struct {
is_defer: bool,
src: ?[]const u8,
element: *parser.Element,
- // The javascript to load after we successfully load the script
- onload: ?[]const u8,
+ // The javascript to load after we successfully load the script
+ onload: ?Callback,
+ onerror: ?Callback,
// The javascript to load if we have an error executing the script
// For now, we ignore this, since we still have a lot of errors that we
@@ -621,7 +622,12 @@ const Script = struct {
javascript,
};
- fn init(e: *parser.Element) !?Script {
+ const Callback = union(enum) {
+ string: []const u8,
+ function: Env.Function,
+ };
+
+ fn init(e: *parser.Element, page_: ?*const Page) !?Script {
if (try parser.elementGetAttribute(e, "nomodule") != null) {
// these scripts should only be loaded if we don't support modules
// but since we do support modules, we can just skip them.
@@ -632,11 +638,42 @@ const Script = struct {
return null;
};
+ var onload: ?Callback = null;
+ var onerror: ?Callback = null;
+
+ if (page_) |page| {
+ // If we're given the page, then it means the script is dynamic
+ // and we need to load the onload and onerror function (if there are
+ // any) from our WebAPI.
+ // This page == null is an optimization which isn't technically
+ // correct, as a static script could have a dynamic onload/onerror
+ // attached to it. But this seems quite unlikely and it does help
+ // optimize loading scripts, of which there can be hundreds for a
+ // page.
+ const HTMLScriptElement = @import("html/elements.zig").HTMLScriptElement;
+ if (page.getNodeWrapper(HTMLScriptElement, @ptrCast(e))) |se| {
+ if (se.onload) |function| {
+ onload = .{ .function = function };
+ }
+ if (se.onerror) |function| {
+ onerror = .{ .function = function };
+ }
+ }
+ } else {
+ if (try parser.elementGetAttribute(e, "onload")) |string| {
+ onload = .{ .string = string };
+ }
+ if (try parser.elementGetAttribute(e, "onerror")) |string| {
+ onerror = .{ .string = string };
+ }
+ }
+
return .{
.kind = kind,
.element = e,
+ .onload = onload,
+ .onerror = onerror,
.src = try parser.elementGetAttribute(e, "src"),
- .onload = try parser.elementGetAttribute(e, "onload"),
.is_async = try parser.elementGetAttribute(e, "async") != null,
.is_defer = try parser.elementGetAttribute(e, "defer") != null,
};
@@ -653,9 +690,9 @@ const Script = struct {
return .javascript;
}
- if (std.mem.eql(u8, script_type, "application/javascript")) return .javascript;
- if (std.mem.eql(u8, script_type, "text/javascript")) return .javascript;
- if (std.mem.eql(u8, script_type, "module")) return .module;
+ if (std.ascii.eqlIgnoreCase(script_type, "application/javascript")) return .javascript;
+ if (std.ascii.eqlIgnoreCase(script_type, "text/javascript")) return .javascript;
+ if (std.ascii.eqlIgnoreCase(script_type, "module")) return .module;
return null;
}
@@ -666,7 +703,7 @@ const Script = struct {
defer try_catch.deinit();
const src = self.src orelse "inline";
- const res = switch (self.kind) {
+ _ = switch (self.kind) {
.javascript => page.scope.exec(body, src),
.module => blk: {
switch (try page.scope.module(body, src)) {
@@ -681,17 +718,46 @@ const Script = struct {
if (try try_catch.err(page.arena)) |msg| {
log.warn(.page, "eval script", .{ .src = src, .err = msg });
}
+ try self.executeCallback("onerror", page);
return error.JsErr;
};
- _ = res;
+ try self.executeCallback("onload", page);
+ }
- if (self.onload) |onload| {
- _ = page.scope.exec(onload, "script_on_load") catch {
- if (try try_catch.err(page.arena)) |msg| {
- log.warn(.page, "eval onload", .{ .src = src, .err = msg });
- }
- return error.JsErr;
- };
+ fn executeCallback(self: *const Script, comptime typ: []const u8, page: *Page) !void {
+ const callback = @field(self, typ) orelse return;
+ switch (callback) {
+ .string => |str| {
+ var try_catch: Env.TryCatch = undefined;
+ try_catch.init(page.scope);
+ defer try_catch.deinit();
+ _ = page.scope.exec(str, typ) catch {
+ if (try try_catch.err(page.arena)) |msg| {
+ log.warn(.page, "script callback", .{
+ .src = self.src,
+ .err = msg,
+ .type = typ,
+ .@"inline" = true,
+ });
+ }
+ };
+ },
+ .function => |f| {
+ const Event = @import("events/event.zig").Event;
+ const loadevt = try parser.eventCreate();
+ defer parser.eventDestroy(loadevt);
+
+ var result: Env.Function.Result = undefined;
+ f.tryCall(void, .{try Event.toInterface(loadevt)}, &result) catch {
+ log.warn(.page, "script callback", .{
+ .src = self.src,
+ .type = typ,
+ .err = result.exception,
+ .stack = result.stack,
+ .@"inline" = false,
+ });
+ };
+ },
}
}
};
@@ -719,11 +785,11 @@ fn timestamp() u32 {
// after the document is loaded, it's ok to execute any async and defer scripts
// immediately.
pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
- var script = Script.init(element.?) catch |err| {
+ const self: *Page = @alignCast(@ptrCast(ctx.?));
+ var script = Script.init(element.?, self) catch |err| {
log.warn(.page, "script added init error", .{ .err = err });
return;
} orelse return;
- const self: *Page = @alignCast(@ptrCast(ctx.?));
self.evalScript(&script);
}