diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 4777b932..6ab014c4 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -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 diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 1e2d3505..5ff6917d 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -329,6 +329,7 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts) }, inline + js.Array, js.Function, js.Object, js.Promise, diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index 220fed4d..7aa2125c 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -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, diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index aaad5cc5..8d1daba2 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -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,26 +230,44 @@ 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 wrap(idx: u32, 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(); + 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; + caller.init(v8_isolate); + defer caller.deinit(); - return caller.getIndex(T, getter, idx, handle.?, .{ - .as_typed_array = opts.as_typed_array, - .null_as_undefined = opts.null_as_undefined, - }); - } - }.wrap }; + return caller.getIndex(T, getter, idx, handle.?, .{ + .as_typed_array = opts.as_typed_array, + .null_as_undefined = opts.null_as_undefined, + }); + } + }.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; } }; diff --git a/src/browser/tests/document/query_selector_all.html b/src/browser/tests/document/query_selector_all.html index c98de73c..4e46f7e3 100644 --- a/src/browser/tests/document/query_selector_all.html +++ b/src/browser/tests/document/query_selector_all.html @@ -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)); } diff --git a/src/browser/webapi/PluginArray.zig b/src/browser/webapi/PluginArray.zig index a256e01f..cc9a5968 100644 --- a/src/browser/webapi/PluginArray.zig +++ b/src/browser/webapi/PluginArray.zig @@ -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 { diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 194ce397..f6ec5176 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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, .{}); diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index 42bcb2ab..c9843895 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -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 }); }; diff --git a/src/browser/webapi/collections/HTMLAllCollection.zig b/src/browser/webapi/collections/HTMLAllCollection.zig index 32fb3056..acada474 100644 --- a/src/browser/webapi/collections/HTMLAllCollection.zig +++ b/src/browser/webapi/collections/HTMLAllCollection.zig @@ -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, .{}); diff --git a/src/browser/webapi/collections/HTMLCollection.zig b/src/browser/webapi/collections/HTMLCollection.zig index b4c2ccb7..0172541b 100644 --- a/src/browser/webapi/collections/HTMLCollection.zig +++ b/src/browser/webapi/collections/HTMLCollection.zig @@ -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, .{}); diff --git a/src/browser/webapi/collections/HTMLFormControlsCollection.zig b/src/browser/webapi/collections/HTMLFormControlsCollection.zig index 20f9210b..fe5f1a97 100644 --- a/src/browser/webapi/collections/HTMLFormControlsCollection.zig +++ b/src/browser/webapi/collections/HTMLFormControlsCollection.zig @@ -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, .{}); }; diff --git a/src/browser/webapi/collections/HTMLOptionsCollection.zig b/src/browser/webapi/collections/HTMLOptionsCollection.zig index 781c858a..f743f4a4 100644 --- a/src/browser/webapi/collections/HTMLOptionsCollection.zig +++ b/src/browser/webapi/collections/HTMLOptionsCollection.zig @@ -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, .{}); diff --git a/src/browser/webapi/collections/NodeList.zig b/src/browser/webapi/collections/NodeList.zig index bf43ef85..2f236a27 100644 --- a/src/browser/webapi/collections/NodeList.zig +++ b/src/browser/webapi/collections/NodeList.zig @@ -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; + } }; diff --git a/src/browser/webapi/collections/RadioNodeList.zig b/src/browser/webapi/collections/RadioNodeList.zig index f236f3a7..9504f28e 100644 --- a/src/browser/webapi/collections/RadioNodeList.zig +++ b/src/browser/webapi/collections/RadioNodeList.zig @@ -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, .{}); }; diff --git a/src/browser/webapi/css/CSSRuleList.zig b/src/browser/webapi/css/CSSRuleList.zig index 4a700237..7e727a56 100644 --- a/src/browser/webapi/css/CSSRuleList.zig +++ b/src/browser/webapi/css/CSSRuleList.zig @@ -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 }); }; diff --git a/src/browser/webapi/css/StyleSheetList.zig b/src/browser/webapi/css/StyleSheetList.zig index 8a019a18..c44dc601 100644 --- a/src/browser/webapi/css/StyleSheetList.zig +++ b/src/browser/webapi/css/StyleSheetList.zig @@ -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 }); }; diff --git a/src/browser/webapi/element/Attribute.zig b/src/browser/webapi/element/Attribute.zig index e12fde69..dc28f8b7 100644 --- a/src/browser/webapi/element/Attribute.zig +++ b/src/browser/webapi/element/Attribute.zig @@ -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, .{});