diff --git a/src/browser/Page.zig b/src/browser/Page.zig
index 17f25ade..fb70f9d8 100644
--- a/src/browser/Page.zig
+++ b/src/browser/Page.zig
@@ -1348,6 +1348,34 @@ pub fn createComment(self: *Page, text: []const u8) !*Node {
return cd.asNode();
}
+pub fn createCDATASection(self: *Page, data: []const u8) !*Node {
+ // Validate that the data doesn't contain "]]>"
+ if (std.mem.indexOf(u8, data, "]]>") != null) {
+ return error.InvalidCharacterError;
+ }
+
+ const owned_data = try self.dupeString(data);
+
+ // First allocate the Text node separately
+ const text_node = try self._factory.create(CData.Text{
+ ._proto = undefined,
+ });
+
+ // Then create the CData with cdata_section variant
+ const cd = try self._factory.node(CData{
+ ._proto = undefined,
+ ._type = .{ .cdata_section = .{
+ ._proto = text_node,
+ } },
+ ._data = owned_data,
+ });
+
+ // Set up the back pointer from Text to CData
+ text_node._proto = cd;
+
+ return cd.asNode();
+}
+
pub fn dupeString(self: *Page, value: []const u8) ![]const u8 {
if (String.intern(value)) |v| {
return v;
diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig
index 7f67f3a0..7cd2aace 100644
--- a/src/browser/js/bridge.zig
+++ b/src/browser/js/bridge.zig
@@ -491,6 +491,7 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/CData.zig"),
@import("../webapi/cdata/Comment.zig"),
@import("../webapi/cdata/Text.zig"),
+ @import("../webapi/cdata/CDATASection.zig"),
@import("../webapi/collections.zig"),
@import("../webapi/Console.zig"),
@import("../webapi/Crypto.zig"),
diff --git a/src/browser/tests/cdata/cdata_section.html b/src/browser/tests/cdata/cdata_section.html
new file mode 100644
index 00000000..5c991bb5
--- /dev/null
+++ b/src/browser/tests/cdata/cdata_section.html
@@ -0,0 +1,217 @@
+cdataClassName
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/CData.zig b/src/browser/webapi/CData.zig
index 0d2bc53e..a22a0d6c 100644
--- a/src/browser/webapi/CData.zig
+++ b/src/browser/webapi/CData.zig
@@ -24,6 +24,7 @@ const Page = @import("../Page.zig");
const Node = @import("Node.zig");
pub const Text = @import("cdata/Text.zig");
pub const Comment = @import("cdata/Comment.zig");
+pub const CDATASection = @import("cdata/CDATASection.zig");
const CData = @This();
@@ -34,6 +35,9 @@ _data: []const u8 = "",
pub const Type = union(enum) {
text: Text,
comment: Comment,
+ // This should be under Text, but that would require storing a _type union
+ // in text, which would add 8 bytes to every text node.
+ cdata_section: CDATASection,
};
pub fn asNode(self: *CData) *Node {
@@ -53,6 +57,7 @@ pub fn className(self: *const CData) []const u8 {
return switch (self._type) {
.text => "[object Text]",
.comment => "[object Comment]",
+ .cdata_section => "[object CDATASection]",
};
}
@@ -128,6 +133,7 @@ pub fn format(self: *const CData, writer: *std.io.Writer) !void {
return switch (self._type) {
.text => writer.print("{s}", .{self._data}),
.comment => writer.print("", .{self._data}),
+ .cdata_section => writer.print("", .{self._data}),
};
}
@@ -248,7 +254,7 @@ pub const JsApi = struct {
pub const bridge = js.Bridge(CData);
pub const Meta = struct {
- pub const name = "CData";
+ pub const name = "CharacterData";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig
index 928b97c0..1973b8f0 100644
--- a/src/browser/webapi/Document.zig
+++ b/src/browser/webapi/Document.zig
@@ -226,6 +226,13 @@ pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node
return page.createTextNode(data);
}
+pub fn createCDATASection(self: *const Document, data: []const u8, page: *Page) !*Node {
+ switch (self._type) {
+ .html => return error.NotSupported,
+ .generic => return page.createCDATASection(data),
+ }
+}
+
const Range = @import("Range.zig");
pub fn createRange(_: *const Document, page: *Page) !*Range {
return Range.init(page);
@@ -353,6 +360,7 @@ pub const JsApi = struct {
pub const createDocumentFragment = bridge.function(Document.createDocumentFragment, .{});
pub const createComment = bridge.function(Document.createComment, .{});
pub const createTextNode = bridge.function(Document.createTextNode, .{});
+ pub const createCDATASection = bridge.function(Document.createCDATASection, .{ .dom_exception = true });
pub const createRange = bridge.function(Document.createRange, .{});
pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true });
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index 32a2b18a..91560010 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -242,6 +242,7 @@ pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void {
.cdata => |c| switch (c._type) {
.comment => continue,
.text => try c.render(writer, .{ .trim_right = false, .trim_left = false }),
+ .cdata_section => try writer.writeAll(c._data),
},
.document => {},
.document_type => {},
diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig
index ef6de4a7..796aa018 100644
--- a/src/browser/webapi/Node.zig
+++ b/src/browser/webapi/Node.zig
@@ -213,6 +213,7 @@ pub fn getNodeName(self: *const Node, page: *Page) []const u8 {
.element => |el| el.getTagNameSpec(&page.buf),
.cdata => |cd| switch (cd._type) {
.text => "#text",
+ .cdata_section => "#cdata-section",
.comment => "#comment",
},
.document => "#document",
@@ -228,6 +229,7 @@ pub fn nodeType(self: *const Node) u8 {
.attribute => 2,
.cdata => |cd| switch (cd._type) {
.text => 3,
+ .cdata_section => 4,
.comment => 8,
},
.document => 9,
@@ -507,13 +509,14 @@ pub fn normalize(self: *Node, page: *Page) !void {
return self._normalize(page.call_arena, &buffer, page);
}
-pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) error{ OutOfMemory, StringTooLarge, NotSupported, NotImplemented }!*Node {
+pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) error{ OutOfMemory, StringTooLarge, NotSupported, NotImplemented, InvalidCharacterError }!*Node {
const deep = deep_ orelse false;
switch (self._type) {
.cdata => |cd| {
const data = cd.getData();
return switch (cd._type) {
.text => page.createTextNode(data),
+ .cdata_section => page.createCDATASection(data),
.comment => page.createComment(data),
};
},
diff --git a/src/browser/webapi/cdata/CDATASection.zig b/src/browser/webapi/cdata/CDATASection.zig
new file mode 100644
index 00000000..9a9d9e08
--- /dev/null
+++ b/src/browser/webapi/cdata/CDATASection.zig
@@ -0,0 +1,35 @@
+// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+const js = @import("../../js/js.zig");
+
+const Text = @import("Text.zig");
+
+const CDATASection = @This();
+
+_proto: *Text,
+
+pub const JsApi = struct {
+ pub const bridge = js.Bridge(CDATASection);
+
+ pub const Meta = struct {
+ pub const name = "CDATASection";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_id: bridge.ClassId = undefined;
+ };
+};