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);
}
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
// and we need to compare it to null;
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,
};
if (comptime getter) {
if (comptime with_value) {
info.getReturnValue().set(try local.zigValueToJs(non_error_ret, opts));
}
// intercepted

View File

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

View File

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

View File

@@ -46,8 +46,8 @@ pub fn Builder(comptime T: type) type {
return Function.init(T, func, opts);
}
pub fn indexed(comptime getter_func: anytype, comptime opts: Indexed.Opts) Indexed {
return Indexed.init(T, getter_func, opts);
pub fn indexed(comptime getter_func: anytype, comptime enumerator_func: anytype, comptime opts: Indexed.Opts) Indexed {
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 {
@@ -230,14 +230,17 @@ pub const Accessor = struct {
pub const Indexed = struct {
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 {
as_typed_array: bool = false,
null_as_undefined: bool = false,
};
fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) Indexed {
return .{ .getter = struct {
fn init(comptime T: type, comptime getter: anytype, comptime enumerator: anytype, comptime opts: Opts) Indexed {
var indexed = Indexed{
.enumerator = null,
.getter = struct {
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
@@ -249,7 +252,22 @@ pub const Indexed = struct {
.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, Array.from(result).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.toString()), Object.keys(result));
}
</script>

View File

@@ -63,7 +63,7 @@ pub const JsApi = struct {
pub const length = bridge.property(0, .{ .template = false });
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 item = bridge.function(_item, .{});
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 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 scrollX = bridge.accessor(Window.getScrollX, 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 symbol_iterator = bridge.iterator(DOMTokenList.values, .{});
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 @"[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 item = bridge.function(_item, .{});

View File

@@ -136,7 +136,7 @@ pub const JsApi = struct {
};
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 item = bridge.function(_item, .{});

View File

@@ -138,7 +138,7 @@ pub const JsApi = struct {
};
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 namedItem = bridge.function(HTMLFormControlsCollection.namedItem, .{});
};

View File

@@ -102,7 +102,7 @@ pub const JsApi = struct {
pub const length = bridge.accessor(HTMLOptionsCollection.length, null, .{});
// 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 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 @"[]" = 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 keys = bridge.function(NodeList.keys, .{});
pub const values = bridge.function(NodeList.values, .{});
pub const entries = bridge.function(NodeList.entries, .{});
pub const forEach = bridge.function(NodeList.forEach, .{});
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 @"[]" = 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 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 @"[]" = 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 @"[]" = 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 @"[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 getNamedItem = bridge.function(NamedNodeMap.getByName, .{});
pub const setNamedItem = bridge.function(NamedNodeMap.set, .{});