Fix 3 WPT cases for NodeIterator

- Prevent recursive filters
- Rethrow on filter exception
- Add no-op (as per spec) `detach`
This commit is contained in:
Karl Seguin
2026-02-09 18:19:36 +08:00
parent 2e5d04389b
commit e2645e4126
5 changed files with 58 additions and 9 deletions

View File

@@ -336,6 +336,7 @@ fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror,
} }
const js_err: *const v8.Value = switch (err) { const js_err: *const v8.Value = switch (err) {
error.TryCatchRethrow => return,
error.InvalidArgument => isolate.createTypeError("invalid argument"), error.InvalidArgument => isolate.createTypeError("invalid argument"),
error.OutOfMemory => isolate.createError("out of memory"), error.OutOfMemory => isolate.createError("out of memory"),
error.IllegalConstructor => isolate.createError("Illegal Contructor"), error.IllegalConstructor => isolate.createError("Illegal Contructor"),

View File

@@ -69,7 +69,15 @@ pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Objec
pub fn call(self: *const Function, comptime T: type, args: anytype) !T { pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
var caught: js.TryCatch.Caught = undefined; var caught: js.TryCatch.Caught = undefined;
return self._tryCallWithThis(T, self.getThis(), args, &caught) catch |err| { return self._tryCallWithThis(T, self.getThis(), args, &caught, .{}) catch |err| {
log.warn(.js, "call caught", .{ .err = err, .caught = caught });
return err;
};
}
pub fn callRethrow(self: *const Function, comptime T: type, args: anytype) !T {
var caught: js.TryCatch.Caught = undefined;
return self._tryCallWithThis(T, self.getThis(), args, &caught, .{ .rethrow = true }) catch |err| {
log.warn(.js, "call caught", .{ .err = err, .caught = caught }); log.warn(.js, "call caught", .{ .err = err, .caught = caught });
return err; return err;
}; };
@@ -77,21 +85,24 @@ pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T { pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
var caught: js.TryCatch.Caught = undefined; var caught: js.TryCatch.Caught = undefined;
return self._tryCallWithThis(T, this, args, &caught) catch |err| { return self._tryCallWithThis(T, this, args, &caught, .{}) catch |err| {
log.warn(.js, "callWithThis caught", .{ .err = err, .caught = caught }); log.warn(.js, "callWithThis caught", .{ .err = err, .caught = caught });
return err; return err;
}; };
} }
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T { pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T {
return self._tryCallWithThis(T, self.getThis(), args, caught); return self._tryCallWithThis(T, self.getThis(), args, caught, .{});
} }
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T { pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
return self._tryCallWithThis(T, this, args, caught); return self._tryCallWithThis(T, this, args, caught, .{});
} }
pub fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T { const CallOpts = struct {
rethrow: bool = false,
};
fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught, comptime opts: CallOpts) !T {
caught.* = .{}; caught.* = .{};
const local = self.local; const local = self.local;
@@ -145,6 +156,10 @@ pub fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype,
defer try_catch.deinit(); defer try_catch.deinit();
const handle = v8.v8__Function__Call(self.handle, local.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse { const handle = v8.v8__Function__Call(self.handle, local.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
if ((comptime opts.rethrow) and try_catch.hasCaught()) {
try_catch.rethrow();
return error.TryCatchRethrow;
}
caught.* = try_catch.caughtOrError(local.call_arena, error.JSExecCallback); caught.* = try_catch.caughtOrError(local.call_arena, error.JSExecCallback);
return error.JSExecCallback; return error.JSExecCallback;
}; };

View File

@@ -20,6 +20,8 @@ const std = @import("std");
const js = @import("js.zig"); const js = @import("js.zig");
const v8 = js.v8; const v8 = js.v8;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const TryCatch = @This(); const TryCatch = @This();
@@ -32,8 +34,19 @@ pub fn init(self: *TryCatch, l: *const js.Local) void {
v8.v8__TryCatch__CONSTRUCT(&self.handle, l.isolate.handle); v8.v8__TryCatch__CONSTRUCT(&self.handle, l.isolate.handle);
} }
pub fn hasCaught(self: TryCatch) bool {
return v8.v8__TryCatch__HasCaught(&self.handle);
}
pub fn rethrow(self: *TryCatch) void {
if (comptime IS_DEBUG) {
std.debug.assert(self.hasCaught());
}
_ = v8.v8__TryCatch__ReThrow(&self.handle);
}
pub fn caught(self: TryCatch, allocator: Allocator) ?Caught { pub fn caught(self: TryCatch, allocator: Allocator) ?Caught {
if (!v8.v8__TryCatch__HasCaught(&self.handle)) { if (self.hasCaught() == false) {
return null; return null;
} }

View File

@@ -31,6 +31,7 @@ _what_to_show: u32,
_filter: NodeFilter, _filter: NodeFilter,
_reference_node: *Node, _reference_node: *Node,
_pointer_before_reference_node: bool, _pointer_before_reference_node: bool,
_active: bool = false,
pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, page: *Page) !*DOMNodeIterator { pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, page: *Page) !*DOMNodeIterator {
const node_filter = try NodeFilter.init(filter); const node_filter = try NodeFilter.init(filter);
@@ -64,6 +65,13 @@ pub fn getFilter(self: *const DOMNodeIterator) ?FilterOpts {
} }
pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node { pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
if (self._active) {
return error.InvalidStateError;
}
self._active = true;
defer self._active = false;
var node = self._reference_node; var node = self._reference_node;
var before_node = self._pointer_before_reference_node; var before_node = self._pointer_before_reference_node;
@@ -95,6 +103,13 @@ pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
} }
pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node { pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node {
if (self._active) {
return error.InvalidStateError;
}
self._active = true;
defer self._active = false;
var node = self._reference_node; var node = self._reference_node;
var before_node = self._pointer_before_reference_node; var before_node = self._pointer_before_reference_node;
@@ -119,6 +134,10 @@ pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node {
} }
} }
pub fn detach(_: *const DOMNodeIterator) void {
// no-op legacy
}
fn filterNode(self: *const DOMNodeIterator, node: *Node, page: *Page) !i32 { fn filterNode(self: *const DOMNodeIterator, node: *Node, page: *Page) !i32 {
// First check whatToShow // First check whatToShow
if (!NodeFilter.shouldShow(node, self._what_to_show)) { if (!NodeFilter.shouldShow(node, self._what_to_show)) {
@@ -181,6 +200,7 @@ pub const JsApi = struct {
pub const whatToShow = bridge.accessor(DOMNodeIterator.getWhatToShow, null, .{}); pub const whatToShow = bridge.accessor(DOMNodeIterator.getWhatToShow, null, .{});
pub const filter = bridge.accessor(DOMNodeIterator.getFilter, null, .{}); pub const filter = bridge.accessor(DOMNodeIterator.getFilter, null, .{});
pub const nextNode = bridge.function(DOMNodeIterator.nextNode, .{}); pub const nextNode = bridge.function(DOMNodeIterator.nextNode, .{ .dom_exception = true });
pub const previousNode = bridge.function(DOMNodeIterator.previousNode, .{}); pub const previousNode = bridge.function(DOMNodeIterator.previousNode, .{ .dom_exception = true });
pub const detach = bridge.function(DOMNodeIterator.detach, .{});
}; };

View File

@@ -67,7 +67,7 @@ pub const SHOW_NOTATION: u32 = 0x800;
pub fn acceptNode(self: *const NodeFilter, node: *Node, local: *const js.Local) !i32 { pub fn acceptNode(self: *const NodeFilter, node: *Node, local: *const js.Local) !i32 {
const func = self._func orelse return FILTER_ACCEPT; const func = self._func orelse return FILTER_ACCEPT;
return local.toLocal(func).call(i32, .{node}); return local.toLocal(func).callRethrow(i32, .{node});
} }
pub fn shouldShow(node: *const Node, what_to_show: u32) bool { pub fn shouldShow(node: *const Node, what_to_show: u32) bool {