Skip to content

Commit ef64fa3

Browse files
committed
Execute onload for dynamic script
Add support for onerror for static and dynamic scripts. Make script type checking case insensitive.
1 parent 6adb46a commit ef64fa3

File tree

2 files changed

+111
-21
lines changed

2 files changed

+111
-21
lines changed

src/browser/html/elements.zig

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const std = @import("std");
1919

2020
const parser = @import("../netsurf.zig");
2121
const generate = @import("../../runtime/generate.zig");
22+
const Env = @import("../env.zig").Env;
2223
const Page = @import("../page.zig").Page;
2324

2425
const URL = @import("../url/url.zig").URL;
@@ -746,6 +747,9 @@ pub const HTMLScriptElement = struct {
746747
pub const prototype = *HTMLElement;
747748
pub const subtype = .node;
748749

750+
onload: ?Env.Function = null,
751+
onerror: ?Env.Function = null,
752+
749753
pub fn get_src(self: *parser.Script) !?[]const u8 {
750754
return try parser.elementGetAttribute(
751755
parser.scriptToElt(self),
@@ -854,6 +858,26 @@ pub const HTMLScriptElement = struct {
854858

855859
return try parser.elementRemoveAttribute(parser.scriptToElt(self), "nomodule");
856860
}
861+
862+
pub fn get_onload(script: *parser.Script, page: *Page) !?Env.Function {
863+
const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null;
864+
return self.onload;
865+
}
866+
867+
pub fn set_onload(script: *parser.Script, function: ?Env.Function, page: *Page) !void {
868+
const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script));
869+
self.onload = function;
870+
}
871+
872+
pub fn get_onerror(script: *parser.Script, page: *Page) !?Env.Function {
873+
const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null;
874+
return self.onerror;
875+
}
876+
877+
pub fn set_onerror(script: *parser.Script, function: ?Env.Function, page: *Page) !void {
878+
const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script));
879+
self.onerror = function;
880+
}
857881
};
858882

859883
pub const HTMLSourceElement = struct {

src/browser/page.zig

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ pub const Page = struct {
327327
continue;
328328
}
329329

330-
const script = try Script.init(e) orelse continue;
330+
const script = try Script.init(e, null) orelse continue;
331331

332332
// TODO use fetchpriority
333333
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#fetchpriority
@@ -568,7 +568,7 @@ pub const Page = struct {
568568
}
569569

570570
pub fn getOrCreateNodeWrapper(self: *Page, comptime T: type, node: *parser.Node) !*T {
571-
if (try self.getNodeWrapper(T, node)) |wrap| {
571+
if (self.getNodeWrapper(T, node)) |wrap| {
572572
return wrap;
573573
}
574574

@@ -579,7 +579,7 @@ pub const Page = struct {
579579
return wrap;
580580
}
581581

582-
pub fn getNodeWrapper(_: *Page, comptime T: type, node: *parser.Node) !?*T {
582+
pub fn getNodeWrapper(_: *const Page, comptime T: type, node: *parser.Node) ?*T {
583583
if (parser.nodeGetEmbedderData(node)) |wrap| {
584584
return @alignCast(@ptrCast(wrap));
585585
}
@@ -608,8 +608,9 @@ const Script = struct {
608608
is_defer: bool,
609609
src: ?[]const u8,
610610
element: *parser.Element,
611-
// The javascript to load after we successfully load the script
612-
onload: ?[]const u8,
611+
// The javascript to load after we successfully load the script
612+
onload: ?Callback,
613+
onerror: ?Callback,
613614

614615
// The javascript to load if we have an error executing the script
615616
// For now, we ignore this, since we still have a lot of errors that we
@@ -621,7 +622,12 @@ const Script = struct {
621622
javascript,
622623
};
623624

624-
fn init(e: *parser.Element) !?Script {
625+
const Callback = union(enum) {
626+
string: []const u8,
627+
function: Env.Function,
628+
};
629+
630+
fn init(e: *parser.Element, page_: ?*const Page) !?Script {
625631
if (try parser.elementGetAttribute(e, "nomodule") != null) {
626632
// these scripts should only be loaded if we don't support modules
627633
// but since we do support modules, we can just skip them.
@@ -632,11 +638,42 @@ const Script = struct {
632638
return null;
633639
};
634640

641+
var onload: ?Callback = null;
642+
var onerror: ?Callback = null;
643+
644+
if (page_) |page| {
645+
// If we're given the page, then it means the script is dynamic
646+
// and we need to load the onload and onerror function (if there are
647+
// any) from our WebAPI.
648+
// This page == null is an optimization which isn't technically
649+
// correct, as a static script could have a dynamic onload/onerror
650+
// attached to it. But this seems quite unlikely and it does help
651+
// optimize loading scripts, of which there can be hundreds for a
652+
// page.
653+
const HTMLScriptElement = @import("html/elements.zig").HTMLScriptElement;
654+
if (page.getNodeWrapper(HTMLScriptElement, @ptrCast(e))) |se| {
655+
if (se.onload) |function| {
656+
onload = .{ .function = function };
657+
}
658+
if (se.onerror) |function| {
659+
onerror = .{ .function = function };
660+
}
661+
}
662+
} else {
663+
if (try parser.elementGetAttribute(e, "onload")) |string| {
664+
onload = .{ .string = string };
665+
}
666+
if (try parser.elementGetAttribute(e, "onerror")) |string| {
667+
onerror = .{ .string = string };
668+
}
669+
}
670+
635671
return .{
636672
.kind = kind,
637673
.element = e,
674+
.onload = onload,
675+
.onerror = onerror,
638676
.src = try parser.elementGetAttribute(e, "src"),
639-
.onload = try parser.elementGetAttribute(e, "onload"),
640677
.is_async = try parser.elementGetAttribute(e, "async") != null,
641678
.is_defer = try parser.elementGetAttribute(e, "defer") != null,
642679
};
@@ -653,9 +690,9 @@ const Script = struct {
653690
return .javascript;
654691
}
655692

656-
if (std.mem.eql(u8, script_type, "application/javascript")) return .javascript;
657-
if (std.mem.eql(u8, script_type, "text/javascript")) return .javascript;
658-
if (std.mem.eql(u8, script_type, "module")) return .module;
693+
if (std.ascii.eqlIgnoreCase(script_type, "application/javascript")) return .javascript;
694+
if (std.ascii.eqlIgnoreCase(script_type, "text/javascript")) return .javascript;
695+
if (std.ascii.eqlIgnoreCase(script_type, "module")) return .module;
659696

660697
return null;
661698
}
@@ -666,7 +703,7 @@ const Script = struct {
666703
defer try_catch.deinit();
667704

668705
const src = self.src orelse "inline";
669-
const res = switch (self.kind) {
706+
_ = switch (self.kind) {
670707
.javascript => page.scope.exec(body, src),
671708
.module => blk: {
672709
switch (try page.scope.module(body, src)) {
@@ -681,17 +718,46 @@ const Script = struct {
681718
if (try try_catch.err(page.arena)) |msg| {
682719
log.warn(.page, "eval script", .{ .src = src, .err = msg });
683720
}
721+
try self.executeCallback("onerror", page);
684722
return error.JsErr;
685723
};
686-
_ = res;
724+
try self.executeCallback("onload", page);
725+
}
687726

688-
if (self.onload) |onload| {
689-
_ = page.scope.exec(onload, "script_on_load") catch {
690-
if (try try_catch.err(page.arena)) |msg| {
691-
log.warn(.page, "eval onload", .{ .src = src, .err = msg });
692-
}
693-
return error.JsErr;
694-
};
727+
fn executeCallback(self: *const Script, comptime typ: []const u8, page: *Page) !void {
728+
const callback = @field(self, typ) orelse return;
729+
switch (callback) {
730+
.string => |str| {
731+
var try_catch: Env.TryCatch = undefined;
732+
try_catch.init(page.scope);
733+
defer try_catch.deinit();
734+
_ = page.scope.exec(str, typ) catch {
735+
if (try try_catch.err(page.arena)) |msg| {
736+
log.warn(.page, "script callback", .{
737+
.src = self.src,
738+
.err = msg,
739+
.type = typ,
740+
.@"inline" = true,
741+
});
742+
}
743+
};
744+
},
745+
.function => |f| {
746+
const Event = @import("events/event.zig").Event;
747+
const loadevt = try parser.eventCreate();
748+
defer parser.eventDestroy(loadevt);
749+
750+
var result: Env.Function.Result = undefined;
751+
f.tryCall(void, .{try Event.toInterface(loadevt)}, &result) catch {
752+
log.warn(.page, "script callback", .{
753+
.src = self.src,
754+
.type = typ,
755+
.err = result.exception,
756+
.stack = result.stack,
757+
.@"inline" = false,
758+
});
759+
};
760+
},
695761
}
696762
}
697763
};
@@ -719,11 +785,11 @@ fn timestamp() u32 {
719785
// after the document is loaded, it's ok to execute any async and defer scripts
720786
// immediately.
721787
pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
722-
var script = Script.init(element.?) catch |err| {
788+
const self: *Page = @alignCast(@ptrCast(ctx.?));
789+
var script = Script.init(element.?, self) catch |err| {
723790
log.warn(.page, "script added init error", .{ .err = err });
724791
return;
725792
} orelse return;
726793

727-
const self: *Page = @alignCast(@ptrCast(ctx.?));
728794
self.evalScript(&script);
729795
}

0 commit comments

Comments
 (0)