diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 8d5aaf4d8..794be75f1 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -209,6 +209,59 @@ fn _getIndex(comptime T: type, local: *const Local, func: anytype, idx: u32, inf return handleIndexedReturn(T, F, true, local, ret, info, opts); } +pub fn setIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, js_value: *const v8.Value, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u32 { + const local = &self.local; + + var hs: js.HandleScope = undefined; + hs.init(local.isolate); + defer hs.deinit(); + + const info = PropertyCallbackInfo{ .handle = handle }; + return _setIndex(T, local, func, idx, .{ .local = &self.local, .handle = js_value }, info, opts) catch |err| { + handleError(T, @TypeOf(func), local, err, info); + return js.Intercepted.no; + }; +} + +fn _setIndex(comptime T: type, local: *const Local, func: anytype, idx: u32, js_value: js.Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u32 { + const F = @TypeOf(func); + var args: ParameterTypes(F) = undefined; + @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); + @field(args, "1") = idx; + @field(args, "2") = try local.jsValueToZig(@TypeOf(@field(args, "2")), js_value); + if (@typeInfo(F).@"fn".params.len == 4) { + @field(args, "3") = getGlobalArg(@TypeOf(args.@"3"), local.ctx); + } + const ret = @call(.auto, func, args); + return handleIndexedReturn(T, F, false, local, ret, info, opts); +} + +pub fn deleteIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u32 { + const local = &self.local; + + var hs: js.HandleScope = undefined; + hs.init(local.isolate); + defer hs.deinit(); + + const info = PropertyCallbackInfo{ .handle = handle }; + return _deleteIndex(T, local, func, idx, info, opts) catch |err| { + handleError(T, @TypeOf(func), local, err, info); + return js.Intercepted.no; + }; +} + +fn _deleteIndex(comptime T: type, local: *const Local, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) !u32 { + const F = @TypeOf(func); + var args: ParameterTypes(F) = undefined; + @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); + @field(args, "1") = idx; + if (@typeInfo(F).@"fn".params.len == 3) { + @field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx); + } + const ret = @call(.auto, func, args); + return handleIndexedReturn(T, F, false, local, ret, info, opts); +} + pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u32 { const local = &self.local; @@ -313,6 +366,66 @@ fn _getEnumerator(comptime T: type, local: *const Local, func: anytype, info: Pr return handleIndexedReturn(T, F, true, local, ret, info, opts); } +pub fn getIndexQuery(self: *Caller, comptime T: type, func: anytype, idx: u32, handle: *const v8.PropertyCallbackInfo) u32 { + const local = &self.local; + + var hs: js.HandleScope = undefined; + hs.init(local.isolate); + defer hs.deinit(); + + const info = PropertyCallbackInfo{ .handle = handle }; + return _getIndexQuery(T, local, func, idx, info) catch |err| { + handleError(T, @TypeOf(func), local, err, info); + return js.Intercepted.no; + }; +} + +fn _getIndexQuery(comptime T: type, local: *const Local, func: anytype, idx: u32, info: PropertyCallbackInfo) !u32 { + const F = @TypeOf(func); + var args: ParameterTypes(F) = undefined; + @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); + @field(args, "1") = idx; + if (@typeInfo(F).@"fn".params.len == 3) { + @field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx); + } + if (@call(.auto, func, args) == false) { + return js.Intercepted.no; + } + info.getReturnValue().set(try local.zigValueToJs(@as(u32, v8.None), .{})); + return js.Intercepted.yes; +} + +pub fn getNamedQuery(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo) u32 { + const local = &self.local; + + var hs: js.HandleScope = undefined; + hs.init(local.isolate); + defer hs.deinit(); + + const info = PropertyCallbackInfo{ .handle = handle }; + return _getNamedQuery(T, local, func, name, info) catch |err| { + handleError(T, @TypeOf(func), local, err, info); + return js.Intercepted.no; + }; +} + +fn _getNamedQuery(comptime T: type, local: *const Local, func: anytype, name: *const v8.Name, info: PropertyCallbackInfo) !u32 { + const F = @TypeOf(func); + var args: ParameterTypes(F) = undefined; + @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); + @field(args, "1") = try nameToString(local, @TypeOf(args.@"1"), name); + if (@typeInfo(F).@"fn".params.len == 3) { + @field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx); + } + if (@call(.auto, func, args) == false) { + return js.Intercepted.no; + } + // The property exists as a supported property name; report it as an + // enumerable, writable, configurable data property (PropertyAttribute.None). + info.getReturnValue().set(try local.zigValueToJs(@as(u32, v8.None), .{})); + return js.Intercepted.yes; +} + fn handleIndexedReturn(comptime T: type, comptime F: type, comptime with_value: bool, local: *const Local, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u32 { // need to unwrap this error immediately for when opts.null_as_undefined == true // and we need to compare it to null; diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index 1560b3794..75ef4de46 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -443,10 +443,15 @@ fn countExternalReferences() comptime_int { if (value.enumerator != null) { count += 1; } + if (value.setter != null) count += 1; + if (value.deleter != null) count += 1; + if (value.query != null) count += 1; } else if (T == bridge.NamedIndexed) { count += 1; if (value.setter != null) count += 1; if (value.deleter != null) count += 1; + if (value.enumerator != null) count += 1; + if (value.query != null) count += 1; } } } @@ -519,6 +524,18 @@ fn collectExternalReferences() [countExternalReferences()]isize { references[idx] = @bitCast(@intFromPtr(enumerator)); idx += 1; } + if (value.setter) |setter| { + references[idx] = @bitCast(@intFromPtr(setter)); + idx += 1; + } + if (value.deleter) |deleter| { + references[idx] = @bitCast(@intFromPtr(deleter)); + idx += 1; + } + if (value.query) |query| { + references[idx] = @bitCast(@intFromPtr(query)); + idx += 1; + } } else if (T == bridge.NamedIndexed) { references[idx] = @bitCast(@intFromPtr(value.getter)); idx += 1; @@ -530,6 +547,14 @@ fn collectExternalReferences() [countExternalReferences()]isize { references[idx] = @bitCast(@intFromPtr(deleter)); idx += 1; } + if (value.enumerator) |enumerator| { + references[idx] = @bitCast(@intFromPtr(enumerator)); + idx += 1; + } + if (value.query) |query| { + references[idx] = @bitCast(@intFromPtr(query)); + idx += 1; + } } } } @@ -792,9 +817,9 @@ fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolat var configuration: v8.IndexedPropertyHandlerConfiguration = .{ .getter = value.getter, .enumerator = value.enumerator, - .setter = null, - .query = null, - .deleter = null, + .setter = value.setter, + .query = value.query, + .deleter = value.deleter, .definer = null, .descriptor = null, .index_of = null, @@ -807,9 +832,9 @@ fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolat var configuration: v8.NamedPropertyHandlerConfiguration = .{ .getter = value.getter, .setter = value.setter, - .query = null, + .query = value.query, .deleter = value.deleter, - .enumerator = null, + .enumerator = value.enumerator, .definer = null, .descriptor = null, .data = null, diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 116da83b1..627563a95 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -46,11 +46,15 @@ pub fn Builder(comptime T: type) type { } 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); + return Indexed.init(T, getter_func, null, null, null, enumerator_func, opts); } - pub fn namedIndexed(comptime getter_func: anytype, setter_func: anytype, deleter_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed { - return NamedIndexed.init(T, getter_func, setter_func, deleter_func, opts); + pub fn indexedReadWrite(comptime getter_func: anytype, setter_func: anytype, deleter_func: anytype, query_func: anytype, comptime enumerator_func: anytype, comptime opts: Indexed.Opts) Indexed { + return Indexed.init(T, getter_func, setter_func, deleter_func, query_func, enumerator_func, opts); + } + + pub fn namedIndexed(comptime getter_func: anytype, setter_func: anytype, deleter_func: anytype, enumerator_func: anytype, query_func: anytype, comptime opts: NamedIndexed.Opts) NamedIndexed { + return NamedIndexed.init(T, getter_func, setter_func, deleter_func, enumerator_func, query_func, opts); } pub fn iterator(comptime func: anytype, comptime opts: Iterator.Opts) Iterator { @@ -261,13 +265,16 @@ pub const Accessor = struct { pub const Indexed = struct { getter: *const fn (idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32, enumerator: ?*const fn (handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32, + setter: ?*const fn (idx: u32, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, + deleter: ?*const fn (idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, + query: ?*const fn (idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, const Opts = struct { as_typed_array: bool = false, null_as_undefined: bool = false, }; - fn init(comptime T: type, comptime getter: anytype, comptime enumerator: anytype, comptime opts: Opts) Indexed { + fn init(comptime T: type, comptime getter: anytype, setter: anytype, deleter: anytype, query: anytype, comptime enumerator: anytype, comptime opts: Opts) Indexed { var indexed = Indexed{ .enumerator = null, .getter = struct { @@ -301,6 +308,57 @@ pub const Indexed = struct { }.wrap; } + if (@typeInfo(@TypeOf(setter)) != .null) { + indexed.setter = struct { + fn wrap(idx: u32, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 { + const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; + var caller: Caller = undefined; + if (!caller.init(v8_isolate)) { + return js.Intercepted.no; + } + defer caller.deinit(); + + return caller.setIndex(T, setter, idx, c_value.?, handle.?, .{ + .as_typed_array = opts.as_typed_array, + .null_as_undefined = opts.null_as_undefined, + }); + } + }.wrap; + } + + if (@typeInfo(@TypeOf(deleter)) != .null) { + indexed.deleter = struct { + fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 { + const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; + var caller: Caller = undefined; + if (!caller.init(v8_isolate)) { + return js.Intercepted.no; + } + defer caller.deinit(); + + return caller.deleteIndex(T, deleter, idx, handle.?, .{ + .as_typed_array = opts.as_typed_array, + .null_as_undefined = opts.null_as_undefined, + }); + } + }.wrap; + } + + if (@typeInfo(@TypeOf(query)) != .null) { + indexed.query = struct { + fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 { + const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; + var caller: Caller = undefined; + if (!caller.init(v8_isolate)) { + return js.Intercepted.no; + } + defer caller.deinit(); + + return caller.getIndexQuery(T, query, idx, handle.?); + } + }.wrap; + } + return indexed; } }; @@ -309,6 +367,8 @@ pub const NamedIndexed = struct { getter: *const fn (c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32, setter: ?*const fn (c_name: ?*const v8.Name, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, deleter: ?*const fn (c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, + enumerator: ?*const fn (handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, + query: ?*const fn (c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 = null, const Opts = struct { as_typed_array: bool = false, @@ -319,7 +379,7 @@ pub const NamedIndexed = struct { ce_reactions: bool = false, }; - fn init(comptime T: type, comptime getter: anytype, setter: anytype, deleter: anytype, comptime opts: Opts) NamedIndexed { + fn init(comptime T: type, comptime getter: anytype, setter: anytype, deleter: anytype, enumerator: anytype, query: anytype, comptime opts: Opts) NamedIndexed { const getter_fn = struct { fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 { const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; @@ -392,10 +452,38 @@ pub const NamedIndexed = struct { } }.wrap; + const enumerator_fn = if (@typeInfo(@TypeOf(enumerator)) == .null) null else struct { + fn wrap(handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 { + const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; + var caller: Caller = undefined; + if (!caller.init(v8_isolate)) { + return js.Intercepted.no; + } + defer caller.deinit(); + + return caller.getEnumerator(T, enumerator, handle.?, .{}); + } + }.wrap; + + const query_fn = if (@typeInfo(@TypeOf(query)) == .null) null else struct { + fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u32 { + const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; + var caller: Caller = undefined; + if (!caller.init(v8_isolate)) { + return js.Intercepted.no; + } + defer caller.deinit(); + + return caller.getNamedQuery(T, query, c_name.?, handle.?); + } + }.wrap; + return .{ .getter = getter_fn, .setter = setter_fn, .deleter = deleter_fn, + .enumerator = enumerator_fn, + .query = query_fn, }; } }; diff --git a/src/browser/tests/storage.html b/src/browser/tests/storage.html index 53aa2f926..ae34b6f05 100644 --- a/src/browser/tests/storage.html +++ b/src/browser/tests/storage.html @@ -51,11 +51,14 @@ localStorage.setItem('number', '123'); testing.expectEqual('123', localStorage.getItem('number')); + // null/undefined keys are stringified per WebIDL (DOMString coercion). localStorage.setItem(null, 'null_key_value'); - testing.expectEqual(null, localStorage.getItem(null)); + testing.expectEqual('null_key_value', localStorage.getItem(null)); + testing.expectEqual('null_key_value', localStorage.getItem('null')); localStorage.setItem(undefined, 'undefined_key_value'); - testing.expectEqual(null, localStorage.getItem(undefined)); + testing.expectEqual('undefined_key_value', localStorage.getItem(undefined)); + testing.expectEqual('undefined_key_value', localStorage.getItem('undefined')); localStorage.clear(); testing.expectEqual(0, localStorage.length); diff --git a/src/browser/webapi/PluginArray.zig b/src/browser/webapi/PluginArray.zig index cc9a5968b..ebf2cd88b 100644 --- a/src/browser/webapi/PluginArray.zig +++ b/src/browser/webapi/PluginArray.zig @@ -64,7 +64,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, .{ .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, null, .{ .null_as_undefined = true }); pub const item = bridge.function(_item, .{}); fn _item(self: *const PluginArray, index: i32) ?*Plugin { if (index < 0) { diff --git a/src/browser/webapi/collections/HTMLAllCollection.zig b/src/browser/webapi/collections/HTMLAllCollection.zig index c8c930349..1c4a5acda 100644 --- a/src/browser/webapi/collections/HTMLAllCollection.zig +++ b/src/browser/webapi/collections/HTMLAllCollection.zig @@ -172,7 +172,7 @@ pub const JsApi = struct { pub const length = bridge.accessor(HTMLAllCollection.length, null, .{}); 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, null, .{ .null_as_undefined = true }); pub const item = bridge.function(_item, .{}); fn _item(self: *HTMLAllCollection, index: i32, frame: *Frame) ?*Element { diff --git a/src/browser/webapi/collections/HTMLCollection.zig b/src/browser/webapi/collections/HTMLCollection.zig index 509580289..1f2ad4471 100644 --- a/src/browser/webapi/collections/HTMLCollection.zig +++ b/src/browser/webapi/collections/HTMLCollection.zig @@ -155,7 +155,7 @@ pub const JsApi = struct { return self.getByName(name, frame) orelse error.NotHandled; } - }.wrap, null, null, .{ .null_as_undefined = true }); + }.wrap, null, null, null, null, .{ .null_as_undefined = true }); pub const item = bridge.function(_item, .{}); fn _item(self: *HTMLCollection, index: i32, frame: *Frame) ?*Element { diff --git a/src/browser/webapi/collections/HTMLFormControlsCollection.zig b/src/browser/webapi/collections/HTMLFormControlsCollection.zig index 6d042dd59..2147e8131 100644 --- a/src/browser/webapi/collections/HTMLFormControlsCollection.zig +++ b/src/browser/webapi/collections/HTMLFormControlsCollection.zig @@ -139,6 +139,6 @@ pub const JsApi = struct { pub const length = bridge.accessor(HTMLFormControlsCollection.length, null, .{}); 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, 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 ca58d7862..83a837b06 100644 --- a/src/browser/webapi/collections/HTMLOptionsCollection.zig +++ b/src/browser/webapi/collections/HTMLOptionsCollection.zig @@ -101,7 +101,7 @@ pub const JsApi = struct { // Indexed access 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, null, .{ .null_as_undefined = true }); pub const selectedIndex = bridge.accessor(HTMLOptionsCollection.getSelectedIndex, HTMLOptionsCollection.setSelectedIndex, .{}); pub const add = bridge.function(HTMLOptionsCollection.add, .{ .ce_reactions = true }); diff --git a/src/browser/webapi/css/CSSStyleProperties.zig b/src/browser/webapi/css/CSSStyleProperties.zig index 04d451c24..726a22695 100644 --- a/src/browser/webapi/css/CSSStyleProperties.zig +++ b/src/browser/webapi/css/CSSStyleProperties.zig @@ -397,5 +397,5 @@ pub const JsApi = struct { pub var class_id: bridge.ClassId = undefined; }; - pub const @"[]" = bridge.namedIndexed(CSSStyleProperties.getNamed, CSSStyleProperties.setNamed, null, .{}); + pub const @"[]" = bridge.namedIndexed(CSSStyleProperties.getNamed, CSSStyleProperties.setNamed, null, null, null, .{}); }; diff --git a/src/browser/webapi/element/Attribute.zig b/src/browser/webapi/element/Attribute.zig index a1fc9683e..0f4600050 100644 --- a/src/browser/webapi/element/Attribute.zig +++ b/src/browser/webapi/element/Attribute.zig @@ -540,7 +540,7 @@ pub const NamedNodeMap = struct { pub const length = bridge.accessor(NamedNodeMap.length, null, .{}); 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, null, .{ .null_as_undefined = true }); pub const getNamedItem = bridge.function(NamedNodeMap.getByName, .{}); pub const setNamedItem = bridge.function(NamedNodeMap.set, .{ .ce_reactions = true }); pub const removeNamedItem = bridge.function(NamedNodeMap.removeByName, .{ .ce_reactions = true }); diff --git a/src/browser/webapi/element/DOMStringMap.zig b/src/browser/webapi/element/DOMStringMap.zig index a7fff6551..e00355e9b 100644 --- a/src/browser/webapi/element/DOMStringMap.zig +++ b/src/browser/webapi/element/DOMStringMap.zig @@ -136,5 +136,5 @@ pub const JsApi = struct { pub var class_id: bridge.ClassId = undefined; }; - pub const @"[]" = bridge.namedIndexed(getProperty, setProperty, deleteProperty, .{ .null_as_undefined = true, .ce_reactions = true }); + pub const @"[]" = bridge.namedIndexed(getProperty, setProperty, deleteProperty, null, null, .{ .null_as_undefined = true, .ce_reactions = true }); }; diff --git a/src/browser/webapi/storage/storage.zig b/src/browser/webapi/storage/storage.zig index 04cf50650..1af9b6afb 100644 --- a/src/browser/webapi/storage/storage.zig +++ b/src/browser/webapi/storage/storage.zig @@ -89,14 +89,11 @@ pub const Lookup = struct { self._size = 0; } - pub fn getItem(self: *const Lookup, key_: ?[]const u8) ?[]const u8 { - const k = key_ orelse return null; + pub fn getItem(self: *const Lookup, k: []const u8) ?[]const u8 { return self._data.get(k); } - pub fn setItem(self: *Lookup, key_: ?[]const u8, value: []const u8) !void { - const k = key_ orelse return; - + pub fn setItem(self: *Lookup, k: []const u8, value: []const u8) !void { const old_len = if (self._data.get(k)) |old| old.len else 0; std.debug.assert(old_len <= self._size); if (self._size - old_len + value.len > max_size) { @@ -120,8 +117,7 @@ pub const Lookup = struct { } } - pub fn removeItem(self: *Lookup, key_: ?[]const u8) void { - const k = key_ orelse return; + pub fn removeItem(self: *Lookup, k: []const u8) void { const kv = self._data.fetchRemove(k) orelse return; self._size -= kv.value.len; self._allocator.free(kv.key); @@ -154,6 +150,21 @@ pub const Lookup = struct { return @intCast(self._data.count()); } + pub fn hasItem(self: *const Lookup, k: []const u8) bool { + return self._data.contains(k); + } + + pub fn keys(self: *const Lookup, exec: *const js.Execution) !js.Array { + const local = exec.js.local.?; + var arr = local.newArray(self.getLength()); + var it = self._data.keyIterator(); + var i: u32 = 0; + while (it.next()) |k| : (i += 1) { + _ = try arr.set(i, k.*, .{}); + } + return arr; + } + pub const JsApi = struct { pub const bridge = js.Bridge(Lookup); @@ -169,7 +180,30 @@ pub const Lookup = struct { pub const removeItem = bridge.function(Lookup.removeItem, .{}); pub const clear = bridge.function(Lookup.clear, .{}); pub const key = bridge.function(Lookup.key, .{}); - pub const @"[str]" = bridge.namedIndexed(Lookup.getItem, Lookup.setItem, null, .{ .null_as_undefined = true }); + pub const @"[str]" = bridge.namedIndexed(Lookup.getItem, Lookup.setItem, Lookup.removeItem, Lookup.keys, Lookup.hasItem, .{ .null_as_undefined = true }); + pub const @"[int]" = bridge.indexedReadWrite(_getByIndex, _setByIndex, _removeByIndex, _indexHas, null, .{ .null_as_undefined = true }); + + // v8 routes storage[9] to the indexed interceptor, so we need to do the + // int -> string conversion + fn _getByIndex(self: *const Lookup, idx: u32) ?[]const u8 { + var buf: [10]u8 = undefined; + return self._data.get(std.fmt.bufPrint(&buf, "{d}", .{idx}) catch unreachable); + } + + fn _setByIndex(self: *Lookup, idx: u32, value: []const u8) !void { + var buf: [10]u8 = undefined; + return self.setItem(std.fmt.bufPrint(&buf, "{d}", .{idx}) catch unreachable, value); + } + + fn _removeByIndex(self: *Lookup, idx: u32) void { + var buf: [10]u8 = undefined; + return self.removeItem(std.fmt.bufPrint(&buf, "{d}", .{idx}) catch unreachable); + } + + fn _indexHas(self: *const Lookup, idx: u32) bool { + var buf: [10]u8 = undefined; + return self._data.contains(std.fmt.bufPrint(&buf, "{d}", .{idx}) catch unreachable); + } }; };