Work on HTMLTemplateElement

Implement Html5ever get_template_contents and add_attrs_if_missing callbacks
This commit is contained in:
Karl Seguin
2025-11-15 22:04:39 +08:00
parent c311828217
commit 19dfea7762
15 changed files with 527 additions and 33 deletions

View File

@@ -722,7 +722,7 @@ pub fn appendNew(self: *Page, parent: *Node, child: Node.NodeOrText) !void {
// called from the parser when the node and all its children have been added // called from the parser when the node and all its children have been added
pub fn nodeComplete(self: *Page, node: *Node) !void { pub fn nodeComplete(self: *Page, node: *Node) !void {
Node.Build.call(node, "complete", .{ node, self }) catch |err| { Node.Build.call(node, "complete", .{ node, self }) catch |err| {
log.err(.bug, "build.complete", .{ .tag = node.getTag(), .err = err }); log.err(.bug, "build.complete", .{ .tag = node.getNodeName(self), .err = err });
return err; return err;
}; };
return self.nodeIsReady(true, node); return self.nodeIsReady(true, node);
@@ -979,6 +979,12 @@ pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_
attribute_iterator, attribute_iterator,
.{ ._proto = undefined }, .{ ._proto = undefined },
), ),
asUint("template") => return self.createHtmlElementT(
Element.Html.Template,
namespace,
attribute_iterator,
.{ ._proto = undefined, ._content = undefined },
),
else => {}, else => {},
}, },
else => {}, else => {},
@@ -1015,7 +1021,7 @@ fn createHtmlElementT(self: *Page, comptime E: type, namespace: Element.Namespac
const node = element.asNode(); const node = element.asNode();
if (@hasDecl(E, "Build") and @hasDecl(E.Build, "created")) { if (@hasDecl(E, "Build") and @hasDecl(E.Build, "created")) {
@call(.auto, @field(E.Build, "created"), .{ node, self }) catch |err| { @call(.auto, @field(E.Build, "created"), .{ node, self }) catch |err| {
log.err(.page, "build.created", .{ .tag = node.getTag(), .err = err }); log.err(.page, "build.created", .{ .tag = node.getNodeName(self), .err = err });
return err; return err;
}; };
} }

View File

@@ -518,6 +518,7 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/element/html/Script.zig"), @import("../webapi/element/html/Script.zig"),
@import("../webapi/element/html/Select.zig"), @import("../webapi/element/html/Select.zig"),
@import("../webapi/element/html/Style.zig"), @import("../webapi/element/html/Style.zig"),
@import("../webapi/element/html/Template.zig"),
@import("../webapi/element/html/TextArea.zig"), @import("../webapi/element/html/TextArea.zig"),
@import("../webapi/element/html/Title.zig"), @import("../webapi/element/html/Title.zig"),
@import("../webapi/element/html/UL.zig"), @import("../webapi/element/html/UL.zig"),

View File

@@ -66,6 +66,7 @@ const Error = struct {
create_comment, create_comment,
append_doctype_to_document, append_doctype_to_document,
add_attrs_if_missing, add_attrs_if_missing,
get_template_content,
}; };
}; };
@@ -83,6 +84,7 @@ pub fn parse(self: *Parser, html: []const u8) void {
createCommentCallback, createCommentCallback,
appendDoctypeToDocument, appendDoctypeToDocument,
addAttrsIfMissingCallback, addAttrsIfMissingCallback,
getTemplateContentsCallback
); );
} }
@@ -100,6 +102,7 @@ pub fn parseFragment(self: *Parser, html: []const u8) void {
createCommentCallback, createCommentCallback,
appendDoctypeToDocument, appendDoctypeToDocument,
addAttrsIfMissingCallback, addAttrsIfMissingCallback,
getTemplateContentsCallback
); );
} }
@@ -134,6 +137,7 @@ pub const Streaming = struct {
createCommentCallback, createCommentCallback,
appendDoctypeToDocument, appendDoctypeToDocument,
addAttrsIfMissingCallback, addAttrsIfMissingCallback,
getTemplateContentsCallback
) orelse return error.ParserCreationFailed; ) orelse return error.ParserCreationFailed;
} }
@@ -245,6 +249,28 @@ fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.Attrib
} }
} }
fn getTemplateContentsCallback(ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque {
const self: *Parser = @ptrCast(@alignCast(ctx));
return self._getTemplateContentsCallback(getNode(target_ref)) catch |err| {
self.err = .{ .err = err, .source = .get_template_content };
return null;
};
}
fn _getTemplateContentsCallback(self: *Parser, node: *Node) !*anyopaque {
const element = node.as(Element);
const template = element._type.html.is(Element.Html.Template) orelse unreachable;
const content_node = template.getContent().asNode();
// Create a ParsedNode wrapper for the content DocumentFragment
const pn = try self.arena.create(ParsedNode);
pn.* = .{
.data = null,
.node = content_node,
};
return pn;
}
fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque { fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque {
const pn: *ParsedNode = @ptrCast(@alignCast(ctx)); const pn: *ParsedNode = @ptrCast(@alignCast(ctx));
// For non-elements, data is null. But, we expect this to only ever // For non-elements, data is null. But, we expect this to only ever

View File

@@ -31,6 +31,7 @@ pub extern "c" fn html5ever_parse_document(
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque, createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void, appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void, addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
) void; ) void;
pub extern "c" fn html5ever_parse_fragment( pub extern "c" fn html5ever_parse_fragment(
@@ -46,6 +47,7 @@ pub extern "c" fn html5ever_parse_fragment(
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque, createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void, appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void, addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
) void; ) void;
pub extern "c" fn html5ever_attribute_iterator_next(ctx: *anyopaque) Nullable(Attribute); pub extern "c" fn html5ever_attribute_iterator_next(ctx: *anyopaque) Nullable(Attribute);
@@ -70,6 +72,7 @@ pub extern "c" fn html5ever_streaming_parser_create(
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque, createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void, appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void,
addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void, addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void,
getTemplateContentsCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque) callconv(.c) ?*anyopaque,
) ?*anyopaque; ) ?*anyopaque;
pub extern "c" fn html5ever_streaming_parser_feed( pub extern "c" fn html5ever_streaming_parser_feed(

View File

@@ -100,3 +100,79 @@
testing.expectEqual(test2, document.getElementById("test2")); testing.expectEqual(test2, document.getElementById("test2"));
} }
</script> </script>
<script id=cloneNode_shallow>
{
const df = document.createDocumentFragment();
const div1 = document.createElement("div");
div1.textContent = "Content 1";
const div2 = document.createElement("div");
div2.textContent = "Content 2";
df.appendChild(div1);
df.appendChild(div2);
const clone = df.cloneNode(false);
testing.expectEqual("DocumentFragment", clone.constructor.name);
testing.expectEqual(0, clone.childElementCount);
testing.expectEqual(2, df.childElementCount);
}
</script>
<script id=cloneNode_deep>
{
const df = document.createDocumentFragment();
const div1 = document.createElement("div");
div1.id = "original1";
div1.className = "test";
div1.textContent = "Content 1";
const div2 = document.createElement("div");
div2.id = "original2";
div2.textContent = "Content 2";
df.appendChild(div1);
df.appendChild(div2);
const clone = df.cloneNode(true);
testing.expectEqual("DocumentFragment", clone.constructor.name);
testing.expectEqual(2, clone.childElementCount);
const clonedDiv1 = clone.firstElementChild;
testing.expectEqual("DIV", clonedDiv1.tagName);
testing.expectEqual("original1", clonedDiv1.id);
testing.expectEqual("test", clonedDiv1.className);
testing.expectEqual("Content 1", clonedDiv1.textContent);
const clonedDiv2 = clone.lastElementChild;
testing.expectEqual("original2", clonedDiv2.id);
testing.expectEqual("Content 2", clonedDiv2.textContent);
testing.expectFalse(div1.isSameNode(clonedDiv1));
testing.expectFalse(div2.isSameNode(clonedDiv2));
testing.expectEqual(2, df.childElementCount);
}
</script>
<script id=cloneNode_nested>
{
const df = document.createDocumentFragment();
const outer = document.createElement("div");
outer.id = "outer";
const inner = document.createElement("span");
inner.id = "inner";
inner.textContent = "Nested content";
outer.appendChild(inner);
df.appendChild(outer);
const clone = df.cloneNode(true);
testing.expectEqual(1, clone.childElementCount);
const clonedOuter = clone.firstElementChild;
testing.expectEqual("outer", clonedOuter.id);
testing.expectEqual(1, clonedOuter.childElementCount);
const clonedInner = clonedOuter.firstElementChild;
testing.expectEqual("SPAN", clonedInner.tagName);
testing.expectEqual("inner", clonedInner.id);
testing.expectEqual("Nested content", clonedInner.textContent);
}
</script>

View File

@@ -0,0 +1,168 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<body>
<template id="basic">
<div class="container">
<h1>Hello Template</h1>
<p>This is template content</p>
</div>
</template>
<template id="nested">
<div class="outer">
<span id="inner1">First</span>
<span id="inner2">Second</span>
</div>
</template>
<template id="empty"></template>
<script id=content_property>
{
const template = $('#basic');
const content = template.content;
testing.expectEqual('[object DocumentFragment]', content.toString());
testing.expectEqual(true, content instanceof DocumentFragment);
}
</script>
<script id=content_has_children>
{
const template = $('#basic');
const content = template.content;
const div = content.firstElementChild;
testing.expectTrue(div !== null);
testing.expectEqual('DIV', div.tagName);
testing.expectEqual('container', div.className);
const h1 = div.querySelector('h1');
testing.expectTrue(h1 !== null);
testing.expectEqual('Hello Template', h1.textContent);
const p = div.querySelector('p');
testing.expectTrue(p !== null);
testing.expectEqual('This is template content', p.textContent);
}
</script>
<script id=template_children_not_in_dom>
{
const template = $('#basic');
testing.expectEqual(0, template.children.length);
testing.expectEqual(null, template.firstElementChild);
const h1InDoc = document.querySelector('#basic h1');
testing.expectEqual(null, h1InDoc);
}
</script>
<script id=nested_template_content>
{
const template = $('#nested');
const content = template.content;
const outer = content.firstElementChild;
testing.expectEqual('DIV', outer.tagName);
testing.expectEqual('outer', outer.className);
const inner1 = content.querySelector('#inner1');
testing.expectTrue(inner1 !== null);
testing.expectEqual('First', inner1.textContent);
const inner2 = content.querySelector('#inner2');
testing.expectTrue(inner2 !== null);
testing.expectEqual('Second', inner2.textContent);
}
</script>
<script id=empty_template>
{
const template = $('#empty');
const content = template.content;
testing.expectEqual('[object DocumentFragment]', content.toString());
testing.expectEqual(null, content.firstElementChild);
testing.expectEqual(0, content.childElementCount);
}
</script>
<script id=query_selector_on_content>
{
const template = $('#nested');
const content = template.content;
const spans = content.querySelectorAll('span');
testing.expectEqual(2, spans.length);
testing.expectEqual('inner1', spans[0].id);
testing.expectEqual('inner2', spans[1].id);
}
</script>
<script id=modify_content>
{
const template = $('#basic');
const content = template.content;
testing.expectEqual(1, content.childElementCount);
const newDiv = document.createElement('div');
newDiv.id = 'added';
newDiv.textContent = 'Added element';
content.append(newDiv);
testing.expectEqual(2, content.childElementCount);
}
</script>
<script id=clone_template_content>
{
const template = $('#basic');
const content = template.content;
const clone = content.cloneNode(true);
testing.expectEqual('[object DocumentFragment]', clone.toString());
const div = clone.firstElementChild;
testing.expectTrue(div !== null);
testing.expectEqual('container', div.className);
const h1 = clone.querySelector('h1');
testing.expectEqual('Hello Template', h1.textContent);
}
</script>
<script id=instantiate_template>
{
const template = $('#nested');
const content = template.content;
const originalOuter = content.firstElementChild;
testing.expectEqual(2, originalOuter.childElementCount);
const clone = content.cloneNode(true);
testing.expectEqual(1, clone.childElementCount);
const clonedDiv = clone.firstElementChild;
testing.expectEqual('DIV', clonedDiv.tagName);
testing.expectEqual('outer', clonedDiv.className);
testing.expectEqual(2, clonedDiv.childElementCount);
document.body.appendChild(clone);
testing.expectEqual(0, clone.childElementCount);
const outerInDoc = document.body.querySelector('.outer');
testing.expectTrue(outerInDoc !== null);
testing.expectEqual(clonedDiv, outerInDoc);
testing.expectEqual(2, outerInDoc.childElementCount);
const inner1 = outerInDoc.firstElementChild;
testing.expectEqual('SPAN', inner1.tagName);
testing.expectEqual('inner1', inner1.id);
testing.expectEqual('First', inner1.textContent);
}
</script>

View File

@@ -1,38 +1,168 @@
<!DOCTYPE html> <!DOCTYPE html>
<script src="../testing.js"></script> <script src="../../testing.js"></script>
<body>
<template id="basic">
<div class="container">
<h1>Hello Template</h1>
<p>This is template content</p>
</div>
</template>
<div id=c></div> <template id="nested">
<div class="outer">
<span id="inner1">First</span>
<span id="inner2">Second</span>
</div>
</template>
<script id=template> <template id="empty"></template>
let t = document.createElement('template');
let d = document.createElement('div');
d.id = 'abc';
t.content.append(d);
testing.expectEqual(null, document.getElementById('abc')); <script id=content_property>
document.getElementById('c').appendChild(t.content.cloneNode(true)); {
testing.expectEqual('abc', document.getElementById('abc').id); const template = $('#basic');
const content = template.content;
t.innerHTML = '<span>over</span><p>9000!</p>'; testing.expectEqual('[object DocumentFragment]', content.toString());
testing.expectEqual(2, t.content.childNodes.length); testing.expectEqual(true, content instanceof DocumentFragment);
testing.expectEqual('SPAN', t.content.childNodes[0].tagName); }
testing.expectEqual('over', t.content.childNodes[0].innerHTML);
testing.expectEqual('P', t.content.childNodes[1].tagName);
testing.expectEqual('9000!', t.content.childNodes[1].innerHTML);
</script> </script>
<template id="hello"><p>hello, world</p></template> <script id=content_has_children>
{
const template = $('#basic');
const content = template.content;
<script id=template_parsing> const div = content.firstElementChild;
const tt = document.getElementById('hello'); testing.expectTrue(div !== null);
testing.expectEqual('<p>hello, world</p>', tt.innerHTML); testing.expectEqual('DIV', div.tagName);
testing.expectEqual('container', div.className);
// > The Node.childNodes property of the <template> element is always empty const h1 = div.querySelector('h1');
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template#usage_notes testing.expectTrue(h1 !== null);
testing.expectEqual(0, tt.childNodes.length); testing.expectEqual('Hello Template', h1.textContent);
let out = document.createElement('div'); const p = div.querySelector('p');
out.appendChild(tt.content.cloneNode(true)); testing.expectTrue(p !== null);
testing.expectEqual('This is template content', p.textContent);
testing.expectEqual('<p>hello, world</p>', out.innerHTML); }
</script>
<script id=template_children_not_in_dom>
{
const template = $('#basic');
testing.expectEqual(0, template.children.length);
testing.expectEqual(null, template.firstElementChild);
const h1InDoc = document.querySelector('#basic h1');
testing.expectEqual(null, h1InDoc);
}
</script>
<script id=nested_template_content>
{
const template = $('#nested');
const content = template.content;
const outer = content.firstElementChild;
testing.expectEqual('DIV', outer.tagName);
testing.expectEqual('outer', outer.className);
const inner1 = content.querySelector('#inner1');
testing.expectTrue(inner1 !== null);
testing.expectEqual('First', inner1.textContent);
const inner2 = content.querySelector('#inner2');
testing.expectTrue(inner2 !== null);
testing.expectEqual('Second', inner2.textContent);
}
</script>
<script id=empty_template>
{
const template = $('#empty');
const content = template.content;
testing.expectEqual('[object DocumentFragment]', content.toString());
testing.expectEqual(null, content.firstElementChild);
testing.expectEqual(0, content.childElementCount);
}
</script>
<script id=query_selector_on_content>
{
const template = $('#nested');
const content = template.content;
const spans = content.querySelectorAll('span');
testing.expectEqual(2, spans.length);
testing.expectEqual('inner1', spans[0].id);
testing.expectEqual('inner2', spans[1].id);
}
</script>
<script id=modify_content>
{
const template = $('#basic');
const content = template.content;
testing.expectEqual(1, content.childElementCount);
const newDiv = document.createElement('div');
newDiv.id = 'added';
newDiv.textContent = 'Added element';
content.append(newDiv);
testing.expectEqual(2, content.childElementCount);
}
</script>
<script id=clone_template_content>
{
const template = $('#basic');
const content = template.content;
const clone = content.cloneNode(true);
testing.expectEqual('[object DocumentFragment]', clone.toString());
const div = clone.firstElementChild;
testing.expectTrue(div !== null);
testing.expectEqual('container', div.className);
const h1 = clone.querySelector('h1');
testing.expectEqual('Hello Template', h1.textContent);
}
</script>
<script id=instantiate_template>
{
const template = $('#nested');
const content = template.content;
const originalOuter = content.firstElementChild;
testing.expectEqual(2, originalOuter.childElementCount);
const clone = content.cloneNode(true);
testing.expectEqual(1, clone.childElementCount);
const clonedDiv = clone.firstElementChild;
testing.expectEqual('DIV', clonedDiv.tagName);
testing.expectEqual('outer', clonedDiv.className);
testing.expectEqual(2, clonedDiv.childElementCount);
document.body.appendChild(clone);
testing.expectEqual(0, clone.childElementCount);
const outerInDoc = document.body.querySelector('.outer');
testing.expectTrue(outerInDoc !== null);
testing.expectEqual(clonedDiv, outerInDoc);
testing.expectEqual(2, outerInDoc.childElementCount);
const inner1 = outerInDoc.firstElementChild;
testing.expectEqual('SPAN', inner1.tagName);
testing.expectEqual('inner1', inner1.id);
testing.expectEqual('First', inner1.textContent);
}
</script> </script>

View File

@@ -136,6 +136,24 @@ pub fn replaceChildren(self: *DocumentFragment, nodes: []const Node.NodeOrText,
} }
} }
pub fn cloneFragment(self: *DocumentFragment, deep: bool, page: *Page) !*Node {
const fragment = try DocumentFragment.init(page);
const fragment_node = fragment.asNode();
if (deep) {
const node = self.asNode();
const self_is_connected = node.isConnected();
var child_it = node.childrenIterator();
while (child_it.next()) |child| {
const cloned_child = try child.cloneNode(true, page);
try page.appendNode(fragment_node, cloned_child, .{ .child_already_connected = self_is_connected });
}
}
return fragment_node;
}
pub const JsApi = struct { pub const JsApi = struct {
pub const bridge = js.Bridge(DocumentFragment); pub const bridge = js.Bridge(DocumentFragment);

View File

@@ -146,6 +146,7 @@ pub fn getTagNameLower(self: *const Element) []const u8 {
.script => "script", .script => "script",
.select => "select", .select => "select",
.style => "style", .style => "style",
.template => "template",
.text_area => "textarea", .text_area => "textarea",
.title => "title", .title => "title",
.ul => "ul", .ul => "ul",
@@ -188,6 +189,7 @@ pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 {
.script => "SCRIPT", .script => "SCRIPT",
.select => "SELECT", .select => "SELECT",
.style => "STYLE", .style => "STYLE",
.template => "TEMPLATE",
.text_area => "TEXTAREA", .text_area => "TEXTAREA",
.title => "TITLE", .title => "TITLE",
.ul => "UL", .ul => "UL",
@@ -730,6 +732,7 @@ pub fn getTag(self: *const Element) Tag {
.script => .script, .script => .script,
.select => .select, .select => .select,
.option => .option, .option => .option,
.template => .template,
.text_area => .textarea, .text_area => .textarea,
.input => .input, .input => .input,
.link => .link, .link => .link,
@@ -797,6 +800,7 @@ pub const Tag = enum {
style, style,
svg, svg,
text, text,
template,
textarea, textarea,
title, title,
ul, ul,

View File

@@ -196,7 +196,7 @@ pub fn setTextContent(self: *Node, data: []const u8, page: *Page) !void {
} }
} }
pub fn getNodeName(self: *const Node, page: *Page) ![]const u8 { pub fn getNodeName(self: *const Node, page: *Page) []const u8 {
return switch (self._type) { return switch (self._type) {
.element => |el| el.getTagNameSpec(&page.buf), .element => |el| el.getTagNameSpec(&page.buf),
.cdata => |cd| switch (cd._type) { .cdata => |cd| switch (cd._type) {
@@ -428,7 +428,7 @@ pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) error{ OutOfMemory, Str
.element => |el| return el.cloneElement(deep, page), .element => |el| return el.cloneElement(deep, page),
.document => return error.NotSupported, .document => return error.NotSupported,
.document_type => return error.NotSupported, .document_type => return error.NotSupported,
.document_fragment => return error.NotImplemented, .document_fragment => |frag| return frag.cloneFragment(deep, page),
.attribute => return error.NotSupported, .attribute => return error.NotSupported,
} }
} }

View File

@@ -45,6 +45,7 @@ pub const Form = @import("html/Form.zig");
pub const Heading = @import("html/Heading.zig"); pub const Heading = @import("html/Heading.zig");
pub const Unknown = @import("html/Unknown.zig"); pub const Unknown = @import("html/Unknown.zig");
pub const Generic = @import("html/Generic.zig"); pub const Generic = @import("html/Generic.zig");
pub const Template = @import("html/Template.zig");
pub const TextArea = @import("html/TextArea.zig"); pub const TextArea = @import("html/TextArea.zig");
pub const Paragraph = @import("html/Paragraph.zig"); pub const Paragraph = @import("html/Paragraph.zig");
pub const Select = @import("html/Select.zig"); pub const Select = @import("html/Select.zig");
@@ -81,6 +82,7 @@ pub const Type = union(enum) {
script: *Script, script: *Script,
select: Select, select: Select,
style: Style, style: Style,
template: *Template,
text_area: *TextArea, text_area: *TextArea,
title: Title, title: Title,
ul: UL, ul: UL,
@@ -119,6 +121,7 @@ pub fn className(self: *const HtmlElement) []const u8 {
.generic => "[object HTMLElement]", .generic => "[object HTMLElement]",
.script => "[object HtmlScriptElement]", .script => "[object HtmlScriptElement]",
.select => "[object HTMLSelectElement]", .select => "[object HTMLSelectElement]",
.template => "[object HTMLTemplateElement]",
.option => "[object HTMLOptionElement]", .option => "[object HTMLOptionElement]",
.text_area => "[object HtmlTextAreaElement]", .text_area => "[object HtmlTextAreaElement]",
.input => "[object HtmlInputElement]", .input => "[object HtmlInputElement]",

View File

@@ -0,0 +1,49 @@
const std = @import("std");
const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const HtmlElement = @import("../Html.zig");
const DocumentFragment = @import("../../DocumentFragment.zig");
const Template = @This();
_proto: *HtmlElement,
_content: *DocumentFragment,
pub fn asElement(self: *Template) *Element {
return self._proto._proto;
}
pub fn asNode(self: *Template) *Node {
return self.asElement().asNode();
}
pub fn getContent(self: *Template) *DocumentFragment {
return self._content;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Template);
pub const Meta = struct {
pub const name = "HTMLTemplateElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const content = bridge.accessor(Template.getContent, null, .{});
};
pub const Build = struct {
pub fn created(node: *Node, page: *Page) !void {
const self = node.as(Template);
// Create the template content DocumentFragment
self._content = try DocumentFragment.init(page);
}
};
const testing = @import("../../../../testing.zig");
test "WebApi: Template" {
try testing.htmlRunner("element/html/template.html", .{});
}

View File

@@ -45,6 +45,7 @@ pub extern "C" fn html5ever_parse_document(
create_comment_callback: CreateCommentCallback, create_comment_callback: CreateCommentCallback,
append_doctype_to_document: AppendDoctypeToDocumentCallback, append_doctype_to_document: AppendDoctypeToDocumentCallback,
add_attrs_if_missing_callback: AddAttrsIfMissingCallback, add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
get_template_contents_callback: GetTemplateContentsCallback,
) -> () { ) -> () {
if html.is_null() || len == 0 { if html.is_null() || len == 0 {
return (); return ();
@@ -65,6 +66,7 @@ pub extern "C" fn html5ever_parse_document(
create_comment_callback: create_comment_callback, create_comment_callback: create_comment_callback,
append_doctype_to_document: append_doctype_to_document, append_doctype_to_document: append_doctype_to_document,
add_attrs_if_missing_callback: add_attrs_if_missing_callback, add_attrs_if_missing_callback: add_attrs_if_missing_callback,
get_template_contents_callback: get_template_contents_callback,
}; };
let bytes = unsafe { std::slice::from_raw_parts(html, len) }; let bytes = unsafe { std::slice::from_raw_parts(html, len) };
@@ -87,6 +89,7 @@ pub extern "C" fn html5ever_parse_fragment(
create_comment_callback: CreateCommentCallback, create_comment_callback: CreateCommentCallback,
append_doctype_to_document: AppendDoctypeToDocumentCallback, append_doctype_to_document: AppendDoctypeToDocumentCallback,
add_attrs_if_missing_callback: AddAttrsIfMissingCallback, add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
get_template_contents_callback: GetTemplateContentsCallback,
) -> () { ) -> () {
if html.is_null() || len == 0 { if html.is_null() || len == 0 {
return (); return ();
@@ -107,6 +110,7 @@ pub extern "C" fn html5ever_parse_fragment(
create_comment_callback: create_comment_callback, create_comment_callback: create_comment_callback,
append_doctype_to_document: append_doctype_to_document, append_doctype_to_document: append_doctype_to_document,
add_attrs_if_missing_callback: add_attrs_if_missing_callback, add_attrs_if_missing_callback: add_attrs_if_missing_callback,
get_template_contents_callback: get_template_contents_callback,
}; };
let bytes = unsafe { std::slice::from_raw_parts(html, len) }; let bytes = unsafe { std::slice::from_raw_parts(html, len) };
@@ -188,6 +192,7 @@ pub extern "C" fn html5ever_streaming_parser_create(
create_comment_callback: CreateCommentCallback, create_comment_callback: CreateCommentCallback,
append_doctype_to_document: AppendDoctypeToDocumentCallback, append_doctype_to_document: AppendDoctypeToDocumentCallback,
add_attrs_if_missing_callback: AddAttrsIfMissingCallback, add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
get_template_contents_callback: GetTemplateContentsCallback,
) -> *mut c_void { ) -> *mut c_void {
let arena = Box::new(typed_arena::Arena::new()); let arena = Box::new(typed_arena::Arena::new());
@@ -211,6 +216,7 @@ pub extern "C" fn html5ever_streaming_parser_create(
create_comment_callback: create_comment_callback, create_comment_callback: create_comment_callback,
append_doctype_to_document: append_doctype_to_document, append_doctype_to_document: append_doctype_to_document,
add_attrs_if_missing_callback: add_attrs_if_missing_callback, add_attrs_if_missing_callback: add_attrs_if_missing_callback,
get_template_contents_callback: get_template_contents_callback,
}; };
// Create a parser which implements TendrilSink for streaming parsing // Create a parser which implements TendrilSink for streaming parsing

View File

@@ -56,6 +56,7 @@ pub struct Sink<'arena> {
pub create_comment_callback: CreateCommentCallback, pub create_comment_callback: CreateCommentCallback,
pub append_doctype_to_document: AppendDoctypeToDocumentCallback, pub append_doctype_to_document: AppendDoctypeToDocumentCallback,
pub add_attrs_if_missing_callback: AddAttrsIfMissingCallback, pub add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
pub get_template_contents_callback: GetTemplateContentsCallback,
} }
impl<'arena> TreeSink for Sink<'arena> { impl<'arena> TreeSink for Sink<'arena> {
@@ -101,8 +102,9 @@ impl<'arena> TreeSink for Sink<'arena> {
} }
fn get_template_contents(&self, target: &Ref) -> Ref { fn get_template_contents(&self, target: &Ref) -> Ref {
_ = target; unsafe {
panic!("get_template_contents") return (self.get_template_contents_callback)(self.ctx, *target);
}
} }
fn is_mathml_annotation_xml_integration_point(&self, target: &Ref) -> bool { fn is_mathml_annotation_xml_integration_point(&self, target: &Ref) -> bool {

View File

@@ -57,6 +57,8 @@ pub type AddAttrsIfMissingCallback = unsafe extern "C" fn(
attributes: *mut c_void, attributes: *mut c_void,
) -> (); ) -> ();
pub type GetTemplateContentsCallback = unsafe extern "C" fn(ctx: Ref, target: Ref) -> Ref;
pub type Ref = *const c_void; pub type Ref = *const c_void;
#[repr(C)] #[repr(C)]