Skip to content

Commit ce14f0b

Browse files
committed
Improves correctness of NodeIterator
Minor improvement to correctness of TreeWalker. Fun fact, this is the first time, that I've run into, where we have to default null and undefined to different values. Also, tweaked the WPT test runner. WPT test results use | as a field delimiter. But a WPT test (and, I assume a message) can contain a |. So we had at least a few tests that were being reported as failed, only because the result line was weird / unexpected. No great robust way to parse this, but I changed it to look explicitly for |Pass or |Fail and use those positions as anchor points.
1 parent 557f844 commit ce14f0b

File tree

5 files changed

+143
-29
lines changed

5 files changed

+143
-29
lines changed

src/browser/dom/document.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,12 +249,12 @@ pub const Document = struct {
249249
return Node.replaceChildren(parser.documentToNode(self), nodes);
250250
}
251251

252-
pub fn _createTreeWalker(_: *parser.Document, root: *parser.Node, what_to_show: ?u32, filter: ?TreeWalker.TreeWalkerOpts) !TreeWalker {
253-
return try TreeWalker.init(root, what_to_show, filter);
252+
pub fn _createTreeWalker(_: *parser.Document, root: *parser.Node, what_to_show: ?TreeWalker.WhatToShow, filter: ?TreeWalker.TreeWalkerOpts) !TreeWalker {
253+
return TreeWalker.init(root, what_to_show, filter);
254254
}
255255

256-
pub fn _createNodeIterator(_: *parser.Document, root: *parser.Node, what_to_show: ?u32, filter: ?NodeIterator.NodeIteratorOpts) !NodeIterator {
257-
return try NodeIterator.init(root, what_to_show, filter);
256+
pub fn _createNodeIterator(_: *parser.Document, root: *parser.Node, what_to_show: ?NodeIterator.WhatToShow, filter: ?NodeIterator.NodeIteratorOpts) !NodeIterator {
257+
return NodeIterator.init(root, what_to_show, filter);
258258
}
259259

260260
pub fn getActiveElement(self: *parser.Document, page: *Page) !?*parser.Element {

src/browser/dom/node_iterator.zig

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,42 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
2021
const parser = @import("../netsurf.zig");
2122
const Env = @import("../env.zig").Env;
2223
const NodeFilter = @import("node_filter.zig");
2324
const Node = @import("node.zig").Node;
2425
const NodeUnion = @import("node.zig").Union;
26+
const DOMException = @import("exceptions.zig").DOMException;
2527

2628
// https://developer.mozilla.org/en-US/docs/Web/API/NodeIterator
2729
// While this is similar to TreeWalker it has its own implementation as there are several subtle differences
2830
// For example:
2931
// - nextNode returns the reference node, whereas TreeWalker returns the next node
3032
// - Skip and reject are equivalent for NodeIterator, for TreeWalker they are different
3133
pub const NodeIterator = struct {
34+
pub const Exception = DOMException;
35+
3236
root: *parser.Node,
3337
reference_node: *parser.Node,
3438
what_to_show: u32,
3539
filter: ?NodeIteratorOpts,
3640
filter_func: ?Env.Function,
37-
3841
pointer_before_current: bool = true,
42+
// used to track / block recursive filters
43+
is_in_callback: bool = false,
44+
45+
// One of the few cases where null and undefined resolve to different default.
46+
// We need the raw JsObject so that we can probe the tri state:
47+
// null, undefined or i32.
48+
pub const WhatToShow = Env.JsObject;
3949

4050
pub const NodeIteratorOpts = union(enum) {
4151
function: Env.Function,
4252
object: struct { acceptNode: Env.Function },
4353
};
4454

45-
pub fn init(node: *parser.Node, what_to_show: ?u32, filter: ?NodeIteratorOpts) !NodeIterator {
55+
pub fn init(node: *parser.Node, what_to_show_: ?WhatToShow, filter: ?NodeIteratorOpts) !NodeIterator {
4656
var filter_func: ?Env.Function = null;
4757
if (filter) |f| {
4858
filter_func = switch (f) {
@@ -51,10 +61,21 @@ pub const NodeIterator = struct {
5161
};
5262
}
5363

64+
var what_to_show: u32 = undefined;
65+
if (what_to_show_) |wts| {
66+
switch (try wts.triState(NodeIterator, "what_to_show", u32)) {
67+
.null => what_to_show = 0,
68+
.undefined => what_to_show = NodeFilter.NodeFilter._SHOW_ALL,
69+
.value => |v| what_to_show = v,
70+
}
71+
} else {
72+
what_to_show = NodeFilter.NodeFilter._SHOW_ALL;
73+
}
74+
5475
return .{
5576
.root = node,
5677
.reference_node = node,
57-
.what_to_show = what_to_show orelse NodeFilter.NodeFilter._SHOW_ALL,
78+
.what_to_show = what_to_show,
5879
.filter = filter,
5980
.filter_func = filter_func,
6081
};
@@ -81,9 +102,13 @@ pub const NodeIterator = struct {
81102
}
82103

83104
pub fn _nextNode(self: *NodeIterator) !?NodeUnion {
84-
if (self.pointer_before_current) { // Unlike TreeWalker, NodeIterator starts at the first node
85-
self.pointer_before_current = false;
105+
try self.callbackStart();
106+
defer self.callbackEnd();
107+
108+
if (self.pointer_before_current) {
109+
// Unlike TreeWalker, NodeIterator starts at the first node
86110
if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) {
111+
self.pointer_before_current = false;
87112
return try Node.toInterface(self.reference_node);
88113
}
89114
}
@@ -107,13 +132,19 @@ pub const NodeIterator = struct {
107132
}
108133

109134
pub fn _previousNode(self: *NodeIterator) !?NodeUnion {
135+
try self.callbackStart();
136+
defer self.callbackEnd();
137+
110138
if (!self.pointer_before_current) {
111-
self.pointer_before_current = true;
112139
if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) {
113-
return try Node.toInterface(self.reference_node); // Still need to verify as last may be first as well
140+
self.pointer_before_current = true;
141+
// Still need to verify as last may be first as well
142+
return try Node.toInterface(self.reference_node);
114143
}
115144
}
116-
if (self.reference_node == self.root) return null;
145+
if (self.reference_node == self.root) {
146+
return null;
147+
}
117148

118149
var current = self.reference_node;
119150
while (try parser.nodePreviousSibling(current)) |previous| {
@@ -151,6 +182,11 @@ pub const NodeIterator = struct {
151182
return null;
152183
}
153184

185+
pub fn _detach(self: *const NodeIterator) void {
186+
// no-op as per spec
187+
_ = self;
188+
}
189+
154190
fn firstChild(self: *const NodeIterator, node: *parser.Node) !?*parser.Node {
155191
const children = try parser.nodeGetChildNodes(node);
156192
const child_count = try parser.nodeListLength(children);
@@ -217,6 +253,18 @@ pub const NodeIterator = struct {
217253

218254
return null;
219255
}
256+
257+
fn callbackStart(self: *NodeIterator) !void {
258+
if (self.is_in_callback) {
259+
// this is the correct DOMExeption
260+
return error.InvalidState;
261+
}
262+
self.is_in_callback = true;
263+
}
264+
265+
fn callbackEnd(self: *NodeIterator) void {
266+
self.is_in_callback = false;
267+
}
220268
};
221269

222270
const testing = @import("../../testing.zig");

src/browser/dom/tree_walker.zig

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ pub const TreeWalker = struct {
3333
filter: ?TreeWalkerOpts,
3434
filter_func: ?Env.Function,
3535

36+
// One of the few cases where null and undefined resolve to different default.
37+
// We need the raw JsObject so that we can probe the tri state:
38+
// null, undefined or i32.
39+
pub const WhatToShow = Env.JsObject;
40+
3641
pub const TreeWalkerOpts = union(enum) {
3742
function: Env.Function,
3843
object: struct { acceptNode: Env.Function },
3944
};
4045

41-
pub fn init(node: *parser.Node, what_to_show: ?u32, filter: ?TreeWalkerOpts) !TreeWalker {
46+
pub fn init(node: *parser.Node, what_to_show_: ?WhatToShow, filter: ?TreeWalkerOpts) !TreeWalker {
4247
var filter_func: ?Env.Function = null;
4348

4449
if (filter) |f| {
@@ -48,10 +53,21 @@ pub const TreeWalker = struct {
4853
};
4954
}
5055

56+
var what_to_show: u32 = undefined;
57+
if (what_to_show_) |wts| {
58+
switch (try wts.triState(TreeWalker, "what_to_show", u32)) {
59+
.null => what_to_show = 0,
60+
.undefined => what_to_show = NodeFilter.NodeFilter._SHOW_ALL,
61+
.value => |v| what_to_show = v,
62+
}
63+
} else {
64+
what_to_show = NodeFilter.NodeFilter._SHOW_ALL;
65+
}
66+
5167
return .{
5268
.root = node,
5369
.current_node = node,
54-
.what_to_show = what_to_show orelse NodeFilter.NodeFilter._SHOW_ALL,
70+
.what_to_show = what_to_show,
5571
.filter = filter,
5672
.filter_func = filter_func,
5773
};

src/main_wpt.zig

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -263,30 +263,33 @@ const Writer = struct {
263263
if (line.len == 0) {
264264
break;
265265
}
266-
var fields = std.mem.splitScalar(u8, line, '|');
267-
const case_name = fields.next() orelse {
268-
std.debug.print("invalid result line: {s}\n", .{line});
269-
return error.InvalidResult;
270-
};
271-
272-
const text_status = fields.next() orelse {
273-
std.debug.print("invalid result line: {s}\n", .{line});
274-
return error.InvalidResult;
275-
};
276-
277-
const case_pass = std.mem.eql(u8, text_status, "Pass");
278-
if (case_pass) {
266+
// case names can have | in them, so we can't simply split on |
267+
var case_name = line;
268+
var case_pass = false; // so pessimistic!
269+
var case_message: []const u8 = "";
270+
271+
if (std.mem.endsWith(u8, line, "|Pass")) {
272+
case_name = line[0 .. line.len - 5];
273+
case_pass = true;
279274
case_pass_count += 1;
280275
} else {
281-
// If 1 case fails, we treat the entire file as a fail.
276+
// both cases names and messages can have | in them. Our only
277+
// chance to "parse" this is to anchor off the |Fail.
278+
const pos = std.mem.indexOf(u8, line, "|Fail") orelse {
279+
std.debug.print("invalid result line: {s}\n", .{line});
280+
return error.InvalidResult;
281+
};
282+
283+
case_name = line[0..pos];
284+
case_message = line[pos + 1 ..];
282285
pass = false;
283286
case_fail_count += 1;
284287
}
285288

286289
try cases.append(self.arena, .{
287290
.name = case_name,
288291
.pass = case_pass,
289-
.message = fields.next(),
292+
.message = case_message,
290293
});
291294
}
292295

src/runtime/js.zig

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,27 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
941941
fn jsValueToZig(self: *JsContext, comptime named_function: NamedFunction, comptime T: type, js_value: v8.Value) !T {
942942
switch (@typeInfo(T)) {
943943
.optional => |o| {
944+
if (comptime isJsObject(o.child)) {
945+
// If type type is a ?JsObject, then we want to pass
946+
// a JsObject, not null. Consider a function,
947+
// _doSomething(arg: ?Env.JsObjet) void { ... }
948+
//
949+
// And then these three calls:
950+
// doSomething();
951+
// doSomething(null);
952+
//
953+
// In the first case, we'll pass `null`. But in the
954+
// second, we'll pass a JsObject which represents
955+
// null.
956+
// If we don't have this code, both cases will
957+
// pass in `null` and the the doSomething won't
958+
// be able to tell if `null` was explicitly passed
959+
// or whether no parameter was passed.
960+
return JsObject{
961+
.js_obj = js_value.castTo(v8.Object),
962+
.js_context = self,
963+
};
964+
}
944965
if (js_value.isNullOrUndefined()) {
945966
return null;
946967
}
@@ -1938,6 +1959,24 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
19381959
return try js_context.createFunction(js_value);
19391960
}
19401961

1962+
pub fn isNull(self: JsObject) bool {
1963+
return self.js_obj.toValue().isNull();
1964+
}
1965+
1966+
pub fn isUndefined(self: JsObject) bool {
1967+
return self.js_obj.toValue().isUndefined();
1968+
}
1969+
1970+
pub fn triState(self: JsObject, comptime Struct: type, comptime name: []const u8, comptime T: type) !TriState(T) {
1971+
if (self.isNull()) {
1972+
return .{ .null = {} };
1973+
}
1974+
if (self.isUndefined()) {
1975+
return .{ .undefined = {} };
1976+
}
1977+
return .{ .value = try self.toZig(Struct, name, T) };
1978+
}
1979+
19411980
pub fn isNullOrUndefined(self: JsObject) bool {
19421981
return self.js_obj.toValue().isNullOrUndefined();
19431982
}
@@ -1965,6 +2004,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
19652004
const named_function = comptime NamedFunction.init(Struct, name);
19662005
return self.js_context.jsValueToZig(named_function, T, self.js_obj.toValue());
19672006
}
2007+
2008+
pub fn TriState(comptime T: type) type {
2009+
return union(enum) {
2010+
null: void,
2011+
undefined: void,
2012+
value: T,
2013+
};
2014+
}
19682015
};
19692016

19702017
// This only exists so that we know whether a function wants the opaque

0 commit comments

Comments
 (0)