Skip to content

Commit ff05ce1

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 7202d75 commit ff05ce1

File tree

3 files changed

+118
-25
lines changed

3 files changed

+118
-25
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;
@@ -739,6 +740,9 @@ pub const HTMLScriptElement = struct {
739740
pub const prototype = *HTMLElement;
740741
pub const subtype = .node;
741742

743+
onload: ?Env.Function = null,
744+
onerror: ?Env.Function = null,
745+
742746
pub fn get_src(self: *parser.Script) !?[]const u8 {
743747
return try parser.elementGetAttribute(
744748
parser.scriptToElt(self),
@@ -847,6 +851,26 @@ pub const HTMLScriptElement = struct {
847851

848852
return try parser.elementRemoveAttribute(parser.scriptToElt(self), "nomodule");
849853
}
854+
855+
pub fn get_onload(script: *parser.Script, page: *Page) !?Env.Function {
856+
const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null;
857+
return self.onload;
858+
}
859+
860+
pub fn set_onload(script: *parser.Script, function: ?Env.Function, page: *Page) !void {
861+
const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script));
862+
self.onload = function;
863+
}
864+
865+
pub fn get_onerror(script: *parser.Script, page: *Page) !?Env.Function {
866+
const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null;
867+
return self.onerror;
868+
}
869+
870+
pub fn set_onerror(script: *parser.Script, function: ?Env.Function, page: *Page) !void {
871+
const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script));
872+
self.onerror = function;
873+
}
850874
};
851875

852876
pub const HTMLSourceElement = struct {

src/browser/netsurf.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2133,7 +2133,11 @@ pub inline fn documentCreateAttributeNS(doc: *Document, ns: []const u8, qname: [
21332133
return attr.?;
21342134
}
21352135

2136-
pub fn documentSetScriptAddedCallback(doc: *Document, ctx: *anyopaque, callback: c.dom_script_added_callback,) void {
2136+
pub fn documentSetScriptAddedCallback(
2137+
doc: *Document,
2138+
ctx: *anyopaque,
2139+
callback: c.dom_script_added_callback,
2140+
) void {
21372141
c._dom_document_set_script_added_callback(doc, ctx, callback);
21382142
}
21392143

src/browser/page.zig

Lines changed: 89 additions & 24 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
@@ -392,7 +392,6 @@ pub const Page = struct {
392392
};
393393
}
394394

395-
396395
// evalScript evaluates the src in priority.
397396
// if no src is present, we evaluate the text source.
398397
// https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
@@ -401,7 +400,7 @@ pub const Page = struct {
401400
try parser.documentHTMLSetCurrentScript(html_doc, @ptrCast(script.element));
402401

403402
defer parser.documentHTMLSetCurrentScript(html_doc, null) catch |err| {
404-
log.err(.page, "clear document script", .{.err = err});
403+
log.err(.page, "clear document script", .{ .err = err });
405404
};
406405

407406
const src = script.src orelse {
@@ -569,7 +568,7 @@ pub const Page = struct {
569568
}
570569

571570
pub fn getOrCreateNodeWrapper(self: *Page, comptime T: type, node: *parser.Node) !*T {
572-
if (try self.getNodeWrapper(T, node)) |wrap| {
571+
if (self.getNodeWrapper(T, node)) |wrap| {
573572
return wrap;
574573
}
575574

@@ -580,7 +579,7 @@ pub const Page = struct {
580579
return wrap;
581580
}
582581

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

615615
// The javascript to load if we have an error executing the script
616616
// For now, we ignore this, since we still have a lot of errors that we
@@ -622,7 +622,12 @@ const Script = struct {
622622
javascript,
623623
};
624624

625-
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 {
626631
if (try parser.elementGetAttribute(e, "nomodule") != null) {
627632
// these scripts should only be loaded if we don't support modules
628633
// but since we do support modules, we can just skip them.
@@ -633,11 +638,42 @@ const Script = struct {
633638
return null;
634639
};
635640

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+
636671
return .{
637672
.kind = kind,
638673
.element = e,
674+
.onload = onload,
675+
.onerror = onerror,
639676
.src = try parser.elementGetAttribute(e, "src"),
640-
.onload = try parser.elementGetAttribute(e, "onload"),
641677
.is_async = try parser.elementGetAttribute(e, "async") != null,
642678
.is_defer = try parser.elementGetAttribute(e, "defer") != null,
643679
};
@@ -654,9 +690,9 @@ const Script = struct {
654690
return .javascript;
655691
}
656692

657-
if (std.mem.eql(u8, script_type, "application/javascript")) return .javascript;
658-
if (std.mem.eql(u8, script_type, "text/javascript")) return .javascript;
659-
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;
660696

661697
return null;
662698
}
@@ -667,7 +703,7 @@ const Script = struct {
667703
defer try_catch.deinit();
668704

669705
const src = self.src orelse "inline";
670-
const res = switch (self.kind) {
706+
_ = switch (self.kind) {
671707
.javascript => page.scope.exec(body, src),
672708
.module => blk: {
673709
switch (try page.scope.module(body, src)) {
@@ -682,17 +718,46 @@ const Script = struct {
682718
if (try try_catch.err(page.arena)) |msg| {
683719
log.warn(.page, "eval script", .{ .src = src, .err = msg });
684720
}
721+
try self.executeCallback("onerror", page);
685722
return error.JsErr;
686723
};
687-
_ = res;
724+
try self.executeCallback("onload", page);
725+
}
688726

689-
if (self.onload) |onload| {
690-
_ = page.scope.exec(onload, "script_on_load") catch {
691-
if (try try_catch.err(page.arena)) |msg| {
692-
log.warn(.page, "eval onload", .{ .src = src, .err = msg });
693-
}
694-
return error.JsErr;
695-
};
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+
},
696761
}
697762
}
698763
};
@@ -720,11 +785,11 @@ fn timestamp() u32 {
720785
// after the document is loaded, it's ok to execute any async and defer scripts
721786
// immediately.
722787
pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
723-
var script = Script.init(element.?) catch |err| {
724-
log.warn(.page, "script added init error", .{.err = err});
788+
const self: *Page = @alignCast(@ptrCast(ctx.?));
789+
var script = Script.init(element.?, self) catch |err| {
790+
log.warn(.page, "script added init error", .{ .err = err });
725791
return;
726792
} orelse return;
727793

728-
const self: *Page = @alignCast(@ptrCast(ctx.?));
729794
self.evalScript(&script);
730795
}

0 commit comments

Comments
 (0)