Skip to content
File

Blob: src/workerd/jsg/type-wrapper.h

cpp692 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// The TypeWrapper knows how to convert a variety of types between C++ and JavaScript.
9 
10#include <workerd/jsg/buffersource.h>
11#include <workerd/jsg/dom-exception.h>
12#include <workerd/jsg/function.h>
13#include <workerd/jsg/iterator.h>
14#include <workerd/jsg/jsg.h>
15#include <workerd/jsg/jsvalue.h>
16#include <workerd/jsg/resource.h>
17#include <workerd/jsg/struct.h>
18#include <workerd/jsg/util.h>
19#include <workerd/jsg/value.h>
20#include <workerd/jsg/web-idl.h>
21#include <workerd/jsg/wrappable.h>
22 
23#include <v8-wasm.h>
24 
25namespace workerd::jsg {
26 
27// True if there is an unwrap() overload which does *not* take a v8::Value to unwrap for this
28// parameter type T. This is useful to identify types like TypeHandlers and v8::Isolate* which
29// functions can declare they accept at the end of their parameter list, but which are not created
30// from any particular JS value.
31// A concept that identifies types that can be unwrapped without needing a JS value
32template <typename TypeWrapper, typename T>
33concept ValueLessParameter =
34 requires(TypeWrapper wrapper, Lock& js, v8::Local<v8::Context> context, T* ptr) {
35 wrapper.unwrap(js, context, ptr);
36 };
37 
38// =======================================================================================
39// RequiredArgCount_ specialization — counts leading required JS-visible arguments.
40//
41// This completes the definition of requiredArgumentCount<TypeWrapper, T> declared in meta.h.
42// It lives here (rather than meta.h or web-idl.h) because it needs the ValueLessParameter
43// concept above to automatically detect ALL injected parameter types — TypeHandler<T>,
44// InjectConfiguration<T> (e.g. CompatibilityFlags::Reader), and any future valueless types.
45//
46// Arguments<T> (variadic rest args) is detected separately via isArguments<>() because it
47// does not satisfy ValueLessParameter — it consumes remaining JS arguments rather than being
48// injected by the runtime.
49//
50// Template instantiation of requiredArgumentCount happens from resource.h templates, which
51// are only instantiated after this header is fully parsed, so these specializations are
52// guaranteed to be visible at the point of use.
53namespace detail {
54 
55template <typename TypeWrapper>
56struct RequiredArgCount_<TypeWrapper, TypeList<>> {
57 static constexpr int value = 0;
58};
59 
60template <typename TypeWrapper, typename Head, typename... Tail>
61struct RequiredArgCount_<TypeWrapper, TypeList<Head, Tail...>> {
62 using D = kj::Decay<Head>;
63 static constexpr int value = (isArguments<D>() || ValueLessParameter<TypeWrapper, D>)
64 ? RequiredArgCount_<TypeWrapper, TypeList<Tail...>>::value // skip injected args
65 : (webidl::isOptional<D> ? 0 // optional arg — stop counting required
66 : 1 + RequiredArgCount_<TypeWrapper, TypeList<Tail...>>::value);
67};
68 
69} // namespace detail
70 
71// TypeWrapper mixin for V8 handles.
72//
73// This is just a trivial pass-through.
74class V8HandleWrapper {
75 public:
76 template <V8Value T>
77 static constexpr const std::type_info& getName(v8::Local<T>*) {
78 return typeid(T);
79 }
80 
81 template <V8Value T>
82 v8::Local<T> wrap(jsg::Lock& js,
83 v8::Local<v8::Context> context,
84 kj::Maybe<v8::Local<v8::Object>> creator,
85 v8::Local<T> value) {
86 return value;
87 }
88 
89 kj::Maybe<v8::Local<v8::Value>> tryUnwrap(Lock& js,
90 v8::Local<v8::Context> context,
91 v8::Local<v8::Value> handle,
92 v8::Local<v8::Value>*,
93 kj::Maybe<v8::Local<v8::Object>> parentObject) {
94 return handle;
95 }
96 
97#define JSG_FOR_EACH_V8_VALUE_SUBCLASS(f) \
98 f(ArrayBuffer) f(ArrayBufferView) f(TypedArray) f(DataView) f(Int8Array) f(Uint8Array) \
99 f(Uint8ClampedArray) f(Int16Array) f(Uint16Array) f(Int32Array) f(Uint32Array) \
100 f(Float16Array) f(Float32Array) f(Float64Array) f(Object) f(String) f(Function) \
101 f(WasmMemoryObject) f(BigInt)
102 
103 // Define a tryUnwrap() overload for each interesting subclass of v8::Value.
104#define JSG_DEFINE_TRY_UNWRAP(type) \
105 kj::Maybe<v8::Local<v8::type>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context, \
106 v8::Local<v8::Value> handle, v8::Local<v8::type>*, \
107 kj::Maybe<v8::Local<v8::Object>> parentObject) { \
108 if (handle->Is##type()) { \
109 return handle.As<v8::type>(); \
110 } \
111 return kj::none; \
112 } \
113 \
114 kj::Maybe<v8::Global<v8::type>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context, \
115 v8::Local<v8::Value> handle, v8::Global<v8::type>*, \
116 kj::Maybe<v8::Local<v8::Object>> parentObject) { \
117 if (handle->Is##type()) { \
118 return v8::Global<v8::type>(js.v8Isolate, handle.As<v8::type>()); \
119 } \
120 return kj::none; \
121 } \
122 \
123 kj::Maybe<V8Ref<v8::type>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context, \
124 v8::Local<v8::Value> handle, V8Ref<v8::type>*, \
125 kj::Maybe<v8::Local<v8::Object>> parentObject) { \
126 if (handle->Is##type()) { \
127 return V8Ref<v8::type>(js.v8Isolate, handle.As<v8::type>()); \
128 } \
129 return kj::none; \
130 } \
131 template <typename T = v8::type, typename = decltype(&T::GetIdentityHash)> \
132 kj::Maybe<HashableV8Ref<T>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context, \
133 v8::Local<v8::Value> handle, HashableV8Ref<v8::type>*, \
134 kj::Maybe<v8::Local<v8::Object>> parentObject) { \
135 if (handle->Is##type()) { \
136 return HashableV8Ref<v8::type>(js.v8Isolate, handle.As<v8::type>()); \
137 } \
138 return kj::none; \
139 }
140 
141 JSG_FOR_EACH_V8_VALUE_SUBCLASS(JSG_DEFINE_TRY_UNWRAP)
142 
143#undef JSG_DEFINE_TRY_UNWRAP
144#undef JSG_FOR_EACH_V8_VALUE_SUBCLASS
145 
146 template <V8Value T>
147 static constexpr const std::type_info& getName(v8::Global<T>*) {
148 return typeid(T);
149 }
150 
151 template <V8Value T>
152 v8::Local<T> wrap(jsg::Lock& js,
153 v8::Local<v8::Context> context,
154 kj::Maybe<v8::Local<v8::Object>> creator,
155 v8::Global<T> value) {
156 return value.Get(js.v8Isolate);
157 }
158 
159 kj::Maybe<v8::Global<v8::Value>> tryUnwrap(Lock& js,
160 v8::Local<v8::Context> context,
161 v8::Local<v8::Value> handle,
162 v8::Global<v8::Value>*,
163 kj::Maybe<v8::Local<v8::Object>> parentObject) {
164 return v8::Global<v8::Value>(js.v8Isolate, handle);
165 }
166 
167 template <V8Value T>
168 static constexpr const std::type_info& getName(V8Ref<T>*) {
169 return typeid(T);
170 }
171 
172 template <V8Value T>
173 v8::Local<T> wrap(jsg::Lock& js,
174 v8::Local<v8::Context> context,
175 kj::Maybe<v8::Local<v8::Object>> creator,
176 V8Ref<T> value) {
177 return value.getHandle(js.v8Isolate);
178 }
179 
180 kj::Maybe<V8Ref<v8::Value>> tryUnwrap(Lock& js,
181 v8::Local<v8::Context> context,
182 v8::Local<v8::Value> handle,
183 V8Ref<v8::Value>*,
184 kj::Maybe<v8::Local<v8::Object>> parentObject) {
185 return V8Ref<v8::Value>(js.v8Isolate, handle);
186 }
187};
188 
189class UnimplementedWrapper {
190 public:
191 static constexpr const std::type_info& getName(Unimplemented*) {
192 return typeid(Unimplemented);
193 }
194 
195 v8::Local<v8::Value> wrap(jsg::Lock& js,
196 v8::Local<v8::Context> context,
197 kj::Maybe<v8::Local<v8::Object>> creator,
198 Unimplemented value) = delete;
199 kj::Maybe<Unimplemented> tryUnwrap(Lock& js,
200 v8::Local<v8::Context> context,
201 v8::Local<v8::Value> handle,
202 Unimplemented*,
203 kj::Maybe<v8::Local<v8::Object>> parentObject) {
204 // Can only be `undefined`.
205 if (handle->IsUndefined()) {
206 return Unimplemented();
207 } else {
208 return kj::none;
209 }
210 }
211};
212 
213// The application can use this type to extend TypeWrapper with its own custom mixins. The
214// template `Extension` is a mixin which will be inherited by the TypeWrapper. It will be passed
215// the full TypeWrapper specialization as a type parameter. See TypeWrapper, below, for an
216// explanation of the mixin design and use of CRTP.
217//
218// Specify `TypeWrapperExtension` in the same list as your API types. Example:
219//
220// template <typename TypeWrapper>
221// class MyMixin {
222// public:
223// // ... implementation ...
224// };
225//
226// JSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyApiType1, MyApiType2,
227// jsg::TypeWrapperExtension<MyMixin>, ...)
228//
229// The extension mixin must declare the following methods:
230//
231// static constexpr const char* getName(T* dummy);
232// v8::Local<v8::Value> wrap(jsg::Lock& js, v8::Local<v8::Context> jsContext,
233// kj::Maybe<v8::Local<v8::Object>> creator,
234// T cppValue);
235// kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Context> jsContext, v8::Local<v8::Value> jsHandle,
236// T* dummy, kj::Maybe<v8::Local<v8::Object>> parentObject);
237//
238// Ref<T, v8::Context> newContext(v8::Isolate* isolate, T* dummy, Args&&... args);
239// template <bool isContext = false>
240// v8::Local<v8::FunctionTemplate> getTemplate(v8::Isolate* isolate, T*)
241//
242// Note that most mixins do not actually need the last two methods. Unfortunately, due to
243// limitation of the C++ `using` directive, we can't easily make these optional. You can,
244// however, declare them deleted, like:
245//
246// void newContext() = delete;
247// void getTemplate() = delete;
248//
249// The mixin's constructor can optionally accept a configuration value as its parameter, which
250// works the same way as the second parameter to `JSG_RESOURCE_TYPE`.
251template <template <typename TypeWrapper> typename Extension>
252class TypeWrapperExtension {
253 public:
254 static const JsgKind JSG_KIND = JsgKind::EXTENSION;
255};
256 
257// Include this type in the FFI type list to implement auto-injection of a parameter type based
258// on configuration. `Configuration` must be a type that can be constructed from the isolate's
259// meta configuration object. Wrapped functions will be able to accept `Configuration` as a
260// parameter type, and instead of being converted from a JavaScript parameter, it will instead
261// receive the isolate-global configuration.
262//
263// `Configuration` can be a reference type.
264template <typename Configuration>
265class InjectConfiguration {
266 public:
267 static const JsgKind JSG_KIND = JsgKind::EXTENSION;
268};
269 
270// Selects the appropriate mixin to support wrapping/unwrapping type T, which is one of the API
271// types passed to JSG_DECLARE_ISOLATE_TYPE() by the application.
272template <typename Self, typename T, JsgKind kind = T::JSG_KIND>
273class TypeWrapperBase;
274 
275// Specialization of TypeWrapperBase for types that have a JSG_RESOURCE_TYPE block.
276template <typename Self, typename T>
277class TypeWrapperBase<Self, T, JsgKind::RESOURCE>: public ResourceWrapper<Self, T> {
278 public:
279 template <typename MetaConfiguration>
280 TypeWrapperBase(MetaConfiguration& config): ResourceWrapper<Self, T>(config) {}
281 
282 void unwrap() = delete; // ResourceWrapper only implements tryUnwrap(), not unwrap()
283};
284 
285// Specialization of TypeWrapperBase for types that have a JSG_STRUCT block.
286template <typename Self, typename T>
287class TypeWrapperBase<Self, T, JsgKind::STRUCT>
288 : public StructWrapper<Self, T, typename T::template JsgFieldWrappers<Self, T>> {
289 public:
290 template <typename MetaConfiguration>
291 TypeWrapperBase(MetaConfiguration& config) {}
292 
293 inline void initTypeWrapper() {}
294 
295 void unwrap() = delete; // StructWrapper only implements tryUnwrap(), not unwrap()
296};
297 
298// Specialization of TypeWrapperBase for TypeWrapperExtension.
299template <typename Self, template <typename> typename Extension>
300class TypeWrapperBase<Self, TypeWrapperExtension<Extension>, JsgKind::EXTENSION>
301 : public Extension<Self> {
302 template <typename MetaConfiguration>
303 static constexpr bool sfinae(decltype(Extension<Self>(kj::instance<MetaConfiguration&>()))*) {
304 return true; // extension constructor takes configuration argument
305 }
306 template <typename MetaConfiguration>
307 static constexpr bool sfinae(...) {
308 return false; // extension constructor does not take arguments
309 }
310 
311 public:
312 template <typename MetaConfiguration,
313 typename = kj::EnableIf<!sfinae<MetaConfiguration>(static_cast<Extension<Self>*>(nullptr))>>
314 TypeWrapperBase(MetaConfiguration& config) {}
315 
316 template <typename MetaConfiguration,
317 typename = kj::EnableIf<sfinae<MetaConfiguration>(static_cast<Extension<Self>*>(nullptr))>>
318 TypeWrapperBase(MetaConfiguration& config, bool = false): Extension<Self>(config) {}
319 
320 void unwrap() = delete; // extensions only implement tryUnwrap(), not unwrap()
321 
322 inline void initTypeWrapper() {}
323};
324 
325// Specialization of TypeWrapperBase for InjectConfiguration.
326template <typename Self, typename Configuration>
327class TypeWrapperBase<Self, InjectConfiguration<Configuration>, JsgKind::EXTENSION> {
328 public:
329 template <typename MetaConfiguration>
330 TypeWrapperBase(MetaConfiguration& config): configuration(kj::fwd<MetaConfiguration>(config)) {}
331 
332 static constexpr const char* getName(kj::Decay<Configuration>*) {
333 return "Configuration";
334 }
335 
336 Configuration unwrap(Lock& js, v8::Local<v8::Context> context, Configuration*) {
337 return configuration;
338 }
339 
340 void tryUnwrap() = delete;
341 void wrap() = delete;
342 void newContext() = delete;
343 void getTemplate() = delete;
344 
345 inline void initTypeWrapper() {}
346 
347 private:
348 Configuration configuration;
349};
350 
351// The TypeWrapper class aggregates functionality to convert between C++ values and JavaScript
352// values. It primarily implements two methods:
353//
354// v8::Local<v8::Value> wrap(v8::Local<v8::Context> jsContext,
355// kj::Maybe<v8::Local<v8::Object>> creator
356// T cppValue);
357// // Converts cppValue to JavaScript.
358// //
359// // `creator` is non-null when converting the return value of a method; in this case,
360// // `creator` is the object on which the method was called. This is useful for some types
361// // (like Promises) where the KJ convention is to assume that the creator must outlive the
362// // returned object.
363//
364// T unwrap<T>(v8::Local<v8::Context> jsContext, v8::Local<v8::Value> jsHandle);
365// // Converts jsValue to C++, expecting type T.
366//
367// The design is based on mixins: TypeWrapper derives from classes that handle each individual
368// type. Each mixin is expected to implement the following methods:
369//
370// static constexpr const char* getName(T* dummy);
371// // Return the name of the type for the purpose of TypeError exception messages. Note that
372// // you can also return `const std::type_info&` here, in which case the type name will
373// // be derived by stripping off the namespace from the C++ type name.
374//
375// v8::Local<v8::Value> wrap(v8::Local<v8::Context> jsContext,
376// kj::Maybe<v8::Local<v8::Object>> creator,
377// T cppValue);
378// // Converts cppValue to JavaScript.
379//
380// kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Context> jsContext,
381// v8::Local<v8::Value> jsHandle, T* dummy,
382// kj::Maybe<v8::Local<v8::Object>> parentObject);
383// // Converts jsValue to C++, expecting type T. If the input is not of type T, returns
384// // null. If we're unwrapping a field of an object, then `parentObject` is the handle to
385// // the object; this is useful when unwrapping a function, to bind `this`.
386// //
387// // Note that only a shallow type check is performed. E.g. if a struct type is expected,
388// // tryUnwrap() will only return null if the input is not a JS Object. If it is an object,
389// // but one of its fields is the wrong type, tryUnwrap() will throw a TypeError. The idea
390// // here is that `tryUnwrap()` should only do the amount of type checking that one would
391// // typically do in JavaScript to distinguish a variant type (e.g. "string or number").
392// // Typically this is limited to what you can do with the `typeof` and `instanceof`
393// // keywords on the top-level value.
394//
395// Note the `dummy` parameters of type T*. These will always be passed `nullptr`. The purpose of
396// these parameters is to select the correct overload for the desired type. Normally, one would
397// use an explicit template parameter for this, but that only works if all the methods are
398// actually specializations of the same template method declaration. That's not the case here,
399// because we're inheriting totally independent method declarations from all our mixins. So, we
400// have to slum it by passing `(T*)nullptr` as an argument purely for overload selection.
401//
402// Note that many of these mixins need to call back to the TypeWrapper recursively. For example,
403// OptionalWrapper (for Optional<T>) will need to call back to unwrap the inner T. To that end,
404// we use the Curiously Recurring Template Pattern, passing the TypeWrapper type itself to its
405// superclasses, so that they can cast themselves back to the subclass type and call it
406// recursively. See:
407//
408// https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
409//
410// Actually, TypeWrapper itself *also* takes itself as a template parameter called `Self`. This
411// is primarily done as a trick in order to make compiler error messages less difficult to read.
412// The `Self` parameter to `TypeWrapper` is actually a specific subclass of TypeWrapper. See
413// JSG_DECLARE_ISOLATE_TYPE in setup.h.
414//
415// Note that a pointer to the TypeWrapper object is stored in the V8 context's "embedder data",
416// in slot 1, so that we can get back to it from V8 callbacks.
417template <typename Self, typename... T>
418class TypeWrapper: public DynamicResourceTypeMap<Self>,
419 public TypeWrapperBase<Self, T>...,
420 public PrimitiveWrapper,
421 public NameWrapper,
422 public StringWrapper,
423 public OptionalWrapper<Self>,
424 public LenientOptionalWrapper<Self>,
425 public MaybeWrapper<Self>,
426 public OneOfWrapper<Self>,
427 public ArrayWrapper<Self>,
428 public SetWrapper<Self>,
429 public SequenceWrapper<Self>,
430 public GeneratorWrapper<Self>,
431 public ArrayBufferWrapper,
432 public DictWrapper<Self>,
433 public DateWrapper,
434 public BufferSourceWrapper,
435 public FunctionWrapper<Self>,
436 public PromiseWrapper<Self>,
437 public NonCoercibleWrapper<Self>,
438 public MemoizedIdentityWrapper<Self>,
439 public IdentifiedWrapper<Self>,
440 public SelfRefWrapper,
441 public ExceptionWrapper<Self>,
442 public ObjectWrapper<Self>,
443 public V8HandleWrapper,
444 public UnimplementedWrapper,
445 public JsValueWrapper {
446 // TODO(soon): Should the TypeWrapper object be stored on the isolate rather than the context?
447 public:
448 template <typename MetaConfiguration>
449 TypeWrapper(v8::Isolate* isolate, MetaConfiguration&& configuration)
450 : TypeWrapperBase<Self, T>(configuration)...,
451 MaybeWrapper<Self>(configuration),
452 GeneratorWrapper<Self>(configuration),
453 PromiseWrapper<Self>(configuration),
454 config(getConfig(configuration)) {
455 isolate->SetData(SET_DATA_TYPE_WRAPPER, this);
456 }
457 KJ_DISALLOW_COPY_AND_MOVE(TypeWrapper);
458 
459 void initTypeWrapper() {
460 (TypeWrapperBase<Self, T>::initTypeWrapper(), ...);
461 }
462 
463 static TypeWrapper& from(v8::Isolate* isolate) {
464 return *reinterpret_cast<TypeWrapper*>(isolate->GetData(SET_DATA_TYPE_WRAPPER));
465 }
466 
467 bool isFastApiEnabled() const {
468 return config.fastApiEnabled;
469 }
470 
471 using TypeWrapperBase<Self, T>::getName...;
472 using TypeWrapperBase<Self, T>::wrap...;
473 using TypeWrapperBase<Self, T>::newContext...;
474 using TypeWrapperBase<Self, T>::unwrap...;
475 using TypeWrapperBase<Self, T>::tryUnwrap...;
476 using TypeWrapperBase<Self, T>::getTemplate...;
477 
478#define USING_WRAPPER(Name) \
479 using Name::getName; \
480 using Name::wrap; \
481 using Name::tryUnwrap
482 
483 USING_WRAPPER(PrimitiveWrapper);
484 USING_WRAPPER(NameWrapper);
485 USING_WRAPPER(StringWrapper);
486 USING_WRAPPER(OptionalWrapper<Self>);
487 USING_WRAPPER(LenientOptionalWrapper<Self>);
488 USING_WRAPPER(MaybeWrapper<Self>);
489 USING_WRAPPER(OneOfWrapper<Self>);
490 USING_WRAPPER(ArrayWrapper<Self>);
491 USING_WRAPPER(SetWrapper<Self>);
492 USING_WRAPPER(SequenceWrapper<Self>);
493 USING_WRAPPER(GeneratorWrapper<Self>);
494 USING_WRAPPER(ArrayBufferWrapper);
495 USING_WRAPPER(DictWrapper<Self>);
496 USING_WRAPPER(DateWrapper);
497 USING_WRAPPER(BufferSourceWrapper);
498 USING_WRAPPER(FunctionWrapper<Self>);
499 USING_WRAPPER(PromiseWrapper<Self>);
500 USING_WRAPPER(NonCoercibleWrapper<Self>);
501 USING_WRAPPER(MemoizedIdentityWrapper<Self>);
502 USING_WRAPPER(IdentifiedWrapper<Self>);
503 USING_WRAPPER(SelfRefWrapper);
504 USING_WRAPPER(ExceptionWrapper<Self>);
505 USING_WRAPPER(ObjectWrapper<Self>);
506 USING_WRAPPER(V8HandleWrapper);
507 USING_WRAPPER(UnimplementedWrapper);
508 USING_WRAPPER(JsValueWrapper);
509#undef USING_WRAPPER
510 
511 template <typename U>
512 class TypeHandlerImpl;
513 
514 template <typename U>
515 static constexpr TypeHandlerImpl<U> TYPE_HANDLER_INSTANCE = TypeHandlerImpl<U>();
516 
517 template <typename U>
518 static constexpr const char* getName(TypeHandler<U>*) {
519 return "TypeHandler";
520 }
521 
522 template <typename U>
523 const TypeHandler<U>& unwrap(Lock& js, v8::Local<v8::Context>, TypeHandler<U>*) {
524 // if you're here because of compiler error template garbage, you forgot to register
525 // a type with JSG_DECLARE_ISOLATE_TYPE
526 return TYPE_HANDLER_INSTANCE<U>;
527 }
528 
529 template <typename U>
530 kj::Maybe<const TypeHandler<U>&> tryUnwrap(Lock& js,
531 v8::Local<v8::Context> context,
532 v8::Local<v8::Value> handle,
533 TypeHandler<U>*,
534 kj::Maybe<v8::Local<v8::Object>> parentObject) {
535 // TypeHandler is not a value that needs to be unwrapped from JS
536 return TYPE_HANDLER_INSTANCE<U>;
537 }
538 
539 template <typename U>
540 auto unwrap(Lock& js,
541 v8::Local<v8::Context> context,
542 v8::Local<v8::Value> handle,
543 TypeErrorContext errorContext,
544 kj::Maybe<v8::Local<v8::Object>> parentObject = kj::none) -> RemoveRvalueRef<U> {
545 auto maybe =
546 this->tryUnwrap(js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject);
547 KJ_IF_SOME(result, maybe) {
548 return kj::fwd<RemoveMaybe<decltype(maybe)>>(result);
549 } else {
550 throwTypeError(
551 js.v8Isolate, errorContext, TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr)));
552 }
553 }
554 
555 template <typename U, FastApiPrimitive A>
556 auto unwrapFastApi(
557 jsg::Lock& js, v8::Local<v8::Context> context, A& arg, TypeErrorContext errorContext) -> A {
558 return arg;
559 }
560 
561 template <typename U>
562 auto unwrapFastApi(jsg::Lock& js,
563 v8::Local<v8::Context> context,
564 v8::Local<v8::Value>& arg,
565 TypeErrorContext errorContext) -> RemoveRvalueRef<U> {
566 return unwrap<U>(js, context, arg, errorContext);
567 }
568 
569 // Helper for unwrapping function/method arguments correctly. Specifically, we need logic to
570 // handle the case where the user passes in fewer arguments than the function has parameters.
571 template <typename U>
572 auto unwrap(Lock& js,
573 v8::Local<v8::Context> context,
574 const v8::FunctionCallbackInfo<v8::Value>& args,
575 size_t parameterIndex,
576 TypeErrorContext errorContext) -> RemoveRvalueRef<U> {
577 using V = kj::Decay<U>;
578 
579 if constexpr (isArguments<V>()) {
580 using E = V::ElementType;
581 size_t size = args.Length() >= parameterIndex ? args.Length() - parameterIndex : 0;
582 auto builder = kj::heapArrayBuilder<E>(size);
583 for (size_t i = parameterIndex; i < args.Length(); i++) {
584 builder.add(unwrap<E>(js, context, args[i], errorContext));
585 }
586 return builder.finish();
587 } else if constexpr (ValueLessParameter<Self, V>) {
588 // C++ parameters which don't unwrap JS values, like TypeHandlers or v8::FunctionCallbackInfo.
589 return unwrap(js, context, static_cast<V*>(nullptr));
590 } else {
591 if constexpr (!webidl::OptionalType<V> && !kj::isSameType<V, Unimplemented>()) {
592 // TODO(perf): Better to perform this parameter index check once, at the unwrap<U>() call
593 // site. We'll need function length properties implemented correctly for that, most
594 // likely -- see EW-386.
595 if (parameterIndex >= args.Length()) {
596 // We're unwrapping a nonexistent argument into a required parameter. Since Web IDL
597 // nullable types (Maybe<T>) can be initialized from `undefined`, we need to explicitly
598 // throw here, or else `f(Maybe<T>)` could be called like `f()`.
599 throwTypeError(
600 js.v8Isolate, errorContext, TypeWrapper::getName(static_cast<V*>(nullptr)));
601 }
602 }
603 
604 // If we get here, we're either unwrapping into an optional or unimplemented parameter, in
605 // which cases we're fine with nonexistent arguments implying `undefined`, or we have an
606 // argument at this parameter index.
607 return unwrap<U>(js, context, args[parameterIndex], errorContext);
608 }
609 }
610 
611 template <typename Holder, typename U>
612 void initReflection(Holder* holder, PropertyReflection<U>& reflection) {
613 reflection.self = holder;
614 reflection.unwrapper = [](v8::Isolate* isolate, v8::Local<v8::Object> object,
615 kj::StringPtr name) -> kj::Maybe<U> {
616 auto context = isolate->GetCurrentContext();
617 auto& js = Lock::from(isolate);
618 auto value = jsg::check(object->Get(context, v8StrIntern(isolate, name)));
619 if (value->IsUndefined()) {
620 return kj::none;
621 } else {
622 // TypeErrorContext::structField() produces a pretty good error message for this case.
623 return from(isolate).template unwrap<U>(
624 js, context, value, TypeErrorContext::structField(typeid(Holder), name.cStr()), object);
625 }
626 };
627 }
628 
629 template <typename Holder, typename... U>
630 void initReflection(Holder* holder, PropertyReflection<U>&... reflections) {
631 (initReflection(holder, reflections), ...);
632 }
633 
634 private:
635 const JsgConfig config;
636};
637 
638template <typename Self, typename... Types>
639template <typename T>
640class TypeWrapper<Self, Types...>::TypeHandlerImpl final: public TypeHandler<T> {
641 public:
642 v8::Local<v8::Value> wrap(Lock& js, T value) const override {
643 auto isolate = js.v8Isolate;
644 auto context = js.v8Context();
645 return TypeWrapper::from(isolate).wrap(js, context, kj::none, kj::mv(value));
646 }
647 
648 kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Value> handle) const override {
649 auto isolate = js.v8Isolate;
650 auto context = js.v8Context();
651 return TypeWrapper::from(isolate).tryUnwrap(
652 js, context, handle, static_cast<T*>(nullptr), kj::none);
653 }
654};
655 
656// This macro helps cut down on template spam in error messages. Instead of instantiating Isolate
657// directly, do:
658//
659// JSG_DECLARE_ISOLATE_TYPE(MyIsolate, SomeApiType, AnotherApiType, ...);
660//
661// `MyIsolate` becomes your custom Isolate type, which will support wrapping all of the listed
662// API types.
663#define JSG_DECLARE_ISOLATE_TYPE(Type, ...) \
664 class Type##_TypeWrapper; \
665 using Type##_TypeWrapperBase = \
666 ::workerd::jsg::TypeWrapper<Type##_TypeWrapper, jsg::DOMException, ##__VA_ARGS__>; \
667 class Type##_TypeWrapper final: public Type##_TypeWrapperBase { \
668 public: \
669 [[maybe_unused]] static constexpr bool trackCallCounts = false; \
670 using Type##_TypeWrapperBase::TypeWrapper; \
671 }; \
672 class Type final: public ::workerd::jsg::Isolate<Type##_TypeWrapper> { \
673 public: \
674 using ::workerd::jsg::Isolate<Type##_TypeWrapper>::Isolate; \
675 }
676 
677#define JSG_DECLARE_DEBUG_ISOLATE_TYPE(Type, ...) \
678 class Type##_TypeWrapper; \
679 using Type##_TypeWrapperBase = \
680 ::workerd::jsg::TypeWrapper<Type##_TypeWrapper, jsg::DOMException, ##__VA_ARGS__>; \
681 class Type##_TypeWrapper final: public Type##_TypeWrapperBase { \
682 public: \
683 [[maybe_unused]] static constexpr bool trackCallCounts = true; \
684 using Type##_TypeWrapperBase::TypeWrapper; \
685 }; \
686 class Type final: public ::workerd::jsg::Isolate<Type##_TypeWrapper> { \
687 public: \
688 using ::workerd::jsg::Isolate<Type##_TypeWrapper>::Isolate; \
689 }
690 
691} // namespace workerd::jsg