improve domexception

This commit is contained in:
Karl Seguin
2025-12-16 19:16:42 +08:00
parent 761b35b199
commit 8873e613d2
11 changed files with 56 additions and 47 deletions

View File

@@ -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;

View File

@@ -23,7 +23,7 @@
<main>Main content</main>
<script id=byId name="test1">
testing.expectError("Syntax Error", () => document.querySelector(''));
testing.expectError("SyntaxError: Syntax Error", () => document.querySelector(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);

View File

@@ -32,7 +32,7 @@
</script>
<script id=script1 name="test1">
testing.expectError("Syntax Error", () => document.querySelectorAll(''));
testing.expectError("SyntaxError: Syntax Error", () => document.querySelectorAll(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);

View File

@@ -120,16 +120,15 @@
<div id="content">
<a id="link" href="foo" class="ok">OK</a>
</div>
<!-- <script id=hierarchy_error>
<script id=hierarchy_error>
let link = $('#link');
let content = $('#content');
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(true, err instanceof DOMException);
testing.expectEqual(true, err instanceof Error);
}, () => link.appendChild(content));
</script> -->
</script>

View File

@@ -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);

View File

@@ -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);

View File

@@ -24,7 +24,7 @@
<script id=errors>
{
const root = $('#root');
testing.expectError("Syntax Error", () => root.querySelectorAll(''));
testing.expectError("SyntaxError: Syntax Error", () => root.querySelectorAll(''));
testing.withError((err) => {
testing.expectEqual(12, err.code);
testing.expectEqual("SyntaxError", err.name);

View File

@@ -45,8 +45,8 @@
const container = $('#container');
// Empty selectors
testing.expectError("Syntax Error", () => container.querySelector(''));
testing.expectError("Syntax Error", () => document.querySelectorAll(''));
testing.expectError("SyntaxError: Syntax Error", () => container.querySelector(''));
testing.expectError("SyntaxError: Syntax Error", () => document.querySelectorAll(''));
}
</script>

View File

@@ -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));

View File

@@ -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 {

View File

@@ -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, .{});