From e586e9013f8494b1c72057d4ff13648317b9c647 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 10 Jul 2026 21:50:31 +0200 Subject: [PATCH] webapi: DOMImplementation owns its document; validate doctype names Fixes 81 failing tests in WPT /dom/nodes/DOMImplementation-createDocumentType.html (1/82 -> 82/82): - DOMImplementation was an empty singleton-style object with no link to its document, so a doctype created through another document's implementation (e.g. createHTMLDocument().implementation) reported the frame's main document as its ownerDocument. The implementation object now stores its associated document and registers created doctypes in the frame's node-owner-document map, following the createDocumentFragment pattern. - createDocumentType now validates the qualified name against the doctype name production (no ASCII whitespace or '>'), throwing InvalidCharacterError. Coverage: /dom/nodes/DOMImplementation-createDocumentType.html 1/82 -> 82/82. Co-Authored-By: Claude Fable 5 --- src/browser/webapi/DOMImplementation.zig | 23 +++++++++++++++++++---- src/browser/webapi/Document.zig | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/browser/webapi/DOMImplementation.zig b/src/browser/webapi/DOMImplementation.zig index 21e30804c..2ee3df218 100644 --- a/src/browser/webapi/DOMImplementation.zig +++ b/src/browser/webapi/DOMImplementation.zig @@ -23,10 +23,26 @@ const Document = @import("Document.zig"); const DocumentType = @import("DocumentType.zig"); const DOMImplementation = @This(); -_pad: bool = false, -pub fn createDocumentType(_: *const DOMImplementation, qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, frame: *Frame) !*DocumentType { - return DocumentType.init(qualified_name, public_id, system_id, frame); +// The document this implementation object belongs to: nodes created through +// it are owned by that document, not necessarily the frame's main document. +_document: *Document, + +pub fn createDocumentType(self: *const DOMImplementation, qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, frame: *Frame) !*DocumentType { + // Per spec, qualifiedName must match the doctype name production: any + // characters except ASCII whitespace or '>'. + for (qualified_name) |c| { + switch (c) { + '\t', '\n', 0x0C, '\r', ' ', '>' => return error.InvalidCharacterError, + else => {}, + } + } + + const doctype = try DocumentType.init(qualified_name, public_id, system_id, frame); + if (self._document != frame.document) { + try frame.setNodeOwnerDocument(doctype.asNode(), self._document); + } + return doctype; } pub fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, frame: *Frame) !*Document { @@ -98,7 +114,6 @@ pub const JsApi = struct { pub const name = "DOMImplementation"; pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; - pub const empty_with_no_proto = true; }; pub const createDocumentType = bridge.function(DOMImplementation.createDocumentType, .{}); diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index e3b711b7f..dd2ed5781 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -418,7 +418,7 @@ pub fn querySelectorAll(self: *Document, input: String, frame: *Frame) !*Selecto pub fn getImplementation(self: *Document, frame: *Frame) !*DOMImplementation { if (self._implementation) |impl| return impl; - const impl = try frame._factory.create(DOMImplementation{}); + const impl = try frame._factory.create(DOMImplementation{ ._document = self }); self._implementation = impl; return impl; }