diff --git a/src/browser/Page.zig b/src/browser/Page.zig
index f0c914ad..0599bab5 100644
--- a/src/browser/Page.zig
+++ b/src/browser/Page.zig
@@ -1010,6 +1010,14 @@ pub fn scriptAddedCallback(self: *Page, comptime from_parser: bool, script: *Ele
return;
}
+ if (comptime from_parser) {
+ // parser-inserted scripts have force-async set to false, but only if
+ // they have src or non-empty content
+ if (script._src.len > 0 or script.asNode().firstChild() != null) {
+ script._force_async = false;
+ }
+ }
+
self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| {
log.err(.page, "page.scriptAddedCallback", .{
.err = err,
@@ -2643,6 +2651,8 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
}
}
+ const parent_is_connected = parent.isConnected();
+
// Tri-state behavior for mutations:
// 1. from_parser=true, parse_mode=document -> no mutations (initial document parse)
// 2. from_parser=true, parse_mode=fragment -> mutations (innerHTML additions)
@@ -2658,6 +2668,15 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
// When the parser adds the node, nodeIsReady is only called when the
// nodeComplete() callback is executed.
try self.nodeIsReady(false, child);
+
+ // Check if text was added to a script that hasn't started yet.
+ if (child._type == .cdata and parent_is_connected) {
+ if (parent.is(Element.Html.Script)) |script| {
+ if (!script._executed) {
+ try self.nodeIsReady(false, parent);
+ }
+ }
+ }
}
// Notify mutation observers about childList change
@@ -2696,7 +2715,6 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
}
const parent_in_shadow = parent.is(ShadowRoot) != null or parent.isInShadowTree();
- const parent_is_connected = parent.isConnected();
if (!parent_in_shadow and !parent_is_connected) {
return;
diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig
index 6f55f43b..2baeef8d 100644
--- a/src/browser/ScriptManager.zig
+++ b/src/browser/ScriptManager.zig
@@ -159,7 +159,6 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
//
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/CData.zig b/src/browser/webapi/CData.zig
index 4fb6de6f..4e806c85 100644
--- a/src/browser/webapi/CData.zig
+++ b/src/browser/webapi/CData.zig
@@ -151,8 +151,13 @@ pub fn asNode(self: *CData) *Node {
pub fn is(self: *CData, comptime T: type) ?*T {
inline for (@typeInfo(Type).@"union".fields) |f| {
- if (f.type == T and @field(Type, f.name) == self._type) {
- return &@field(self._type, f.name);
+ if (@field(Type, f.name) == self._type) {
+ if (f.type == T) {
+ return &@field(self._type, f.name);
+ }
+ if (f.type == *T) {
+ return @field(self._type, f.name);
+ }
}
}
return null;
diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig
index 3673a1e7..2eca4047 100644
--- a/src/browser/webapi/Node.zig
+++ b/src/browser/webapi/Node.zig
@@ -285,6 +285,19 @@ pub fn getTextContentAlloc(self: *Node, allocator: Allocator) error{WriteFailed}
return data[0 .. data.len - 1 :0];
}
+/// Returns the "child text content" which is the concatenation of the data
+/// of all the Text node children of the node, in tree order.
+/// This differs from textContent which includes all descendant text.
+/// See: https://dom.spec.whatwg.org/#concept-child-text-content
+pub fn getChildTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!void {
+ var it = self.childrenIterator();
+ while (it.next()) |child| {
+ if (child.is(CData.Text)) |text| {
+ try writer.writeAll(text._proto._data.str());
+ }
+ }
+}
+
pub fn setTextContent(self: *Node, data: []const u8, page: *Page) !void {
switch (self._type) {
.element => |el| {
diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig
index ad3d9ef7..00860d5f 100644
--- a/src/browser/webapi/element/html/Script.zig
+++ b/src/browser/webapi/element/html/Script.zig
@@ -31,6 +31,8 @@ const Script = @This();
_proto: *HtmlElement,
_src: []const u8 = "",
_executed: bool = false,
+// dynamic scripts are forced to be async by default
+_force_async: bool = true,
pub fn asElement(self: *Script) *Element {
return self._proto._proto;
@@ -83,10 +85,11 @@ pub fn setCharset(self: *Script, value: []const u8, page: *Page) !void {
}
pub fn getAsync(self: *const Script) bool {
- return self.asConstElement().getAttributeSafe(comptime .wrap("async")) != null;
+ return self._force_async or self.asConstElement().getAttributeSafe(comptime .wrap("async")) != null;
}
pub fn setAsync(self: *Script, value: bool, page: *Page) !void {
+ self._force_async = false;
if (value) {
try self.asElement().setAttributeSafe(comptime .wrap("async"), .wrap(""), page);
} else {
@@ -136,7 +139,12 @@ pub const JsApi = struct {
try self.asNode().getTextContent(&buf.writer);
return buf.written();
}
- pub const text = bridge.accessor(_innerText, Script.setInnerText, .{});
+ pub const text = bridge.accessor(_text, Script.setInnerText, .{});
+ fn _text(self: *Script, page: *const Page) ![]const u8 {
+ var buf = std.Io.Writer.Allocating.init(page.call_arena);
+ try self.asNode().getChildTextContent(&buf.writer);
+ return buf.written();
+ }
};
pub const Build = struct {