File
Blob: src/workerd/jsg/type-wrapper.h
| 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 | |
| 25 | namespace 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 |
| 32 | template <typename TypeWrapper, typename T> |
| 33 | concept 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. |
| 53 | namespace detail { |
| 54 | |
| 55 | template <typename TypeWrapper> |
| 56 | struct RequiredArgCount_<TypeWrapper, TypeList<>> { |
| 57 | static constexpr int value = 0; |
| 58 | }; |
| 59 | |
| 60 | template <typename TypeWrapper, typename Head, typename... Tail> |
| 61 | struct 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. |
| 74 | class 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 | |
| 189 | class 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`. |
| 251 | template <template <typename TypeWrapper> typename Extension> |
| 252 | class 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. |
| 264 | template <typename Configuration> |
| 265 | class 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. |
| 272 | template <typename Self, typename T, JsgKind kind = T::JSG_KIND> |
| 273 | class TypeWrapperBase; |
| 274 | |
| 275 | // Specialization of TypeWrapperBase for types that have a JSG_RESOURCE_TYPE block. |
| 276 | template <typename Self, typename T> |
| 277 | class 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. |
| 286 | template <typename Self, typename T> |
| 287 | class 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. |
| 299 | template <typename Self, template <typename> typename Extension> |
| 300 | class 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. |
| 326 | template <typename Self, typename Configuration> |
| 327 | class 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. |
| 417 | template <typename Self, typename... T> |
| 418 | class 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 | |
| 638 | template <typename Self, typename... Types> |
| 639 | template <typename T> |
| 640 | class 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 |