DOMImplementation and DocumentType skeletons

This commit is contained in:
Karl Seguin
2025-11-01 14:02:18 +08:00
parent 618b28a292
commit d397d75aca
8 changed files with 209 additions and 3 deletions

View File

@@ -26,6 +26,7 @@ pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer) error{WriteFailed}!
}
},
.document => try children(node, opts, writer),
.document_type => {},
.document_fragment => try children(node, opts, writer),
.attribute => unreachable,
}
@@ -49,6 +50,9 @@ pub fn toJSON(node: *Node, writer: *std.json.Stringify) !void {
.document => {
try writer.write("document");
},
.document_type => {
try writer.write("document_type");
},
.element => |*el| {
try writer.write("element");
try writer.objectField("tag");

View File

@@ -446,7 +446,9 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/HTMLDocument.zig"),
@import("../webapi/KeyValueList.zig"),
@import("../webapi/DocumentFragment.zig"),
@import("../webapi/DocumentType.zig"),
@import("../webapi/DOMException.zig"),
@import("../webapi/DOMImplementation.zig"),
@import("../webapi/DOMTreeWalker.zig"),
@import("../webapi/DOMNodeIterator.zig"),
@import("../webapi/NodeFilter.zig"),

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<head>
<title>DOMImplementation Test</title>
<script src="testing.js"></script>
</head>
<body>
</body>
<script id=implementation>
{
const impl = document.implementation;
testing.expectEqual('[object DOMImplementation]', impl.toString());
}
</script>
<script id=hasFeature>
{
const impl = document.implementation;
testing.expectEqual(true, impl.hasFeature('XML', '1.0'));
testing.expectEqual(true, impl.hasFeature('HTML', '2.0'));
testing.expectEqual(true, impl.hasFeature('', null));
}
</script>
<script id=createDocumentType>
{
const impl = document.implementation;
const doctype = impl.createDocumentType('html', '', '');
testing.expectEqual(10, doctype.nodeType);
testing.expectEqual('html', doctype.nodeName);
testing.expectEqual('html', doctype.name);
testing.expectEqual('', doctype.publicId);
testing.expectEqual('', doctype.systemId);
testing.expectEqual('[object DocumentType]', doctype.toString());
}
</script>
<script id=createDocumentTypeWithIds>
{
const impl = document.implementation;
const doctype = impl.createDocumentType(
'svg',
'-//W3C//DTD SVG 1.1//EN',
'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
);
testing.expectEqual('svg', doctype.name);
testing.expectEqual('-//W3C//DTD SVG 1.1//EN', doctype.publicId);
testing.expectEqual('http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd', doctype.systemId);
}
</script>
<script id=createDocumentTypeNullIds>
{
const impl = document.implementation;
const doctype = impl.createDocumentType('html', null, null);
testing.expectEqual('html', doctype.name);
testing.expectEqual('', doctype.publicId);
testing.expectEqual('', doctype.systemId);
}
</script>

View File

@@ -0,0 +1,57 @@
const std = @import("std");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Node = @import("Node.zig");
const DocumentType = @import("DocumentType.zig");
const DOMImplementation = @This();
pub fn createDocumentType(_: *const DOMImplementation, qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, page: *Page) !*DocumentType {
const name = try page.dupeString(qualified_name);
const pub_id = try page.dupeString(public_id orelse "");
const sys_id = try page.dupeString(system_id orelse "");
const doctype = try page._factory.node(DocumentType{
._proto = undefined,
._name = name,
._public_id = pub_id,
._system_id = sys_id,
});
return doctype;
}
pub fn hasFeature(_: *const DOMImplementation, _: []const u8, _: ?[]const u8) bool {
// Modern DOM spec says this should always return true
// This method is deprecated and kept for compatibility only
return true;
}
pub fn className(_: *const DOMImplementation) []const u8 {
return "[object DOMImplementation]";
}
pub const JsApi = struct {
pub const bridge = js.Bridge(DOMImplementation);
pub const Meta = struct {
pub const name = "DOMImplementation";
pub const prototype_chain = bridge.prototypeChain();
pub var class_index: u16 = 0;
pub const empty_with_no_proto = true;
};
pub const createDocumentType = bridge.function(DOMImplementation.createDocumentType, .{ .dom_exception = true });
pub const hasFeature = bridge.function(DOMImplementation.hasFeature, .{});
pub const toString = bridge.function(_toString, .{});
fn _toString(_: *const DOMImplementation) []const u8 {
return "[object DOMImplementation]";
}
};
const testing = @import("../../testing.zig");
test "WebApi: DOMImplementation" {
try testing.htmlRunner("domimplementation.html", .{});
}

View File

@@ -12,6 +12,7 @@ const Selector = @import("selector/Selector.zig");
const NodeFilter = @import("NodeFilter.zig");
const DOMTreeWalker = @import("DOMTreeWalker.zig");
const DOMNodeIterator = @import("DOMNodeIterator.zig");
const DOMImplementation = @import("DOMImplementation.zig");
pub const HTMLDocument = @import("HTMLDocument.zig");
@@ -124,6 +125,10 @@ pub fn className(_: *const Document) []const u8 {
return "[object Document]";
}
pub fn getImplementation(_: *const Document) DOMImplementation {
return .{};
}
pub fn createDocumentFragment(_: *const Document, page: *Page) !*@import("DocumentFragment.zig") {
return @import("DocumentFragment.zig").init(page);
}
@@ -176,6 +181,7 @@ pub const JsApi = struct {
pub const URL = bridge.accessor(Document.getURL, null, .{});
pub const documentElement = bridge.accessor(Document.getDocumentElement, null, .{});
pub const readyState = bridge.accessor(Document.getReadyState, null, .{});
pub const implementation = bridge.accessor(Document.getImplementation, null, .{});
pub const createElement = bridge.function(Document.createElement, .{});
pub const createElementNS = bridge.function(Document.createElementNS, .{});

View File

@@ -0,0 +1,60 @@
const std = @import("std");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Node = @import("Node.zig");
const DocumentType = @This();
_proto: *Node,
_name: []const u8,
_public_id: []const u8,
_system_id: []const u8,
pub fn asNode(self: *DocumentType) *Node {
return self._proto;
}
pub fn asEventTarget(self: *DocumentType) *@import("EventTarget.zig") {
return self._proto.asEventTarget();
}
pub fn getName(self: *const DocumentType) []const u8 {
return self._name;
}
pub fn getPublicId(self: *const DocumentType) []const u8 {
return self._public_id;
}
pub fn getSystemId(self: *const DocumentType) []const u8 {
return self._system_id;
}
pub fn className(_: *const DocumentType) []const u8 {
return "[object DocumentType]";
}
pub const JsApi = struct {
pub const bridge = js.Bridge(DocumentType);
pub const Meta = struct {
pub const name = "DocumentType";
pub const prototype_chain = bridge.prototypeChain();
pub var class_index: u16 = 0;
};
pub const name = bridge.accessor(DocumentType.getName, null, .{});
pub const publicId = bridge.accessor(DocumentType.getPublicId, null, .{});
pub const systemId = bridge.accessor(DocumentType.getSystemId, null, .{});
pub const toString = bridge.function(_toString, .{});
fn _toString(self: *const DocumentType) []const u8 {
return self.className();
}
};
const testing = @import("../../testing.zig");
test "WebApi: DOMImplementation" {
try testing.htmlRunner("domimplementation.html", .{});
}

View File

@@ -11,8 +11,10 @@ const collections = @import("collections.zig");
pub const CData = @import("CData.zig");
pub const Element = @import("Element.zig");
pub const Document = @import("Document.zig");
pub const HTMLDocument = @import("HTMLDocument.zig");
pub const Children = @import("children.zig").Children;
pub const DocumentFragment = @import("DocumentFragment.zig");
pub const DocumentType = @import("DocumentType.zig");
const Allocator = std.mem.Allocator;
const LinkedList = std.DoublyLinkedList;
@@ -29,6 +31,7 @@ pub const Type = union(enum) {
cdata: *CData,
element: *Element,
document: *Document,
document_type: *DocumentType,
attribute: *Element.Attribute,
document_fragment: *DocumentFragment,
};
@@ -76,6 +79,11 @@ pub fn is(self: *Node, comptime T: type) ?*T {
return doc.is(T);
}
},
.document_type => |dt| {
if (T == DocumentType) {
return dt;
}
},
.document_fragment => |doc| {
if (T == DocumentFragment) {
return doc;
@@ -145,6 +153,7 @@ pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!vo
.element => |el| return el.getInnerText(writer),
.cdata => |c| try writer.writeAll(c.getData()),
.document => {},
.document_type => {},
.document_fragment => {},
.attribute => |attr| try writer.writeAll(attr._value),
}
@@ -163,6 +172,7 @@ pub fn setTextContent(self: *Node, data: []const u8, page: *Page) !void {
.element => |el| return el.replaceChildren(&.{.{ .text = data }}, page),
.cdata => |c| c._data = try page.arena.dupe(u8, data),
.document => {},
.document_type => {},
.document_fragment => {},
.attribute => |attr| return attr.setValue(data, page),
}
@@ -176,6 +186,7 @@ pub fn getNodeName(self: *const Node, page: *Page) ![]const u8 {
.comment => "#comment",
},
.document => "#document",
.document_type => |dt| dt.getName(),
.document_fragment => "#document-fragment",
.attribute => |attr| attr._name,
};
@@ -190,6 +201,7 @@ pub fn nodeType(self: *const Node) u8 {
.comment => 8,
},
.document => 9,
.document_type => 10,
.document_fragment => 11,
};
}
@@ -333,6 +345,7 @@ pub fn getNodeValue(self: *const Node) ?[]const u8 {
.attribute => |attr| attr._value,
.element => null,
.document => null,
.document_type => null,
.document_fragment => null,
};
}
@@ -343,6 +356,7 @@ pub fn setNodeValue(self: *const Node, value: ?[]const u8, page: *Page) !void {
.attribute => |attr| try attr.setValue(value, page),
.element => {},
.document => {},
.document_type => {},
.document_fragment => {},
}
}
@@ -350,11 +364,11 @@ pub fn setNodeValue(self: *const Node, value: ?[]const u8, page: *Page) !void {
pub fn format(self: *Node, writer: *std.Io.Writer) !void {
// // If you need extra debugging:
// return @import("../dump.zig").deep(self, .{}, writer);
return switch (self._type) {
.cdata => |cd| cd.format(writer),
.element => |el| writer.print("{f}", .{el}),
.document => writer.writeAll("<document>"),
.document_type => writer.writeAll("<doctype>"),
.document_fragment => writer.writeAll("<document_fragment>"),
.attribute => |attr| writer.print("{f}", .{attr}),
};
@@ -380,8 +394,7 @@ pub fn className(self: *const Node) []const u8 {
pub fn normalize(self: *Node, page: *Page) !void {
var buffer: std.ArrayListUnmanaged(u8) = .empty;
const arena = page.call_arena;
return self._normalize(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 {
@@ -396,6 +409,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,
.attribute => return error.NotSupported,
}
@@ -612,6 +626,7 @@ pub const JsApi = struct {
.cdata => |cdata| return cdata.getData(),
.attribute => |attr| return attr._value,
.document => return null,
.document_type => return null,
.document_fragment => return null,
}
}

View File

@@ -249,6 +249,7 @@ pub const JsApi = struct {
pub const self = bridge.accessor(Window.getWindow, null, .{ .cache = "self" });
pub const window = bridge.accessor(Window.getWindow, null, .{ .cache = "window" });
pub const parent = bridge.accessor(Window.getWindow, null, .{ .cache = "parent" });
pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = "console" });
pub const navigator = bridge.accessor(Window.getNavigator, null, .{ .cache = "navigator" });
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" });