Dynamically added scripts default to async

This commit is contained in:
Karl Seguin
2025-12-21 16:51:39 +08:00
parent 32c83d166d
commit 8fbd64955f
4 changed files with 24 additions and 8 deletions

View File

@@ -895,8 +895,8 @@ pub fn tick(self: *Page) void {
self.js.runMicrotasks(); self.js.runMicrotasks();
} }
pub fn scriptAddedCallback(self: *Page, script: *Element.Html.Script) !void { pub fn scriptAddedCallback(self: *Page, comptime from_parser: bool, script: *Element.Html.Script) !void {
self._script_manager.addFromElement(script, "parsing") catch |err| { self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| {
log.err(.page, "page.scriptAddedCallback", .{ log.err(.page, "page.scriptAddedCallback", .{
.err = err, .err = err,
.src = script.asElement().getAttributeSafe("src"), .src = script.asElement().getAttributeSafe("src"),
@@ -2180,7 +2180,7 @@ fn nodeIsReady(self: *Page, comptime from_parser: bool, node: *Node) !void {
return; return;
} }
self.scriptAddedCallback(script) catch |err| { self.scriptAddedCallback(from_parser, script) catch |err| {
log.err(.page, "page.nodeIsReady", .{ .err = err }); log.err(.page, "page.nodeIsReady", .{ .err = err });
return err; return err;
}; };

View File

@@ -143,7 +143,7 @@ fn clearList(list: *std.DoublyLinkedList) void {
} }
} }
pub fn addFromElement(self: *ScriptManager, script_element: *Element.Html.Script, comptime ctx: []const u8) !void { pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_element: *Element.Html.Script, comptime ctx: []const u8) !void {
if (script_element._executed) { if (script_element._executed) {
// If a script tag gets dynamically created and added to the dom: // If a script tag gets dynamically created and added to the dom:
// document.getElementsByTagName('head')[0].appendChild(script) // document.getElementsByTagName('head')[0].appendChild(script)
@@ -220,12 +220,25 @@ pub fn addFromElement(self: *ScriptManager, script_element: *Element.Html.Script
if (source == .@"inline") { if (source == .@"inline") {
break :blk if (kind == .module) .@"defer" else .normal; break :blk if (kind == .module) .@"defer" else .normal;
} }
if (element.getAttributeSafe("async") != null) { if (element.getAttributeSafe("async") != null) {
break :blk .async; break :blk .async;
} }
// Check for defer or module (before checking dynamic script default)
if (kind == .module or element.getAttributeSafe("defer") != null) { if (kind == .module or element.getAttributeSafe("defer") != null) {
break :blk .@"defer"; break :blk .@"defer";
} }
// For dynamically-inserted scripts (not from parser), default to async
// unless async was explicitly set to false (which removes the attribute)
// and defer was set to true (checked above)
if (comptime !from_parser) {
// Script has src and no explicit async/defer attributes
// Per HTML spec, dynamically created scripts default to async
break :blk .async;
}
break :blk .normal; break :blk .normal;
}, },
}; };

View File

@@ -2,12 +2,15 @@
<head></head> <head></head>
<script src="../../../testing.js"></script> <script src="../../../testing.js"></script>
<script id=append_before_src> <script id=append_before_src1>
loaded1 = 0; loaded1 = 0;
const script1 = document.createElement('script'); const script1 = document.createElement('script');
script1.async = false;
script1.src = "dynamic1.js"; script1.src = "dynamic1.js";
document.getElementsByTagName('head')[0].appendChild(script1); document.getElementsByTagName('head')[0].appendChild(script1);
testing.eventually(() => {
testing.expectEqual(1, loaded1); testing.expectEqual(1, loaded1);
});
</script> </script>
<script id=no_double_execute> <script id=no_double_execute>
@@ -17,7 +20,7 @@
}); });
</script> </script>
<script id=append_before_src> <script id=append_before_src2>
loaded2 = 0; loaded2 = 0;
const script2a = document.createElement('script'); const script2a = document.createElement('script');
script2a.src = "dynamic2.js"; script2a.src = "dynamic2.js";

View File

@@ -54,7 +54,7 @@ pub fn setSrc(self: *Script, src: []const u8, page: *Page) !void {
try element.setAttributeSafe("src", src, page); try element.setAttributeSafe("src", src, page);
self._src = element.getAttributeSafe("src") orelse unreachable; self._src = element.getAttributeSafe("src") orelse unreachable;
if (element.asNode().isConnected()) { if (element.asNode().isConnected()) {
try page.scriptAddedCallback(self); try page.scriptAddedCallback(false, self);
} }
} }