From e2645e41266156a52bb83a472aa7ac22b8e4e41f Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 9 Feb 2026 18:19:36 +0800 Subject: [PATCH] Fix 3 WPT cases for NodeIterator - Prevent recursive filters - Rethrow on filter exception - Add no-op (as per spec) `detach` --- src/browser/js/Caller.zig | 1 + src/browser/js/Function.zig | 25 ++++++++++++++++++++----- src/browser/js/TryCatch.zig | 15 ++++++++++++++- src/browser/webapi/DOMNodeIterator.zig | 24 ++++++++++++++++++++++-- src/browser/webapi/NodeFilter.zig | 2 +- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index b999c6e0..5fe4ab4c 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -336,6 +336,7 @@ fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, } const js_err: *const v8.Value = switch (err) { + error.TryCatchRethrow => return, error.InvalidArgument => isolate.createTypeError("invalid argument"), error.OutOfMemory => isolate.createError("out of memory"), error.IllegalConstructor => isolate.createError("Illegal Contructor"), diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 2038deee..e9bccb61 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -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 { 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 }); 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 { 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 }); return err; }; } 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 { - 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.* = .{}; const local = self.local; @@ -145,6 +156,10 @@ pub fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype, 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 { + if ((comptime opts.rethrow) and try_catch.hasCaught()) { + try_catch.rethrow(); + return error.TryCatchRethrow; + } caught.* = try_catch.caughtOrError(local.call_arena, error.JSExecCallback); return error.JSExecCallback; }; diff --git a/src/browser/js/TryCatch.zig b/src/browser/js/TryCatch.zig index f96524c4..d0f7a7d8 100644 --- a/src/browser/js/TryCatch.zig +++ b/src/browser/js/TryCatch.zig @@ -20,6 +20,8 @@ const std = @import("std"); const js = @import("js.zig"); const v8 = js.v8; +const IS_DEBUG = @import("builtin").mode == .Debug; + const Allocator = std.mem.Allocator; 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); } +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 { - if (!v8.v8__TryCatch__HasCaught(&self.handle)) { + if (self.hasCaught() == false) { return null; } diff --git a/src/browser/webapi/DOMNodeIterator.zig b/src/browser/webapi/DOMNodeIterator.zig index 3015951b..a4956ca7 100644 --- a/src/browser/webapi/DOMNodeIterator.zig +++ b/src/browser/webapi/DOMNodeIterator.zig @@ -31,6 +31,7 @@ _what_to_show: u32, _filter: NodeFilter, _reference_node: *Node, _pointer_before_reference_node: bool, +_active: bool = false, pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, page: *Page) !*DOMNodeIterator { 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 { + if (self._active) { + return error.InvalidStateError; + } + + self._active = true; + defer self._active = false; + var node = self._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 { + if (self._active) { + return error.InvalidStateError; + } + + self._active = true; + defer self._active = false; + var node = self._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 { // First check whatToShow 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 filter = bridge.accessor(DOMNodeIterator.getFilter, null, .{}); - pub const nextNode = bridge.function(DOMNodeIterator.nextNode, .{}); - pub const previousNode = bridge.function(DOMNodeIterator.previousNode, .{}); + pub const nextNode = bridge.function(DOMNodeIterator.nextNode, .{ .dom_exception = true }); + pub const previousNode = bridge.function(DOMNodeIterator.previousNode, .{ .dom_exception = true }); + pub const detach = bridge.function(DOMNodeIterator.detach, .{}); }; diff --git a/src/browser/webapi/NodeFilter.zig b/src/browser/webapi/NodeFilter.zig index 2dc371dc..69ef3a16 100644 --- a/src/browser/webapi/NodeFilter.zig +++ b/src/browser/webapi/NodeFilter.zig @@ -67,7 +67,7 @@ pub const SHOW_NOTATION: u32 = 0x800; pub fn acceptNode(self: *const NodeFilter, node: *Node, local: *const js.Local) !i32 { 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 {