mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Add Document.replaceChildren
Improve correctness (hierarchy validation) of various Document functions.
This commit is contained in:
@@ -201,8 +201,8 @@ cdataClassName<!DOCTYPE html>
|
|||||||
root.appendChild(cdata);
|
root.appendChild(cdata);
|
||||||
root.appendChild(elem2);
|
root.appendChild(elem2);
|
||||||
|
|
||||||
testing.expectEqual('LAST', cdata.nextElementSibling.tagName);
|
testing.expectEqual('last', cdata.nextElementSibling.tagName);
|
||||||
testing.expectEqual('FIRST', cdata.previousElementSibling.tagName);
|
testing.expectEqual('first', cdata.previousElementSibling.tagName);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
344
src/browser/tests/document/replace_children.html
Normal file
344
src/browser/tests/document/replace_children.html
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>document.replaceChildren Tests</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="test">Original content</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script id=error_multiple_elements>
|
||||||
|
{
|
||||||
|
// Test that we cannot have more than one Element child
|
||||||
|
const doc = new Document();
|
||||||
|
const div1 = doc.createElement('div');
|
||||||
|
const div2 = doc.createElement('div');
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.replaceChildren(div1, div2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_multiple_elements_via_fragment>
|
||||||
|
{
|
||||||
|
// Test that we cannot have more than one Element child via DocumentFragment
|
||||||
|
const doc = new Document();
|
||||||
|
const fragment = doc.createDocumentFragment();
|
||||||
|
fragment.appendChild(doc.createElement('div'));
|
||||||
|
fragment.appendChild(doc.createElement('span'));
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.replaceChildren(fragment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_multiple_doctypes>
|
||||||
|
{
|
||||||
|
// Test that we cannot have more than one DocumentType child
|
||||||
|
const doc = new Document();
|
||||||
|
const doctype1 = doc.implementation.createDocumentType('html', '', '');
|
||||||
|
const doctype2 = doc.implementation.createDocumentType('html', '', '');
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.replaceChildren(doctype1, doctype2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_text_node>
|
||||||
|
{
|
||||||
|
// Test that we cannot insert Text nodes directly into Document
|
||||||
|
const doc = new Document();
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.replaceChildren('Just text');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_text_with_element>
|
||||||
|
{
|
||||||
|
// Test that we cannot insert Text nodes even with valid Element
|
||||||
|
const doc = new Document();
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.replaceChildren('Text 1', html, 'Text 2');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_append_multiple_elements>
|
||||||
|
{
|
||||||
|
// Test that append also validates
|
||||||
|
const doc = new Document();
|
||||||
|
doc.append(doc.createElement('html'));
|
||||||
|
|
||||||
|
const div = doc.createElement('div');
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.append(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_prepend_multiple_elements>
|
||||||
|
{
|
||||||
|
// Test that prepend also validates
|
||||||
|
const doc = new Document();
|
||||||
|
doc.prepend(doc.createElement('html'));
|
||||||
|
|
||||||
|
const div = doc.createElement('div');
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.prepend(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_append_text>
|
||||||
|
{
|
||||||
|
// Test that append rejects text nodes
|
||||||
|
const doc = new Document();
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.append('text');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_prepend_text>
|
||||||
|
{
|
||||||
|
// Test that prepend rejects text nodes
|
||||||
|
const doc = new Document();
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.prepend('text');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_with_single_element>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
html.id = 'replaced';
|
||||||
|
html.textContent = 'New content';
|
||||||
|
|
||||||
|
doc.replaceChildren(html);
|
||||||
|
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
testing.expectEqual(html, doc.firstChild);
|
||||||
|
testing.expectEqual('replaced', doc.firstChild.id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_with_comments>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const comment1 = doc.createComment('Comment 1');
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
const comment2 = doc.createComment('Comment 2');
|
||||||
|
|
||||||
|
doc.replaceChildren(comment1, html, comment2);
|
||||||
|
|
||||||
|
testing.expectEqual(3, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.firstChild.nodeName);
|
||||||
|
testing.expectEqual('Comment 1', doc.firstChild.textContent);
|
||||||
|
testing.expectEqual('html', doc.childNodes[1].nodeName);
|
||||||
|
testing.expectEqual('#comment', doc.lastChild.nodeName);
|
||||||
|
testing.expectEqual('Comment 2', doc.lastChild.textContent);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_with_empty>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
// First add some content
|
||||||
|
const div = doc.createElement('div');
|
||||||
|
doc.replaceChildren(div);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
|
||||||
|
// Now replace with nothing
|
||||||
|
doc.replaceChildren();
|
||||||
|
|
||||||
|
testing.expectEqual(0, doc.childNodes.length);
|
||||||
|
testing.expectEqual(null, doc.firstChild);
|
||||||
|
testing.expectEqual(null, doc.lastChild);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_removes_old_children>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const comment1 = doc.createComment('old');
|
||||||
|
|
||||||
|
doc.replaceChildren(comment1);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
testing.expectEqual(doc, comment1.parentNode);
|
||||||
|
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
html.id = 'new';
|
||||||
|
|
||||||
|
doc.replaceChildren(html);
|
||||||
|
|
||||||
|
// Old child should be removed
|
||||||
|
testing.expectEqual(null, comment1.parentNode);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
testing.expectEqual('new', doc.firstChild.id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_with_document_fragment_valid>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const fragment = doc.createDocumentFragment();
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
const comment = doc.createComment('comment');
|
||||||
|
|
||||||
|
fragment.appendChild(comment);
|
||||||
|
fragment.appendChild(html);
|
||||||
|
|
||||||
|
doc.replaceChildren(fragment);
|
||||||
|
|
||||||
|
// Fragment contents should be moved
|
||||||
|
testing.expectEqual(2, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.firstChild.nodeName);
|
||||||
|
testing.expectEqual('html', doc.lastChild.nodeName);
|
||||||
|
|
||||||
|
// Fragment should be empty now
|
||||||
|
testing.expectEqual(0, fragment.childNodes.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_maintains_child_order>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const nodes = [];
|
||||||
|
|
||||||
|
// Document can have: comment, processing instruction, doctype, element
|
||||||
|
nodes.push(doc.createComment('comment'));
|
||||||
|
nodes.push(doc.createElement('html'));
|
||||||
|
|
||||||
|
doc.replaceChildren(...nodes);
|
||||||
|
|
||||||
|
testing.expectEqual(2, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.childNodes[0].nodeName);
|
||||||
|
testing.expectEqual('html', doc.childNodes[1].nodeName);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_with_nested_structure>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const outer = doc.createElement('html');
|
||||||
|
outer.id = 'outer';
|
||||||
|
const middle = doc.createElement('body');
|
||||||
|
middle.id = 'middle';
|
||||||
|
const inner = doc.createElement('span');
|
||||||
|
inner.id = 'inner';
|
||||||
|
inner.textContent = 'Nested';
|
||||||
|
|
||||||
|
middle.appendChild(inner);
|
||||||
|
outer.appendChild(middle);
|
||||||
|
|
||||||
|
doc.replaceChildren(outer);
|
||||||
|
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
testing.expectEqual('outer', doc.firstChild.id);
|
||||||
|
|
||||||
|
const foundInner = doc.getElementById('inner');
|
||||||
|
testing.expectEqual(inner, foundInner);
|
||||||
|
testing.expectEqual('Nested', foundInner.textContent);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=consecutive_replaces>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const html1 = doc.createElement('html');
|
||||||
|
html1.id = 'first-replace';
|
||||||
|
doc.replaceChildren(html1);
|
||||||
|
testing.expectEqual('first-replace', doc.firstChild.id);
|
||||||
|
|
||||||
|
// Replace element with comments
|
||||||
|
const comment = doc.createComment('in between');
|
||||||
|
doc.replaceChildren(comment);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.firstChild.nodeName);
|
||||||
|
|
||||||
|
// Replace comments with new element
|
||||||
|
const html2 = doc.createElement('html');
|
||||||
|
html2.id = 'second-replace';
|
||||||
|
doc.replaceChildren(html2);
|
||||||
|
testing.expectEqual('second-replace', doc.firstChild.id);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
|
||||||
|
// First element should no longer be in document
|
||||||
|
testing.expectEqual(null, html1.parentNode);
|
||||||
|
testing.expectEqual(null, comment.parentNode);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=replace_with_comments_only>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const comment1 = doc.createComment('First');
|
||||||
|
const comment2 = doc.createComment('Second');
|
||||||
|
|
||||||
|
doc.replaceChildren(comment1, comment2);
|
||||||
|
|
||||||
|
testing.expectEqual(2, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.firstChild.nodeName);
|
||||||
|
testing.expectEqual('First', doc.firstChild.textContent);
|
||||||
|
testing.expectEqual('#comment', doc.lastChild.nodeName);
|
||||||
|
testing.expectEqual('Second', doc.lastChild.textContent);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=error_fragment_with_text>
|
||||||
|
{
|
||||||
|
// DocumentFragment with text should fail when inserted into Document
|
||||||
|
const doc = new Document();
|
||||||
|
const fragment = doc.createDocumentFragment();
|
||||||
|
fragment.appendChild(doc.createTextNode('text'));
|
||||||
|
fragment.appendChild(doc.createElement('html'));
|
||||||
|
|
||||||
|
testing.expectError('HierarchyRequest', () => {
|
||||||
|
doc.replaceChildren(fragment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=append_valid_nodes>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const comment = doc.createComment('test');
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
|
||||||
|
doc.append(comment);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
|
||||||
|
doc.append(html);
|
||||||
|
testing.expectEqual(2, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.firstChild.nodeName);
|
||||||
|
testing.expectEqual('html', doc.lastChild.nodeName);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=prepend_valid_nodes>
|
||||||
|
{
|
||||||
|
const doc = new Document();
|
||||||
|
const html = doc.createElement('html');
|
||||||
|
const comment = doc.createComment('test');
|
||||||
|
|
||||||
|
doc.prepend(html);
|
||||||
|
testing.expectEqual(1, doc.childNodes.length);
|
||||||
|
|
||||||
|
doc.prepend(comment);
|
||||||
|
testing.expectEqual(2, doc.childNodes.length);
|
||||||
|
testing.expectEqual('#comment', doc.firstChild.nodeName);
|
||||||
|
testing.expectEqual('html', doc.lastChild.nodeName);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
function expectError(expected, fn) {
|
function expectError(expected, fn) {
|
||||||
withError((err) => {
|
withError((err) => {
|
||||||
expectEqual(expected, err.toString());
|
expectEqual(true, err.toString().includes(expected));
|
||||||
}, fn);
|
}, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,11 +125,11 @@ const CreateElementOptions = struct {
|
|||||||
|
|
||||||
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
||||||
const namespace: Element.Namespace = blk: {
|
const namespace: Element.Namespace = blk: {
|
||||||
if (self._type == .xml) {
|
if (self._type == .html) {
|
||||||
@branchHint(.unlikely);
|
break :blk .html;
|
||||||
break :blk .xml;
|
|
||||||
}
|
}
|
||||||
break :blk .html;
|
// Generic and XML documents create XML elements
|
||||||
|
break :blk .xml;
|
||||||
};
|
};
|
||||||
const node = try page.createElementNS(namespace, name, null);
|
const node = try page.createElementNS(namespace, name, null);
|
||||||
const element = node.as(Element);
|
const element = node.as(Element);
|
||||||
@@ -432,20 +432,103 @@ pub fn importNode(_: *const Document, node: *Node, deep_: ?bool, page: *Page) !*
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn append(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
pub fn append(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||||
|
try validateDocumentNodes(self, nodes, false);
|
||||||
|
|
||||||
|
page.domChanged();
|
||||||
const parent = self.asNode();
|
const parent = self.asNode();
|
||||||
|
const parent_is_connected = parent.isConnected();
|
||||||
|
|
||||||
for (nodes) |node_or_text| {
|
for (nodes) |node_or_text| {
|
||||||
const child = try node_or_text.toNode(page);
|
const child = try node_or_text.toNode(page);
|
||||||
_ = try parent.appendChild(child, page);
|
|
||||||
|
// DocumentFragments are special - append all their children
|
||||||
|
if (child.is(Node.DocumentFragment)) |_| {
|
||||||
|
try page.appendAllChildren(child, parent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var child_connected = false;
|
||||||
|
if (child._parent) |previous_parent| {
|
||||||
|
child_connected = child.isConnected();
|
||||||
|
page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||||
|
}
|
||||||
|
try page.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||||
|
try validateDocumentNodes(self, nodes, false);
|
||||||
|
|
||||||
|
page.domChanged();
|
||||||
const parent = self.asNode();
|
const parent = self.asNode();
|
||||||
|
const parent_is_connected = parent.isConnected();
|
||||||
|
|
||||||
var i = nodes.len;
|
var i = nodes.len;
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
i -= 1;
|
i -= 1;
|
||||||
const child = try nodes[i].toNode(page);
|
const child = try nodes[i].toNode(page);
|
||||||
_ = try parent.insertBefore(child, parent.firstChild(), page);
|
|
||||||
|
// DocumentFragments are special - need to insert all their children
|
||||||
|
if (child.is(Node.DocumentFragment)) |frag| {
|
||||||
|
const first_child = parent.firstChild();
|
||||||
|
var frag_child = frag.asNode().lastChild();
|
||||||
|
while (frag_child) |fc| {
|
||||||
|
const prev = fc.previousSibling();
|
||||||
|
page.removeNode(frag.asNode(), fc, .{ .will_be_reconnected = parent_is_connected });
|
||||||
|
if (first_child) |before| {
|
||||||
|
try page.insertNodeRelative(parent, fc, .{ .before = before }, .{});
|
||||||
|
} else {
|
||||||
|
try page.appendNode(parent, fc, .{});
|
||||||
|
}
|
||||||
|
frag_child = prev;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var child_connected = false;
|
||||||
|
if (child._parent) |previous_parent| {
|
||||||
|
child_connected = child.isConnected();
|
||||||
|
page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||||
|
}
|
||||||
|
|
||||||
|
const first_child = parent.firstChild();
|
||||||
|
if (first_child) |before| {
|
||||||
|
try page.insertNodeRelative(parent, child, .{ .before = before }, .{ .child_already_connected = child_connected });
|
||||||
|
} else {
|
||||||
|
try page.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replaceChildren(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||||
|
try validateDocumentNodes(self, nodes, true);
|
||||||
|
|
||||||
|
page.domChanged();
|
||||||
|
const parent = self.asNode();
|
||||||
|
|
||||||
|
// Remove all existing children
|
||||||
|
var it = parent.childrenIterator();
|
||||||
|
while (it.next()) |child| {
|
||||||
|
page.removeNode(parent, child, .{ .will_be_reconnected = false });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append new children
|
||||||
|
const parent_is_connected = parent.isConnected();
|
||||||
|
for (nodes) |node_or_text| {
|
||||||
|
const child = try node_or_text.toNode(page);
|
||||||
|
|
||||||
|
// DocumentFragments are special - append all their children
|
||||||
|
if (child.is(Node.DocumentFragment)) |_| {
|
||||||
|
try page.appendAllChildren(child, parent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var child_connected = false;
|
||||||
|
if (child._parent) |previous_parent| {
|
||||||
|
child_connected = child.isConnected();
|
||||||
|
page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||||
|
}
|
||||||
|
try page.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,6 +782,95 @@ pub fn setAdoptedStyleSheets(self: *Document, sheets: js.Object) !void {
|
|||||||
self._adopted_style_sheets = try sheets.persist();
|
self._adopted_style_sheets = try sheets.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validates that nodes can be inserted into a Document, respecting Document constraints:
|
||||||
|
// - At most one Element child
|
||||||
|
// - At most one DocumentType child
|
||||||
|
// - No Document, Attribute, or Text nodes
|
||||||
|
// - Only Element, DocumentType, Comment, and ProcessingInstruction are allowed
|
||||||
|
// When replacing=true, existing children are not counted (for replaceChildren)
|
||||||
|
fn validateDocumentNodes(self: *Document, nodes: []const Node.NodeOrText, comptime replacing: bool) !void {
|
||||||
|
const parent = self.asNode();
|
||||||
|
|
||||||
|
// Check existing elements and doctypes (unless we're replacing all children)
|
||||||
|
var has_element = false;
|
||||||
|
var has_doctype = false;
|
||||||
|
|
||||||
|
if (!replacing) {
|
||||||
|
var it = parent.childrenIterator();
|
||||||
|
while (it.next()) |child| {
|
||||||
|
if (child._type == .element) {
|
||||||
|
has_element = true;
|
||||||
|
} else if (child._type == .document_type) {
|
||||||
|
has_doctype = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate new nodes
|
||||||
|
for (nodes) |node_or_text| {
|
||||||
|
switch (node_or_text) {
|
||||||
|
.text => {
|
||||||
|
// Text nodes are not allowed as direct children of Document
|
||||||
|
return error.HierarchyError;
|
||||||
|
},
|
||||||
|
.node => |child| {
|
||||||
|
// Check if it's a DocumentFragment - need to validate its children
|
||||||
|
if (child.is(Node.DocumentFragment)) |frag| {
|
||||||
|
var frag_it = frag.asNode().childrenIterator();
|
||||||
|
while (frag_it.next()) |frag_child| {
|
||||||
|
// Document can only contain: Element, DocumentType, Comment, ProcessingInstruction
|
||||||
|
switch (frag_child._type) {
|
||||||
|
.element => {
|
||||||
|
if (has_element) {
|
||||||
|
return error.HierarchyError;
|
||||||
|
}
|
||||||
|
has_element = true;
|
||||||
|
},
|
||||||
|
.document_type => {
|
||||||
|
if (has_doctype) {
|
||||||
|
return error.HierarchyError;
|
||||||
|
}
|
||||||
|
has_doctype = true;
|
||||||
|
},
|
||||||
|
.cdata => |cd| switch (cd._type) {
|
||||||
|
.comment, .processing_instruction => {}, // Allowed
|
||||||
|
.text, .cdata_section => return error.HierarchyError, // Not allowed in Document
|
||||||
|
},
|
||||||
|
.document, .attribute, .document_fragment => return error.HierarchyError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Validate node type for direct insertion
|
||||||
|
switch (child._type) {
|
||||||
|
.element => {
|
||||||
|
if (has_element) {
|
||||||
|
return error.HierarchyError;
|
||||||
|
}
|
||||||
|
has_element = true;
|
||||||
|
},
|
||||||
|
.document_type => {
|
||||||
|
if (has_doctype) {
|
||||||
|
return error.HierarchyError;
|
||||||
|
}
|
||||||
|
has_doctype = true;
|
||||||
|
},
|
||||||
|
.cdata => |cd| switch (cd._type) {
|
||||||
|
.comment, .processing_instruction => {}, // Allowed
|
||||||
|
.text, .cdata_section => return error.HierarchyError, // Not allowed in Document
|
||||||
|
},
|
||||||
|
.document, .attribute, .document_fragment => return error.HierarchyError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for cycles
|
||||||
|
if (child.contains(parent)) {
|
||||||
|
return error.HierarchyError;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ReadyState = enum {
|
const ReadyState = enum {
|
||||||
loading,
|
loading,
|
||||||
interactive,
|
interactive,
|
||||||
@@ -768,8 +940,9 @@ pub const JsApi = struct {
|
|||||||
pub const getElementsByName = bridge.function(Document.getElementsByName, .{});
|
pub const getElementsByName = bridge.function(Document.getElementsByName, .{});
|
||||||
pub const adoptNode = bridge.function(Document.adoptNode, .{ .dom_exception = true });
|
pub const adoptNode = bridge.function(Document.adoptNode, .{ .dom_exception = true });
|
||||||
pub const importNode = bridge.function(Document.importNode, .{ .dom_exception = true });
|
pub const importNode = bridge.function(Document.importNode, .{ .dom_exception = true });
|
||||||
pub const append = bridge.function(Document.append, .{});
|
pub const append = bridge.function(Document.append, .{ .dom_exception = true });
|
||||||
pub const prepend = bridge.function(Document.prepend, .{});
|
pub const prepend = bridge.function(Document.prepend, .{ .dom_exception = true });
|
||||||
|
pub const replaceChildren = bridge.function(Document.replaceChildren, .{ .dom_exception = true });
|
||||||
pub const elementFromPoint = bridge.function(Document.elementFromPoint, .{});
|
pub const elementFromPoint = bridge.function(Document.elementFromPoint, .{});
|
||||||
pub const elementsFromPoint = bridge.function(Document.elementsFromPoint, .{});
|
pub const elementsFromPoint = bridge.function(Document.elementsFromPoint, .{});
|
||||||
pub const write = bridge.function(Document.write, .{ .dom_exception = true });
|
pub const write = bridge.function(Document.write, .{ .dom_exception = true });
|
||||||
|
|||||||
Reference in New Issue
Block a user