diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 794be75f1..a6e8903a4 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -209,59 +209,6 @@ 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; @@ -288,6 +235,59 @@ fn _getNamedIndex(comptime T: type, local: *const Local, func: anytype, name: *c 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, comptime returnsBool(F), 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, comptime returnsBool(F), local, ret, info, opts); +} + pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, js_value: *const v8.Value, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u32 { const local = &self.local; @@ -312,7 +312,7 @@ fn _setNamedIndex(comptime T: type, local: *const Local, func: anytype, name: *c @field(args, "3") = getGlobalArg(@TypeOf(args.@"3"), local.ctx); } const ret = @call(.auto, func, args); - return handleIndexedReturn(T, F, false, local, ret, info, opts); + return handleIndexedReturn(T, F, comptime returnsBool(F), local, ret, info, opts); } pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u32 { @@ -338,7 +338,7 @@ fn _deleteNamedIndex(comptime T: type, local: *const Local, func: anytype, name: @field(args, "2") = getGlobalArg(@TypeOf(args.@"2"), local.ctx); } const ret = @call(.auto, func, args); - return handleIndexedReturn(T, F, false, local, ret, info, opts); + return handleIndexedReturn(T, F, comptime returnsBool(F), local, ret, info, opts); } pub fn getEnumerator(self: *Caller, comptime T: type, func: anytype, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u32 { @@ -453,6 +453,18 @@ fn handleIndexedReturn(comptime T: type, comptime F: type, comptime with_value: return js.Intercepted.yes; } +// Setter/deleter interceptors normally return void: intercepting is enough +// to mark the operation successful. When they return a bool instead, it is +// forwarded as the v8 return value; false marks the operation as failed, +// which makes v8 throw a TypeError in strict mode. +fn returnsBool(comptime F: type) bool { + const RT = @typeInfo(F).@"fn".return_type.?; + return switch (@typeInfo(RT)) { + .error_union => |eu| eu.payload == bool, + else => RT == bool, + }; +} + fn isInErrorSet(err: anyerror, comptime T: type) bool { inline for (@typeInfo(T).error_set.?) |e| { if (err == @field(anyerror, e.name)) return true; diff --git a/src/browser/webapi/collections/HTMLCollection.zig b/src/browser/webapi/collections/HTMLCollection.zig index 1f2ad4471..586bb9d5e 100644 --- a/src/browser/webapi/collections/HTMLCollection.zig +++ b/src/browser/webapi/collections/HTMLCollection.zig @@ -146,7 +146,7 @@ pub const JsApi = struct { }; pub const length = bridge.accessor(HTMLCollection.length, null, .{}); - pub const @"[int]" = bridge.indexed(HTMLCollection.getAtIndex, null, .{ .null_as_undefined = true }); + pub const @"[int]" = bridge.indexedReadWrite(HTMLCollection.getAtIndex, null, deleteAtIndex, null, null, .{ .null_as_undefined = true }); pub const @"[str]" = bridge.namedIndexed(struct { pub fn wrap(self: *HTMLCollection, name: []const u8, frame: *Frame) !?*Element { if (name.len == 0) { @@ -155,7 +155,24 @@ pub const JsApi = struct { return self.getByName(name, frame) orelse error.NotHandled; } - }.wrap, null, null, null, null, .{ .null_as_undefined = true }); + }.wrap, null, deleteByName, null, null, .{ .null_as_undefined = true }); + + // Supported indexed and named properties can't be deleted (delete returns + // false, which throws a TypeError in strict mode); unsupported ones follow + // the ordinary [[Delete]] path. + fn deleteAtIndex(self: *HTMLCollection, idx: u32, frame: *Frame) !bool { + if (idx < self.length(frame)) { + return false; + } + return error.NotHandled; + } + + fn deleteByName(self: *HTMLCollection, name: []const u8, frame: *Frame) !bool { + if (self.getByName(name, frame) != null) { + return false; + } + return error.NotHandled; + } pub const item = bridge.function(_item, .{}); fn _item(self: *HTMLCollection, index: i32, frame: *Frame) ?*Element {