Skip to content

Commit b8fc60d

Browse files
authored
Merge pull request #975 from lightpanda-io/dynamic_script
Support dynamic scripts which are added to the DOM before src is set
2 parents 2ac1d39 + f0ca972 commit b8fc60d

File tree

4 files changed

+44
-37
lines changed

4 files changed

+44
-37
lines changed

src/browser/ScriptManager.zig

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -143,32 +143,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
143143
return;
144144
};
145145

146-
var onload: ?Script.Callback = null;
147-
var onerror: ?Script.Callback = null;
148-
149146
const page = self.page;
150-
if (page.getNodeState(@ptrCast(element))) |se| {
151-
// if the script has a node state, then it was dynamically added and thus
152-
// the onload/onerror were saved in the state (if there are any)
153-
if (se.onload) |function| {
154-
onload = .{ .function = function };
155-
}
156-
if (se.onerror) |function| {
157-
onerror = .{ .function = function };
158-
}
159-
} else {
160-
// if the script has no node state, then it could still be dynamically
161-
// added (could have been dynamically added, but no attributes were set
162-
// which required a node state to be created) or it could be a inline
163-
// <script>.
164-
if (try parser.elementGetAttribute(element, "onload")) |string| {
165-
onload = .{ .string = string };
166-
}
167-
if (try parser.elementGetAttribute(element, "onerror")) |string| {
168-
onerror = .{ .string = string };
169-
}
170-
}
171-
172147
var source: Script.Source = undefined;
173148
var remote_url: ?[:0]const u8 = null;
174149
if (try parser.elementGetAttribute(element, "src")) |src| {
@@ -184,8 +159,6 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
184159

185160
var script = Script{
186161
.kind = kind,
187-
.onload = onload,
188-
.onerror = onerror,
189162
.element = element,
190163
.source = source,
191164
.url = remote_url orelse page.url.raw,
@@ -558,8 +531,6 @@ const Script = struct {
558531
is_async: bool,
559532
is_defer: bool,
560533
source: Source,
561-
onload: ?Callback,
562-
onerror: ?Callback,
563534
element: *parser.Element,
564535

565536
const Kind = enum {
@@ -644,7 +615,7 @@ const Script = struct {
644615
}
645616

646617
fn executeCallback(self: *const Script, comptime typ: []const u8, page: *Page) void {
647-
const callback = @field(self, typ) orelse return;
618+
const callback = self.getCallback(typ, page) orelse return;
648619

649620
switch (callback) {
650621
.string => |str| {
@@ -687,6 +658,23 @@ const Script = struct {
687658
},
688659
}
689660
}
661+
662+
fn getCallback(self: *const Script, comptime typ: []const u8, page: *Page) ?Callback {
663+
const element = self.element;
664+
// first we check if there was an el.onload set directly on the
665+
// element in JavaScript (if so, it'd be stored in the node state)
666+
if (page.getNodeState(@ptrCast(element))) |se| {
667+
if (@field(se, typ)) |function| {
668+
return .{ .function = function };
669+
}
670+
}
671+
// if we have no node state, or if the node state has no onload/onerror
672+
// then check for the onload/onerror attribute
673+
if (parser.elementGetAttribute(element, typ) catch null) |string| {
674+
return .{ .string = string };
675+
}
676+
return null;
677+
}
690678
};
691679

692680
const BufferPool = struct {

src/browser/events/event.zig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ pub const EventHandler = struct {
262262
},
263263
}
264264
}
265-
266265
const callback = (try listener.callback(target)) orelse return null;
267266

268267
if (signal) |s| {

src/browser/html/elements.zig

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -862,12 +862,23 @@ pub const HTMLScriptElement = struct {
862862
) orelse "";
863863
}
864864

865-
pub fn set_src(self: *parser.Script, v: []const u8) !void {
866-
return try parser.elementSetAttribute(
865+
pub fn set_src(self: *parser.Script, v: []const u8, page: *Page) !void {
866+
try parser.elementSetAttribute(
867867
parser.scriptToElt(self),
868868
"src",
869869
v,
870870
);
871+
872+
if (try Node.get_isConnected(@alignCast(@ptrCast(self)))) {
873+
// There are sites which do set the src AFTER appending the script
874+
// tag to the document:
875+
// const s = document.createElement('script');
876+
// document.getElementsByTagName('body')[0].appendChild(s);
877+
// s.src = '...';
878+
// This should load the script.
879+
// addFromElement protects against double execution.
880+
try page.script_manager.addFromElement(@alignCast(@ptrCast(self)));
881+
}
871882
}
872883

873884
pub fn get_type(self: *parser.Script) !?[]const u8 {
@@ -878,7 +889,7 @@ pub const HTMLScriptElement = struct {
878889
}
879890

880891
pub fn set_type(self: *parser.Script, v: []const u8) !void {
881-
return try parser.elementSetAttribute(
892+
try parser.elementSetAttribute(
882893
parser.scriptToElt(self),
883894
"type",
884895
v,
@@ -893,7 +904,7 @@ pub const HTMLScriptElement = struct {
893904
}
894905

895906
pub fn set_text(self: *parser.Script, v: []const u8) !void {
896-
return try parser.elementSetAttribute(
907+
try parser.elementSetAttribute(
897908
parser.scriptToElt(self),
898909
"text",
899910
v,
@@ -908,7 +919,7 @@ pub const HTMLScriptElement = struct {
908919
}
909920

910921
pub fn set_integrity(self: *parser.Script, v: []const u8) !void {
911-
return try parser.elementSetAttribute(
922+
try parser.elementSetAttribute(
912923
parser.scriptToElt(self),
913924
"integrity",
914925
v,

src/browser/page.zig

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1110,13 +1110,22 @@ fn timestamp() u32 {
11101110
// so that's handled. And because we're only executing the inline <script> tags
11111111
// after the document is loaded, it's ok to execute any async and defer scripts
11121112
// immediately.
1113-
pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.C) void {
1113+
pub export fn scriptAddedCallback(ctx: ?*anyopaque, element: ?*parser.Element) callconv(.c) void {
11141114
const self: *Page = @alignCast(@ptrCast(ctx.?));
1115+
11151116
if (self.delayed_navigation) {
11161117
// if we're planning on navigating to another page, don't run this script
11171118
return;
11181119
}
11191120

1121+
// It's posisble for a script to be dynamically added without a src.
1122+
// const s = document.createElement('script');
1123+
// document.getElementsByTagName('body')[0].appendChild(s);
1124+
// The src can be set after. We handle that in HTMLScriptElement.set_src,
1125+
// but it's important we don't pass such elements to the script_manager
1126+
// here, else the script_manager will flag it as already-processed.
1127+
_ = parser.elementGetAttribute(element.?, "src") catch return orelse return;
1128+
11201129
self.script_manager.addFromElement(element.?) catch |err| {
11211130
log.warn(.browser, "dynamic script", .{ .err = err });
11221131
};

0 commit comments

Comments
 (0)