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