diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 2c2d7710..843ca037 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1492,6 +1492,35 @@ pub fn createCDATASection(self: *Page, data: []const u8) !*Node { return cd.asNode(); } +pub fn createProcessingInstruction(self: *Page, target: []const u8, data: []const u8) !*Node { + // Validate target doesn't contain "?>" + if (std.mem.indexOf(u8, target, "?>") != null) { + return error.InvalidCharacterError; + } + + // Validate target follows XML name rules (similar to attribute name validation) + try Element.Attribute.validateAttributeName(target); + + const owned_target = try self.dupeString(target); + const owned_data = try self.dupeString(data); + + const pi = try self._factory.create(CData.ProcessingInstruction{ + ._proto = undefined, + ._target = owned_target, + }); + + const cd = try self._factory.node(CData{ + ._proto = undefined, + ._type = .{ .processing_instruction = pi }, + ._data = owned_data, + }); + + // Set up the back pointer from ProcessingInstruction to CData + pi._proto = cd; + + return cd.asNode(); +} + pub fn dupeString(self: *Page, value: []const u8) ![]const u8 { if (String.intern(value)) |v| { return v; diff --git a/src/browser/dump.zig b/src/browser/dump.zig index adcef586..b1ca4b29 100644 --- a/src/browser/dump.zig +++ b/src/browser/dump.zig @@ -74,6 +74,12 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri try writer.writeAll(""); + } else if (node.is(Node.CData.ProcessingInstruction)) |pi| { + try writer.writeAll(""); } else { if (shouldEscapeText(node._parent)) { try writeEscapedText(cd.getData(), writer); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index d379ebf2..a37097e1 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -492,6 +492,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/cdata/Comment.zig"), @import("../webapi/cdata/Text.zig"), @import("../webapi/cdata/CDATASection.zig"), + @import("../webapi/cdata/ProcessingInstruction.zig"), @import("../webapi/collections.zig"), @import("../webapi/Console.zig"), @import("../webapi/Crypto.zig"), @@ -506,6 +507,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/css/StyleSheetList.zig"), @import("../webapi/Document.zig"), @import("../webapi/HTMLDocument.zig"), + @import("../webapi/XMLDocument.zig"), @import("../webapi/History.zig"), @import("../webapi/KeyValueList.zig"), @import("../webapi/DocumentFragment.zig"), diff --git a/src/browser/tests/domexception.html b/src/browser/tests/domexception.html new file mode 100644 index 00000000..3f2825fd --- /dev/null +++ b/src/browser/tests/domexception.html @@ -0,0 +1,135 @@ + + + DOMException Test + + + + + + + + + + + + + + + + + + + + +
+ OK +
+ diff --git a/src/browser/tests/domimplementation.html b/src/browser/tests/domimplementation.html index 7ff1b0e0..d8980a8f 100644 --- a/src/browser/tests/domimplementation.html +++ b/src/browser/tests/domimplementation.html @@ -55,7 +55,177 @@ const impl = document.implementation; const doctype = impl.createDocumentType('html', null, null); testing.expectEqual('html', doctype.name); - testing.expectEqual('', doctype.publicId); - testing.expectEqual('', doctype.systemId); + testing.expectEqual('null', doctype.publicId); + testing.expectEqual('null', doctype.systemId); + } + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/legacy/dom/implementation.html b/src/browser/tests/legacy/dom/implementation.html index 81cce804..6dcc0838 100644 --- a/src/browser/tests/legacy/dom/implementation.html +++ b/src/browser/tests/legacy/dom/implementation.html @@ -8,7 +8,7 @@ testing.expectEqual("[object HTMLDocument]", doc.toString()); testing.expectEqual("foo", doc.title); testing.expectEqual("[object HTMLBodyElement]", doc.body.toString()); - testing.expectEqual("[object Document]", impl.createDocument(null, 'foo').toString()); + testing.expectEqual("[object XMLDocument]", impl.createDocument(null, 'foo').toString()); testing.expectEqual("[object DocumentType]", impl.createDocumentType('foo', 'bar', 'baz').toString()); testing.expectEqual(true, impl.hasFeature()); diff --git a/src/browser/tests/node/replace_child.html b/src/browser/tests/node/replace_child.html index deea60a8..18832fd9 100644 --- a/src/browser/tests/node/replace_child.html +++ b/src/browser/tests/node/replace_child.html @@ -24,7 +24,7 @@ testing.withError((err) => { testing.expectEqual(3, err.code); - testing.expectEqual("HierarchyError", err.name); + testing.expectEqual("HierarchyRequestError", err.name); testing.expectEqual("Hierarchy Error", err.message); }, () => d1.replaceChild(c4, c3)); diff --git a/src/browser/tests/processing_instruction.html b/src/browser/tests/processing_instruction.html new file mode 100644 index 00000000..9a6ef119 --- /dev/null +++ b/src/browser/tests/processing_instruction.html @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/CData.zig b/src/browser/webapi/CData.zig index 82afee54..c7ac88cc 100644 --- a/src/browser/webapi/CData.zig +++ b/src/browser/webapi/CData.zig @@ -25,6 +25,7 @@ const Node = @import("Node.zig"); pub const Text = @import("cdata/Text.zig"); pub const Comment = @import("cdata/Comment.zig"); pub const CDATASection = @import("cdata/CDATASection.zig"); +pub const ProcessingInstruction = @import("cdata/ProcessingInstruction.zig"); const CData = @This(); @@ -38,6 +39,7 @@ pub const Type = union(enum) { // This should be under Text, but that would require storing a _type union // in text, which would add 8 bytes to every text node. cdata_section: CDATASection, + processing_instruction: *ProcessingInstruction, }; pub fn asNode(self: *CData) *Node { @@ -58,6 +60,7 @@ pub fn className(self: *const CData) []const u8 { .text => "[object Text]", .comment => "[object Comment]", .cdata_section => "[object CDATASection]", + .processing_instruction => "[object ProcessingInstruction]", }; } @@ -139,6 +142,7 @@ pub fn format(self: *const CData, writer: *std.io.Writer) !void { .text => writer.print("{s}", .{self._data}), .comment => writer.print("", .{self._data}), .cdata_section => writer.print("", .{self._data}), + .processing_instruction => |pi| writer.print("", .{ pi._target, self._data }), }; } @@ -147,6 +151,18 @@ pub fn getLength(self: *const CData) usize { } pub fn isEqualNode(self: *const CData, other: *const CData) bool { + if (std.meta.activeTag(self._type) != std.meta.activeTag(other._type)) { + return false; + } + + if (self._type == .processing_instruction) { + @branchHint(.unlikely); + if (std.mem.eql(u8, self._type.processing_instruction._target, other._type.processing_instruction._target) == false) { + return false; + } + // if the _targets are equal, we still want to compare the data + } + return std.mem.eql(u8, self.getData(), other.getData()); } diff --git a/src/browser/webapi/DOMException.zig b/src/browser/webapi/DOMException.zig index 7ae241d2..f2558752 100644 --- a/src/browser/webapi/DOMException.zig +++ b/src/browser/webapi/DOMException.zig @@ -16,14 +16,24 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +const std = @import("std"); const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); const DOMException = @This(); -_code: Code = .none, -pub fn init() DOMException { - return .{}; +_code: Code = .none, +_custom_message: ?[]const u8 = null, +_custom_name: ?[]const u8 = null, + +pub fn init(message: ?[]const u8, name: ?[]const u8) DOMException { + // If name is provided, try to map it to a legacy code + const code = if (name) |n| Code.fromName(n) else .none; + return .{ + ._code = code, + ._custom_message = message, + ._custom_name = name, + }; } pub fn fromError(err: anyerror) ?DOMException { @@ -34,6 +44,21 @@ pub fn fromError(err: anyerror) ?DOMException { error.NotSupported => .{ ._code = .not_supported }, error.HierarchyError => .{ ._code = .hierarchy_error }, error.IndexSizeError => .{ ._code = .index_size_error }, + error.InvalidStateError => .{ ._code = .invalid_state_error }, + error.WrongDocument => .{ ._code = .wrong_document_error }, + error.NoModificationAllowed => .{ ._code = .no_modification_allowed_error }, + error.InUseAttribute => .{ ._code = .inuse_attribute_error }, + error.InvalidModification => .{ ._code = .invalid_modification_error }, + error.NamespaceError => .{ ._code = .namespace_error }, + error.InvalidAccess => .{ ._code = .invalid_access_error }, + error.SecurityError => .{ ._code = .security_error }, + error.NetworkError => .{ ._code = .network_error }, + error.AbortError => .{ ._code = .abort_error }, + error.URLMismatch => .{ ._code = .url_mismatch_error }, + error.QuotaExceeded => .{ ._code = .quota_exceeded_error }, + error.TimeoutError => .{ ._code = .timeout_error }, + error.InvalidNodeType => .{ ._code = .invalid_node_type_error }, + error.DataClone => .{ ._code = .data_clone_error }, else => null, }; } @@ -43,18 +68,40 @@ pub fn getCode(self: *const DOMException) u8 { } pub fn getName(self: *const DOMException) []const u8 { + if (self._custom_name) |name| { + return name; + } + return switch (self._code) { .none => "Error", + .index_size_error => "IndexSizeError", + .hierarchy_error => "HierarchyRequestError", + .wrong_document_error => "WrongDocumentError", .invalid_character_error => "InvalidCharacterError", - .index_size_error => "IndexSizeErorr", - .syntax_error => "SyntaxError", + .no_modification_allowed_error => "NoModificationAllowedError", .not_found => "NotFoundError", .not_supported => "NotSupportedError", - .hierarchy_error => "HierarchyError", + .inuse_attribute_error => "InUseAttributeError", + .invalid_state_error => "InvalidStateError", + .syntax_error => "SyntaxError", + .invalid_modification_error => "InvalidModificationError", + .namespace_error => "NamespaceError", + .invalid_access_error => "InvalidAccessError", + .security_error => "SecurityError", + .network_error => "NetworkError", + .abort_error => "AbortError", + .url_mismatch_error => "URLMismatchError", + .quota_exceeded_error => "QuotaExceededError", + .timeout_error => "TimeoutError", + .invalid_node_type_error => "InvalidNodeTypeError", + .data_clone_error => "DataCloneError", }; } pub fn getMessage(self: *const DOMException) []const u8 { + if (self._custom_message) |msg| { + return msg; + } return switch (self._code) { .none => "", .invalid_character_error => "Error: Invalid Character", @@ -63,17 +110,76 @@ pub fn getMessage(self: *const DOMException) []const u8 { .not_supported => "Not Supported", .not_found => "Not Found", .hierarchy_error => "Hierarchy Error", + else => @tagName(self._code), }; } +pub fn toString(self: *const DOMException) []const u8 { + if (self._custom_message) |msg| { + return msg; + } + return switch (self._code) { + .none => "Error", + else => self.getMessage(), + }; +} + +pub fn className(_: *const DOMException) []const u8 { + return "[object DOMException]"; +} + const Code = enum(u8) { none = 0, index_size_error = 1, hierarchy_error = 3, + wrong_document_error = 4, invalid_character_error = 5, + no_modification_allowed_error = 7, not_found = 8, not_supported = 9, + inuse_attribute_error = 10, + invalid_state_error = 11, syntax_error = 12, + invalid_modification_error = 13, + namespace_error = 14, + invalid_access_error = 15, + security_error = 18, + network_error = 19, + abort_error = 20, + url_mismatch_error = 21, + quota_exceeded_error = 22, + timeout_error = 23, + invalid_node_type_error = 24, + data_clone_error = 25, + + /// Maps a standard error name to its legacy code + /// Returns .none (code 0) for non-legacy error names + pub fn fromName(name: []const u8) Code { + const lookup = std.StaticStringMap(Code).initComptime(.{ + .{ "IndexSizeError", .index_size_error }, + .{ "HierarchyRequestError", .hierarchy_error }, + .{ "WrongDocumentError", .wrong_document_error }, + .{ "InvalidCharacterError", .invalid_character_error }, + .{ "NoModificationAllowedError", .no_modification_allowed_error }, + .{ "NotFoundError", .not_found }, + .{ "NotSupportedError", .not_supported }, + .{ "InUseAttributeError", .inuse_attribute_error }, + .{ "InvalidStateError", .invalid_state_error }, + .{ "SyntaxError", .syntax_error }, + .{ "InvalidModificationError", .invalid_modification_error }, + .{ "NamespaceError", .namespace_error }, + .{ "InvalidAccessError", .invalid_access_error }, + .{ "SecurityError", .security_error }, + .{ "NetworkError", .network_error }, + .{ "AbortError", .abort_error }, + .{ "URLMismatchError", .url_mismatch_error }, + .{ "QuotaExceededError", .quota_exceeded_error }, + .{ "TimeoutError", .timeout_error }, + .{ "InvalidNodeTypeError", .invalid_node_type_error }, + .{ "DataCloneError", .data_clone_error }, + }); + return lookup.get(name) orelse .none; + } }; pub const JsApi = struct { @@ -89,5 +195,10 @@ pub const JsApi = struct { pub const code = bridge.accessor(DOMException.getCode, null, .{}); pub const name = bridge.accessor(DOMException.getName, null, .{}); pub const message = bridge.accessor(DOMException.getMessage, null, .{}); - pub const toString = bridge.function(DOMException.getMessage, .{}); + pub const toString = bridge.function(DOMException.toString, .{}); }; + +const testing = @import("../../testing.zig"); +test "WebApi: DOMException" { + try testing.htmlRunner("domexception.html", .{}); +} diff --git a/src/browser/webapi/DOMImplementation.zig b/src/browser/webapi/DOMImplementation.zig index e2a86357..467b5ae2 100644 --- a/src/browser/webapi/DOMImplementation.zig +++ b/src/browser/webapi/DOMImplementation.zig @@ -21,14 +21,17 @@ const std = @import("std"); const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); const Node = @import("Node.zig"); +const Document = @import("Document.zig"); +const HTMLDocument = @import("HTMLDocument.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 ""); + // Firefox converts null to the string "null", not empty string + const pub_id = if (public_id) |p| try page.dupeString(p) else "null"; + const sys_id = if (system_id) |s| try page.dupeString(s) else "null"; const doctype = try page._factory.node(DocumentType{ ._proto = undefined, @@ -40,7 +43,60 @@ pub fn createDocumentType(_: *const DOMImplementation, qualified_name: []const u return doctype; } -pub fn hasFeature(_: *const DOMImplementation, _: []const u8, _: ?[]const u8) bool { +pub fn createHTMLDocument(_: *const DOMImplementation, title: ?[]const u8, page: *Page) !*Document { + const document = (try page._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument(); + document._ready_state = .complete; + + { + const doctype = try page._factory.node(DocumentType{ + ._proto = undefined, + ._name = "html", + ._public_id = "", + ._system_id = "", + }); + _ = try document.asNode().appendChild(doctype.asNode(), page); + } + + const html_node = try page.createElement(null, "html", null); + _ = try document.asNode().appendChild(html_node, page); + + const head_node = try page.createElement(null, "head", null); + _ = try html_node.appendChild(head_node, page); + + if (title) |t| { + const title_node = try page.createElement(null, "title", null); + _ = try head_node.appendChild(title_node, page); + const text_node = try page.createTextNode(t); + _ = try title_node.appendChild(text_node, page); + } + + const body_node = try page.createElement(null, "body", null); + _ = try html_node.appendChild(body_node, page); + + return document; +} + +pub fn createDocument(_: *const DOMImplementation, namespace: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, page: *Page) !*Document { + // Create XML Document + const document = (try page._factory.document(Node.Document.XMLDocument{ ._proto = undefined })).asDocument(); + + // Append doctype if provided + if (doctype) |dt| { + _ = try document.asNode().appendChild(dt.asNode(), page); + } + + // Create and append root element if qualified_name provided + if (qualified_name) |qname| { + if (qname.len > 0) { + const root = try page.createElement(namespace, qname, null); + _ = try document.asNode().appendChild(root, page); + } + } + + return document; +} + +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; @@ -61,6 +117,8 @@ pub const JsApi = struct { }; pub const createDocumentType = bridge.function(DOMImplementation.createDocumentType, .{ .dom_exception = true }); + pub const createDocument = bridge.function(DOMImplementation.createDocument, .{}); + pub const createHTMLDocument = bridge.function(DOMImplementation.createHTMLDocument, .{}); pub const hasFeature = bridge.function(DOMImplementation.hasFeature, .{}); pub const toString = bridge.function(_toString, .{}); diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 87019a54..5f3fab95 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -35,6 +35,7 @@ const DOMImplementation = @import("DOMImplementation.zig"); const StyleSheetList = @import("css/StyleSheetList.zig"); pub const HTMLDocument = @import("HTMLDocument.zig"); +pub const XMLDocument = @import("XMLDocument.zig"); const Document = @This(); @@ -50,6 +51,7 @@ _style_sheets: ?*StyleSheetList = null, pub const Type = union(enum) { generic, html: *HTMLDocument, + xml: *XMLDocument, }; pub fn is(self: *Document, comptime T: type) ?*T { @@ -59,6 +61,11 @@ pub fn is(self: *Document, comptime T: type) ?*T { return html; } }, + .xml => |xml| { + if (T == XMLDocument) { + return xml; + } + }, .generic => {}, } return null; @@ -83,6 +90,7 @@ pub fn getURL(_: *const Document, page: *const Page) [:0]const u8 { pub fn getContentType(self: *const Document) []const u8 { return switch (self._type) { .html => "text/html", + .xml => "application/xml", .generic => "application/xml", }; } @@ -217,6 +225,7 @@ pub fn className(self: *const Document) []const u8 { return switch (self._type) { .generic => "[object Document]", .html => "[object HTMLDocument]", + .xml => "[object XMLDocument]", }; } @@ -239,10 +248,15 @@ pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node pub fn createCDATASection(self: *const Document, data: []const u8, page: *Page) !*Node { switch (self._type) { .html => return error.NotSupported, + .xml => return page.createCDATASection(data), .generic => return page.createCDATASection(data), } } +pub fn createProcessingInstruction(_: *const Document, target: []const u8, data: []const u8, page: *Page) !*Node { + return page.createProcessingInstruction(target, data); +} + const Range = @import("Range.zig"); pub fn createRange(_: *const Document, page: *Page) !*Range { return Range.init(page); @@ -432,6 +446,7 @@ pub const JsApi = struct { pub const createTextNode = bridge.function(Document.createTextNode, .{}); pub const createAttribute = bridge.function(Document.createAttribute, .{ .dom_exception = true }); pub const createCDATASection = bridge.function(Document.createCDATASection, .{ .dom_exception = true }); + pub const createProcessingInstruction = bridge.function(Document.createProcessingInstruction, .{ .dom_exception = true }); pub const createRange = bridge.function(Document.createRange, .{}); pub const createEvent = bridge.function(Document.createEvent, .{ .dom_exception = true }); pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{}); diff --git a/src/browser/webapi/DocumentType.zig b/src/browser/webapi/DocumentType.zig index aab8052e..0bced680 100644 --- a/src/browser/webapi/DocumentType.zig +++ b/src/browser/webapi/DocumentType.zig @@ -71,8 +71,3 @@ pub const JsApi = struct { return self.className(); } }; - -const testing = @import("../../testing.zig"); -test "WebApi: DOMImplementation" { - try testing.htmlRunner("domimplementation.html", .{}); -} diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index c47896eb..0e68d7fd 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -332,6 +332,8 @@ fn _getInnerText(self: *Element, writer: *std.Io.Writer, state: *innerTextState) // CDATA sections should not be used within HTML. They are // considered comments and are not displayed. .cdata_section => {}, + // Processing instructions are not displayed in innerText + .processing_instruction => {}, }, .document => {}, .document_type => {}, diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index e27ca505..1495cb09 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -229,8 +229,8 @@ pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!vo .element => { var it = self.childrenIterator(); while (it.next()) |child| { - // ignore comments and TODO processing instructions. - if (child.is(CData.Comment) != null) { + // ignore comments and processing instructions. + if (child.is(CData.Comment) != null or child.is(CData.ProcessingInstruction) != null) { continue; } try child.getTextContent(writer); @@ -270,6 +270,7 @@ pub fn getNodeName(self: *const Node, buf: []u8) []const u8 { .text => "#text", .cdata_section => "#cdata-section", .comment => "#comment", + .processing_instruction => |pi| pi._target, }, .document => "#document", .document_type => |dt| dt.getName(), @@ -285,6 +286,7 @@ pub fn getNodeType(self: *const Node) u8 { .cdata => |cd| switch (cd._type) { .text => 3, .cdata_section => 4, + .processing_instruction => 7, .comment => 8, }, .document => 9, @@ -603,6 +605,7 @@ pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) error{ OutOfMemory, Str .text => page.createTextNode(data), .cdata_section => page.createCDATASection(data), .comment => page.createComment(data), + .processing_instruction => |pi| page.createProcessingInstruction(pi._target, data), }; }, .element => |el| return el.cloneElement(deep, page), diff --git a/src/browser/webapi/XMLDocument.zig b/src/browser/webapi/XMLDocument.zig new file mode 100644 index 00000000..437e4fa4 --- /dev/null +++ b/src/browser/webapi/XMLDocument.zig @@ -0,0 +1,52 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const js = @import("../js/js.zig"); + +const Document = @import("Document.zig"); +const Node = @import("Node.zig"); + +const XMLDocument = @This(); + +_proto: *Document, + +pub fn asDocument(self: *XMLDocument) *Document { + return self._proto; +} + +pub fn asNode(self: *XMLDocument) *Node { + return self._proto.asNode(); +} + +pub fn asEventTarget(self: *XMLDocument) *@import("EventTarget.zig") { + return self._proto.asEventTarget(); +} + +pub fn className(_: *const XMLDocument) []const u8 { + return "[object XMLDocument]"; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(XMLDocument); + + pub const Meta = struct { + pub const name = "XMLDocument"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; diff --git a/src/browser/webapi/cdata/ProcessingInstruction.zig b/src/browser/webapi/cdata/ProcessingInstruction.zig new file mode 100644 index 00000000..97024d4e --- /dev/null +++ b/src/browser/webapi/cdata/ProcessingInstruction.zig @@ -0,0 +1,47 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const js = @import("../../js/js.zig"); + +const CData = @import("../CData.zig"); + +const ProcessingInstruction = @This(); + +_proto: *CData, +_target: []const u8, + +pub fn getTarget(self: *const ProcessingInstruction) []const u8 { + return self._target; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(ProcessingInstruction); + + pub const Meta = struct { + pub const name = "ProcessingInstruction"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const target = bridge.accessor(ProcessingInstruction.getTarget, null, .{}); +}; + +const testing = @import("../../../testing.zig"); +test "WebApi: ProcessingInstruction" { + try testing.htmlRunner("processing_instruction.html", .{}); +}