mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-17 00:38:59 +00:00
Work on HTMLTemplateElement
Implement Html5ever get_template_contents and add_attrs_if_missing callbacks
This commit is contained in:
@@ -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
|
||||
pub fn nodeComplete(self: *Page, node: *Node) !void {
|
||||
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 self.nodeIsReady(true, node);
|
||||
@@ -979,6 +979,12 @@ pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_
|
||||
attribute_iterator,
|
||||
.{ ._proto = undefined },
|
||||
),
|
||||
asUint("template") => return self.createHtmlElementT(
|
||||
Element.Html.Template,
|
||||
namespace,
|
||||
attribute_iterator,
|
||||
.{ ._proto = undefined, ._content = undefined },
|
||||
),
|
||||
else => {},
|
||||
},
|
||||
else => {},
|
||||
@@ -1015,7 +1021,7 @@ fn createHtmlElementT(self: *Page, comptime E: type, namespace: Element.Namespac
|
||||
const node = element.asNode();
|
||||
if (@hasDecl(E, "Build") and @hasDecl(E.Build, "created")) {
|
||||
@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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -518,6 +518,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/element/html/Script.zig"),
|
||||
@import("../webapi/element/html/Select.zig"),
|
||||
@import("../webapi/element/html/Style.zig"),
|
||||
@import("../webapi/element/html/Template.zig"),
|
||||
@import("../webapi/element/html/TextArea.zig"),
|
||||
@import("../webapi/element/html/Title.zig"),
|
||||
@import("../webapi/element/html/UL.zig"),
|
||||
|
||||
@@ -66,6 +66,7 @@ const Error = struct {
|
||||
create_comment,
|
||||
append_doctype_to_document,
|
||||
add_attrs_if_missing,
|
||||
get_template_content,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -83,6 +84,7 @@ pub fn parse(self: *Parser, html: []const u8) void {
|
||||
createCommentCallback,
|
||||
appendDoctypeToDocument,
|
||||
addAttrsIfMissingCallback,
|
||||
getTemplateContentsCallback
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,6 +102,7 @@ pub fn parseFragment(self: *Parser, html: []const u8) void {
|
||||
createCommentCallback,
|
||||
appendDoctypeToDocument,
|
||||
addAttrsIfMissingCallback,
|
||||
getTemplateContentsCallback
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,6 +137,7 @@ pub const Streaming = struct {
|
||||
createCommentCallback,
|
||||
appendDoctypeToDocument,
|
||||
addAttrsIfMissingCallback,
|
||||
getTemplateContentsCallback
|
||||
) 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 {
|
||||
const pn: *ParsedNode = @ptrCast(@alignCast(ctx));
|
||||
// For non-elements, data is null. But, we expect this to only ever
|
||||
|
||||
@@ -31,6 +31,7 @@ pub extern "c" fn html5ever_parse_document(
|
||||
createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque,
|
||||
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) 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;
|
||||
|
||||
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,
|
||||
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) 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;
|
||||
|
||||
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,
|
||||
appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) 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;
|
||||
|
||||
pub extern "c" fn html5ever_streaming_parser_feed(
|
||||
|
||||
@@ -100,3 +100,79 @@
|
||||
testing.expectEqual(test2, document.getElementById("test2"));
|
||||
}
|
||||
</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>
|
||||
|
||||
168
src/browser/tests/element/html/template.html
Normal file
168
src/browser/tests/element/html/template.html
Normal 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>
|
||||
@@ -1,38 +1,168 @@
|
||||
<!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>
|
||||
let t = document.createElement('template');
|
||||
let d = document.createElement('div');
|
||||
d.id = 'abc';
|
||||
t.content.append(d);
|
||||
<template id="empty"></template>
|
||||
|
||||
testing.expectEqual(null, document.getElementById('abc'));
|
||||
document.getElementById('c').appendChild(t.content.cloneNode(true));
|
||||
testing.expectEqual('abc', document.getElementById('abc').id);
|
||||
<script id=content_property>
|
||||
{
|
||||
const template = $('#basic');
|
||||
const content = template.content;
|
||||
|
||||
t.innerHTML = '<span>over</span><p>9000!</p>';
|
||||
testing.expectEqual(2, t.content.childNodes.length);
|
||||
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);
|
||||
testing.expectEqual('[object DocumentFragment]', content.toString());
|
||||
testing.expectEqual(true, content instanceof DocumentFragment);
|
||||
}
|
||||
</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 tt = document.getElementById('hello');
|
||||
testing.expectEqual('<p>hello, world</p>', tt.innerHTML);
|
||||
const div = content.firstElementChild;
|
||||
testing.expectTrue(div !== null);
|
||||
testing.expectEqual('DIV', div.tagName);
|
||||
testing.expectEqual('container', div.className);
|
||||
|
||||
// > 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);
|
||||
const h1 = div.querySelector('h1');
|
||||
testing.expectTrue(h1 !== null);
|
||||
testing.expectEqual('Hello Template', h1.textContent);
|
||||
|
||||
let out = document.createElement('div');
|
||||
out.appendChild(tt.content.cloneNode(true));
|
||||
|
||||
testing.expectEqual('<p>hello, world</p>', out.innerHTML);
|
||||
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>
|
||||
|
||||
@@ -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 bridge = js.Bridge(DocumentFragment);
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ pub fn getTagNameLower(self: *const Element) []const u8 {
|
||||
.script => "script",
|
||||
.select => "select",
|
||||
.style => "style",
|
||||
.template => "template",
|
||||
.text_area => "textarea",
|
||||
.title => "title",
|
||||
.ul => "ul",
|
||||
@@ -188,6 +189,7 @@ pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 {
|
||||
.script => "SCRIPT",
|
||||
.select => "SELECT",
|
||||
.style => "STYLE",
|
||||
.template => "TEMPLATE",
|
||||
.text_area => "TEXTAREA",
|
||||
.title => "TITLE",
|
||||
.ul => "UL",
|
||||
@@ -730,6 +732,7 @@ pub fn getTag(self: *const Element) Tag {
|
||||
.script => .script,
|
||||
.select => .select,
|
||||
.option => .option,
|
||||
.template => .template,
|
||||
.text_area => .textarea,
|
||||
.input => .input,
|
||||
.link => .link,
|
||||
@@ -797,6 +800,7 @@ pub const Tag = enum {
|
||||
style,
|
||||
svg,
|
||||
text,
|
||||
template,
|
||||
textarea,
|
||||
title,
|
||||
ul,
|
||||
|
||||
@@ -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) {
|
||||
.element => |el| el.getTagNameSpec(&page.buf),
|
||||
.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),
|
||||
.document => 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ pub const Form = @import("html/Form.zig");
|
||||
pub const Heading = @import("html/Heading.zig");
|
||||
pub const Unknown = @import("html/Unknown.zig");
|
||||
pub const Generic = @import("html/Generic.zig");
|
||||
pub const Template = @import("html/Template.zig");
|
||||
pub const TextArea = @import("html/TextArea.zig");
|
||||
pub const Paragraph = @import("html/Paragraph.zig");
|
||||
pub const Select = @import("html/Select.zig");
|
||||
@@ -81,6 +82,7 @@ pub const Type = union(enum) {
|
||||
script: *Script,
|
||||
select: Select,
|
||||
style: Style,
|
||||
template: *Template,
|
||||
text_area: *TextArea,
|
||||
title: Title,
|
||||
ul: UL,
|
||||
@@ -119,6 +121,7 @@ pub fn className(self: *const HtmlElement) []const u8 {
|
||||
.generic => "[object HTMLElement]",
|
||||
.script => "[object HtmlScriptElement]",
|
||||
.select => "[object HTMLSelectElement]",
|
||||
.template => "[object HTMLTemplateElement]",
|
||||
.option => "[object HTMLOptionElement]",
|
||||
.text_area => "[object HtmlTextAreaElement]",
|
||||
.input => "[object HtmlInputElement]",
|
||||
|
||||
49
src/browser/webapi/element/html/Template.zig
Normal file
49
src/browser/webapi/element/html/Template.zig
Normal 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", .{});
|
||||
}
|
||||
@@ -45,6 +45,7 @@ pub extern "C" fn html5ever_parse_document(
|
||||
create_comment_callback: CreateCommentCallback,
|
||||
append_doctype_to_document: AppendDoctypeToDocumentCallback,
|
||||
add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
|
||||
get_template_contents_callback: GetTemplateContentsCallback,
|
||||
) -> () {
|
||||
if html.is_null() || len == 0 {
|
||||
return ();
|
||||
@@ -65,6 +66,7 @@ pub extern "C" fn html5ever_parse_document(
|
||||
create_comment_callback: create_comment_callback,
|
||||
append_doctype_to_document: append_doctype_to_document,
|
||||
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) };
|
||||
@@ -87,6 +89,7 @@ pub extern "C" fn html5ever_parse_fragment(
|
||||
create_comment_callback: CreateCommentCallback,
|
||||
append_doctype_to_document: AppendDoctypeToDocumentCallback,
|
||||
add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
|
||||
get_template_contents_callback: GetTemplateContentsCallback,
|
||||
) -> () {
|
||||
if html.is_null() || len == 0 {
|
||||
return ();
|
||||
@@ -107,6 +110,7 @@ pub extern "C" fn html5ever_parse_fragment(
|
||||
create_comment_callback: create_comment_callback,
|
||||
append_doctype_to_document: append_doctype_to_document,
|
||||
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) };
|
||||
@@ -188,6 +192,7 @@ pub extern "C" fn html5ever_streaming_parser_create(
|
||||
create_comment_callback: CreateCommentCallback,
|
||||
append_doctype_to_document: AppendDoctypeToDocumentCallback,
|
||||
add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
|
||||
get_template_contents_callback: GetTemplateContentsCallback,
|
||||
) -> *mut c_void {
|
||||
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,
|
||||
append_doctype_to_document: append_doctype_to_document,
|
||||
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
|
||||
|
||||
@@ -56,6 +56,7 @@ pub struct Sink<'arena> {
|
||||
pub create_comment_callback: CreateCommentCallback,
|
||||
pub append_doctype_to_document: AppendDoctypeToDocumentCallback,
|
||||
pub add_attrs_if_missing_callback: AddAttrsIfMissingCallback,
|
||||
pub get_template_contents_callback: GetTemplateContentsCallback,
|
||||
}
|
||||
|
||||
impl<'arena> TreeSink for Sink<'arena> {
|
||||
@@ -101,8 +102,9 @@ impl<'arena> TreeSink for Sink<'arena> {
|
||||
}
|
||||
|
||||
fn get_template_contents(&self, target: &Ref) -> Ref {
|
||||
_ = target;
|
||||
panic!("get_template_contents")
|
||||
unsafe {
|
||||
return (self.get_template_contents_callback)(self.ctx, *target);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_mathml_annotation_xml_integration_point(&self, target: &Ref) -> bool {
|
||||
|
||||
@@ -57,6 +57,8 @@ pub type AddAttrsIfMissingCallback = unsafe extern "C" fn(
|
||||
attributes: *mut c_void,
|
||||
) -> ();
|
||||
|
||||
pub type GetTemplateContentsCallback = unsafe extern "C" fn(ctx: Ref, target: Ref) -> Ref;
|
||||
|
||||
pub type Ref = *const c_void;
|
||||
|
||||
#[repr(C)]
|
||||
|
||||
Reference in New Issue
Block a user