From 8873e613d207b142c404416a814517898b307e6b Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 16 Dec 2025 19:16:42 +0800 Subject: [PATCH] improve domexception --- src/browser/js/ExecutionWorld.zig | 23 +++++++--------- .../tests/document/query_selector.html | 2 +- .../tests/document/query_selector_all.html | 2 +- src/browser/tests/domexception.html | 9 +++---- src/browser/tests/element/matches.html | 2 +- src/browser/tests/element/query_selector.html | 2 +- .../tests/element/query_selector_all.html | 2 +- .../tests/element/selector_invalid.html | 4 +-- src/browser/tests/legacy/dom/exceptions.html | 5 ++-- src/browser/webapi/DOMException.zig | 25 +++++++++-------- src/browser/webapi/Node.zig | 27 ++++++++++++++----- 11 files changed, 56 insertions(+), 47 deletions(-) diff --git a/src/browser/js/ExecutionWorld.zig b/src/browser/js/ExecutionWorld.zig index 77a5865d..e9a8786d 100644 --- a/src/browser/js/ExecutionWorld.zig +++ b/src/browser/js/ExecutionWorld.zig @@ -91,8 +91,7 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal const global_template = js_global.getInstanceTemplate(); global_template.setInternalFieldCount(1); - // Configure the missing property callback on the global - // object. + // Configure the missing property callback on the global object. if (global_callback != null) { const configuration = v8.NamedPropertyHandlerConfiguration{ .getter = struct { @@ -162,15 +161,17 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal } errdefer if (enter) handle_scope.?.deinit(); + const js_global = v8_context.getGlobal(); { // If we want to overwrite the built-in console, we have to // delete the built-in one. - const js_obj = v8_context.getGlobal(); + const console_key = v8.String.initUtf8(isolate, "console"); - if (js_obj.deleteValue(v8_context, console_key) == false) { + if (js_global.deleteValue(v8_context, console_key) == false) { return error.ConsoleDeleteError; } } + const context_id = env.context_id; env.context_id = context_id + 1; @@ -195,17 +196,11 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal v8_context.setEmbedderData(1, data); } - // @ZIGDOM // Custom exception - // NOTE: there is no way in v8 to subclass the Error built-in type - // TODO: this is an horrible hack - // inline for (JsApi) |JsApi| { - // const Struct = s.defaultValue().?; - // if (@hasDecl(Struct, "ErrorSet")) { - // const script = comptime JsApi.Meta.name ++ ".prototype.__proto__ = Error.prototype"; - // _ = try context.exec(script, "errorSubclass"); - // } - // } + // TODO: this is an horrible hack, I can't figure out how to do this cleanly. + { + _ = try context.exec("DOMException.prototype.__proto__ = Error.prototype", "errorSubclass"); + } try context.setupGlobal(); return context; diff --git a/src/browser/tests/document/query_selector.html b/src/browser/tests/document/query_selector.html index bd4b0d57..b83c3928 100644 --- a/src/browser/tests/document/query_selector.html +++ b/src/browser/tests/document/query_selector.html @@ -23,7 +23,7 @@
Main content
diff --git a/src/browser/tests/element/matches.html b/src/browser/tests/element/matches.html index 324453cb..5e1721b5 100644 --- a/src/browser/tests/element/matches.html +++ b/src/browser/tests/element/matches.html @@ -66,7 +66,7 @@ { const container = $('#test-container'); - testing.expectError("Syntax Error", () => container.matches('')); + testing.expectError("SyntaxError: Syntax Error", () => container.matches('')); testing.withError((err) => { testing.expectEqual(12, err.code); testing.expectEqual("SyntaxError", err.name); diff --git a/src/browser/tests/element/query_selector.html b/src/browser/tests/element/query_selector.html index 9750bd1a..9564ca6d 100644 --- a/src/browser/tests/element/query_selector.html +++ b/src/browser/tests/element/query_selector.html @@ -12,7 +12,7 @@ const p1 = $('#p1'); testing.expectEqual(null, p1.querySelector('#p1')); - testing.expectError("Syntax Error", () => p1.querySelector('')); + testing.expectError("SyntaxError: Syntax Error", () => p1.querySelector('')); testing.withError((err) => { testing.expectEqual(12, err.code); testing.expectEqual("SyntaxError", err.name); diff --git a/src/browser/tests/element/query_selector_all.html b/src/browser/tests/element/query_selector_all.html index f203abe4..eeedc876 100644 --- a/src/browser/tests/element/query_selector_all.html +++ b/src/browser/tests/element/query_selector_all.html @@ -24,7 +24,7 @@ diff --git a/src/browser/tests/legacy/dom/exceptions.html b/src/browser/tests/legacy/dom/exceptions.html index c6bb91f1..654f68ca 100644 --- a/src/browser/tests/legacy/dom/exceptions.html +++ b/src/browser/tests/legacy/dom/exceptions.html @@ -10,10 +10,9 @@ let link = $('#link'); testing.withError((err) => { - const msg = "Failed to execute 'appendChild' on 'Node': The new child element contains the parent."; testing.expectEqual(3, err.code); - testing.expectEqual(msg, err.message); - testing.expectEqual('HierarchyRequestError: ' + msg, err.toString()); + testing.expectEqual('Hierarchy Error', err.message); + testing.expectEqual('HierarchyRequestError: Hierarchy Error', err.toString()); testing.expectEqual(true, err instanceof DOMException); testing.expectEqual(true, err instanceof Error); }, () => link.appendChild(content)); diff --git a/src/browser/webapi/DOMException.zig b/src/browser/webapi/DOMException.zig index f2558752..f392e676 100644 --- a/src/browser/webapi/DOMException.zig +++ b/src/browser/webapi/DOMException.zig @@ -23,16 +23,16 @@ const Page = @import("../Page.zig"); const DOMException = @This(); _code: Code = .none, -_custom_message: ?[]const u8 = null, _custom_name: ?[]const u8 = null, +_custom_message: ?[]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, + ._custom_message = message, }; } @@ -104,8 +104,8 @@ pub fn getMessage(self: *const DOMException) []const u8 { } return switch (self._code) { .none => "", - .invalid_character_error => "Error: Invalid Character", - .index_size_error => "IndexSizeError: Index or size is negative or greater than the allowed amount", + .invalid_character_error => "Invalid Character", + .index_size_error => "Index or size is negative or greater than the allowed amount", .syntax_error => "Syntax Error", .not_supported => "Not Supported", .not_found => "Not Found", @@ -114,14 +114,17 @@ pub fn getMessage(self: *const DOMException) []const u8 { }; } -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 toString(self: *const DOMException, page: *Page) ![]const u8 { + const msg = blk: { + if (self._custom_message) |msg| { + break :blk msg; + } + switch (self._code) { + .none => return "Error", + else => break :blk self.getMessage(), + } }; + return std.fmt.bufPrint(&page.buf, "{s}: {s}", .{ self.getName(), msg }) catch return msg; } pub fn className(_: *const DOMException) []const u8 { diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 1495cb09..41357bcc 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -190,12 +190,27 @@ pub fn parentElement(self: *const Node) ?*Element { return parent.is(Element); } +// Validates that a node can be inserted as a child of parent. +fn validateNodeInsertion(parent: *Node, node: *Node) !void { + // Check if parent is a valid type to have children + if (parent._type != .document and parent._type != .element and parent._type != .document_fragment) { + return error.HierarchyError; + } + + // Check if node contains parent (would create a cycle) + if (node.contains(parent)) { + return error.HierarchyError; + } +} + pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node { if (child.is(DocumentFragment)) |_| { try page.appendAllChildren(child, self); return child; } + try validateNodeInsertion(self, child); + page.domChanged(); // If the child is currently connected, and if its new parent is connected, @@ -435,6 +450,8 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page return new_node; } + try validateNodeInsertion(self, new_node); + const child_already_connected = new_node.isConnected(); // Check if we're adopting the node to a different document const child_root = new_node.getRootNode(null); @@ -464,12 +481,8 @@ pub fn replaceChild(self: *Node, new_child: *Node, old_child: *Node, page: *Page if (old_child._parent == null or old_child._parent.? != self) { return error.HierarchyError; } - if (self._type != .document and self._type != .element) { - return error.HierarchyError; - } - if (new_child.contains(self)) { - return error.HierarchyError; - } + + try validateNodeInsertion(self, new_child); _ = try self.insertBefore(new_child, old_child, page); page.removeNode(self, old_child, .{ .will_be_reconnected = false }); @@ -840,7 +853,7 @@ pub const JsApi = struct { pub const previousSibling = bridge.accessor(Node.previousSibling, null, .{}); pub const parentNode = bridge.accessor(Node.parentNode, null, .{}); pub const parentElement = bridge.accessor(Node.parentElement, null, .{}); - pub const appendChild = bridge.function(Node.appendChild, .{}); + pub const appendChild = bridge.function(Node.appendChild, .{ .dom_exception = true }); pub const childNodes = bridge.accessor(Node.childNodes, null, .{}); pub const isConnected = bridge.accessor(Node.isConnected, null, .{}); pub const ownerDocument = bridge.accessor(Node.ownerDocument, null, .{});