Skip to content

Commit 9b4d1d4

Browse files
committed
Allow this argument to TokenList forEach
JsObject can now be used as a normal parameter. It'll receive the opaque value. This is largely useful when a Zig function takes an argument which it needs to pass back into a callback. JsThis is now a thin wrapper around JsObject for functions that was the JsObject of the receiver. This is for advanced usage where the Zig function wants to manipulate the v8.Object that represents the zig value. postAttach is an example of such usage.
1 parent a2291b0 commit 9b4d1d4

File tree

6 files changed

+98
-31
lines changed

6 files changed

+98
-31
lines changed

src/browser/dom/html_collection.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const utils = @import("utils.z");
2424
const Element = @import("element.zig").Element;
2525
const Union = @import("element.zig").Union;
2626

27-
const JsObject = @import("../env.zig").JsObject;
27+
const JsThis = @import("../env.zig").JsThis;
2828

2929
const Walker = @import("walker.zig").Walker;
3030
const WalkerDepthFirst = @import("walker.zig").WalkerDepthFirst;
@@ -443,15 +443,15 @@ pub const HTMLCollection = struct {
443443
return null;
444444
}
445445

446-
pub fn postAttach(self: *HTMLCollection, js_obj: JsObject) !void {
446+
pub fn postAttach(self: *HTMLCollection, js_this: JsThis) !void {
447447
const len = try self.get_length();
448448
for (0..len) |i| {
449449
const node = try self.item(@intCast(i)) orelse unreachable;
450450
const e = @as(*parser.Element, @ptrCast(node));
451-
try js_obj.setIndex(@intCast(i), e);
451+
try js_this.setIndex(@intCast(i), e);
452452

453453
if (try item_name(e)) |name| {
454-
try js_obj.set(name, e);
454+
try js_this.set(name, e);
455455
}
456456
}
457457
}

src/browser/dom/mutation_observer.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const parser = @import("../netsurf.zig");
2222
const SessionState = @import("../env.zig").SessionState;
2323

2424
const Env = @import("../env.zig").Env;
25-
const JsObject = @import("../env.zig").JsObject;
25+
const JsThis = @import("../env.zig").JsThis;
2626
const NodeList = @import("nodelist.zig").NodeList;
2727

2828
pub const Interfaces = .{
@@ -184,9 +184,9 @@ pub const MutationRecords = struct {
184184
return null;
185185
};
186186
}
187-
pub fn postAttach(self: *const MutationRecords, js_obj: JsObject) !void {
187+
pub fn postAttach(self: *const MutationRecords, js_this: JsThis) !void {
188188
if (self.first) |mr| {
189-
try js_obj.set("0", mr);
189+
try js_this.set("0", mr);
190190
}
191191
}
192192
};

src/browser/dom/nodelist.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const std = @import("std");
2020

2121
const parser = @import("../netsurf.zig");
2222

23-
const JsObject = @import("../env.zig").JsObject;
23+
const JsThis = @import("../env.zig").JsThis;
2424
const Callback = @import("../env.zig").Callback;
2525
const SessionState = @import("../env.zig").SessionState;
2626

@@ -177,11 +177,11 @@ pub const NodeList = struct {
177177
}
178178

179179
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
180-
pub fn postAttach(self: *NodeList, js_obj: JsObject) !void {
180+
pub fn postAttach(self: *NodeList, js_this: JsThis) !void {
181181
const len = self.get_length();
182182
for (0..len) |i| {
183183
const node = try self._item(@intCast(i)) orelse unreachable;
184-
try js_obj.setIndex(i, node);
184+
try js_this.setIndex(i, node);
185185
}
186186
}
187187
};

src/browser/dom/token_list.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const parser = @import("../netsurf.zig");
2222
const iterator = @import("../iterator/iterator.zig");
2323

2424
const Callback = @import("../env.zig").Callback;
25+
const JsObject = @import("../env.zig").JsObject;
2526
const SessionState = @import("../env.zig").SessionState;
2627
const DOMException = @import("exceptions.zig").DOMException;
2728

@@ -138,11 +139,11 @@ pub const DOMTokenList = struct {
138139
}
139140

140141
// TODO handle thisArg
141-
pub fn _forEach(self: *parser.TokenList, cbk: Callback) !void {
142+
pub fn _forEach(self: *parser.TokenList, cbk: Callback, this_arg: JsObject) !void {
142143
var entries = _entries(self);
143144
while (try entries._next()) |entry| {
144145
var result: Callback.Result = undefined;
145-
cbk.tryCall(.{ entry.@"1", entry.@"0", self }, &result) catch {
146+
cbk.tryCallWithThis(this_arg, .{ entry.@"1", entry.@"0", self }, &result) catch {
146147
log.err("callback error: {s}", .{result.exception});
147148
log.debug("stack:\n{s}", .{result.stack orelse "???"});
148149
};

src/browser/env.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const Interfaces = generate.Tuple(.{
2121
@import("xmlserializer/xmlserializer.zig").Interfaces,
2222
});
2323

24+
pub const JsThis = Env.JsThis;
2425
pub const JsObject = Env.JsObject;
2526
pub const Callback = Env.Callback;
2627
pub const Env = js.Env(*SessionState, Interfaces{});

src/runtime/js.zig

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,27 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
902902
_ = self.scope_arena.reset(.{ .retain_with_limit = 1024 * 64 });
903903
}
904904

905+
// Given an anytype, turns it into a v8.Object. The anytype could be:
906+
// 1 - A V8.object already
907+
// 2 - Our this JsObject wrapper around a V8.Object
908+
// 3 - A zig instance that has previously been given to V8
909+
// (i.e., the value has to be known to the executor)
910+
fn valueToExistingObject(self: *const Executor, value: anytype) !v8.Object {
911+
if (@TypeOf(value) == v8.Object) {
912+
return value;
913+
}
914+
915+
if (@TypeOf(value) == JsObject) {
916+
return value.js_obj;
917+
}
918+
919+
const persistent_object = self.scope.?.identity_map.get(@intFromPtr(value)) orelse {
920+
return error.InvalidThisForCallback;
921+
};
922+
923+
return persistent_object.castToObject();
924+
}
925+
905926
// Wrap a v8.Value, largely so that we can provide a convenient
906927
// toString function
907928
fn createValue(self: *const Executor, value: v8.Value) Value {
@@ -1003,7 +1024,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
10031024
gop.value_ptr.* = js_persistent;
10041025

10051026
if (@hasDecl(ptr.child, "postAttach")) {
1006-
const obj_wrap = JsObject{ .js_obj = js_obj, .executor = self };
1027+
const obj_wrap = JsThis{ .obj = .{ .js_obj = js_obj, .executor = self } };
10071028
switch (@typeInfo(@TypeOf(ptr.child.postAttach)).@"fn".params.len) {
10081029
2 => try value.postAttach(obj_wrap),
10091030
3 => try value.postAttach(self.state, obj_wrap),
@@ -1094,7 +1115,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
10941115
pub const Callback = struct {
10951116
id: usize,
10961117
executor: *Executor,
1097-
this: ?v8.Object = null,
1118+
_this: ?v8.Object = null,
10981119
func: PersistentFunction,
10991120

11001121
// We use this when mapping a JS value to a Zig object. We can't
@@ -1110,22 +1131,23 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
11101131
};
11111132

11121133
pub fn setThis(self: *Callback, value: anytype) !void {
1113-
const persistent_object = self.executor.scope.?.identity_map.get(@intFromPtr(value)) orelse {
1114-
return error.InvalidThisForCallback;
1115-
};
1116-
self.this = persistent_object.castToObject();
1134+
self._this = try self.executor.valueToExistingObject(value);
11171135
}
11181136

11191137
pub fn call(self: *const Callback, args: anytype) !void {
1120-
return self.callWithThis(self.this orelse self.executor.context.getGlobal(), args);
1138+
return self.callWithThis(self.getThis(), args);
11211139
}
11221140

11231141
pub fn tryCall(self: *const Callback, args: anytype, result: *Result) !void {
1142+
return self.tryCallWithThis(self.getThis(), args, result);
1143+
}
1144+
1145+
pub fn tryCallWithThis(self: *const Callback, this: anytype, args: anytype, result: *Result) !void {
11241146
var try_catch: TryCatch = undefined;
11251147
try_catch.init(self.executor);
11261148
defer try_catch.deinit();
11271149

1128-
self.call(args) catch |err| {
1150+
self.callWithThis(this, args) catch |err| {
11291151
if (try_catch.hasCaught()) {
11301152
const allocator = self.executor.scope.?.call_arena;
11311153
result.stack = try_catch.stack(allocator) catch null;
@@ -1138,9 +1160,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
11381160
};
11391161
}
11401162

1141-
fn callWithThis(self: *const @This(), js_this: v8.Object, args: anytype) !void {
1163+
pub fn callWithThis(self: *const Callback, this: anytype, args: anytype) !void {
11421164
const executor = self.executor;
11431165

1166+
const js_this = try executor.valueToExistingObject(this);
1167+
11441168
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
11451169
const fields = @typeInfo(@TypeOf(aargs)).@"struct".fields;
11461170
var js_args: [fields.len]v8.Value = undefined;
@@ -1154,8 +1178,12 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
11541178
}
11551179
}
11561180

1181+
fn getThis(self: *const Callback) v8.Object {
1182+
return self._this orelse self.executor.context.getGlobal();
1183+
}
1184+
11571185
// debug/helper to print the source of the JS callback
1158-
fn printFunc(self: *const @This()) !void {
1186+
fn printFunc(self: Callback) !void {
11591187
const executor = self.executor;
11601188
const value = self.func.castToFunction().toValue();
11611189
const src = try valueToString(executor.call_arena.allocator(), value, executor.isolate, executor.context);
@@ -1198,6 +1226,28 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
11981226
}
11991227
};
12001228

1229+
// This only exists so that we know whether a function wants the opaque
1230+
// JS argument (JsObject), or if it wants the receiver as an opaque
1231+
// value.
1232+
// JsObject is normally used when a method wants an opaque JS object
1233+
// that it'll pass into a callback.
1234+
// JsThis is used when the function wants to do advanced manipulation
1235+
// of the v8.Object bound to the instance. For example, postAttach is an
1236+
// example of using JsThis.
1237+
pub const JsThis = struct {
1238+
obj: JsObject,
1239+
1240+
const _JSTHIS_ID_KLUDGE = true;
1241+
1242+
pub fn setIndex(self: JsThis, index: usize, value: anytype) !void {
1243+
return self.obj.setIndex(index, value);
1244+
}
1245+
1246+
pub fn set(self: JsThis, key: []const u8, value: anytype) !void {
1247+
return self.obj.set(key, value);
1248+
}
1249+
};
1250+
12011251
pub const TryCatch = struct {
12021252
inner: v8.TryCatch,
12031253
executor: *const Executor,
@@ -1761,14 +1811,14 @@ fn Caller(comptime E: type) type {
17611811
break :blk params[0 .. params.len - 1];
17621812
}
17631813

1764-
// If the last parameter is a JsObject, set it, and exclude it
1814+
// If the last parameter is a special JsThis, set it, and exclude it
17651815
// from our params slice, because we don't want to bind it to
17661816
// a JS argument
1767-
if (comptime isJsObject(params[params.len - 1].type.?)) {
1768-
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{
1769-
.handle = info.getThis(),
1817+
if (comptime isJsThis(params[params.len - 1].type.?)) {
1818+
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{
1819+
.js_obj = info.getThis(),
17701820
.executor = self.executor,
1771-
};
1821+
} };
17721822

17731823
// AND the 2nd last parameter is state
17741824
if (params.len > 1 and comptime isState(params[params.len - 2].type.?)) {
@@ -1827,9 +1877,9 @@ fn Caller(comptime E: type) type {
18271877
}
18281878

18291879
if (comptime isState(param.type.?)) {
1830-
@compileError("State must be the last parameter (or 2nd last if there's a JsObject): " ++ named_function.full_name);
1831-
} else if (comptime isJsObject(param.type.?)) {
1832-
@compileError("JsObject must be the last parameter: " ++ named_function.full_name);
1880+
@compileError("State must be the last parameter (or 2nd last if there's a JsThis): " ++ named_function.full_name);
1881+
} else if (comptime isJsThis(param.type.?)) {
1882+
@compileError("JsThis must be the last parameter: " ++ named_function.full_name);
18331883
} else if (i >= js_parameter_count) {
18341884
if (@typeInfo(param.type.?) != .optional) {
18351885
return error.InvalidArgument;
@@ -1929,9 +1979,20 @@ fn Caller(comptime E: type) type {
19291979
if (!js_value.isObject()) {
19301980
return error.InvalidArgument;
19311981
}
1982+
1983+
const js_obj = js_value.castTo(v8.Object);
1984+
1985+
if (comptime isJsObject(T)) {
1986+
// Caller wants an opaque JsObject. Probably a parameter
1987+
// that it needs to pass back into a callback
1988+
return E.JsObject{
1989+
.js_obj = js_obj,
1990+
.executor = self.executor,
1991+
};
1992+
}
1993+
19321994
const context = self.context;
19331995
const isolate = self.isolate;
1934-
const js_obj = js_value.castTo(v8.Object);
19351996

19361997
var value: T = undefined;
19371998
inline for (s.fields) |field| {
@@ -2017,6 +2078,10 @@ fn Caller(comptime E: type) type {
20172078
fn isJsObject(comptime T: type) bool {
20182079
return @typeInfo(T) == .@"struct" and @hasDecl(T, "_JSOBJECT_ID_KLUDGE");
20192080
}
2081+
2082+
fn isJsThis(comptime T: type) bool {
2083+
return @typeInfo(T) == .@"struct" and @hasDecl(T, "_JSTHIS_ID_KLUDGE");
2084+
}
20202085
};
20212086
}
20222087

0 commit comments

Comments
 (0)