mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 08:18:59 +00:00
add CDATASection
This commit is contained in:
@@ -1348,6 +1348,34 @@ pub fn createComment(self: *Page, text: []const u8) !*Node {
|
|||||||
return cd.asNode();
|
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 {
|
pub fn dupeString(self: *Page, value: []const u8) ![]const u8 {
|
||||||
if (String.intern(value)) |v| {
|
if (String.intern(value)) |v| {
|
||||||
return v;
|
return v;
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/CData.zig"),
|
@import("../webapi/CData.zig"),
|
||||||
@import("../webapi/cdata/Comment.zig"),
|
@import("../webapi/cdata/Comment.zig"),
|
||||||
@import("../webapi/cdata/Text.zig"),
|
@import("../webapi/cdata/Text.zig"),
|
||||||
|
@import("../webapi/cdata/CDATASection.zig"),
|
||||||
@import("../webapi/collections.zig"),
|
@import("../webapi/collections.zig"),
|
||||||
@import("../webapi/Console.zig"),
|
@import("../webapi/Console.zig"),
|
||||||
@import("../webapi/Crypto.zig"),
|
@import("../webapi/Crypto.zig"),
|
||||||
|
|||||||
217
src/browser/tests/cdata/cdata_section.html
Normal file
217
src/browser/tests/cdata/cdata_section.html
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
cdataClassName<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<div id="container"></div>
|
||||||
|
|
||||||
|
<script id="createInHTMLDocument">
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
document.createCDATASection('test');
|
||||||
|
testing.fail('Should have thrown NotSupportedError');
|
||||||
|
} catch (err) {
|
||||||
|
testing.expectEqual('NotSupportedError', err.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="createInXMLDocument">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('Hello World');
|
||||||
|
|
||||||
|
testing.expectEqual(4, cdata.nodeType);
|
||||||
|
testing.expectEqual('#cdata-section', cdata.nodeName);
|
||||||
|
testing.expectEqual('Hello World', cdata.data);
|
||||||
|
testing.expectEqual(11, cdata.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataWithSpecialChars">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('<tag>&"quotes"</tag>');
|
||||||
|
|
||||||
|
testing.expectEqual('<tag>&"quotes"</tag>', cdata.data);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataRejectsEndMarker">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
|
||||||
|
testing.withError((err) => {
|
||||||
|
testing.expectEqual('InvalidCharacterError', err.name);
|
||||||
|
}, () => doc.createCDATASection('foo ]]> bar'));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataRejectsEndMarkerEdgeCase">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
|
||||||
|
testing.withError((err) => {
|
||||||
|
testing.expectEqual('InvalidCharacterError', err.name);
|
||||||
|
}, () => doc.createCDATASection(']]>'));
|
||||||
|
|
||||||
|
testing.withError((err) => {
|
||||||
|
testing.expectEqual('InvalidCharacterError', err.name);
|
||||||
|
}, () => doc.createCDATASection('start]]>end'));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataAllowsSimilarPatterns">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
|
||||||
|
const cdata1 = doc.createCDATASection(']>');
|
||||||
|
testing.expectEqual(']>', cdata1.data);
|
||||||
|
|
||||||
|
const cdata2 = doc.createCDATASection(']]');
|
||||||
|
testing.expectEqual(']]', cdata2.data);
|
||||||
|
|
||||||
|
const cdata3 = doc.createCDATASection('] ]>');
|
||||||
|
testing.expectEqual('] ]>', cdata3.data);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataCharacterDataMethods">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('Hello');
|
||||||
|
|
||||||
|
cdata.appendData(' World');
|
||||||
|
testing.expectEqual('Hello World', cdata.data);
|
||||||
|
testing.expectEqual(11, cdata.length);
|
||||||
|
|
||||||
|
cdata.deleteData(5, 6);
|
||||||
|
testing.expectEqual('Hello', cdata.data);
|
||||||
|
|
||||||
|
cdata.insertData(0, 'Hi ');
|
||||||
|
testing.expectEqual('Hi Hello', cdata.data);
|
||||||
|
|
||||||
|
cdata.replaceData(0, 3, 'Bye');
|
||||||
|
testing.expectEqual('ByeHello', cdata.data);
|
||||||
|
|
||||||
|
const sub = cdata.substringData(0, 3);
|
||||||
|
testing.expectEqual('Bye', sub);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataInheritance">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('test');
|
||||||
|
|
||||||
|
testing.expectEqual(true, cdata instanceof CDATASection);
|
||||||
|
testing.expectEqual(true, cdata instanceof Text);
|
||||||
|
testing.expectEqual(true, cdata instanceof CharacterData);
|
||||||
|
testing.expectEqual(true, cdata instanceof Node);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataWholeText">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('test data');
|
||||||
|
|
||||||
|
testing.expectEqual('test data', cdata.wholeText);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataClone">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('original data');
|
||||||
|
|
||||||
|
const clone = cdata.cloneNode(false);
|
||||||
|
|
||||||
|
testing.expectEqual(4, clone.nodeType);
|
||||||
|
testing.expectEqual('#cdata-section', clone.nodeName);
|
||||||
|
testing.expectEqual('original data', clone.data);
|
||||||
|
testing.expectEqual(true, clone !== cdata);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataRemove">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('test');
|
||||||
|
|
||||||
|
const root = doc.createElement('root');
|
||||||
|
doc.appendChild(root);
|
||||||
|
root.appendChild(cdata);
|
||||||
|
|
||||||
|
testing.expectEqual(1, root.childNodes.length);
|
||||||
|
testing.expectEqual(root, cdata.parentNode);
|
||||||
|
|
||||||
|
cdata.remove();
|
||||||
|
testing.expectEqual(0, root.childNodes.length);
|
||||||
|
testing.expectEqual(null, cdata.parentNode);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataBeforeAfter">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const root = doc.createElement('root');
|
||||||
|
doc.appendChild(root);
|
||||||
|
|
||||||
|
const cdata = doc.createCDATASection('middle');
|
||||||
|
root.appendChild(cdata);
|
||||||
|
|
||||||
|
const text1 = doc.createTextNode('before');
|
||||||
|
const text2 = doc.createTextNode('after');
|
||||||
|
|
||||||
|
cdata.before(text1);
|
||||||
|
cdata.after(text2);
|
||||||
|
|
||||||
|
testing.expectEqual(3, root.childNodes.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataReplaceWith">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const root = doc.createElement('root');
|
||||||
|
doc.appendChild(root);
|
||||||
|
|
||||||
|
const cdata = doc.createCDATASection('old');
|
||||||
|
root.appendChild(cdata);
|
||||||
|
|
||||||
|
const replacement = doc.createTextNode('new');
|
||||||
|
cdata.replaceWith(replacement);
|
||||||
|
|
||||||
|
testing.expectEqual(1, root.childNodes.length);
|
||||||
|
testing.expectEqual('new', root.childNodes[0].data);
|
||||||
|
testing.expectEqual(null, cdata.parentNode);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataSiblingNavigation">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const root = doc.createElement('root');
|
||||||
|
doc.appendChild(root);
|
||||||
|
|
||||||
|
const elem1 = doc.createElement('first');
|
||||||
|
const cdata = doc.createCDATASection('middle');
|
||||||
|
const elem2 = doc.createElement('last');
|
||||||
|
|
||||||
|
root.appendChild(elem1);
|
||||||
|
root.appendChild(cdata);
|
||||||
|
root.appendChild(elem2);
|
||||||
|
|
||||||
|
testing.expectEqual('LAST', cdata.nextElementSibling.tagName);
|
||||||
|
testing.expectEqual('FIRST', cdata.previousElementSibling.tagName);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="cdataEmptyString">
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const cdata = doc.createCDATASection('');
|
||||||
|
|
||||||
|
testing.expectEqual('', cdata.data);
|
||||||
|
testing.expectEqual(0, cdata.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -24,6 +24,7 @@ const Page = @import("../Page.zig");
|
|||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
pub const Text = @import("cdata/Text.zig");
|
pub const Text = @import("cdata/Text.zig");
|
||||||
pub const Comment = @import("cdata/Comment.zig");
|
pub const Comment = @import("cdata/Comment.zig");
|
||||||
|
pub const CDATASection = @import("cdata/CDATASection.zig");
|
||||||
|
|
||||||
const CData = @This();
|
const CData = @This();
|
||||||
|
|
||||||
@@ -34,6 +35,9 @@ _data: []const u8 = "",
|
|||||||
pub const Type = union(enum) {
|
pub const Type = union(enum) {
|
||||||
text: Text,
|
text: Text,
|
||||||
comment: Comment,
|
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 {
|
pub fn asNode(self: *CData) *Node {
|
||||||
@@ -53,6 +57,7 @@ pub fn className(self: *const CData) []const u8 {
|
|||||||
return switch (self._type) {
|
return switch (self._type) {
|
||||||
.text => "[object Text]",
|
.text => "[object Text]",
|
||||||
.comment => "[object Comment]",
|
.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) {
|
return switch (self._type) {
|
||||||
.text => writer.print("<text>{s}</text>", .{self._data}),
|
.text => writer.print("<text>{s}</text>", .{self._data}),
|
||||||
.comment => writer.print("<!-- {s} -->", .{self._data}),
|
.comment => writer.print("<!-- {s} -->", .{self._data}),
|
||||||
|
.cdata_section => writer.print("<![CDATA[{s}]]>", .{self._data}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +254,7 @@ pub const JsApi = struct {
|
|||||||
pub const bridge = js.Bridge(CData);
|
pub const bridge = js.Bridge(CData);
|
||||||
|
|
||||||
pub const Meta = struct {
|
pub const Meta = struct {
|
||||||
pub const name = "CData";
|
pub const name = "CharacterData";
|
||||||
pub const prototype_chain = bridge.prototypeChain();
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -226,6 +226,13 @@ pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node
|
|||||||
return page.createTextNode(data);
|
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");
|
const Range = @import("Range.zig");
|
||||||
pub fn createRange(_: *const Document, page: *Page) !*Range {
|
pub fn createRange(_: *const Document, page: *Page) !*Range {
|
||||||
return Range.init(page);
|
return Range.init(page);
|
||||||
@@ -353,6 +360,7 @@ pub const JsApi = struct {
|
|||||||
pub const createDocumentFragment = bridge.function(Document.createDocumentFragment, .{});
|
pub const createDocumentFragment = bridge.function(Document.createDocumentFragment, .{});
|
||||||
pub const createComment = bridge.function(Document.createComment, .{});
|
pub const createComment = bridge.function(Document.createComment, .{});
|
||||||
pub const createTextNode = bridge.function(Document.createTextNode, .{});
|
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 createRange = bridge.function(Document.createRange, .{});
|
||||||
pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true });
|
pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true });
|
||||||
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
|
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void {
|
|||||||
.cdata => |c| switch (c._type) {
|
.cdata => |c| switch (c._type) {
|
||||||
.comment => continue,
|
.comment => continue,
|
||||||
.text => try c.render(writer, .{ .trim_right = false, .trim_left = false }),
|
.text => try c.render(writer, .{ .trim_right = false, .trim_left = false }),
|
||||||
|
.cdata_section => try writer.writeAll(c._data),
|
||||||
},
|
},
|
||||||
.document => {},
|
.document => {},
|
||||||
.document_type => {},
|
.document_type => {},
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ pub fn getNodeName(self: *const Node, page: *Page) []const u8 {
|
|||||||
.element => |el| el.getTagNameSpec(&page.buf),
|
.element => |el| el.getTagNameSpec(&page.buf),
|
||||||
.cdata => |cd| switch (cd._type) {
|
.cdata => |cd| switch (cd._type) {
|
||||||
.text => "#text",
|
.text => "#text",
|
||||||
|
.cdata_section => "#cdata-section",
|
||||||
.comment => "#comment",
|
.comment => "#comment",
|
||||||
},
|
},
|
||||||
.document => "#document",
|
.document => "#document",
|
||||||
@@ -228,6 +229,7 @@ pub fn nodeType(self: *const Node) u8 {
|
|||||||
.attribute => 2,
|
.attribute => 2,
|
||||||
.cdata => |cd| switch (cd._type) {
|
.cdata => |cd| switch (cd._type) {
|
||||||
.text => 3,
|
.text => 3,
|
||||||
|
.cdata_section => 4,
|
||||||
.comment => 8,
|
.comment => 8,
|
||||||
},
|
},
|
||||||
.document => 9,
|
.document => 9,
|
||||||
@@ -507,13 +509,14 @@ pub fn normalize(self: *Node, page: *Page) !void {
|
|||||||
return self._normalize(page.call_arena, &buffer, page);
|
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;
|
const deep = deep_ orelse false;
|
||||||
switch (self._type) {
|
switch (self._type) {
|
||||||
.cdata => |cd| {
|
.cdata => |cd| {
|
||||||
const data = cd.getData();
|
const data = cd.getData();
|
||||||
return switch (cd._type) {
|
return switch (cd._type) {
|
||||||
.text => page.createTextNode(data),
|
.text => page.createTextNode(data),
|
||||||
|
.cdata_section => page.createCDATASection(data),
|
||||||
.comment => page.createComment(data),
|
.comment => page.createComment(data),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
35
src/browser/webapi/cdata/CDATASection.zig
Normal file
35
src/browser/webapi/cdata/CDATASection.zig
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user