handle template's original content

When the document fragment is called via the content method on a
templat, it must contain the original template's HTML nodes.
This commit is contained in:
Pierre Tachoire
2025-11-12 11:02:22 +01:00
parent 2f2870c066
commit c4380b91f4
3 changed files with 46 additions and 1 deletions

View File

@@ -390,7 +390,23 @@ pub const Node = struct {
return parser.nodeHasChildNodes(self); return parser.nodeHasChildNodes(self);
} }
fn is_template(self: *parser.Node) !bool {
if (parser.nodeType(self) != .element) {
return false;
}
const e = parser.nodeToElement(self);
return try parser.elementTag(e) == .template;
}
pub fn get_childNodes(self: *parser.Node, page: *Page) !NodeList { pub fn get_childNodes(self: *parser.Node, page: *Page) !NodeList {
// special case for template:
// > The Node.childNodes property of the <template> element is always empty
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template#usage_notes
if (try is_template(self)) {
return .{};
}
const allocator = page.arena; const allocator = page.arena;
var list: NodeList = .{}; var list: NodeList = .{};

View File

@@ -33,6 +33,8 @@ const DataSet = @import("DataSet.zig");
const StyleSheet = @import("../cssom/StyleSheet.zig"); const StyleSheet = @import("../cssom/StyleSheet.zig");
const CSSStyleDeclaration = @import("../cssom/CSSStyleDeclaration.zig"); const CSSStyleDeclaration = @import("../cssom/CSSStyleDeclaration.zig");
const WalkerChildren = @import("../dom/walker.zig").WalkerChildren;
// HTMLElement interfaces // HTMLElement interfaces
pub const Interfaces = .{ pub const Interfaces = .{
Element, Element,
@@ -1200,11 +1202,22 @@ pub const HTMLTemplateElement = struct {
pub const subtype = .node; pub const subtype = .node;
pub fn get_content(self: *parser.Template, page: *Page) !*parser.DocumentFragment { pub fn get_content(self: *parser.Template, page: *Page) !*parser.DocumentFragment {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self))); const n: *parser.Node = @ptrCast(@alignCast(self));
const state = try page.getOrCreateNodeState(n);
if (state.template_content) |tc| { if (state.template_content) |tc| {
return tc; return tc;
} }
const tc = try parser.documentCreateDocumentFragment(@ptrCast(page.window.document)); const tc = try parser.documentCreateDocumentFragment(@ptrCast(page.window.document));
const ntc: *parser.Node = @ptrCast(@alignCast(tc));
// move existing template's childnodes to the fragment.
const walker = WalkerChildren{};
var next: ?*parser.Node = null;
while (true) {
next = try walker.get_next(n, next) orelse break;
_ = try parser.nodeAppendChild(ntc, next.?);
}
state.template_content = tc; state.template_content = tc;
return tc; return tc;
} }

View File

@@ -20,3 +20,19 @@
testing.expectEqual('P', t.content.childNodes[1].tagName); testing.expectEqual('P', t.content.childNodes[1].tagName);
testing.expectEqual('9000!', t.content.childNodes[1].innerHTML); testing.expectEqual('9000!', t.content.childNodes[1].innerHTML);
</script> </script>
<template id="hello"><p>hello, world</p></template>
<script id=template_parsing>
const tt = document.getElementById('hello');
testing.expectEqual('<p>hello, world</p>', tt.innerHTML);
// > The Node.childNodes property of the <template> element is always empty
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template#usage_notes
testing.expectEqual(0, tt.childNodes.length);
let out = document.createElement('div');
out.appendChild(tt.content.cloneNode(true));
testing.expectEqual('<p>hello, world</p>', out.innerHTML);
</script>