Skip to content
File

Blob: src/workerd/jsg/resource.h

cpp2121 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6// INTERNAL IMPLEMENTATION FILE
7//
8// A "resource type" (in KJ parlance) is the opposite of a "value type". In JSG, a resource is a
9// C++ class that will be wrapped and exposed to JavaScript by reference, such that JavaScript code
10// can call back to the class's methods. This differs from, say, a struct type, which will be deeply
11// converted into a JS object when passed into JS.
12 
13#include <workerd/jsg/fast-api.h>
14#include <workerd/jsg/memory.h>
15#include <workerd/jsg/meta.h>
16#include <workerd/jsg/modules.capnp.h>
17// JSG has very entrenched include cycles
18// NOLINTNEXTLINE(misc-header-include-cycle)
19#include <workerd/jsg/ser.h>
20#include <workerd/jsg/util.h>
21#include <workerd/jsg/wrappable.h>
22 
23#include <v8-template.h>
24 
25#include <kj/debug.h>
26#include <kj/map.h>
27#include <kj/tuple.h>
28 
29#include <type_traits>
30#include <typeindex>
31 
32// TODO(cleanup): Remove when unnecessary.
33#if V8_MAJOR_VERSION >= 15 || (V8_MAJOR_VERSION == 14 && V8_MINOR_VERSION >= 7)
34#define HolderV2 Holder
35#endif
36 
37namespace std {
38inline auto KJ_HASHCODE(const std::type_index& idx) {
39 // Make std::type_index (which points to std::type_info) usable as a kj::HashMap key.
40 // TODO(cleanup): This probably shouldn't live here, but where should it live? KJ?
41 return idx.hash_code();
42}
43} // namespace std
44 
45namespace workerd::jsg {
46 
47const auto kIllegalInvocation =
48 "Illegal invocation: function called with incorrect `this` reference. "
49 "See https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors for details."_kj;
50 
51class Serializer;
52class Deserializer;
53 
54// Return true if the type requires GC visitation, which we assume is the case if the type or any
55// superclass (other than Object) declares a `visitForGc()` method.
56template <typename T>
57constexpr bool resourceNeedsGcTracing() {
58 return &T::visitForGc != &Object::visitForGc;
59}
60template <>
61constexpr bool resourceNeedsGcTracing<Object>() {
62 return false;
63}
64 
65// Call obj->visitForGc() if and only if T defines its own `visitForGc()` method -- do not call
66// the parent class's `visitForGc()`.
67template <typename T>
68inline void visitSubclassForGc(T* obj, GcVisitor& visitor) {
69 if constexpr (&T::visitForGc != &T::jsgSuper::visitForGc) {
70 obj->visitForGc(visitor);
71 }
72}
73 
74void throwIfConstructorCalledAsFunction(
75 const v8::FunctionCallbackInfo<v8::Value>& args, const std::type_info& type);
76 
77// The scheduleUnimplemented* variants will schedule an exception on the isolate
78// but do not throw a JsExceptionThrown.
79 
80// Called to throw errors about calling unimplemented functionality. It's assumed these are called
81// directly from the V8 trampoline without liftKj, so they don't throw JsExceptionThrown.
82void scheduleUnimplementedConstructorError(
83 const v8::FunctionCallbackInfo<v8::Value>& args, const std::type_info& type);
84 
85// Called to throw errors about calling unimplemented functionality. It's assumed these are called
86// directly from the V8 trampoline without liftKj, so they don't throw JsExceptionThrown.
87void scheduleUnimplementedMethodError(const v8::FunctionCallbackInfo<v8::Value>& args,
88 const std::type_info& type,
89 const char* methodName);
90 
91// Called to throw errors about calling unimplemented functionality. It's assumed these are called
92// directly from the V8 trampoline without liftKj, so they don't throw JsExceptionThrown.
93void scheduleUnimplementedPropertyError(
94 v8::Isolate* isolate, const std::type_info& type, const char* propertyName);
95 
96template <typename TypeWrapper, typename T>
97class ResourceWrapper;
98 
99// Implements the V8 callback function for calling the static `constructor()` method of the C++
100// class.
101template <typename TypeWrapper,
102 typename T,
103 typename = decltype(T::constructor),
104 typename = ArgumentIndexes<decltype(T::constructor)>>
105struct ConstructorCallback;
106 
107template <typename TypeWrapper, typename T, typename... Args, size_t... indexes>
108struct ConstructorCallback<TypeWrapper, T, Ref<T>(Args...), kj::_::Indexes<indexes...>> {
109 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
110 liftKj(args, [&]() {
111 auto isolate = args.GetIsolate();
112 
113 throwIfConstructorCalledAsFunction(args, typeid(T));
114 
115 auto context = isolate->GetCurrentContext();
116 auto& js = Lock::from(isolate);
117 auto obj = args.This();
118 KJ_ASSERT(obj->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);
119 
120 auto& wrapper = TypeWrapper::from(isolate);
121 
122 Ref<T> ptr = T::constructor(wrapper.template unwrap<Args>(js, context, args, indexes,
123 TypeErrorContext::constructorArgument(typeid(T), indexes))...);
124 if constexpr (T::jsgHasReflection) {
125 ptr->jsgInitReflection(wrapper);
126 }
127 ptr.attachWrapper(isolate, obj);
128 });
129 }
130};
131 
132// Specialization for constructors that take `Lock&` as their first parameter.
133template <typename TypeWrapper, typename T, typename... Args, size_t... indexes>
134struct ConstructorCallback<TypeWrapper, T, Ref<T>(Lock&, Args...), kj::_::Indexes<indexes...>> {
135 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
136 liftKj(args, [&]() {
137 auto isolate = args.GetIsolate();
138 
139 throwIfConstructorCalledAsFunction(args, typeid(T));
140 
141 auto context = isolate->GetCurrentContext();
142 auto& js = Lock::from(isolate);
143 auto obj = args.This();
144 KJ_ASSERT(obj->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);
145 
146 auto& wrapper = TypeWrapper::from(isolate);
147 
148 Ref<T> ptr = T::constructor(Lock::from(isolate),
149 wrapper.template unwrap<Args>(js, context, args, indexes,
150 TypeErrorContext::constructorArgument(typeid(T), indexes))...);
151 if constexpr (T::jsgHasReflection) {
152 ptr->jsgInitReflection(wrapper);
153 }
154 ptr.attachWrapper(isolate, obj);
155 });
156 }
157};
158 
159// Specialization for constructors that take `const v8::FunctionCallbackInfo<v8::Value>&` as
160// their first parameter.
161template <typename TypeWrapper, typename T, typename... Args, size_t... indexes>
162struct ConstructorCallback<TypeWrapper,
163 T,
164 Ref<T>(const v8::FunctionCallbackInfo<v8::Value>&, Args...),
165 kj::_::Indexes<indexes...>> {
166 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
167 liftKj(args, [&]() {
168 auto isolate = args.GetIsolate();
169 
170 throwIfConstructorCalledAsFunction(args, typeid(T));
171 
172 auto context = isolate->GetCurrentContext();
173 auto& js = Lock::from(isolate);
174 auto obj = args.This();
175 KJ_ASSERT(obj->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);
176 
177 auto& wrapper = TypeWrapper::from(isolate);
178 
179 Ref<T> ptr = T::constructor(args,
180 wrapper.template unwrap<Args>(js, context, args, indexes,
181 TypeErrorContext::constructorArgument(typeid(T), indexes))...);
182 if constexpr (T::jsgHasReflection) {
183 ptr->jsgInitReflection(wrapper);
184 }
185 ptr.attachWrapper(isolate, obj);
186 });
187 }
188};
189 
190template <typename TypeWrapper, typename T, typename... Args, size_t... indexes>
191struct ConstructorCallback<TypeWrapper, T, Unimplemented(Args...), kj::_::Indexes<indexes...>> {
192 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
193 scheduleUnimplementedConstructorError(args, typeid(T));
194 }
195};
196 
197// Implements the V8 callback function for calling a method of the C++ class.
198template <typename TypeWrapper,
199 const char* methodName,
200 bool isContext,
201 typename T,
202 typename Method,
203 Method method,
204 typename Indexes>
205struct MethodCallback;
206 
207template <typename TypeWrapper,
208 const char* methodName,
209 bool isContext,
210 typename T,
211 typename U,
212 typename Ret,
213 typename... Args,
214 Ret (U::*method)(Args...),
215 size_t... indexes>
216struct MethodCallback<TypeWrapper,
217 methodName,
218 isContext,
219 T,
220 Ret (U::*)(Args...),
221 method,
222 kj::_::Indexes<indexes...>> {
223 
224 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
225 if constexpr (TypeWrapper::trackCallCounts) {
226 callCounter.slow++;
227 }
228 
229 liftKj(args, [&]() {
230 auto isolate = args.GetIsolate();
231 auto context = isolate->GetCurrentContext();
232 auto obj = args.This();
233 auto& wrapper = TypeWrapper::from(isolate);
234 auto& lock = Lock::from(isolate);
235 auto& self = extractInternalPointer<T, isContext>(context, obj);
236 if constexpr (isVoid<Ret>()) {
237 (self.*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,
238 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
239 } else {
240 return wrapper.wrap(lock, context, obj,
241 (self.*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,
242 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));
243 }
244 });
245 }
246 
247 template <typename ReturnType = Ret>
248 static ReturnType fastCallback(v8::Local<v8::Object> receiver,
249 FastApiJSGToV8<Args>::value... fastArgs,
250 v8::FastApiCallbackOptions& options) {
251 if constexpr (TypeWrapper::trackCallCounts) {
252 callCounter.fast++;
253 }
254 auto isolate = options.isolate;
255 v8::HandleScope handleScope(isolate);
256 auto context = isolate->GetCurrentContext();
257 auto& js = Lock::from(isolate);
258 auto& self = extractInternalPointer<T, isContext>(context, receiver);
259 auto& wrapper = TypeWrapper::from(isolate);
260 
261 return liftKj<Ret>(isolate, [&]() {
262 return (self.*method)(wrapper.template unwrapFastApi<Args>(js, context, fastArgs,
263 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
264 });
265 }
266};
267 
268// Specialization for methods that take `Lock&` as their first parameter.
269template <typename TypeWrapper,
270 const char* methodName,
271 bool isContext,
272 typename T,
273 typename U,
274 typename Ret,
275 typename... Args,
276 Ret (U::*method)(Lock&, Args...),
277 size_t... indexes>
278struct MethodCallback<TypeWrapper,
279 methodName,
280 isContext,
281 T,
282 Ret (U::*)(Lock&, Args...),
283 method,
284 kj::_::Indexes<indexes...>> {
285 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
286 if constexpr (TypeWrapper::trackCallCounts) {
287 callCounter.slow++;
288 }
289 liftKj(args, [&]() {
290 auto isolate = args.GetIsolate();
291 auto context = isolate->GetCurrentContext();
292 auto obj = args.This();
293 auto& wrapper = TypeWrapper::from(isolate);
294 auto& self = extractInternalPointer<T, isContext>(context, obj);
295 auto& lock = Lock::from(isolate);
296 if constexpr (isVoid<Ret>()) {
297 (self.*method)(lock,
298 wrapper.template unwrap<Args>(lock, context, args, indexes,
299 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
300 } else {
301 return wrapper.wrap(lock, context, obj,
302 (self.*method)(lock,
303 wrapper.template unwrap<Args>(lock, context, args, indexes,
304 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));
305 }
306 });
307 }
308 
309 template <typename ReturnType = Ret>
310 static ReturnType fastCallback(v8::Local<v8::Object> receiver,
311 FastApiJSGToV8<Args>::value... fastArgs,
312 v8::FastApiCallbackOptions& options) {
313 if constexpr (TypeWrapper::trackCallCounts) {
314 callCounter.fast++;
315 }
316 auto isolate = options.isolate;
317 v8::HandleScope handleScope(isolate);
318 auto context = isolate->GetCurrentContext();
319 auto& self = extractInternalPointer<T, isContext>(context, receiver);
320 auto& lock = Lock::from(isolate);
321 auto& wrapper = TypeWrapper::from(isolate);
322 
323 return liftKj<Ret>(isolate, [&]() {
324 return (self.*method)(lock,
325 wrapper.template unwrapFastApi<Args>(lock, context, fastArgs,
326 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
327 });
328 }
329};
330 
331// Specialization for methods that take `const v8::FunctionCallbackInfo<v8::Value>&` as their
332// first parameter.
333template <typename TypeWrapper,
334 const char* methodName,
335 bool isContext,
336 typename T,
337 typename U,
338 typename Ret,
339 typename... Args,
340 Ret (U::*method)(const v8::FunctionCallbackInfo<v8::Value>&, Args...),
341 size_t... indexes>
342struct MethodCallback<TypeWrapper,
343 methodName,
344 isContext,
345 T,
346 Ret (U::*)(const v8::FunctionCallbackInfo<v8::Value>&, Args...),
347 method,
348 kj::_::Indexes<indexes...>> {
349 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
350 liftKj(args, [&]() {
351 auto isolate = args.GetIsolate();
352 auto context = isolate->GetCurrentContext();
353 auto obj = args.This();
354 auto& wrapper = TypeWrapper::from(isolate);
355 auto& lock = Lock::from(isolate);
356 auto& self = extractInternalPointer<T, isContext>(context, obj);
357 if constexpr (isVoid<Ret>()) {
358 (self.*method)(args,
359 wrapper.template unwrap<Args>(lock, context, args, indexes,
360 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
361 } else {
362 return wrapper.wrap(lock, context, obj,
363 (self.*method)(args,
364 wrapper.template unwrap<Args>(lock, context, args, indexes,
365 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));
366 }
367 });
368 }
369};
370 
371template <typename TypeWrapper,
372 const char* methodName,
373 bool isContext,
374 typename T,
375 typename U,
376 typename... Args,
377 Unimplemented (U::*method)(Args...),
378 size_t... indexes>
379struct MethodCallback<TypeWrapper,
380 methodName,
381 isContext,
382 T,
383 Unimplemented (U::*)(Args...),
384 method,
385 kj::_::Indexes<indexes...>> {
386 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
387 scheduleUnimplementedMethodError(args, typeid(T), methodName);
388 }
389};
390 
391// Implements the V8 callback function for calling a static method of the C++ class.
392//
393// This is separate from MethodCallback<> because we need to know the interface type, T, and it
394// won't be deducible from Method when Method is a C++ static member function.
395//
396// In the explicit specializations of this template, we use TypeErrorContext::methodArgument() for
397// generating error messages, rather than concoct a new error message format specifically for static
398// methods. This matches Chrome's behavior.
399 
400template <typename TypeWrapper,
401 const char* methodName,
402 typename T,
403 typename Method,
404 Method* method,
405 typename Indexes>
406struct StaticMethodCallback;
407 
408template <typename TypeWrapper,
409 const char* methodName,
410 typename T,
411 typename Ret,
412 typename... Args,
413 Ret (*method)(Args...),
414 size_t... indexes>
415struct StaticMethodCallback<TypeWrapper,
416 methodName,
417 T,
418 Ret(Args...),
419 method,
420 kj::_::Indexes<indexes...>> {
421 
422 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
423 if constexpr (TypeWrapper::trackCallCounts) {
424 callCounter.slow++;
425 }
426 liftKj(args, [&]() {
427 auto isolate = args.GetIsolate();
428 auto context = isolate->GetCurrentContext();
429 auto& wrapper = TypeWrapper::from(isolate);
430 auto& lock = Lock::from(isolate);
431 if constexpr (isVoid<Ret>()) {
432 (*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,
433 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
434 } else {
435 return wrapper.wrap(lock, context, kj::none,
436 (*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,
437 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));
438 }
439 });
440 }
441 
442 template <typename ReturnType = Ret>
443 static ReturnType fastCallback(v8::Local<v8::Object> receiver,
444 FastApiJSGToV8<Args>::value... fastArgs,
445 v8::FastApiCallbackOptions& options) {
446 if constexpr (TypeWrapper::trackCallCounts) {
447 callCounter.fast++;
448 }
449 auto isolate = options.isolate;
450 v8::HandleScope handleScope(isolate);
451 auto context = isolate->GetCurrentContext();
452 auto& lock = Lock::from(isolate);
453 auto& wrapper = TypeWrapper::from(isolate);
454 
455 return liftKj<Ret>(isolate, [&]() {
456 return (*method)(wrapper.template unwrapFastApi<Args>(lock, context, fastArgs,
457 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
458 });
459 }
460};
461 
462// Specialization for methods that take `Lock&` as their first parameter.
463template <typename TypeWrapper,
464 const char* methodName,
465 typename T,
466 typename Ret,
467 typename... Args,
468 Ret (*method)(Lock&, Args...),
469 size_t... indexes>
470struct StaticMethodCallback<TypeWrapper,
471 methodName,
472 T,
473 Ret(Lock&, Args...),
474 method,
475 kj::_::Indexes<indexes...>> {
476 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
477 if constexpr (TypeWrapper::trackCallCounts) {
478 callCounter.slow++;
479 }
480 
481 liftKj(args, [&]() {
482 auto isolate = args.GetIsolate();
483 auto context = isolate->GetCurrentContext();
484 auto& wrapper = TypeWrapper::from(isolate);
485 auto& lock = Lock::from(isolate);
486 if constexpr (isVoid<Ret>()) {
487 (*method)(lock,
488 wrapper.template unwrap<Args>(lock, context, args, indexes,
489 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
490 } else {
491 return wrapper.wrap(lock, context, kj::none,
492 (*method)(lock,
493 wrapper.template unwrap<Args>(lock, context, args, indexes,
494 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));
495 }
496 });
497 }
498 
499 template <typename ReturnType = Ret>
500 static ReturnType fastCallback(v8::Local<v8::Object> receiver,
501 FastApiJSGToV8<Args>::value... fastArgs,
502 v8::FastApiCallbackOptions& options) {
503 if constexpr (TypeWrapper::trackCallCounts) {
504 callCounter.fast++;
505 }
506 auto isolate = options.isolate;
507 v8::HandleScope handleScope(isolate);
508 auto context = isolate->GetCurrentContext();
509 auto& lock = Lock::from(isolate);
510 auto& wrapper = TypeWrapper::from(isolate);
511 
512 return liftKj<Ret>(isolate, [&]() {
513 return (*method)(lock,
514 wrapper.template unwrapFastApi<Args>(lock, context, fastArgs,
515 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
516 });
517 }
518};
519 
520// Specialization for methods that take `const v8::FunctionCallbackInfo<v8::Value>&` as their
521// first parameter.
522template <typename TypeWrapper,
523 const char* methodName,
524 typename T,
525 typename Ret,
526 typename... Args,
527 Ret (*method)(const v8::FunctionCallbackInfo<v8::Value>&, Args...),
528 size_t... indexes>
529struct StaticMethodCallback<TypeWrapper,
530 methodName,
531 T,
532 Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...),
533 method,
534 kj::_::Indexes<indexes...>> {
535 
536 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
537 liftKj(args, [&]() {
538 auto isolate = args.GetIsolate();
539 auto context = isolate->GetCurrentContext();
540 auto& lock = Lock::from(isolate);
541 auto& wrapper = TypeWrapper::from(isolate);
542 if constexpr (isVoid<Ret>()) {
543 (*method)(args,
544 wrapper.template unwrap<Args>(lock, context, args, indexes,
545 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);
546 } else {
547 return wrapper.wrap(lock, context, kj::none,
548 (*method)(args,
549 wrapper.template unwrap<Args>(lock, context, args, indexes,
550 TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));
551 }
552 });
553 }
554};
555 
556template <typename TypeWrapper,
557 const char* methodName,
558 typename T,
559 typename... Args,
560 Unimplemented (*method)(Args...),
561 size_t... indexes>
562struct StaticMethodCallback<TypeWrapper,
563 methodName,
564 T,
565 Unimplemented(Args...),
566 method,
567 kj::_::Indexes<indexes...>> {
568 
569 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
570 scheduleUnimplementedMethodError(args, typeid(T), methodName);
571 }
572};
573 
574// Implements the V8 callback function for static property getters.
575template <typename TypeWrapper,
576 const char* propertyName,
577 typename T,
578 typename Getter,
579 Getter* getter>
580struct StaticPropertyCallback;
581 
582template <typename TypeWrapper, const char* propertyName, typename T, typename Ret, Ret (*getter)()>
583struct StaticPropertyCallback<TypeWrapper, propertyName, T, Ret(), getter> {
584 
585 static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& args) {
586 liftKj(args, [&]() {
587 auto isolate = args.GetIsolate();
588 auto context = isolate->GetCurrentContext();
589 auto& wrapper = TypeWrapper::from(isolate);
590 auto& lock = Lock::from(isolate);
591 
592 if constexpr (isVoid<Ret>()) {
593 (*getter)();
594 } else {
595 return wrapper.wrap(lock, context, kj::none, (*getter)());
596 }
597 });
598 }
599};
600 
601// Specialization for static property getter that takes Lock& as first parameter
602template <typename TypeWrapper,
603 const char* propertyName,
604 typename T,
605 typename Ret,
606 Ret (*getter)(Lock&)>
607struct StaticPropertyCallback<TypeWrapper, propertyName, T, Ret(Lock&), getter> {
608 
609 static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& args) {
610 liftKj(args, [&]() {
611 auto isolate = args.GetIsolate();
612 auto context = isolate->GetCurrentContext();
613 auto& wrapper = TypeWrapper::from(isolate);
614 auto& lock = Lock::from(isolate);
615 
616 if constexpr (isVoid<Ret>()) {
617 (*getter)(lock);
618 } else {
619 return wrapper.wrap(lock, context, kj::none, (*getter)(lock));
620 }
621 });
622 }
623};
624 
625// Implements the V8 callback function for calling a property getter method of a C++ class.
626template <typename TypeWrapper,
627 const char* methodName,
628 typename Method,
629 Method method,
630 bool isContext>
631struct GetterCallback;
632 
633#define JSG_DEFINE_GETTER_CALLBACK_STRUCTS(...) \
634 template <typename TypeWrapper, const char* methodName, typename T, typename Ret, \
635 typename... Args, Ret (T::*method)(Args...) __VA_ARGS__, bool isContext> \
636 struct GetterCallback<TypeWrapper, methodName, Ret (T::*)(Args...) __VA_ARGS__, method, \
637 isContext> { \
638 static constexpr bool enumerable = true; \
639 static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) { \
640 if constexpr (TypeWrapper::trackCallCounts) { \
641 callCounter.slow++; \
642 } \
643 liftKj(info, [&]() { \
644 auto isolate = info.GetIsolate(); \
645 auto context = isolate->GetCurrentContext(); \
646 auto obj = info.HolderV2(); \
647 auto& js = Lock::from(isolate); \
648 auto& wrapper = TypeWrapper::from(isolate); \
649 /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */ \
650 if (!isContext && \
651 !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) { \
652 throwTypeError(isolate, kIllegalInvocation); \
653 } \
654 auto& self = extractInternalPointer<T, isContext>(context, obj); \
655 return wrapper.wrap(js, context, obj, \
656 (self.*method)( \
657 wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...)); \
658 }); \
659 } \
660 template <typename ReturnType = Ret> \
661 static ReturnType fastCallback( \
662 v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) { \
663 if constexpr (TypeWrapper::trackCallCounts) { \
664 callCounter.fast++; \
665 } \
666 auto isolate = options.isolate; \
667 v8::HandleScope handleScope(isolate); \
668 auto context = isolate->GetCurrentContext(); \
669 auto& lock = Lock::from(isolate); \
670 auto& self = extractInternalPointer<T, isContext>(context, receiver); \
671 auto& wrapper = TypeWrapper::from(isolate); \
672 return liftKj<ReturnType>(isolate, [&]() { \
673 return (self.*method)(wrapper.template unwrapFastApi<Args>( \
674 lock, context, static_cast<kj::Decay<Args>*>(nullptr))...); \
675 }); \
676 } \
677 }; \
678 \
679 /* Specialization for methods that take `Lock&` as their first parameter. */ \
680 template <typename TypeWrapper, const char* methodName, typename T, typename Ret, \
681 typename... Args, Ret (T::*method)(Lock&, Args...) __VA_ARGS__, bool isContext> \
682 struct GetterCallback<TypeWrapper, methodName, Ret (T::*)(Lock&, Args...) __VA_ARGS__, method, \
683 isContext> { \
684 static constexpr bool enumerable = true; \
685 static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) { \
686 if constexpr (TypeWrapper::trackCallCounts) { \
687 callCounter.slow++; \
688 } \
689 liftKj(info, [&]() { \
690 auto isolate = info.GetIsolate(); \
691 auto context = isolate->GetCurrentContext(); \
692 auto& js = Lock::from(isolate); \
693 auto obj = info.HolderV2(); \
694 auto& wrapper = TypeWrapper::from(isolate); \
695 /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */ \
696 if (!isContext && \
697 !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) { \
698 throwTypeError(isolate, kIllegalInvocation); \
699 } \
700 auto& self = extractInternalPointer<T, isContext>(context, obj); \
701 return wrapper.wrap(js, context, obj, \
702 (self.*method)( \
703 js, wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...)); \
704 }); \
705 } \
706 template <typename ReturnType = Ret> \
707 static ReturnType fastCallback( \
708 v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) { \
709 if constexpr (TypeWrapper::trackCallCounts) { \
710 callCounter.fast++; \
711 } \
712 auto isolate = options.isolate; \
713 v8::HandleScope handleScope(isolate); \
714 auto context = isolate->GetCurrentContext(); \
715 auto& js = Lock::from(isolate); \
716 auto& self = extractInternalPointer<T, isContext>(context, receiver); \
717 auto& wrapper = TypeWrapper::from(isolate); \
718 return liftKj<ReturnType>(isolate, [&]() { \
719 return (self.*method)(js, \
720 wrapper.template unwrapFastApi<Args>( \
721 js, context, static_cast<kj::Decay<Args>*>(nullptr))...); \
722 }); \
723 } \
724 }; \
725 \
726 template <typename TypeWrapper, const char* propertyName, typename T, \
727 Unimplemented (T::*method)() __VA_ARGS__, bool isContext> \
728 struct GetterCallback<TypeWrapper, propertyName, Unimplemented (T::*)() __VA_ARGS__, method, \
729 isContext> { \
730 static constexpr bool enumerable = false; \
731 static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) { \
732 scheduleUnimplementedPropertyError(info.GetIsolate(), typeid(T), propertyName); \
733 } \
734 template <typename ReturnType = Unimplemented> \
735 static ReturnType fastCallback( \
736 v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) { \
737 scheduleUnimplementedPropertyError(options.isolate, typeid(T), propertyName); \
738 if constexpr (!isVoid<ReturnType>()) { \
739 return ReturnType{}; \
740 } \
741 }; \
742 };
743 
744JSG_DEFINE_GETTER_CALLBACK_STRUCTS()
745JSG_DEFINE_GETTER_CALLBACK_STRUCTS(const)
746 
747#undef JSG_DEFINE_GETTER_CALLBACK_STRUCTS
748 
749template <typename TypeWrapper,
750 const char* methodName,
751 typename Method,
752 Method method,
753 bool isContext>
754struct PropertyGetterCallback;
755 
756#define JSG_DEFINE_PROPERTY_GETTER_CALLBACK_STRUCTS(...) \
757 template <typename TypeWrapper, const char* methodName, typename T, typename Ret, \
758 typename... Args, Ret (T::*method)(Args...) __VA_ARGS__, bool isContext> \
759 struct PropertyGetterCallback<TypeWrapper, methodName, Ret (T::*)(Args...) __VA_ARGS__, method, \
760 isContext> { \
761 static constexpr bool enumerable = true; \
762 static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) { \
763 if constexpr (TypeWrapper::trackCallCounts) { \
764 callCounter.slow++; \
765 } \
766 liftKj(info, [&]() { \
767 auto isolate = info.GetIsolate(); \
768 auto context = isolate->GetCurrentContext(); \
769 auto& js = Lock::from(isolate); \
770 auto obj = info.This(); \
771 auto& wrapper = TypeWrapper::from(isolate); \
772 /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */ \
773 if (!isContext && \
774 !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) { \
775 throwTypeError(isolate, kIllegalInvocation); \
776 } \
777 auto& self = extractInternalPointer<T, isContext>(context, obj); \
778 return wrapper.wrap(js, context, obj, \
779 (self.*method)( \
780 wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...)); \
781 }); \
782 } \
783 template <typename ReturnType = Ret> \
784 static ReturnType fastCallback( \
785 v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) { \
786 if constexpr (TypeWrapper::trackCallCounts) { \
787 callCounter.fast++; \
788 } \
789 auto isolate = options.isolate; \
790 v8::HandleScope handleScope(isolate); \
791 auto context = isolate->GetCurrentContext(); \
792 auto& self = extractInternalPointer<T, isContext>(context, receiver); \
793 auto& wrapper = TypeWrapper::from(isolate); \
794 auto& js = Lock::from(isolate); \
795 \
796 return liftKj<ReturnType>(isolate, [&]() -> ReturnType { \
797 return (self.*method)( \
798 wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...); \
799 }); \
800 } \
801 }; \
802 \
803 /* Specialization for methods that take `Lock&` as their first parameter. */ \
804 template <typename TypeWrapper, const char* methodName, typename T, typename Ret, \
805 typename... Args, Ret (T::*method)(Lock&, Args...) __VA_ARGS__, bool isContext> \
806 struct PropertyGetterCallback<TypeWrapper, methodName, Ret (T::*)(Lock&, Args...) __VA_ARGS__, \
807 method, isContext> { \
808 static constexpr bool enumerable = true; \
809 static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) { \
810 if constexpr (TypeWrapper::trackCallCounts) { \
811 callCounter.slow++; \
812 } \
813 liftKj(info, [&]() { \
814 auto isolate = info.GetIsolate(); \
815 auto context = isolate->GetCurrentContext(); \
816 auto& js = Lock::from(isolate); \
817 auto obj = info.This(); \
818 auto& wrapper = TypeWrapper::from(isolate); \
819 /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */ \
820 if (!isContext && \
821 !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) { \
822 throwTypeError(isolate, kIllegalInvocation); \
823 } \
824 auto& self = extractInternalPointer<T, isContext>(context, obj); \
825 return wrapper.wrap(js, context, obj, \
826 (self.*method)( \
827 js, wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...)); \
828 }); \
829 } \
830 template <typename ReturnType = Ret> \
831 static ReturnType fastCallback( \
832 v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) { \
833 if constexpr (TypeWrapper::trackCallCounts) { \
834 callCounter.fast++; \
835 } \
836 auto isolate = options.isolate; \
837 v8::HandleScope handleScope(isolate); \
838 auto context = isolate->GetCurrentContext(); \
839 auto& self = extractInternalPointer<T, isContext>(context, receiver); \
840 auto& js = Lock::from(isolate); \
841 auto& wrapper = TypeWrapper::from(isolate); \
842 \
843 return liftKj<ReturnType>(isolate, [&]() -> ReturnType { \
844 return (self.*method)( \
845 js, wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...); \
846 }); \
847 } \
848 }; \
849 template <typename TypeWrapper, const char* propertyName, typename T, \
850 Unimplemented (T::*method)() __VA_ARGS__, bool isContext> \
851 struct PropertyGetterCallback<TypeWrapper, propertyName, Unimplemented (T::*)() __VA_ARGS__, \
852 method, isContext> { \
853 static constexpr bool enumerable = false; \
854 static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) { \
855 scheduleUnimplementedPropertyError(info.GetIsolate(), typeid(T), propertyName); \
856 } \
857 template <typename ReturnType = Unimplemented> \
858 static ReturnType fastCallback( \
859 v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) { \
860 scheduleUnimplementedPropertyError(options.isolate, typeid(T), propertyName); \
861 if constexpr (!isVoid<ReturnType>()) { \
862 return ReturnType{}; \
863 } \
864 } \
865 };
866 
867JSG_DEFINE_PROPERTY_GETTER_CALLBACK_STRUCTS()
868JSG_DEFINE_PROPERTY_GETTER_CALLBACK_STRUCTS(const)
869 
870// Implements the V8 callback function for calling a property setter method of a C++ class.
871template <typename TypeWrapper,
872 const char* methodName,
873 typename Method,
874 Method method,
875 bool isContext>
876struct SetterCallback;
877 
878template <typename TypeWrapper,
879 const char* methodName,
880 typename T,
881 typename Arg,
882 void (T::*method)(Arg),
883 bool isContext>
884struct SetterCallback<TypeWrapper, methodName, void (T::*)(Arg), method, isContext> {
885 static void callback(
886 v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {
887 liftKj(info, [&]() {
888 auto isolate = info.GetIsolate();
889 auto context = isolate->GetCurrentContext();
890 auto& js = Lock::from(isolate);
891 auto obj = info.HolderV2();
892 auto& wrapper = TypeWrapper::from(isolate);
893 // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.
894 if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {
895 throwTypeError(isolate, kIllegalInvocation);
896 }
897 auto& self = extractInternalPointer<T, isContext>(context, obj);
898 (self.*method)(wrapper.template unwrap<Arg>(
899 js, context, value, TypeErrorContext::setterArgument(typeid(T), methodName)));
900 });
901 }
902};
903 
904// Specialization for methods that take `Lock&` as their first parameter.
905template <typename TypeWrapper,
906 const char* methodName,
907 typename T,
908 typename Arg,
909 void (T::*method)(Lock&, Arg),
910 bool isContext>
911struct SetterCallback<TypeWrapper, methodName, void (T::*)(Lock&, Arg), method, isContext> {
912 static void callback(
913 v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {
914 liftKj(info, [&]() {
915 auto isolate = info.GetIsolate();
916 auto context = isolate->GetCurrentContext();
917 auto obj = info.HolderV2();
918 auto& wrapper = TypeWrapper::from(isolate);
919 // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.
920 if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {
921 throwTypeError(isolate, kIllegalInvocation);
922 }
923 auto& self = extractInternalPointer<T, isContext>(context, obj);
924 auto& js = Lock::from(isolate);
925 (self.*method)(js,
926 wrapper.template unwrap<Arg>(
927 js, context, value, TypeErrorContext::setterArgument(typeid(T), methodName)));
928 });
929 }
930};
931 
932template <typename TypeWrapper,
933 const char* methodName,
934 typename Method,
935 Method method,
936 bool isContext>
937struct PropertySetterCallback;
938 
939template <typename TypeWrapper,
940 const char* methodName,
941 typename T,
942 typename Arg,
943 void (T::*method)(Arg),
944 bool isContext>
945struct PropertySetterCallback<TypeWrapper, methodName, void (T::*)(Arg), method, isContext> {
946 static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {
947 if constexpr (TypeWrapper::trackCallCounts) {
948 callCounter.slow++;
949 }
950 liftKj(info, [&]() {
951 auto isolate = info.GetIsolate();
952 auto context = isolate->GetCurrentContext();
953 auto& js = Lock::from(isolate);
954 auto obj = info.This();
955 auto& wrapper = TypeWrapper::from(isolate);
956 // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.
957 if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {
958 throwTypeError(isolate, kIllegalInvocation);
959 }
960 auto& self = extractInternalPointer<T, isContext>(context, obj);
961 (self.*method)(wrapper.template unwrap<Arg>(
962 js, context, info[0], TypeErrorContext::setterArgument(typeid(T), methodName)));
963 });
964 }
965 
966 template <typename ReturnType = void>
967 static ReturnType fastCallback(v8::Local<v8::Object> receiver,
968 FastApiJSGToV8<Arg>::value fastArgs,
969 v8::FastApiCallbackOptions& options) {
970 if constexpr (TypeWrapper::trackCallCounts) {
971 callCounter.fast++;
972 }
973 auto isolate = options.isolate;
974 v8::HandleScope handleScope(isolate);
975 auto context = isolate->GetCurrentContext();
976 auto& js = Lock::from(isolate);
977 auto& self = extractInternalPointer<T, isContext>(context, receiver);
978 auto& wrapper = TypeWrapper::from(isolate);
979 
980 liftKj<void>(isolate, [&]() -> void {
981 (self.*method)(wrapper.template unwrapFastApi<Arg>(
982 js, context, fastArgs, TypeErrorContext::setterArgument(typeid(T), methodName)));
983 });
984 }
985};
986 
987// Specialization for methods that take `Lock&` as their first parameter.
988template <typename TypeWrapper,
989 const char* methodName,
990 typename T,
991 typename Arg,
992 void (T::*method)(Lock&, Arg),
993 bool isContext>
994struct PropertySetterCallback<TypeWrapper, methodName, void (T::*)(Lock&, Arg), method, isContext> {
995 static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {
996 if constexpr (TypeWrapper::trackCallCounts) {
997 callCounter.slow++;
998 }
999 liftKj(info, [&]() {
1000 auto isolate = info.GetIsolate();
1001 auto context = isolate->GetCurrentContext();
1002 auto obj = info.This();
1003 auto& wrapper = TypeWrapper::from(isolate);
1004 // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.
1005 if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {
1006 throwTypeError(isolate, kIllegalInvocation);
1007 }
1008 auto& self = extractInternalPointer<T, isContext>(context, obj);
1009 auto& js = Lock::from(isolate);
1010 (self.*method)(js,
1011 wrapper.template unwrap<Arg>(
1012 js, context, info[0], TypeErrorContext::setterArgument(typeid(T), methodName)));
1013 });
1014 }
1015 
1016 template <typename ReturnType = void>
1017 static ReturnType fastCallback(v8::Local<v8::Object> receiver,
1018 FastApiJSGToV8<Arg>::value fastArgs,
1019 v8::FastApiCallbackOptions& options) {
1020 if constexpr (TypeWrapper::trackCallCounts) {
1021 callCounter.fast++;
1022 }
1023 auto isolate = options.isolate;
1024 v8::HandleScope handleScope(isolate);
1025 auto context = isolate->GetCurrentContext();
1026 auto& self = extractInternalPointer<T, isContext>(context, receiver);
1027 auto& lock = Lock::from(isolate);
1028 auto& wrapper = TypeWrapper::from(isolate);
1029 
1030 liftKj<void>(isolate, [&]() -> void {
1031 (self.*method)(lock,
1032 wrapper.template unwrapFastApi<Arg>(
1033 lock, context, fastArgs, TypeErrorContext::setterArgument(typeid(T), methodName)));
1034 });
1035 }
1036};
1037 
1038template <typename T>
1039class TypeHandler;
1040 
1041// Helper to call T::serialize() and pass along any TypeHandlers it needs.
1042template <typename TypeWrapper, typename T, typename Method = decltype(&T::serialize)>
1043struct SerializeInvoker;
1044template <typename TypeWrapper, typename T, typename... Types>
1045struct SerializeInvoker<TypeWrapper,
1046 T,
1047 void (T::*)(Lock&, Serializer&, const TypeHandler<Types>&...)> {
1048 static void call(TypeWrapper& wrapper, T& target, Lock& js, Serializer& serializer) {
1049 target.serialize(js, serializer, TypeWrapper::template TYPE_HANDLER_INSTANCE<Types>...);
1050 }
1051};
1052 
1053// Helper to call T::deserialize() and pass along any TypeHandlers it needs, as well as wrap
1054// the result.
1055template <typename TypeWrapper, typename T, typename Method = decltype(T::deserialize)>
1056struct DeserializeInvoker;
1057template <typename TypeWrapper, typename T, typename Ret, typename Tag, typename... Types>
1058struct DeserializeInvoker<TypeWrapper,
1059 T,
1060 Ret(Lock&, Tag, Deserializer&, const TypeHandler<Types>&...)> {
1061 static v8::Local<v8::Object> call(
1062 TypeWrapper& wrapper, Lock& js, Tag tag, Deserializer& deserializer) {
1063 return wrapper.wrap(js, js.v8Context(), kj::none,
1064 T::deserialize(
1065 js, tag, deserializer, TypeWrapper::template TYPE_HANDLER_INSTANCE<Types>...));
1066 }
1067};
1068 
1069// SFINAE helper to detect if a type has a static method called `constructor`.
1070// Includes a static_assert to provide a clear error if the method exists but is non-static.
1071template <typename T, typename = void>
1072constexpr bool HasConstructorMethod = false;
1073 
1074template <typename T>
1075constexpr bool HasConstructorMethod<T, std::void_t<decltype(&T::constructor)>> = [] {
1076 static_assert(!std::is_member_function_pointer_v<decltype(&T::constructor)>,
1077 "JSG_RESOURCE_TYPE constructor must be a static method.");
1078 return true;
1079}();
1080 
1081// Expose the global scope type as a nested type under the global scope itself, such that for some
1082// global scope type `GlobalScope`, `this.GlobalScope === this.constructor` holds true. Note that
1083// this does not actually allow the user to construct a global scope object (unless it has a static
1084// C++ constructor member function, of course). It is primarily to allow code like
1085// `this instanceof ServiceWorkerGlobalScope` to work correctly.
1086//
1087// This is implemented manually because the obvious way of doing it causes a crash:
1088//
1089// struct GlobalScope {
1090// JSG_RESOURCE_TYPE {
1091// JSG_NESTED_TYPE(GlobalScope); // BAD!
1092// }
1093// };
1094void exposeGlobalScopeType(v8::Isolate* isolate, v8::Local<v8::Context> context);
1095 
1096v8::Local<v8::Symbol> getSymbolDispose(v8::Isolate* isolate);
1097 
1098// A configuration type that can be derived from any input type, because it contains nothing.
1099class NullConfiguration {
1100 public:
1101 template <typename T>
1102 NullConfiguration(T&&) {}
1103};
1104 
1105// TypeWrapper must list this type as its first superclass. The ResourceWrappers that it
1106// subclasses will then be able to register themselves in the map.
1107template <typename TypeWrapper>
1108class DynamicResourceTypeMap {
1109 private:
1110 using ReflectionInitializer = void(jsg::Object& object, TypeWrapper& wrapper);
1111 struct DynamicTypeInfo {
1112 v8::Local<v8::FunctionTemplate> tmpl;
1113 kj::Maybe<ReflectionInitializer&> reflectionInitializer;
1114 };
1115 
1116 DynamicTypeInfo getDynamicTypeInfo(v8::Isolate* isolate, const std::type_info& type) {
1117 KJ_IF_SOME(f, resourceTypeMap.find(std::type_index(type))) {
1118 return (*f)(static_cast<TypeWrapper&>(*this), isolate);
1119 } else {
1120 KJ_FAIL_REQUIRE(
1121 "cannot wrap object type that was not registered with JSG_DECLARE_ISOLATE_TYPE",
1122 typeName(type));
1123 }
1124 }
1125 
1126 using GetTypeInfoFunc = DynamicTypeInfo(TypeWrapper&, v8::Isolate*);
1127 
1128 // Maps type_info values to functions that can be used to get the associated template. Used by
1129 // ResourceWrapper.
1130 //
1131 // Fun fact: Comparing type_index on Linux is a simple pointer comparison. On Windows it is a
1132 // string comparison (of the type names). On Mac arm64 it could be either, there's a special bit
1133 // in the type_info that indicates if it is known to be unique. See
1134 // _LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION in <typeinfo> for more.
1135 kj::HashMap<std::type_index, GetTypeInfoFunc*> resourceTypeMap;
1136 
1137 // Map types to serializers. Given an `Object`, to serialize it, extract its typeinfo and look
1138 // it up in this table. See jsg::Serializer for more about serialization.
1139 //
1140 // "Why not just use a virtual function?" Virtual functions give the wrong semantics for
1141 // serialization, since it's essential that we use the serializer for the most-derived class,
1142 // not for some parent class. If Foo extends Bar, and Bar is serializable, but Foo does not
1143 // declare a serializer, then Foo is *not* serializable. Bar's serializer is not sufficient,
1144 // since it doesn't know about Foo's extensions. But to get the right behavior from virtual
1145 // calls, Foo would have to explicitly override Bar's serialize method to make it throw an
1146 // exception instead. This seems error-prone.
1147 //
1148 // It also just provides nice symmetry with `deserializerMap`.
1149 //
1150 // The SerializeFunc() must always start by writing a tag.
1151 using SerializeFunc = void(TypeWrapper&, Lock& js, jsg::Object& instance, Serializer& serializer);
1152 kj::HashMap<std::type_index, SerializeFunc*> serializerMap;
1153 
1154 // Map tag numbers to deserializer functions.
1155 using DeserializeFunc = v8::Local<v8::Object>(
1156 TypeWrapper&, Lock& js, uint tag, Deserializer& deserializer);
1157 kj::HashMap<uint, DeserializeFunc*> deserializerMap;
1158 
1159 template <typename, typename>
1160 friend class ResourceWrapper;
1161 template <typename>
1162 friend class ObjectWrapper;
1163 template <typename>
1164 friend class Isolate;
1165};
1166 
1167// ======================================================================================
1168// WildcardProperty implementation
1169 
1170class JsValue;
1171 
1172template <typename TypeWrapper, typename T, typename GetNamedMethod, GetNamedMethod getNamedMethod>
1173struct WildcardPropertyCallbacks;
1174 
1175template <typename TypeWrapper,
1176 typename T,
1177 typename U,
1178 typename Ret,
1179 kj::Maybe<Ret> (U::*getNamedMethod)(jsg::Lock&, kj::String)>
1180struct WildcardPropertyCallbacks<TypeWrapper,
1181 T,
1182 kj::Maybe<Ret> (U::*)(jsg::Lock&, kj::String),
1183 getNamedMethod>: public v8::NamedPropertyHandlerConfiguration {
1184 WildcardPropertyCallbacks()
1185 : v8::NamedPropertyHandlerConfiguration(getter,
1186 nullptr,
1187 query,
1188 nullptr,
1189 nullptr,
1190 nullptr,
1191 descriptor,
1192 v8::Local<v8::Value>(),
1193 static_cast<v8::PropertyHandlerFlags>(
1194 static_cast<int>(v8::PropertyHandlerFlags::kNonMasking) |
1195 static_cast<int>(v8::PropertyHandlerFlags::kHasNoSideEffect) |
1196 static_cast<int>(v8::PropertyHandlerFlags::kOnlyInterceptStrings))) {}
1197 
1198 // Query callback is needed for V8 to properly handle property creation with correct
1199 // enumerable attributes when the interceptor is on the instance template.
1200 static v8::Intercepted query(
1201 v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
1202 v8::Intercepted result = v8::Intercepted::kNo;
1203 liftKj(info, [&]() -> v8::Local<v8::Integer> {
1204 auto isolate = info.GetIsolate();
1205 auto context = isolate->GetCurrentContext();
1206 auto obj = info.HolderV2();
1207 auto& wrapper = TypeWrapper::from(isolate);
1208 if (!wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {
1209 throwTypeError(isolate, kIllegalInvocation);
1210 }
1211 auto& self = extractInternalPointer<T, false>(context, obj);
1212 auto& lock = Lock::from(isolate);
1213 if ((self.*getNamedMethod)(lock, kj::str(name.As<v8::String>())) != kj::none) {
1214 result = v8::Intercepted::kYes;
1215 }
1216 return {};
1217 });
1218 return result;
1219 }
1220 
1221 // We're patching hasOwnProperty to return false for interceptors in v8. Additionally always return false for getOwnPropertyDescriptor.
1222 static v8::Intercepted descriptor(
1223 v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
1224 return v8::Intercepted::kNo;
1225 }
1226 
1227 static v8::Intercepted getter(
1228 v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
1229 v8::Intercepted result = v8::Intercepted::kNo;
1230 liftKj(info, [&]() -> v8::Local<v8::Value> {
1231 auto isolate = info.GetIsolate();
1232 auto context = isolate->GetCurrentContext();
1233 auto obj = info.HolderV2();
1234 auto& wrapper = TypeWrapper::from(isolate);
1235 if (!wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {
1236 throwTypeError(isolate, kIllegalInvocation);
1237 }
1238 auto& self = extractInternalPointer<T, false>(context, obj);
1239 auto& lock = Lock::from(isolate);
1240 KJ_IF_SOME(value, (self.*getNamedMethod)(lock, kj::str(name.As<v8::String>()))) {
1241 result = v8::Intercepted::kYes;
1242 return wrapper.wrap(lock, context, obj, kj::fwd<Ret>(value));
1243 } else {
1244 // Return an empty handle to indicate the member doesn't exist.
1245 return {};
1246 }
1247 });
1248 return result;
1249 }
1250};
1251 
1252// ======================================================================================
1253 
1254// Used by the JSG_METHOD macro to register a method on a resource type.
1255template <typename TypeWrapper, typename Self, bool isContext>
1256struct ResourceTypeBuilder {
1257 ResourceTypeBuilder(TypeWrapper& typeWrapper,
1258 v8::Isolate* isolate,
1259 v8::Local<v8::FunctionTemplate> constructor,
1260 v8::Local<v8::ObjectTemplate> instance,
1261 v8::Local<v8::ObjectTemplate> prototype,
1262 v8::Local<v8::Signature> signature)
1263 : typeWrapper(typeWrapper),
1264 isolate(isolate),
1265 constructor(constructor),
1266 instance(instance),
1267 prototype(prototype),
1268 signature(signature) {
1269 // Mark the prototype as belonging to a resource type. Calling `util.inspect()` will see this
1270 // symbol and trigger custom inspect handling. This value of this symbol property maps names
1271 // names of internal pseudo-properties to symbol-keyed-getters for accessing them, or false if
1272 // the property is unimplemented.
1273 // See `JSG_INSPECT_PROPERTY` for more details.
1274 auto symbol = v8::Symbol::ForApi(isolate, v8StrIntern(isolate, "kResourceTypeInspect"_kj));
1275 inspectProperties = v8::ObjectTemplate::New(isolate);
1276 prototype->Set(symbol, inspectProperties,
1277 static_cast<v8::PropertyAttribute>(
1278 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));
1279 }
1280 
1281 template <typename Type, typename GetNamedMethod, GetNamedMethod getNamedMethod>
1282 inline void registerWildcardProperty() {
1283 auto& resourceWrapper = static_cast<ResourceWrapper<TypeWrapper, Type>&>(typeWrapper);
1284 KJ_ASSERT(
1285 resourceWrapper.wildcardHandler == kj::none, "only one wildcard per instance supported");
1286 resourceWrapper.wildcardHandler =
1287 WildcardPropertyCallbacks<TypeWrapper, Type, GetNamedMethod, getNamedMethod>{};
1288 }
1289 
1290 template <typename Type>
1291 inline void registerInherit() {
1292 constructor->Inherit(
1293 typeWrapper.template getTemplate<isContext>(isolate, static_cast<Type*>(nullptr)));
1294 // Propagate wildcard proxy to children. It's a data property, so it should be propagated, but v8 only handles normal data properties.
1295 auto& parentWrapper = static_cast<ResourceWrapper<TypeWrapper, Type>&>(typeWrapper);
1296 if (parentWrapper.wildcardHandler != kj::none) {
1297 auto& selfWrapper = static_cast<ResourceWrapper<TypeWrapper, Self>&>(typeWrapper);
1298 KJ_ASSERT(
1299 selfWrapper.wildcardHandler == kj::none, "only one wildcard per instance supported");
1300 selfWrapper.wildcardHandler = parentWrapper.wildcardHandler;
1301 }
1302 }
1303 
1304 template <const char* name>
1305 inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) {
1306 auto intrinsicPrototype = v8::FunctionTemplate::New(isolate);
1307 intrinsicPrototype->RemovePrototype();
1308 auto prototypeString = ::workerd::jsg::v8StrIntern(isolate, "prototype");
1309 intrinsicPrototype->SetIntrinsicDataProperty(prototypeString, intrinsic);
1310 constructor->Inherit(intrinsicPrototype);
1311 }
1312 
1313 template <typename Method, Method method>
1314 inline void registerCallable() {
1315 // Note that we set the call handler on the instance and not the prototype.
1316 // TODO(cleanup): Specifying the name (for error messages) as "(called as function)" is a bit
1317 // hacky but it's hard to do better while reusing `MethodCallback`.
1318 static const char NAME[] = "(called as function)";
1319 instance->SetCallAsFunctionHandler(&MethodCallback<TypeWrapper, NAME, isContext, Self, Method,
1320 method, ArgumentIndexes<Method>>::callback);
1321 }
1322 
1323 template <const char* name, auto method>
1324 inline void registerMethod() {
1325 // Per Web IDL, function .length = number of required arguments.
1326 constexpr int specLength = requiredArgumentCount<TypeWrapper, decltype(method)>;
1327 const int length = getSpecCompliantPropertyAttributes(isolate) ? specLength : 0;
1328 
1329 if constexpr (isFastApiCompatible<decltype(method)>) {
1330 if (typeWrapper.isFastApiEnabled()) {
1331 auto cFunction = v8::CFunction::Make(MethodCallback<TypeWrapper, name, isContext, Self,
1332 decltype(method), method, ArgumentIndexes<decltype(method)>>::template fastCallback<>);
1333 auto functionTemplate = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate,
1334 &MethodCallback<TypeWrapper, name, isContext, Self, decltype(method), method,
1335 ArgumentIndexes<decltype(method)>>::callback,
1336 v8::Local<v8::Value>(), signature, length, v8::ConstructorBehavior::kThrow,
1337 v8::SideEffectType::kHasSideEffect, {&cFunction, 1});
1338 
1339 prototype->Set(isolate, name, functionTemplate);
1340 return;
1341 }
1342 }
1343 
1344 prototype->Set(isolate, name,
1345 v8::FunctionTemplate::New(isolate,
1346 &MethodCallback<TypeWrapper, name, isContext, Self, decltype(method), method,
1347 ArgumentIndexes<decltype(method)>>::callback,
1348 v8::Local<v8::Value>(), signature, length, v8::ConstructorBehavior::kThrow));
1349 }
1350 
1351 template <const char* name, typename Method, Method method>
1352 inline void registerStaticMethod() {
1353 // Per Web IDL, function .length = number of required arguments.
1354 constexpr int specLength = requiredArgumentCount<TypeWrapper, Method>;
1355 const int length = getSpecCompliantPropertyAttributes(isolate) ? specLength : 0;
1356 
1357 if constexpr (isFastApiCompatible<Method>) {
1358 if (typeWrapper.isFastApiEnabled()) {
1359 auto cFunction = v8::CFunction::Make(StaticMethodCallback<TypeWrapper, name, Self, Method,
1360 method, ArgumentIndexes<Method>>::template fastCallback<>);
1361 
1362 // Create a function template with both slow and fast paths
1363 // Notably, we specify an empty signature because a static method invocation will have no holder
1364 // object.
1365 auto functionTemplate = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate,
1366 &StaticMethodCallback<TypeWrapper, name, Self, Method, method,
1367 ArgumentIndexes<Method>>::callback,
1368 v8::Local<v8::Value>(), v8::Local<v8::Signature>(), length,
1369 v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect, {&cFunction, 1});
1370 functionTemplate->RemovePrototype();
1371 constructor->Set(v8StrIntern(isolate, name), functionTemplate);
1372 return;
1373 }
1374 }
1375 
1376 // Notably, we specify an empty signature because a static method invocation will have no holder
1377 // object.
1378 auto functionTemplate = v8::FunctionTemplate::New(isolate,
1379 &StaticMethodCallback<TypeWrapper, name, Self, Method, method,
1380 ArgumentIndexes<Method>>::callback,
1381 v8::Local<v8::Value>(), v8::Local<v8::Signature>(), length,
1382 v8::ConstructorBehavior::kThrow);
1383 functionTemplate->RemovePrototype();
1384 constructor->Set(v8StrIntern(isolate, name), functionTemplate);
1385 }
1386 
1387 template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>
1388 inline void registerInstanceProperty() {
1389 auto v8Name = v8StrIntern(isolate, name);
1390 
1391 using Gcb = GetterCallback<TypeWrapper, name, Getter, getter, isContext>;
1392 if (!Gcb::enumerable) {
1393 // Mark as unimplemented if `Gcb::enumerable` is `false`. This is only the case when `Getter`
1394 // returns `Unimplemented`.
1395 inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);
1396 }
1397 
1398 instance->SetNativeDataProperty(v8Name, Gcb::callback,
1399 &SetterCallback<TypeWrapper, name, Setter, setter, isContext>::callback,
1400 v8::Local<v8::Value>(),
1401 Gcb::enumerable ? v8::PropertyAttribute::None : v8::PropertyAttribute::DontEnum);
1402 }
1403 
1404 template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>
1405 inline void registerPrototypeProperty() {
1406 auto v8Name = v8StrIntern(isolate, name);
1407 v8::Local<v8::FunctionTemplate> getterFn;
1408 v8::Local<v8::FunctionTemplate> setterFn;
1409 
1410 using Gcb = PropertyGetterCallback<TypeWrapper, name, Getter, getter, isContext>;
1411 using Scb = PropertySetterCallback<TypeWrapper, name, Setter, setter, isContext>;
1412 if (!Gcb::enumerable) {
1413 inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);
1414 }
1415 
1416 const bool specCompliant = getSpecCompliantPropertyAttributes(isolate);
1417 
1418 bool useSlowApi = true;
1419 if constexpr (isFastApiCompatible<Getter> && isFastApiCompatible<Setter>) {
1420 if (typeWrapper.isFastApiEnabled()) {
1421 auto getterCFunction = v8::CFunction::Make(Gcb::template fastCallback<>);
1422 getterFn = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate, &Gcb::callback,
1423 v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow,
1424 v8::SideEffectType::kHasSideEffect, {&getterCFunction, 1});
1425 
1426 auto setterCFunction = v8::CFunction::Make(Scb::template fastCallback<>);
1427 setterFn = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate, &Scb::callback,
1428 v8::Local<v8::Value>(), signature, specCompliant ? 1 : 0,
1429 v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect,
1430 {&setterCFunction, 1});
1431 
1432 useSlowApi = false;
1433 }
1434 }
1435 
1436 if (useSlowApi) {
1437 // Note that we cannot use `SetNativeDataProperty(...)` here the way we do with other
1438 // properties because it will not properly handle the prototype chain when it comes
1439 // to using setters... which is annoying. It means we end up having to use FunctionTemplates
1440 // instead of the more convenient property callbacks.
1441 if (specCompliant) {
1442 // Per Web IDL, getter .length = 0; setter .length = 1.
1443 getterFn = v8::FunctionTemplate::New(
1444 isolate, Gcb::callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0);
1445 setterFn = v8::FunctionTemplate::New(
1446 isolate, &Scb::callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 1);
1447 } else {
1448 getterFn = v8::FunctionTemplate::New(isolate, Gcb::callback);
1449 setterFn = v8::FunctionTemplate::New(isolate, &Scb::callback);
1450 }
1451 }
1452 
1453 if (specCompliant) {
1454 // Per Web IDL, getter .name = "get <name>", setter .name = "set <name>".
1455 getterFn->SetClassName(v8Str(isolate, kj::str("get ", name)));
1456 setterFn->SetClassName(v8Str(isolate, kj::str("set ", name)));
1457 }
1458 
1459 prototype->SetAccessorProperty(v8Name, getterFn, setterFn,
1460 Gcb::enumerable ? v8::PropertyAttribute::None : v8::PropertyAttribute::DontEnum);
1461 }
1462 
1463 template <const char* name, typename Getter, Getter getter>
1464 inline void registerReadonlyInstanceProperty() {
1465 auto v8Name = v8StrIntern(isolate, name);
1466 
1467 using Gcb = GetterCallback<TypeWrapper, name, Getter, getter, isContext>;
1468 if (!Gcb::enumerable) {
1469 inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);
1470 }
1471 
1472 instance->SetNativeDataProperty(v8Name, &Gcb::callback, nullptr, v8::Local<v8::Value>(),
1473 Gcb::enumerable ? v8::PropertyAttribute::ReadOnly
1474 : static_cast<v8::PropertyAttribute>(
1475 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));
1476 }
1477 
1478 template <typename T>
1479 inline void registerReadonlyInstanceProperty(kj::StringPtr name, T value) {
1480 auto v8Name = v8StrIntern(isolate, name);
1481 auto v8Value = typeWrapper.wrap(isolate, kj::none, kj::mv(value));
1482 instance->Set(v8Name, v8Value, v8::PropertyAttribute::ReadOnly);
1483 }
1484 
1485 template <const char* name, typename Getter, Getter getter>
1486 inline void registerReadonlyPrototypeProperty() {
1487 auto v8Name = v8StrIntern(isolate, name);
1488 
1489 using Gcb = PropertyGetterCallback<TypeWrapper, name, Getter, getter, isContext>;
1490 if (!Gcb::enumerable) {
1491 inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);
1492 }
1493 v8::Local<v8::FunctionTemplate> getterFn;
1494 if (getSpecCompliantPropertyAttributes(isolate)) {
1495 // Per Web IDL, getter .name = "get <name>", .length = 0.
1496 getterFn = v8::FunctionTemplate::New(
1497 isolate, Gcb::callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0);
1498 getterFn->SetClassName(v8Str(isolate, kj::str("get ", name)));
1499 } else {
1500 getterFn = v8::FunctionTemplate::New(isolate, Gcb::callback);
1501 }
1502 
1503 prototype->SetAccessorProperty(v8Name, getterFn, {},
1504 Gcb::enumerable ? v8::PropertyAttribute::ReadOnly
1505 : static_cast<v8::PropertyAttribute>(
1506 v8::PropertyAttribute::DontEnum | v8::PropertyAttribute::ReadOnly));
1507 }
1508 
1509 template <const char* name, typename Getter, Getter getter, bool readOnly>
1510 inline void registerLazyInstanceProperty() {
1511 auto v8Name = v8StrIntern(isolate, name);
1512 
1513 using Gcb = GetterCallback<TypeWrapper, name, Getter, getter, isContext>;
1514 if (!Gcb::enumerable) {
1515 inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);
1516 }
1517 
1518 v8::PropertyAttribute attributes =
1519 Gcb::enumerable ? v8::PropertyAttribute::None : v8::PropertyAttribute::DontEnum;
1520 if (readOnly) {
1521 attributes = static_cast<v8::PropertyAttribute>(attributes | v8::PropertyAttribute::ReadOnly);
1522 }
1523 instance->SetLazyDataProperty(v8Name, &Gcb::callback, v8::Local<v8::Value>(), attributes);
1524 }
1525 
1526 template <const char* name, typename Getter, Getter getter>
1527 inline void registerInspectProperty() {
1528 using Gcb = PropertyGetterCallback<TypeWrapper, name, Getter, getter, isContext>;
1529 
1530 auto v8Name = v8StrIntern(isolate, name);
1531 
1532 // Create a new unique symbol so this property can only be accessed through `util.inspect()`
1533 auto symbol = v8::Symbol::New(isolate, v8Name);
1534 inspectProperties->Set(v8Name, symbol, v8::PropertyAttribute::ReadOnly);
1535 
1536 auto getterFn = v8::FunctionTemplate::New(isolate, &Gcb::callback);
1537 prototype->SetAccessorProperty(symbol, getterFn, {},
1538 static_cast<v8::PropertyAttribute>(
1539 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));
1540 }
1541 
1542 template <const char* name, typename T>
1543 inline void registerStaticConstant(T value) {
1544 // The main difference between this and a read-only property is that a static constant has no
1545 // getter but is simply a primitive value set at constructor creation time.
1546 
1547 auto v8Name = v8StrIntern(isolate, name);
1548 auto v8Value = typeWrapper.wrap(isolate, kj::none, kj::mv(value));
1549 
1550 // Per Web IDL, constants are {writable: false, enumerable: true, configurable: false}.
1551 const auto attrs = getSpecCompliantPropertyAttributes(isolate)
1552 ? static_cast<v8::PropertyAttribute>(
1553 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontDelete)
1554 : v8::PropertyAttribute::ReadOnly;
1555 constructor->Set(v8Name, v8Value, attrs);
1556 constructor->PrototypeTemplate()->Set(v8Name, v8Value, attrs);
1557 }
1558 
1559 template <const char* name, typename Getter, Getter getter>
1560 inline void registerStaticProperty() {
1561 constructor->SetNativeDataProperty(v8StrIntern(isolate, name),
1562 &StaticPropertyCallback<TypeWrapper, name, Self, Getter, getter>::callback, nullptr,
1563 v8::Local<v8::Value>(), v8::PropertyAttribute::ReadOnly);
1564 }
1565 
1566 template <const char* name, typename Method, Method method>
1567 inline void registerIterable() {
1568 prototype->Set(v8::Symbol::GetIterator(isolate),
1569 v8::FunctionTemplate::New(isolate,
1570 &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,
1571 ArgumentIndexes<Method>>::callback,
1572 v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),
1573 v8::PropertyAttribute::DontEnum);
1574 }
1575 
1576 template <const char* name, typename Method, Method method>
1577 inline void registerAsyncIterable() {
1578 prototype->Set(v8::Symbol::GetAsyncIterator(isolate),
1579 v8::FunctionTemplate::New(isolate,
1580 &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,
1581 ArgumentIndexes<Method>>::callback,
1582 v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),
1583 v8::PropertyAttribute::DontEnum);
1584 }
1585 
1586 template <const char* name, typename Method, Method method>
1587 inline void registerDispose() {
1588 prototype->Set(v8::Symbol::GetDispose(isolate),
1589 v8::FunctionTemplate::New(isolate,
1590 &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,
1591 ArgumentIndexes<Method>>::callback,
1592 v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),
1593 v8::PropertyAttribute::DontEnum);
1594 }
1595 
1596 template <const char* name, typename Method, Method method>
1597 inline void registerAsyncDispose() {
1598 prototype->Set(v8::Symbol::GetAsyncDispose(isolate),
1599 v8::FunctionTemplate::New(isolate,
1600 &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,
1601 ArgumentIndexes<Method>>::callback,
1602 v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),
1603 v8::PropertyAttribute::DontEnum);
1604 }
1605 
1606 template <typename Type, const char* name>
1607 inline void registerNestedType() {
1608 static_assert(Type::JSG_KIND == ::workerd::jsg::JsgKind::RESOURCE,
1609 "Type is not a resource type, and therefore cannot be declared nested");
1610 
1611 constexpr auto hasGetTemplate =
1612 ::workerd::jsg::isDetected<::workerd::jsg::HasGetTemplateOverload, decltype(typeWrapper),
1613 Type>();
1614 static_assert(
1615 hasGetTemplate, "Type must be listed in JSG_DECLARE_ISOLATE_TYPE to be declared nested.");
1616 
1617 auto tmpl = typeWrapper.getTemplate(isolate, static_cast<Type*>(nullptr));
1618 
1619 // Always install on the prototype so that types declared on parent classes are
1620 // inherited through FunctionTemplate::Inherit().
1621 prototype->Set(isolate, name, tmpl);
1622 
1623 if constexpr (isContext) {
1624 if (getSpecCompliantPropertyAttributes(isolate)) {
1625 // Per Web IDL, [Exposed] interface objects must be own properties of the global
1626 // object with { writable: true, enumerable: false, configurable: true }. When
1627 // building the global scope (isContext == true), also install on the instance
1628 // template so that they become own properties of globalThis.
1629 //
1630 // NOTE: V8's FunctionTemplate::Inherit() does NOT propagate instance-template
1631 // properties to child types. This means nested types declared on a parent
1632 // context type (e.g. WorkerGlobalScope) won't automatically appear as own
1633 // properties of the child (e.g. ServiceWorkerGlobalScope). To keep things
1634 // simple, the leaf context type must declare all its nested types directly -
1635 // don't rely on inheriting nested types from parent context classes.
1636 instance->Set(isolate, name, tmpl, v8::PropertyAttribute::DontEnum);
1637 }
1638 }
1639 }
1640 
1641 inline void registerTypeScriptRoot() { /* only needed for RTTI */ }
1642 
1643 template <const char* tsOverride>
1644 inline void registerTypeScriptOverride() { /* only needed for RTTI */ }
1645 
1646 template <const char* tsDefine>
1647 inline void registerTypeScriptDefine() { /* only needed for RTTI */ }
1648 
1649 inline void registerJsBundle(Bundle::Reader bundle) { /* handled at the second stage */ }
1650 
1651 private:
1652 TypeWrapper& typeWrapper;
1653 v8::Isolate* isolate;
1654 v8::Local<v8::FunctionTemplate> constructor;
1655 v8::Local<v8::ObjectTemplate> instance;
1656 v8::Local<v8::ObjectTemplate> prototype;
1657 v8::Local<v8::ObjectTemplate> inspectProperties;
1658 v8::Local<v8::Signature> signature;
1659};
1660 
1661// initializes javascript parts of a context
1662template <typename TypeWrapper, typename Self>
1663struct JsSetup {
1664 KJ_DISALLOW_COPY_AND_MOVE(JsSetup);
1665 
1666 JsSetup(jsg::Lock& js, v8::Local<v8::Context> context): js(js), context(context) {}
1667 
1668 inline void registerJsBundle(Bundle::Reader bundle) {
1669 ModuleRegistryImpl<TypeWrapper>::from(js)->addBuiltinBundle(bundle);
1670 }
1671 
1672 // the rest of the callbacks are empty
1673 
1674 template <typename Type>
1675 inline void registerInherit() {}
1676 
1677 template <const char* name>
1678 inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) {}
1679 
1680 template <typename Method, Method method>
1681 inline void registerCallable() {}
1682 
1683 template <const char* name, auto method>
1684 inline void registerMethod() {}
1685 
1686 template <const char* name, typename Method, Method method>
1687 inline void registerStaticMethod() {}
1688 
1689 template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>
1690 inline void registerInstanceProperty() {}
1691 
1692 template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>
1693 inline void registerPrototypeProperty() {}
1694 
1695 template <const char* name, typename Getter, Getter getter>
1696 inline void registerReadonlyInstanceProperty() {}
1697 
1698 template <typename T>
1699 inline void registerReadonlyInstanceProperty(kj::StringPtr name, T value) {}
1700 
1701 template <const char* name, typename Getter, Getter getter>
1702 inline void registerReadonlyPrototypeProperty() {}
1703 
1704 template <const char* name, typename Getter, Getter getter, bool readOnly>
1705 inline void registerLazyInstanceProperty() {}
1706 
1707 template <const char* name, typename Getter, Getter getter>
1708 inline void registerInspectProperty() {}
1709 
1710 template <const char* name, typename T>
1711 inline void registerStaticConstant(T value) {}
1712 
1713 template <const char* name, typename Getter, Getter getter>
1714 inline void registerStaticProperty() {}
1715 
1716 template <const char* name, typename Method, Method method>
1717 inline void registerIterable() {}
1718 
1719 template <const char* name, typename Method, Method method>
1720 inline void registerAsyncIterable() {}
1721 
1722 template <typename Type, const char* name>
1723 inline void registerNestedType() {}
1724 
1725 inline void registerTypeScriptRoot() {}
1726 
1727 template <const char* tsOverride>
1728 inline void registerTypeScriptOverride() {}
1729 
1730 template <const char* tsDefine>
1731 inline void registerTypeScriptDefine() {}
1732 
1733 private:
1734 jsg::Lock& js;
1735 v8::Local<v8::Context> context;
1736};
1737 
1738class ModuleRegistryBase {
1739 public:
1740 virtual ~ModuleRegistryBase() noexcept(false) {}
1741 virtual kj::Own<void> attachToIsolate(
1742 Lock& js, const CompilationObserver& observer) const KJ_WARN_UNUSED_RESULT = 0;
1743 
1744 virtual const capnp::SchemaLoader& getSchemaLoader() const = 0;
1745};
1746 
1747struct NewContextOptions {
1748 kj::Maybe<const ModuleRegistryBase&> newModuleRegistry = kj::none;
1749 kj::Maybe<const capnp::SchemaLoader&> schemaLoader = kj::none;
1750 bool enableWeakRef = false;
1751};
1752 
1753// TypeWrapper mixin for resource types (application-defined C++ classes declared with a
1754// JSG_RESOURCE_TYPE block).
1755template <typename TypeWrapper, typename T>
1756class ResourceWrapper {
1757 public:
1758 // If the JSG_RESOURCE_TYPE macro declared a configuration parameter, then `Configuration` will
1759 // be that type, otherwise NullConfiguration which accepts any configuration.
1760 using Configuration = DetectedOr<NullConfiguration, GetConfiguration, T>;
1761 
1762 template <typename MetaConfiguration>
1763 ResourceWrapper(MetaConfiguration&& configuration)
1764 : configuration(kj::fwd<MetaConfiguration>(configuration)) {}
1765 
1766 inline void initTypeWrapper() {
1767 TypeWrapper& wrapper = static_cast<TypeWrapper&>(*this);
1768 wrapper.resourceTypeMap.insert(typeid(T),
1769 [](TypeWrapper& wrapper,
1770 v8::Isolate* isolate) -> DynamicResourceTypeMap<TypeWrapper>::DynamicTypeInfo {
1771 kj::Maybe<typename DynamicResourceTypeMap<TypeWrapper>::ReflectionInitializer&> rinit;
1772 if constexpr (T::jsgHasReflection) {
1773 rinit = [](jsg::Object& object, TypeWrapper& wrapper) {
1774 static_cast<T&>(object).jsgInitReflection(wrapper);
1775 };
1776 }
1777 return {wrapper.getTemplate(isolate, static_cast<T*>(nullptr)), rinit};
1778 });
1779 
1780 if constexpr (static_cast<uint>(T::jsgSerializeTag) !=
1781 static_cast<uint>(T::jsgSuper::jsgSerializeTag)) {
1782 // This type is declared JSG_SERIALIZABLE.
1783 // HACK: The type of `serializer` should be `Serializer&`, not `auto&`, but Clang complains
1784 // about the `writeRawUint32()` call being made on an incomplete type if `ser.h` hasn't been
1785 // included -- *even if* T doesn't declare itself serializable and therefore this branch
1786 // should not be compiled at all! Unsure if this is a compiler bug.
1787 wrapper.serializerMap.insert(
1788 typeid(T), [](TypeWrapper& wrapper, Lock& js, jsg::Object& instance, auto& serializer) {
1789 serializer.writeRawUint32(static_cast<uint>(T::jsgSerializeTag));
1790 SerializeInvoker<TypeWrapper, T>::call(wrapper, static_cast<T&>(instance), js, serializer);
1791 });
1792 
1793 if constexpr (!T::jsgSerializeOneway) {
1794 typename TypeWrapper::DeserializeFunc* deserializeFunc =
1795 [](TypeWrapper& wrapper, Lock& js, uint tag, Deserializer& deserializer) {
1796 // Cast the tag to the application's preferred tag type.
1797 auto typedTag = static_cast<decltype(T::jsgSerializeTag)>(tag);
1798 return DeserializeInvoker<TypeWrapper, T>::call(wrapper, js, typedTag, deserializer);
1799 };
1800 
1801 // We make duplicates here fatal because it's really hard to debug exceptions thrown during
1802 // isolate startup and frankly this is pretty fatal for the runtime anyway.
1803 auto reportDuplicate = [](auto&, auto&&) noexcept {
1804 KJ_FAIL_REQUIRE("JSG_SERIALIZABLE declaration tried to register a duplicate type tag");
1805 };
1806 
1807 wrapper.deserializerMap.upsert(
1808 static_cast<uint>(T::jsgSerializeTag), deserializeFunc, reportDuplicate);
1809 for (auto& oldTag: T::jsgSerializeOldTags) {
1810 wrapper.deserializerMap.upsert(
1811 static_cast<uint>(oldTag), deserializeFunc, reportDuplicate);
1812 }
1813 }
1814 }
1815 }
1816 
1817 static constexpr const std::type_info& getName(T*) {
1818 return typeid(T);
1819 }
1820 
1821 // `Ref<T>` is NOT a resource type -- TypeHandler<Ref<T>> should use the value-oriented
1822 // implementation.
1823 static constexpr const std::type_info& getName(Ref<T>*) {
1824 return typeid(T);
1825 }
1826 
1827 v8::Local<v8::Object> wrap(Lock& js,
1828 v8::Local<v8::Context> context,
1829 kj::Maybe<v8::Local<v8::Object>> creator,
1830 Ref<T>&& value) {
1831 // Wrap a value of type T.
1832 
1833 auto isolate = js.v8Isolate;
1834 
1835 KJ_IF_SOME(h, value->tryGetHandle(isolate)) {
1836 return h;
1837 } else {
1838 auto& type = typeid(*value);
1839 auto& wrapper = static_cast<TypeWrapper&>(*this);
1840 // Check if *value is actually a subclass of T. If so, we need to dynamically look up the
1841 // correct wrapper. But in the common case that it's exactly T, we can skip the lookup.
1842 v8::Local<v8::FunctionTemplate> tmpl;
1843 if (type == typeid(T)) {
1844 tmpl = getTemplate(isolate, nullptr);
1845 if constexpr (T::jsgHasReflection) {
1846 value->jsgInitReflection(wrapper);
1847 }
1848 } else {
1849 auto info = wrapper.getDynamicTypeInfo(isolate, type);
1850 tmpl = info.tmpl;
1851 KJ_IF_SOME(i, info.reflectionInitializer) {
1852 i(*value, wrapper);
1853 }
1854 }
1855 v8::Local<v8::Object> object = check(tmpl->InstanceTemplate()->NewInstance(context));
1856 value.attachWrapper(isolate, object);
1857 return object;
1858 }
1859 }
1860 
1861 template <typename... Args>
1862 JsContext<T> newContext(jsg::Lock& js,
1863 jsg::NewContextOptions options,
1864 CompilationObserver& compilationObserver,
1865 T*,
1866 Args&&... args) {
1867 // Construct an instance of this type to be used as the Javascript global object, creating
1868 // a new JavaScript context. Unfortunately, we have to do some things differently in this
1869 // case, because of quirks in how V8 handles the global object. There appear to be bugs
1870 // that prevent it from being treated uniformly for callback purposes. See:
1871 //
1872 // https://groups.google.com/d/msg/v8-users/RET5b3KOa5E/3EvpRBzwAQAJ
1873 //
1874 // Because of this, our entire type registration system threads through an extra template
1875 // parameter `bool isContext`. When the application decides to create a context using this
1876 // type as the global, we instantiate this separate branch specifically for that type.
1877 // Fortunately, for types that are never used as the global object, we never have to
1878 // instantiate the `isContext = true` branch.
1879 
1880 auto isolate = js.v8Isolate;
1881 auto tmpl = getTemplate<true>(isolate, nullptr)->InstanceTemplate();
1882 v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, tmpl);
1883 auto global = context->Global();
1884 
1885 auto ptr = js.alloc<T>(kj::fwd<Args>(args)...);
1886 if constexpr (T::jsgHasReflection) {
1887 ptr->jsgInitReflection(static_cast<TypeWrapper&>(*this));
1888 }
1889 ptr.attachWrapper(isolate, global);
1890 
1891 // Disable `eval(code)` and `new Function(code)`. (Actually, setting this to `false` really
1892 // means "call the callback registered on the isolate to check" -- setting it to `true` means
1893 // "skip callback and just allow".)
1894 context->AllowCodeGenerationFromStrings(false);
1895 
1896 if (!options.enableWeakRef) {
1897 check(global->Delete(context, v8StrIntern(isolate, "WeakRef"_kj)));
1898 check(global->Delete(context, v8StrIntern(isolate, "FinalizationRegistry"_kj)));
1899 }
1900 
1901 // Store a pointer to this object in slot 1, to be extracted in callbacks.
1902 jsg::setAlignedPointerInEmbedderData(
1903 context, jsg::ContextPointerSlot::GLOBAL_WRAPPER, ptr.get());
1904 // We need to set the highest used index in every context we create to be a nullptr
1905 // This is because we might later on call GetAlignedPointerFromEmbedderData which fails with
1906 // a fatal error if the array is smaller than the given index.
1907 jsg::setAlignedPointerInEmbedderData(
1908 context, jsg::ContextPointerSlot::MAX_POINTER_SLOT, nullptr);
1909 
1910 // (Note: V8 docs say: "Note that index 0 currently has a special meaning for Chrome's
1911 // debugger." We aren't Chrome, but it does appear that some versions of V8 will mess with
1912 // slot 0, causing us to segfault if we try to put anything there. So we avoid it and use slot
1913 // 1, which seems to work just fine.)
1914 
1915 // Expose the type of the global scope in the global scope itself.
1916 exposeGlobalScopeType(isolate, context);
1917 
1918 ptr->setModuleRegistryBackingOwner(([&]() -> kj::Own<void> {
1919 KJ_IF_SOME(newModuleRegistry, options.newModuleRegistry) {
1920 return JSG_WITHIN_CONTEXT_SCOPE(js, context, [&](jsg::Lock& js) {
1921 // The context must be current for attachToIsolate to succeed.
1922 return newModuleRegistry.attachToIsolate(js, compilationObserver);
1923 });
1924 } else {
1925 return ModuleRegistryImpl<TypeWrapper>::install(isolate, context, compilationObserver);
1926 }
1927 })());
1928 
1929 KJ_IF_SOME(loader, options.schemaLoader) {
1930 ptr->setSchemaLoader(loader);
1931 }
1932 
1933 return JSG_WITHIN_CONTEXT_SCOPE(js, context, [&](jsg::Lock& js) {
1934 setupJavascript(js);
1935 return JsContext<T>(context, kj::mv(ptr));
1936 });
1937 }
1938 
1939 kj::Maybe<T&> tryUnwrap(Lock& js,
1940 v8::Local<v8::Context> context,
1941 v8::Local<v8::Value> handle,
1942 T*,
1943 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1944 // Try to unwrap a value of type T.
1945 
1946 if (handle->IsObject()) {
1947 v8::Local<v8::Object> instance =
1948 v8::Local<v8::Object>::Cast(handle)->FindInstanceInPrototypeChain(
1949 getTemplate(js.v8Isolate, nullptr));
1950 if (!instance.IsEmpty()) {
1951 return extractInternalPointer<T, false>(context, instance);
1952 }
1953 }
1954 
1955 return kj::none;
1956 }
1957 
1958 kj::Maybe<Ref<T>> tryUnwrap(Lock& js,
1959 v8::Local<v8::Context> context,
1960 v8::Local<v8::Value> handle,
1961 Ref<T>*,
1962 kj::Maybe<v8::Local<v8::Object>> parentObject) {
1963 // Try to unwrap a value of type Ref<T>.
1964 
1965 KJ_IF_SOME(p, tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)) {
1966 return Ref<T>(kj::addRef(p));
1967 } else {
1968 return kj::none;
1969 }
1970 }
1971 
1972 template <bool isContext = false>
1973 v8::Local<v8::FunctionTemplate> getTemplate(v8::Isolate* isolate, T*) {
1974 v8::Global<v8::FunctionTemplate>& slot = isContext ? contextConstructor : memoizedConstructor;
1975 if (slot.IsEmpty()) {
1976 // Construct lazily.
1977 v8::EscapableHandleScope scope(isolate);
1978 
1979 v8::Local<v8::FunctionTemplate> constructor;
1980 if constexpr (!isContext && HasConstructorMethod<T>) {
1981 // Per Web IDL, constructor .length = number of required arguments.
1982 constexpr int specCtorLength = requiredArgumentCount<TypeWrapper, decltype(T::constructor)>;
1983 const int ctorLength = getSpecCompliantPropertyAttributes(isolate) ? specCtorLength : 0;
1984 constructor =
1985 v8::FunctionTemplate::New(isolate, &ConstructorCallback<TypeWrapper, T>::callback,
1986 v8::Local<v8::Value>(), v8::Local<v8::Signature>(), ctorLength);
1987 } else {
1988 constructor = v8::FunctionTemplate::New(isolate, &throwIllegalConstructor);
1989 }
1990 
1991 auto prototype = constructor->PrototypeTemplate();
1992 
1993 // Signatures protect our methods from being invoked with the wrong `this`.
1994 auto signature = v8::Signature::New(isolate, constructor);
1995 
1996 auto instance = constructor->InstanceTemplate();
1997 
1998 instance->SetInternalFieldCount(Wrappable::INTERNAL_FIELD_COUNT);
1999 
2000 auto classname = v8StrIntern(isolate, typeName(typeid(T)));
2001 
2002 if (getShouldSetToStringTag(isolate)) {
2003 prototype->Set(
2004 v8::Symbol::GetToStringTag(isolate), classname, v8::PropertyAttribute::DontEnum);
2005 }
2006 
2007 // Previously, miniflare would use the lack of a Symbol.toStringTag on a class to
2008 // detect a type that came from the runtime. That's obviously a bit problematic because
2009 // Symbol.toStringTag is required for full compliance on standard web platform APIs.
2010 // To help use cases where it is necessary to detect if a class is a runtime class, we
2011 // will add a special symbol to the prototype of the class to indicate. Note that
2012 // because this uses the global symbol registry user code could still mark their own
2013 // classes with this symbol but that's unlikely to be a problem in any practical case.
2014 auto internalMarker =
2015 v8::Symbol::For(isolate, v8StrIntern(isolate, "cloudflare:internal-class"));
2016 prototype->Set(internalMarker, internalMarker,
2017 static_cast<v8::PropertyAttribute>(v8::PropertyAttribute::DontEnum |
2018 v8::PropertyAttribute::DontDelete | v8::PropertyAttribute::ReadOnly));
2019 
2020 if (getShouldSetImmutablePrototype(isolate)) {
2021 constructor->ReadOnlyPrototype();
2022 }
2023 
2024 constructor->SetClassName(classname);
2025 
2026 static_assert(kj::isSameType<typename T::jsgThis, T>(),
2027 "Name passed to JSG_RESOURCE_TYPE() must be the class's own name.");
2028 
2029 auto& typeWrapper = static_cast<TypeWrapper&>(*this);
2030 
2031 ResourceTypeBuilder<TypeWrapper, T, isContext> builder(
2032 typeWrapper, isolate, constructor, instance, prototype, signature);
2033 
2034 if constexpr (isDetected<GetConfiguration, T>()) {
2035 T::template registerMembers<decltype(builder), T>(builder, configuration);
2036 } else {
2037 T::template registerMembers<decltype(builder), T>(builder);
2038 }
2039 
2040 KJ_IF_SOME(handler, wildcardHandler) {
2041 instance->SetHandler(handler);
2042 }
2043 
2044 auto result = scope.Escape(constructor);
2045 slot.Reset(isolate, result);
2046 return result;
2047 } else {
2048 return slot.Get(isolate);
2049 }
2050 }
2051 
2052 kj::Maybe<v8::NamedPropertyHandlerConfiguration> wildcardHandler;
2053 
2054 private:
2055 Configuration configuration;
2056 v8::Global<v8::FunctionTemplate> memoizedConstructor;
2057 v8::Global<v8::FunctionTemplate> contextConstructor;
2058 
2059 void setupJavascript(jsg::Lock& js) {
2060 JsSetup<TypeWrapper, T> setup(js, js.v8Context());
2061 
2062 if constexpr (isDetected<GetConfiguration, T>()) {
2063 T::template registerMembers<decltype(setup), T>(setup, configuration);
2064 } else {
2065 T::template registerMembers<decltype(setup), T>(setup);
2066 }
2067 }
2068 
2069 template <typename, typename, typename, typename>
2070 friend struct ConstructorCallback;
2071};
2072 
2073// Like ResourceWrapper for T = jsg::Object. We need some special-casing for this type.
2074template <typename TypeWrapper>
2075class ObjectWrapper {
2076 public:
2077 static constexpr const std::type_info& getName(Object*) {
2078 return typeid(Object);
2079 }
2080 
2081 static constexpr const std::type_info& getName(Ref<Object>*) {
2082 return typeid(Object);
2083 }
2084 
2085 // Wrap a value of type T.
2086 v8::Local<v8::Object> wrap(Lock& js,
2087 v8::Local<v8::Context> context,
2088 kj::Maybe<v8::Local<v8::Object>> creator,
2089 Ref<Object>&& value) {
2090 auto isolate = js.v8Isolate;
2091 
2092 KJ_IF_SOME(h, value->tryGetHandle(isolate)) {
2093 return h;
2094 } else {
2095 auto& valueRef = *value; // avoid compiler warning about typeid(*value) having side effects
2096 auto& type = typeid(valueRef);
2097 auto& wrapper = static_cast<TypeWrapper&>(*this);
2098 
2099 // In ResourceWrapper::wrap() we check if the value is a subclass. Here, we assume it is
2100 // always a subclass, because `jsg::Object` cannot be constructed directly.
2101 auto info = wrapper.getDynamicTypeInfo(isolate, type);
2102 v8::Local<v8::FunctionTemplate> tmpl = info.tmpl;
2103 KJ_IF_SOME(i, info.reflectionInitializer) {
2104 i(*value, wrapper);
2105 }
2106 v8::Local<v8::Object> object = check(tmpl->InstanceTemplate()->NewInstance(context));
2107 value.attachWrapper(isolate, object);
2108 return object;
2109 }
2110 }
2111 
2112 // We do not support unwrapping Ref<Object>; use V8Ref<v8::Object> instead.
2113 kj::Maybe<Ref<Object>> tryUnwrap(Lock& js,
2114 v8::Local<v8::Context> context,
2115 v8::Local<v8::Value> handle,
2116 Ref<Object>*,
2117 kj::Maybe<v8::Local<v8::Object>> parentObject) = delete;
2118};
2119 
2120} // namespace workerd::jsg