Merge pull request #1659 from lightpanda-io/nodelist_enumerable
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled

Make NodeList enumerable
This commit is contained in:
Karl Seguin
2026-02-27 08:26:59 +08:00
committed by GitHub
17 changed files with 96 additions and 31 deletions

View File

@@ -251,7 +251,33 @@ fn _deleteNamedIndex(comptime T: type, local: *const Local, func: anytype, name:
return handleIndexedReturn(T, F, false, local, ret, info, opts); return handleIndexedReturn(T, F, false, local, ret, info, opts);
} }
fn handleIndexedReturn(comptime T: type, comptime F: type, comptime getter: bool, local: *const Local, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 { pub fn getEnumerator(self: *Caller, comptime T: type, func: anytype, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
const local = &self.local;
var hs: js.HandleScope = undefined;
hs.init(local.isolate);
defer hs.deinit();
const info = PropertyCallbackInfo{ .handle = handle };
return _getEnumerator(T, local, func, info, opts) catch |err| {
handleError(T, @TypeOf(func), local, err, info, opts);
// not intercepted
return 0;
};
}
fn _getEnumerator(comptime T: type, local: *const Local, func: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
const F = @TypeOf(func);
var args: ParameterTypes(F) = undefined;
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
if (@typeInfo(F).@"fn".params.len == 2) {
@field(args, "1") = local.ctx.page;
}
const ret = @call(.auto, func, args);
return handleIndexedReturn(T, F, true, local, ret, info, opts);
}
fn handleIndexedReturn(comptime T: type, comptime F: type, comptime with_value: bool, local: *const Local, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
// need to unwrap this error immediately for when opts.null_as_undefined == true // need to unwrap this error immediately for when opts.null_as_undefined == true
// and we need to compare it to null; // and we need to compare it to null;
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) { const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
@@ -274,7 +300,7 @@ fn handleIndexedReturn(comptime T: type, comptime F: type, comptime getter: bool
else => ret, else => ret,
}; };
if (comptime getter) { if (comptime with_value) {
info.getReturnValue().set(try local.zigValueToJs(non_error_ret, opts)); info.getReturnValue().set(try local.zigValueToJs(non_error_ret, opts));
} }
// intercepted // intercepted

View File

@@ -329,6 +329,7 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
}, },
inline inline
js.Array,
js.Function, js.Function,
js.Object, js.Object,
js.Promise, js.Promise,

View File

@@ -308,13 +308,18 @@ fn countExternalReferences() comptime_int {
const T = @TypeOf(value); const T = @TypeOf(value);
if (T == bridge.Accessor) { if (T == bridge.Accessor) {
count += 1; // getter count += 1; // getter
if (value.setter != null) count += 1; // setter if (value.setter != null) {
count += 1;
}
} else if (T == bridge.Function) { } else if (T == bridge.Function) {
count += 1; count += 1;
} else if (T == bridge.Iterator) { } else if (T == bridge.Iterator) {
count += 1; count += 1;
} else if (T == bridge.Indexed) { } else if (T == bridge.Indexed) {
count += 1; count += 1;
if (value.enumerator != null) {
count += 1;
}
} else if (T == bridge.NamedIndexed) { } else if (T == bridge.NamedIndexed) {
count += 1; // getter count += 1; // getter
if (value.setter != null) count += 1; if (value.setter != null) count += 1;
@@ -376,6 +381,10 @@ fn collectExternalReferences() [countExternalReferences()]isize {
} else if (T == bridge.Indexed) { } else if (T == bridge.Indexed) {
references[idx] = @bitCast(@intFromPtr(value.getter)); references[idx] = @bitCast(@intFromPtr(value.getter));
idx += 1; idx += 1;
if (value.enumerator) |enumerator| {
references[idx] = @bitCast(@intFromPtr(enumerator));
idx += 1;
}
} else if (T == bridge.NamedIndexed) { } else if (T == bridge.NamedIndexed) {
references[idx] = @bitCast(@intFromPtr(value.getter)); references[idx] = @bitCast(@intFromPtr(value.getter));
idx += 1; idx += 1;
@@ -515,10 +524,10 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
bridge.Indexed => { bridge.Indexed => {
var configuration: v8.IndexedPropertyHandlerConfiguration = .{ var configuration: v8.IndexedPropertyHandlerConfiguration = .{
.getter = value.getter, .getter = value.getter,
.enumerator = value.enumerator,
.setter = null, .setter = null,
.query = null, .query = null,
.deleter = null, .deleter = null,
.enumerator = null,
.definer = null, .definer = null,
.descriptor = null, .descriptor = null,
.data = null, .data = null,

View File

@@ -46,8 +46,8 @@ pub fn Builder(comptime T: type) type {
return Function.init(T, func, opts); return Function.init(T, func, opts);
} }
pub fn indexed(comptime getter_func: anytype, comptime opts: Indexed.Opts) Indexed { pub fn indexed(comptime getter_func: anytype, comptime enumerator_func: anytype, comptime opts: Indexed.Opts) Indexed {
return Indexed.init(T, getter_func, opts); return Indexed.init(T, getter_func, enumerator_func, opts);
} }
pub fn namedIndexed(comptime getter_func: anytype, setter_func: anytype, deleter_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed { pub fn namedIndexed(comptime getter_func: anytype, setter_func: anytype, deleter_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed {
@@ -230,26 +230,44 @@ pub const Accessor = struct {
pub const Indexed = struct { pub const Indexed = struct {
getter: *const fn (idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8, getter: *const fn (idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8,
enumerator: ?*const fn (handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8,
const Opts = struct { const Opts = struct {
as_typed_array: bool = false, as_typed_array: bool = false,
null_as_undefined: bool = false, null_as_undefined: bool = false,
}; };
fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) Indexed { fn init(comptime T: type, comptime getter: anytype, comptime enumerator: anytype, comptime opts: Opts) Indexed {
return .{ .getter = struct { var indexed = Indexed{
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 { .enumerator = null,
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; .getter = struct {
var caller: Caller = undefined; fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
caller.init(v8_isolate); const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
defer caller.deinit(); var caller: Caller = undefined;
caller.init(v8_isolate);
defer caller.deinit();
return caller.getIndex(T, getter, idx, handle.?, .{ return caller.getIndex(T, getter, idx, handle.?, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .null_as_undefined = opts.null_as_undefined,
}); });
} }
}.wrap }; }.wrap,
};
if (@typeInfo(@TypeOf(enumerator)) != .null) {
indexed.enumerator = struct {
fn wrap(handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
defer caller.deinit();
return caller.getEnumerator(T, enumerator, handle.?, .{});
}
}.wrap;
}
return indexed;
} }
}; };

View File

@@ -27,7 +27,9 @@
testing.expectEqual(expected.length, result.length); testing.expectEqual(expected.length, result.length);
testing.expectEqual(expected, Array.from(result).map((e) => e.textContent)); testing.expectEqual(expected, Array.from(result).map((e) => e.textContent));
testing.expectEqual(expected, Array.from(result.values()).map((e) => e.textContent)); testing.expectEqual(expected, Array.from(result.values()).map((e) => e.textContent));
testing.expectEqual(expected.map((e, i) => i), Array.from(result.keys())); testing.expectEqual(expected.map((e, i) => i), Array.from(result.keys()));
testing.expectEqual(expected.map((e, i) => i.toString()), Object.keys(result));
} }
</script> </script>

View File

@@ -63,7 +63,7 @@ pub const JsApi = struct {
pub const length = bridge.property(0, .{ .template = false }); pub const length = bridge.property(0, .{ .template = false });
pub const refresh = bridge.function(PluginArray.refresh, .{}); pub const refresh = bridge.function(PluginArray.refresh, .{});
pub const @"[int]" = bridge.indexed(PluginArray.getAtIndex, .{ .null_as_undefined = true }); pub const @"[int]" = bridge.indexed(PluginArray.getAtIndex, null, .{ .null_as_undefined = true });
pub const @"[str]" = bridge.namedIndexed(PluginArray.getByName, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(PluginArray.getByName, null, null, .{ .null_as_undefined = true });
pub const item = bridge.function(_item, .{}); pub const item = bridge.function(_item, .{});
fn _item(self: *const PluginArray, index: i32) ?*Plugin { fn _item(self: *const PluginArray, index: i32) ?*Plugin {

View File

@@ -776,7 +776,7 @@ pub const JsApi = struct {
pub const getSelection = bridge.function(Window.getSelection, .{}); pub const getSelection = bridge.function(Window.getSelection, .{});
pub const frames = bridge.accessor(Window.getWindow, null, .{}); pub const frames = bridge.accessor(Window.getWindow, null, .{});
pub const index = bridge.indexed(Window.getFrame, .{ .null_as_undefined = true }); pub const index = bridge.indexed(Window.getFrame, null, .{ .null_as_undefined = true });
pub const length = bridge.accessor(Window.getFramesLength, null, .{}); pub const length = bridge.accessor(Window.getFramesLength, null, .{});
pub const scrollX = bridge.accessor(Window.getScrollX, null, .{}); pub const scrollX = bridge.accessor(Window.getScrollX, null, .{});
pub const scrollY = bridge.accessor(Window.getScrollY, null, .{}); pub const scrollY = bridge.accessor(Window.getScrollY, null, .{});

View File

@@ -321,5 +321,5 @@ pub const JsApi = struct {
pub const entries = bridge.function(DOMTokenList.entries, .{}); pub const entries = bridge.function(DOMTokenList.entries, .{});
pub const symbol_iterator = bridge.iterator(DOMTokenList.values, .{}); pub const symbol_iterator = bridge.iterator(DOMTokenList.values, .{});
pub const forEach = bridge.function(DOMTokenList.forEach, .{}); pub const forEach = bridge.function(DOMTokenList.forEach, .{});
pub const @"[]" = bridge.indexed(DOMTokenList.item, .{ .null_as_undefined = true }); pub const @"[]" = bridge.indexed(DOMTokenList.item, null, .{ .null_as_undefined = true });
}; };

View File

@@ -170,7 +170,7 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(HTMLAllCollection.length, null, .{}); pub const length = bridge.accessor(HTMLAllCollection.length, null, .{});
pub const @"[int]" = bridge.indexed(HTMLAllCollection.getAtIndex, .{ .null_as_undefined = true }); pub const @"[int]" = bridge.indexed(HTMLAllCollection.getAtIndex, null, .{ .null_as_undefined = true });
pub const @"[str]" = bridge.namedIndexed(HTMLAllCollection.getByName, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(HTMLAllCollection.getByName, null, null, .{ .null_as_undefined = true });
pub const item = bridge.function(_item, .{}); pub const item = bridge.function(_item, .{});

View File

@@ -136,7 +136,7 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(HTMLCollection.length, null, .{}); pub const length = bridge.accessor(HTMLCollection.length, null, .{});
pub const @"[int]" = bridge.indexed(HTMLCollection.getAtIndex, .{ .null_as_undefined = true }); pub const @"[int]" = bridge.indexed(HTMLCollection.getAtIndex, null, .{ .null_as_undefined = true });
pub const @"[str]" = bridge.namedIndexed(HTMLCollection.getByName, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(HTMLCollection.getByName, null, null, .{ .null_as_undefined = true });
pub const item = bridge.function(_item, .{}); pub const item = bridge.function(_item, .{});

View File

@@ -138,7 +138,7 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(HTMLFormControlsCollection.length, null, .{}); pub const length = bridge.accessor(HTMLFormControlsCollection.length, null, .{});
pub const @"[int]" = bridge.indexed(HTMLFormControlsCollection.getAtIndex, .{ .null_as_undefined = true }); pub const @"[int]" = bridge.indexed(HTMLFormControlsCollection.getAtIndex, null, .{ .null_as_undefined = true });
pub const @"[str]" = bridge.namedIndexed(HTMLFormControlsCollection.namedItem, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(HTMLFormControlsCollection.namedItem, null, null, .{ .null_as_undefined = true });
pub const namedItem = bridge.function(HTMLFormControlsCollection.namedItem, .{}); pub const namedItem = bridge.function(HTMLFormControlsCollection.namedItem, .{});
}; };

View File

@@ -102,7 +102,7 @@ pub const JsApi = struct {
pub const length = bridge.accessor(HTMLOptionsCollection.length, null, .{}); pub const length = bridge.accessor(HTMLOptionsCollection.length, null, .{});
// Indexed access // Indexed access
pub const @"[int]" = bridge.indexed(HTMLOptionsCollection.getAtIndex, .{ .null_as_undefined = true }); pub const @"[int]" = bridge.indexed(HTMLOptionsCollection.getAtIndex, null, .{ .null_as_undefined = true });
pub const @"[str]" = bridge.namedIndexed(HTMLOptionsCollection.getByName, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(HTMLOptionsCollection.getByName, null, null, .{ .null_as_undefined = true });
pub const selectedIndex = bridge.accessor(HTMLOptionsCollection.getSelectedIndex, HTMLOptionsCollection.setSelectedIndex, .{}); pub const selectedIndex = bridge.accessor(HTMLOptionsCollection.getSelectedIndex, HTMLOptionsCollection.setSelectedIndex, .{});

View File

@@ -125,11 +125,20 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(NodeList.length, null, .{}); pub const length = bridge.accessor(NodeList.length, null, .{});
pub const @"[]" = bridge.indexed(NodeList.indexedGet, .{ .null_as_undefined = true }); pub const @"[]" = bridge.indexed(NodeList.indexedGet, getIndexes, .{ .null_as_undefined = true });
pub const item = bridge.function(NodeList.getAtIndex, .{}); pub const item = bridge.function(NodeList.getAtIndex, .{});
pub const keys = bridge.function(NodeList.keys, .{}); pub const keys = bridge.function(NodeList.keys, .{});
pub const values = bridge.function(NodeList.values, .{}); pub const values = bridge.function(NodeList.values, .{});
pub const entries = bridge.function(NodeList.entries, .{}); pub const entries = bridge.function(NodeList.entries, .{});
pub const forEach = bridge.function(NodeList.forEach, .{}); pub const forEach = bridge.function(NodeList.forEach, .{});
pub const symbol_iterator = bridge.iterator(NodeList.values, .{}); pub const symbol_iterator = bridge.iterator(NodeList.values, .{});
fn getIndexes(self: *NodeList, page: *Page) !js.Array {
const len = try self.length(page);
var arr = page.js.local.?.newArray(len);
for (0..len) |i| {
_ = try arr.set(@intCast(i), i, .{});
}
return arr;
}
}; };

View File

@@ -122,7 +122,7 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(RadioNodeList.getLength, null, .{}); pub const length = bridge.accessor(RadioNodeList.getLength, null, .{});
pub const @"[]" = bridge.indexed(RadioNodeList.getAtIndex, .{ .null_as_undefined = true }); pub const @"[]" = bridge.indexed(RadioNodeList.getAtIndex, null, .{ .null_as_undefined = true });
pub const item = bridge.function(RadioNodeList.getAtIndex, .{}); pub const item = bridge.function(RadioNodeList.getAtIndex, .{});
pub const value = bridge.accessor(RadioNodeList.getValue, RadioNodeList.setValue, .{}); pub const value = bridge.accessor(RadioNodeList.getValue, RadioNodeList.setValue, .{});
}; };

View File

@@ -32,5 +32,5 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(CSSRuleList.length, null, .{}); pub const length = bridge.accessor(CSSRuleList.length, null, .{});
pub const @"[]" = bridge.indexed(CSSRuleList.item, .{ .null_as_undefined = true }); pub const @"[]" = bridge.indexed(CSSRuleList.item, null, .{ .null_as_undefined = true });
}; };

View File

@@ -30,5 +30,5 @@ pub const JsApi = struct {
}; };
pub const length = bridge.accessor(StyleSheetList.length, null, .{}); pub const length = bridge.accessor(StyleSheetList.length, null, .{});
pub const @"[]" = bridge.indexed(StyleSheetList.item, .{ .null_as_undefined = true }); pub const @"[]" = bridge.indexed(StyleSheetList.item, null, .{ .null_as_undefined = true });
}; };

View File

@@ -524,7 +524,7 @@ pub const NamedNodeMap = struct {
}; };
pub const length = bridge.accessor(NamedNodeMap.length, null, .{}); pub const length = bridge.accessor(NamedNodeMap.length, null, .{});
pub const @"[int]" = bridge.indexed(NamedNodeMap.getAtIndex, .{ .null_as_undefined = true }); pub const @"[int]" = bridge.indexed(NamedNodeMap.getAtIndex, null, .{ .null_as_undefined = true });
pub const @"[str]" = bridge.namedIndexed(NamedNodeMap.getByName, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(NamedNodeMap.getByName, null, null, .{ .null_as_undefined = true });
pub const getNamedItem = bridge.function(NamedNodeMap.getByName, .{}); pub const getNamedItem = bridge.function(NamedNodeMap.getByName, .{});
pub const setNamedItem = bridge.function(NamedNodeMap.set, .{}); pub const setNamedItem = bridge.function(NamedNodeMap.set, .{});