@@ -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