// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once // Main public interface to JSG library. // // Any files declaring an API to export to JavaScript will need to include this header. #include "util.h" #include "wrappable.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using kj::byte; using kj::uint; #if _MSC_VER using ssize_t = long long; #endif namespace workerd::jsg { kj::String stringifyHandle(v8::Local value); } namespace v8 { // Allows v8 handles to be passed to kj::str() as well as KJ_LOG and related macros. template kj::String KJ_STRINGIFY(v8::Local value) { return workerd::jsg::stringifyHandle(value); } } // namespace v8 namespace workerd::jsg { // ======================================================================================= // Macros for declaring type glue. #define JSG_RESOURCE_TYPE(Type, ...) \ static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = ::workerd::jsg::JsgKind::RESOURCE; \ using jsgSuper = jsgThis; \ using jsgThis = Type; \ inline kj::StringPtr jsgGetMemoryName() const override { \ return #Type##_kjc; \ } \ inline size_t jsgGetMemorySelfSize() const override { \ return sizeof(Type); \ } \ inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override { \ const Type* self = static_cast(this); \ jsgSuper::jsgGetMemoryInfo(tracker); \ ::workerd::jsg::visitSubclassForMemoryInfo(self, tracker); \ } \ template \ friend constexpr bool ::workerd::jsg::resourceNeedsGcTracing(); \ template \ friend void ::workerd::jsg::visitSubclassForGc(T* obj, ::workerd::jsg::GcVisitor& visitor); \ inline void jsgVisitForGc(::workerd::jsg::GcVisitor& visitor) override { \ jsgSuper::jsgVisitForGc(visitor); \ ::workerd::jsg::visitSubclassForGc(this, visitor); \ } \ static void jsgConfiguration(__VA_ARGS__); \ template \ static void registerMembers(Registry& registry, ##__VA_ARGS__) // Begins a block nested inside a C++ class to declare how that class should be accessible in // JavaScript. JSG_RESOURCE_TYPE declares that the class is a "resource type" in KJ parlance. // // https://github.com/sandstorm-io/capnproto/blob/master/style-guide.md#value-types-vs-resource-types // // In short, this means that the type is normally passed by reference, and that when JavaScript // code accesses members of the type, it calls back into C++. This differs from value types, which // are normally deep-copied into JavaScript objects such that C++ is no longer involved. // // Example usage: // // class MyApiType: public jsg::Object { // // Some type we want to expose to JavaScript. // public: // static jsg::Ref constructor(bool b, kj::String s); // // Called when JavaScript invokes `new MyType()`. The name `constructor` is special. // // If you do not declare a constructor, then attempts to construct the type from // // JavaScript will throw an exception, but you'll still be able to construct it in C++ // // using the regular C++ constructor(s). // // void foo(int i, kj::String str); // double bar(); // // Methods that can be called from JavaScript. // // kj::StringPtr getBaz(); // void setBaz(kj::String value); // // Methods implementing a property. // // JSG_RESOURCE_TYPE(MyApiType) { // JSG_METHOD(foo); // JSG_METHOD(bar); // JSG_INSTANCE_PROPERTY(baz, getBaz, setBaz); // } // // private: // void visitForGc(jsg::GcVisitor visitor); // // If this type contains any Ref or Value objects, it must implement visitForGc(), and when // // this is called, it must call `visitor.visit()` on all handles that it knows about. If // // the object doesn't hold any JS handles then it need not implement this. See the // // definition of GcVisitor, below, for more information. // // jsg::Value someValue; // jsg::Ref someOtherResourceObject; // jsg::V8Ref someState; // // Objects of resource type may be destroyed outside of the isolate lock. Therefore, if you // // need to hold a reference to a V8 object in a resource type, you should use one of these // // classes / class templates. In particular, holding a raw v8::Global may result in // // undefined behavior upon destruction. // }; // // Notice that method parameters and return types are automatically converted between C++ and // JavaScript. You specify the full set of types that your JavaScript execution environment will // support when you declare your Isolate (usually in high-level code). // // Additionally, the following types are always supported: // - C++ double, int <-> JS Number // - C++ kj::Date <-> JS Date in return position, JS Date or millisecond unix epoch as argument // - C++ kj::String, kj::StringPtr <-> JS String // - C++ kj::Maybe <-> JS null or T // - C++ jsg::Optional <-> JS undefined or T // - C++ jsg::LenientOptional <-> JS undefined or T (treats type errors as JS undefined) // - C++ kj::OneOf <-> JS T or U or ... // - C++ kj::Array <-> JS Array of T // - C++ kj::Array <-> JS ArrayBuffer // - C++ jsg::Dict <-> JS Object used as a map of strings to values of type T // - C++ jsg::Function <-> JS Function // - C++ jsg::Promise <-> JS Promise // - C++ jsg::Ref <-> JavaScript resource type // - C++ v8::Local <-> JavaScript value // // There is also some magic. If the first parameter to a method has type // `const v8::FunctionCallbackInfo&`, then it will receive the FunctionCallbackInfo // as passed from V8. (For property accessors, this should be PropertyCallbackInfo instead.) This // gives you an escape hatch by which you can directly access the V8 context when needed. In this // case the second parameter to your method will correspond to the first parameter passed from // JavaScript. // // As another piece of magic, you can add some special types to the end of your parameter list in // order to receive functionality from the JavaScript environment itself. These parameters will not // actually correspond to JavaScript parameters, and should always be placed at the end of the // argument list. They are: // // - const jsg::TypeHandler&: Provides callback which can be used to convert between V8 handles // and a C++ object of type T, and (for resource types) to allocate objects of type T on the V8 // heap. The reference is valid only until your method returns. // - `v8::Isolate*`: Receives the V8 isolate pointer. // // In yet more magic, you can add a single configuration parameter to the JSG_RESOURCE_TYPE macro: // // class MyApiType: ... { // public: // JSG_RESOURCE_TYPE(MyApiType, uint apiVersion) { // if (apiVersion > 42) { // using namespace newapi; // JSG_NESTED_TYPE(Widget); // } else { // using namespace oldapi; // JSG_NESTED_TYPE(Widget); // } // } // }; // // Populate the configuration parameter by passing it to the JSG isolate's constructor (the type // declared by JSG_DECLARE_ISOLATE_TYPE in setup.h). // // Different resource types may have different configuration types. However, all the configuration // types must be constructable from a single "meta" configuration type, which is the type of the // configuration passed to the JSG isolate's constructor. // Use inside a JSG_RESOURCE_TYPE to declare that the resource type itself can be invoked as // a function. #define JSG_CALLABLE(name) \ do { \ registry.template registerCallable(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare that the given method should be callable from // JavaScript on instances of the resource type. #define JSG_METHOD(name) \ do { \ static const char NAME[] = #name; \ registry.template registerMethod(); \ } while (false) // Like JSG_METHOD but allows you to specify a different name to use in JavaScript. This is // particularly useful when a JavaScript API wants to use a name that is a keyword in C++. For // example: // // JSG_METHOD_NAMED(delete, delete_); #define JSG_METHOD_NAMED(name, method) \ do { \ static const char NAME[] = #name; \ registry.template registerMethod(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare that the given method should be callable from // JavaScript on the resource type's constructor. #define JSG_STATIC_METHOD(name) \ do { \ static const char NAME[] = #name; \ registry.template registerStaticMethod(); \ } while (false) // Like JSG_METHOD_NAMED, but for static methods. #define JSG_STATIC_METHOD_NAMED(name, method) \ do { \ static const char NAME[] = #name; \ registry.template registerStaticMethod(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to make objects of this type iterable. Pass in the name of // a method returning an object satisfying the requirements of a JavaScript iterator. Note that this // will NOT automatically register the method for you -- you still need to use JSG_METHOD{,_NAMED} // if you plan to expose the method to JavaScript. For example: // // struct Iterable { // static Iterable constructor(); // Iterator entries(); // JSG_RESOURCE_TYPE { // JSG_ITERABLE(entries); // } // }; // // will allow a resource type to be iterated over, but not its entries() function to be called. // // for (let x of new Iterable()) { /* ... */ } // GOOD // for (let x of new Iterable().entries()) { /* ... */ } // BAD! // // To enable the latter case, you would need to use JSG_METHOD(entries), and make Iterator itself // iterable. #define JSG_ITERABLE(method) \ do { \ static const char NAME[] = #method; \ registry.template registerIterable(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to make objects of this type async iterable. Pass in the // name of a method returning a kj::Promise for an object satisfying the requirements of a // JavaScript iterator. #define JSG_ASYNC_ITERABLE(method) \ do { \ static const char NAME[] = #method; \ registry.template registerAsyncIterable(); \ } while (false) // JSG_DISPOSE and JSG_ASYNC_DISPOSE are used to make an object compatible with the // JavaScript using and await using keywords (respectively). These allow variables to // be defined in such a way that they will have their disposer functions automatically // called when the variable goes out of scope, for instance: // // class Foo { [Symbol.dispose]() { console.log('...'); }} // { using foo = new Foo(); } // // When the containing block exits, the [Symbol.dispose] function will be called on // the object, allowing cleanup actions to be performed. // // There are a number of guidelines that should be followed when implementing // disposer methods: // // 1. Always prefer Symbol.dispose over Symbol.asyncDispose, avoid defining both. // 2. Always assume that if the resource needs to be disposed, it's being disposed // in an exception case. Clean disposal should always be explicit. // 3. At least for the time being, the exception will not be available to the // disposer, so it will not be able to propagate the error // 4. Always implement disposal as an idempotent operation and remember that // users can call the disposer methods directly as many times as they want. // 5. Remember that errors thrown from within the disposer will mask the original // error using a SupressedError. #define JSG_DISPOSE(method) \ do { \ static const char NAME[] = #method; \ registry.template registerDispose(); \ } while (false) #define JSG_ASYNC_DISPOSE(method) \ do { \ static const char NAME[] = #method; \ registry.template registerAsyncDispose(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare a property on this object that should be // accessible to JavaScript. `name` is the JavaScript member name, while `getter` and `setter` are // the names of C++ methods that get and set this property. // // WARNING: This is usually not what you want. Usually you want JSG_PROTOTYPE_PROPERTY instead. // Note that V8 implements instance properties by modifying the instance immediately after // construction, which is inefficient and can break some optimizations. For example, any object // with an instance property will not be possible to collect during minor GCs, only major GCs. // Prototype properties are on the prototype, so have no runtime overhead until they are used. #define JSG_INSTANCE_PROPERTY(name, getter, setter) \ do { \ static const char NAME[] = #name; \ registry.template registerInstanceProperty(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare a property on this object's prototype that // should be accessible to JavaScript. `name` is the JavaScript member name, while `getter` and // `setter` are the names of C++ methods that get and set this property. // // The key difference between JSG_INSTANCE_PROPERTY and JSG_PROTOTYPE_PROPERTY is in exactly how // the getters and setters are attached to created JavaScript object. Specifically, // JSG_INSTANCE_PROPERTY is similar to: // // class Foo1 { // constructor() { // Object.defineProperty(this, 'bar', { // get() { /* ... */ }, // set(v) { /* ... */ }, // }); // } // } // // Whereas JSG_PROTOTYPE_PROPERTY is equivalent to: // // class Foo2 { // get bar() { /* ... */ } // set bar(v) { /* ... */ } // } // // The difference here is important because, in the former case, the properties are // defined directly on *instances* of Foo1 as own properties, while in latter case, // the properties are defined on the prototype of all Foo2 instances. In the former // case, using JSG_INSTANCE_PROPERTY, the properties are directly enumerable on all instances // of Foo1 such that calling Object.keys(new Foo1()) returns ['bar']. However, calling // Object.keys(new Foo2()) will return an empty array [] as prototype properties are // not directly enumerable. // // However, that's not the only critical difference. Because instance properties take // precedence over prototype properties, Foo1 is not properly subclassable. If I did: // // class MyFoo1 extends Foo1 { // get bar() { /** .. **/ } // } // // const myFoo1 = new MyFoo1(); // console.log(myFoo1.bar); // // The getter specified in the constructor of Foo1 would be called rather than the // getter defined in the MyFoo1 class, which is not what a user would expect! // This means that any resource type that uses JSG_INSTANCE_PROPERTY to attach properties // will not be properly subclassable. To allow subclasses to work correctly, use // JSG_PROTOTYPE_PROPERTY instead. #define JSG_PROTOTYPE_PROPERTY(name, getter, setter) \ do { \ static const char NAME[] = #name; \ registry.template registerPrototypeProperty(); \ } while (false) // Like JSG_INSTANCE_PROPERTY but creates a property that will throw an exception if // JavaScript tries to assign to it. #define JSG_READONLY_INSTANCE_PROPERTY(name, getter) \ do { \ static const char NAME[] = #name; \ registry.template registerReadonlyInstanceProperty(); \ } while (false) // Like JSG_PROTOTYPE_PROPERTY but creates a property that will throw an exception if JavaScript // tries to assign to it. #define JSG_READONLY_PROTOTYPE_PROPERTY(name, getter) \ do { \ static const char NAME[] = #name; \ registry.template registerReadonlyPrototypeProperty(); \ } while (false) // A lazy property will call the getter the first time the property is access but will then // replace the property definition with a normal instance property using the returned value. // Keep in mind that, as an instance property, these lazily set properties cannot be overridden // by subclasses. They are set directly on the instance object itself when it is created. #define JSG_LAZY_INSTANCE_PROPERTY(name, getter) \ do { \ static const char NAME[] = #name; \ registry.template registerLazyInstanceProperty(); \ } while (false) #define JSG_LAZY_READONLY_INSTANCE_PROPERTY(name, getter) \ do { \ static const char NAME[] = #name; \ registry.template registerLazyInstanceProperty(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare a property that should be shown when calling // `node:util`'s `inspect()` function on values of this type. These properties will be shown when // `console.log()`ing too, and should be used to expose internal state useful for debugging. // `name` is the name of the property (displayed in square brackets), while `getter` is the name of // the C++ method that gets this property's value. #define JSG_INSPECT_PROPERTY(name, getter) \ do { \ static const char NAME[] = #name; \ registry.template registerInspectProperty(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE to expose a static property on the JavaScript constructor. // The property will be read-only and will call the specified static function when accessed. // The function should take no parameters or take jsg::Lock& as the first parameter. // Example: // static int getVersion() { return 42; } // JSG_RESOURCE_TYPE(MyClass) { // JSG_STATIC_READONLY_PROPERTY(getVersion); // Exposes as MyClass.getVersion // } #define JSG_STATIC_READONLY_PROPERTY(name) \ do { \ static const char NAME[] = #name; \ registry.template registerStaticProperty(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE to expose a static property with a different name than the // underlying C++ function. The property will be read-only and will call the specified static // getter function when accessed. The getter can optionally take jsg::Lock& as the first parameter. // Example: // static kj::Array getSupportedTypes() { ... } // JSG_RESOURCE_TYPE(MyClass) { // JSG_STATIC_READONLY_PROPERTY_NAMED(supportedTypes, getSupportedTypes); // MyClass.supportedTypes // } #define JSG_STATIC_READONLY_PROPERTY_NAMED(name, getter) \ do { \ static const char NAME[] = #name; \ registry.template registerStaticProperty(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE to create a static constant member on the constructor and // prototype of this object. Only primitive data types (booleans, strings, numbers) are allowed. // Unlike the JSG_INSTANCE_PROPERTY and JSG_READONLY_PROPERTY macros, this does not use a getter // -- it expects a static constexpr member of a primitive type available in the class by the same // name. For example: // // struct Interface { // static Interface constructor() // static constexpr int FOO_BAR = 123; // JSG_RESOURCE_TYPE { // JSG_STATIC_CONSTANT(FOO_BAR); // } // }; // // will allow all of the following JS expressions to hold true: // // Interface.FOO_BAR === 123 // Interface.prototype.FOO_BAR === 123 // new Interface().FOO_BAR === 123 // Object.getPrototypeOf(new Interface()).FOO_BAR === 123 // // This is useful to implement constant interface members as specified in Web IDL: // https://heycam.github.io/webidl/#idl-constants // // TODO(someday): This should probably also support the null JS value. #define JSG_STATIC_CONSTANT(name) \ do { \ static const char NAME[] = #name; \ registry.template registerStaticConstant(Self::name); \ } while (false) // This works the same as JSG_STATIC_CONSTANT but allows us to provide an alias to an arbitrary c++ // constant instead. For example: // struct Interface { // static Interface constructor() // JSG_RESOURCE_TYPE { // JSG_STATIC_CONSTANT_NAMED(FOO_BAR, SOME_SYSTEM_CONSTANT); // } // }; #define JSG_STATIC_CONSTANT_NAMED(name, constant) \ do { \ static const char NAME[] = #name; \ registry.template registerStaticConstant(constant); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare that this type inherits from another type, // which must also have a JSG_RESOURCE_TYPE block. This type must singly, non-virtually inherit // from the specified type. (Multiple inheritance and virtual inheritance will not work since we // rely on the pointer to the superclass and the subclass having the same numeric value.) #define JSG_INHERIT(Type) \ static_assert(kj::canConvert(), #Type " is not a superclass of this"); \ registry.template registerInherit() // Use inside a JSG_RESOURCE_TYPE block to declare that this type inherits from an intrinsic // prototype. This is primarily useful to inherit from v8::kErrorPrototype, like DOMException, and // v8::kIteratorPrototype. #define JSG_INHERIT_INTRINSIC(intrinsic) \ do { \ static const char NAME[] = #intrinsic; \ registry.template registerInheritIntrinsic(intrinsic); \ } while (false) // An isDetected() operation which detects if the expression t.getTemplate(isolate, &u) is available // for instances `t`, `u` of types T and U. template using HasGetTemplateOverload = decltype(kj::instance().getTemplate( static_cast(nullptr), static_cast(nullptr))); // Use inside a JSG_RESOURCE_TYPE block to declare that the given type should be visible as a // static member of this type. Typically, your "global" type would use several of these // declarations to make other types appear in the global scope. It is not necessary for the types // to be nested in C++. #define JSG_NESTED_TYPE(Type) \ do { \ /* Note that `Type` may be incomplete here, we should be OK with that. */ \ static const char NAME[] = #Type; \ registry.template registerNestedType(); \ } while (false) #define JSG_NESTED_TYPE_NAMED(Type, Name) \ do { \ /* Note that `Type` may be incomplete here, we should be OK with that. */ \ static const char NAME[] = #Name; \ registry.template registerNestedType(); \ } while (false) // Adds reflection to a resource type. See PropertyReflection for usage. #define JSG_REFLECTION(...) \ static constexpr bool jsgHasReflection = true; \ template \ void jsgInitReflection(TypeWrapper& wrapper) { \ jsgSuper::jsgInitReflection(wrapper); \ wrapper.initReflection(this, __VA_ARGS__); \ } // Declares the type serializable. See jsg::Serializer for usage. #define JSG_SERIALIZABLE(TAG, ...) \ static_assert(static_cast(jsgSuper::jsgSerializeTag) != static_cast(TAG)); \ static constexpr auto jsgSerializeTag = TAG; \ static constexpr decltype(jsgSerializeTag) jsgSerializeOldTags[] = {__VA_ARGS__}; \ static constexpr auto jsgSerializeOneway = false // Like JSG_SERIALIZABLE(), but the type has only a serialize() method and no deserialize(). It // is expected that the specified tag actually belongs to some other type, so a serialization // round trip will have the effect of replacing this type with that other type. // // Used e.g. for JsRpcTarget, which becomes JsRpcStub after serialization. #define JSG_ONEWAY_SERIALIZABLE(TAG) \ static_assert(static_cast(jsgSuper::jsgSerializeTag) != static_cast(TAG)); \ static constexpr auto jsgSerializeTag = TAG; \ static constexpr decltype(jsgSerializeTag) jsgSerializeOldTags[] = {}; \ static constexpr auto jsgSerializeOneway = true // Declares a wildcard property getter. If a property is requested that isn't already present on // the object or its prototypes, the wildcard property getter will be given a chance to return the // property. // // WARNING: Be very careful about the property named "then". If it exists and is a function, V8 // will treat your type as a custom thenable, i.e. as a kind of Promise, which means among other // things that any time a Promise would resolve to it, it will try to chain with it. You should // probably return kj::none when "then" is requested. // // Example: // // struct MyType { // // Get the value of the named dynamic property. Returns none if the property doesn't exist. // // `SomeType` can be any type that JSG is able to convert to JavaScript. // kj::Maybe getWildcard(jsg::Lock& js, kj::StringPtr name); // // JSG_RESOURCE_TYPE(MyType) { // JSG_WILDCARD_PROPERTY(getWildcard); // } // }; #define JSG_WILDCARD_PROPERTY(method) \ do { \ registry.template registerWildcardProperty(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to declare that this type should be considered a "root" for // the purposes of automatically generating TypeScript definitions. All "root" types and their // recursively referenced types (e.g. method parameter/return types, property types, inherits, etc) // will be included in the generated TypeScript. See the `## TypeScript` section of the JSG README.md // for more details. #define JSG_TS_ROOT() registry.registerTypeScriptRoot() // Use inside a JSG_RESOURCE_TYPE block to customise the generated TypeScript definition for this type. // This macro accepts a single override parameter containing a partial TypeScript statement definition. // Varargs are accepted so that overrides can contain `,` outside of balanced brackets. See the // `## TypeScript` section of the JSG README.md for many more details and examples. // // The varargs are stringified directly with `#__VA_ARGS__` to capture the user's source verbatim; // see the comment on `JSG_STRING_LITERAL` in macro-meta.h for why a forwarding helper would be // wrong here. The other `JSG_*_TS_OVERRIDE` and `JSG_*_TS_DEFINE` macros below follow the same // pattern for the same reason. #define JSG_TS_OVERRIDE(...) \ do { \ static const char OVERRIDE[] = #__VA_ARGS__; \ registry.template registerTypeScriptOverride(); \ } while (false) // Use inside a JSG_RESOURCE_TYPE block to insert additional TypeScript definitions next to the generated // TypeScript definition for this type. This macro accepts a single define parameter containing one or // more TypeScript definitions (e.g. interfaces, classes, type aliases, consts, ...). Varargs are accepted // so that defines can contain `,` outside of balanced brackets. See the `## TypeScript`section of the JSG // README.md for more details. #define JSG_TS_DEFINE(...) \ do { \ static const char DEFINE[] = #__VA_ARGS__; \ registry.template registerTypeScriptDefine(); \ } while (false) // Like JSG_TS_DEFINE, but accepts a string literal (e.g. a raw string R"(...)") instead of bare tokens. // This avoids the preprocessor parsing the TypeScript content as C++ tokens, which is necessary when the // TypeScript definition contains C++20 keywords like `module` that Clang rejects inside macro arguments. #define JSG_TS_DEFINE_LITERAL(jsg_string_literal) \ do { \ static const char DEFINE[] = jsg_string_literal; \ registry.template registerTypeScriptDefine(); \ } while (false) // Like JSG_TS_ROOT but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT declaration, // inside the same `struct` definition. See the `## TypeScript` section of the JSG README.md for more // details. #define JSG_STRUCT_TS_ROOT() static constexpr bool _JSG_STRUCT_TS_ROOT_DO_NOT_USE_DIRECTLY = true // Like JSG_TS_OVERRIDE but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT // declaration, inside the same `struct` definition. See the `## TypeScript` section of the JSG README.md // for many more details and examples. #define JSG_STRUCT_TS_OVERRIDE(...) \ static constexpr char _JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY[] = #__VA_ARGS__ // Like JSG_STRUCT_TS_OVERRIDE, however it enables dynamic selection of TS_OVERRIDE. // Should be placed adjacent to the JSG_STRUCT declaration, inside the same struct definition. #define JSG_STRUCT_TS_OVERRIDE_DYNAMIC(...) \ static void jsgConfiguration(__VA_ARGS__); \ template \ static void registerTypeScriptDynamicOverride(Registry& registry, ##__VA_ARGS__) // Like JSG_TS_DEFINE but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT // declaration, inside the same `struct` definition. See the `## TypeScript`section of the JSG README.md // for more details. #define JSG_STRUCT_TS_DEFINE(...) \ static constexpr char _JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY[] = #__VA_ARGS__ // Adds a group of javascript modules to the module registry when context is instantiated. // bundle is of a Bundle type from workerd/jsg/modules.capnp. // Modules will be resolved according to their type and module registry normal resolve rules. #define JSG_CONTEXT_JS_BUNDLE(bundle) \ do { \ registry.registerJsBundle(bundle); \ } while (false) // true when T has _JSG_STRUCT_TS_ROOT_DO_NOT_USE_DIRECTLY field generated by JSG_STRUCT_TS_ROOT template concept HasStructTypeScriptRoot = requires { T::_JSG_STRUCT_TS_ROOT_DO_NOT_USE_DIRECTLY; }; // true when T has _JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY field generated by JSG_STRUCT_TS_OVERRIDE template concept HasStructTypeScriptOverride = requires { T::_JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY; }; // true when T has _JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY field generated by JSG_STRUCT_TS_DEFINE template concept HasStructTypeScriptDefine = requires { T::_JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY; }; // Nest this inside a simple struct declaration in order to support translating it to/from a // JavaScript object / Web IDL dictionary. // // struct MyStruct { // double foo; // kj::String bar; // kj::String $public; // // JSG_STRUCT(foo, bar, $public); // }; // // All of the types which are supported as method parameter / return types are also supported as // struct field types. // // Note that if you use `jsg::Optional` as a field type, then the field will not be present at // all in JavaScript when the optional is null in C++ (as opposed to the field being present but // assigned the value `undefined`). // // Note that if a `validate` function is provided, then it will be called after the struct is // unwrapped from v8. This would be an appropriate time to throw an error. // Signature: void validate(jsg::Lock& js); // Example: // struct ValidatingFoo { // kj::String abc; // void validate(jsg::Lock& js) { // JSG_REQUIRE(abc.size() != 0, TypeError, "Field 'abc' had no length in 'ValidatingFoo'."); // } // JSG_STRUCT(abc); // }; // // In this example the validate method would throw a `TypeError` if the size of the `abc` field was zero. // // Fields with a starting '$' will have that dollar sign prefix stripped in the JS binding. A // motivating example to enact that change was WebCrypto which has a field in a dictionary called // "public". '$' was chosen as a token we can use because it's a character for a C++ identifier. If // the Javascript field needs to contain '$' for some reason (which they probably shouldn't since // identifiers starting with $ are rare in JS land, especially for things the runtime would be // exporting), then you should be able to use '$$' as the identifier prefix in C++ since only the // first '$' gets stripped. #define JSG_STRUCT(...) \ static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = ::workerd::jsg::JsgKind::STRUCT; \ static constexpr char JSG_FOR_EACH(JSG_STRUCT_FIELD_NAME, , __VA_ARGS__); \ template \ using JsgFieldWrappers = \ ::workerd::jsg::TypeTuple; \ template \ static v8::Local jsgGetTemplate(v8::Isolate* isolate) { \ kj::Vector names; \ JSG_FOR_EACH(JSG_STRUCT_FIELD_COL, , __VA_ARGS__); \ auto namesPtr = names.asPtr().asConst(); \ return v8::DictionaryTemplate::New( \ isolate, v8::MemorySpan(namesPtr.begin(), namesPtr.size())); \ } \ template \ static void registerMembersInternal(Registry& registry, Config arg) { \ JSG_FOR_EACH(JSG_STRUCT_REGISTER_MEMBER, , __VA_ARGS__); \ if constexpr (::workerd::jsg::HasStructTypeScriptRoot) { \ registry.registerTypeScriptRoot(); \ } \ if constexpr (requires(jsg::GetConfiguration arg) { \ registerTypeScriptDynamicOverride(registry, arg); \ }) { \ registerTypeScriptDynamicOverride(registry, arg); \ } else if constexpr (::workerd::jsg::HasStructTypeScriptOverride) { \ registry.template registerTypeScriptOverride< \ Self::_JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY>(); \ } \ if constexpr (::workerd::jsg::HasStructTypeScriptDefine) { \ registry \ .template registerTypeScriptDefine(); \ } \ } \ template \ static void registerMembers(Registry& registry) \ requires(!jsg::HasConfiguration) \ { \ registerMembersInternal(registry, nullptr); \ } \ template \ static void registerMembers(Registry& registry, jsg::GetConfiguration arg) \ requires jsg::HasConfiguration \ { \ registerMembersInternal>(registry, arg); \ } template inline consteval size_t prefixLengthToStrip(const char (&s)[N]) { return s[0] == '$' ? 1 : 0; } // This string may not be what's actually exported to v8. For example, if it starts with a `$`, then // this value will still contain the `$` even though the `FieldWrapper` template argument will have // it stripped. #define JSG_STRUCT_FIELD_NAME(_, name) name##_JSG_NAME_DO_NOT_USE_DIRECTLY[] = #name #define JSG_STRUCT_FIELD_COL(_, name) \ ::workerd::jsg::jsgAddToStructNames().name), \ name##_JSG_NAME_DO_NOT_USE_DIRECTLY, ::workerd::jsg::prefixLengthToStrip(#name)>(names) // (Internal implementation details for JSG_STRUCT.) #define JSG_STRUCT_FIELD(_, name) \ ::workerd::jsg::FieldWrapper().name), \ &Self::name, name##_JSG_NAME_DO_NOT_USE_DIRECTLY, \ ::workerd::jsg::prefixLengthToStrip(#name)> // (Internal implementation details for JSG_STRUCT.) #define JSG_STRUCT_REGISTER_MEMBER(_, name) \ registry.template registerStructProperty().name), &Self::name>( \ name##_JSG_NAME_DO_NOT_USE_DIRECTLY) // Indexes for adding API data to V8's Isolate object. enum SetDataIndex { // The jsg::IsolateBase for a particular V8 isolate. SET_DATA_ISOLATE_BASE, // The TypeWrapper object for a particular V8 isolate. SET_DATA_TYPE_WRAPPER, // The lock associated with the V8 isolate. SET_DATA_LOCK, // The Worker::Isolate associated with the V8 isolate. SET_DATA_ISOLATE, // The address of the base of the 4Gbyte compressed pointer area. // If we are using the sandbox it's also the base of the sandbox. SET_DATA_CAGE_BASE, // Used by JSG<->Rust integration. SET_DATA_RUST_REALM, // The number of slots workerd uses in the API data for Isolate objects. SET_DATA_SLOTS_IN_USE, }; // ======================================================================================= // Special types // // These types can be used in C++ to represent various JavaScript idioms / Web IDL types. class Lock; WD_STRONG_BOOL(RequireEsm); // Arbitrary V8 data, wrapped for storage from C++. You can't do much with it, so instead you // should probably use V8Ref, a version of this that's strongly typed. // // When storing a Value inside a C++ object that is itself exported back to JavaScript, make sure // to implement GC visitation -- see GcVisitor, below. // // It is safe to destroy a strong jsg::Data object outside of the isolate lock. In this case, // the underlying V8 handles will be added to a queue, to be destroyed the next time a thread // locks the isolate. This means their destruction is non-deterministic, but that is true of V8 // objects anyway, due to the GC. Weak jsg::Data (i.e., those which are reachable by V8's GC, // see GcVisitor below) must still be destroyed under the isolate lock to guard against concurrent // modification with the GC. // // Move construction and move assignment of strong jsg::Data is well-defined even without // holding the isolate lock. That is, it is safe to move Values unless you have implemented GC // visitation for them. Moving jsg::Data which are reachable via GC visitation is undefined // behavior outside of an isolate lock. class Data { public: Data(decltype(nullptr)) {} ~Data() noexcept(false) { destroy(); } Data(Data&& other) noexcept: isolate(other.isolate), handle(kj::mv(other.handle)) { KJ_IF_SOME(t, other.tracedHandle) { moveFromTraced(other, t); } other.isolate = nullptr; } Data& operator=(Data&& other) { if (this != &other) { destroy(); isolate = other.isolate; handle = kj::mv(other.handle); other.isolate = nullptr; KJ_IF_SOME(t, other.tracedHandle) { moveFromTraced(other, t); } } assertInvariant(); other.assertInvariant(); return *this; } KJ_DISALLOW_COPY(Data); Data(v8::Isolate* isolate, v8::Local handle) : isolate(isolate), handle(isolate, handle) {} // Get the raw underlying v8 handle. v8::Local getHandle(v8::Isolate* isolate) const { return handle.Get(isolate); } // Get the raw underlying v8 handle. v8::Local getHandle(Lock& js) const; Data addRef(v8::Isolate* isolate) { return Data(isolate, getHandle(isolate)); } Data addRef(Lock& js); inline bool operator==(const Data& other) const { return handle == other.handle; } inline bool operator==(const v8::Local& other) const { return handle == other; } private: // The isolate with which the handles below are associated. v8::Isolate* isolate = nullptr; // Handle to the value which will be marked strong if any untraced C++ references exist, weak // otherwise. v8::Global handle; // When `handle` is weak, `tracedHandle` is a copy of it used to integrate with V8 GC tracing. // When `handle` is strong, we null out `tracedHandle`, because we don't need it, and it is // illegal to hold onto a traced handle without actually marking it during each trace. kj::Maybe> tracedHandle; friend class GcVisitor; void destroy(); // Debugging helpers. // Assert that only empty values are associated with null isolates. // // Note that we use IASSERT (which is only enabled in debug) here because this function is // intended to be invoked from the move ctor and assignment operator. We expect them to be // invoked a lot and want them to be as optimizable as possible. void assertInvariant() { KJ_IASSERT(isolate != nullptr || handle.IsEmpty()); } // Implement move constructor when the source of the move has previously been visited for // garbage collection. void moveFromTraced(Data& other, v8::TracedReference& otherTracedRef) noexcept; friend class MemoryTracker; }; // A drop-in replacement for v8::Global. Its big feature is that, like jsg::Data, a // jsg::V8Ref is safe to destroy outside of the isolate lock. // // Generally you should prefer using jsg::Value (for v8::Value) or jsg::Ref. Use a // jsg::V8Ref when you need the type-safety of holding a handle to a specific V8 type. template class V8Ref: private Data { public: V8Ref(decltype(nullptr)): Data(nullptr) {} V8Ref(v8::Isolate* isolate, v8::Local handle): Data(isolate, handle) {} V8Ref(V8Ref&& other) noexcept: Data(kj::mv(other)) {} V8Ref& operator=(V8Ref&& other) { Data::operator=(kj::mv(other)); return *this; } KJ_DISALLOW_COPY(V8Ref); v8::Local getHandle(v8::Isolate* isolate) const { if constexpr (std::is_base_of()) { // V8 doesn't let us cast directly from v8::Data to subtypes of v8::Value, so we're forced to // use this double cast... Ech. return Data::getHandle(isolate).template As().template As(); } else { return Data::getHandle(isolate).template As(); } } v8::Local getHandle(jsg::Lock& js) const; V8Ref addRef(v8::Isolate* isolate) { return V8Ref(isolate, getHandle(isolate)); } V8Ref addRef(jsg::Lock& js); V8Ref deepClone(jsg::Lock& js); inline bool operator==(const V8Ref& other) const { return Data::operator==(other); } inline bool operator==(const v8::Local& other) const { return Data::operator==(other); } template V8Ref cast(jsg::Lock& js); private: friend class GcVisitor; friend class MemoryTracker; }; using Value = V8Ref; // Like V8Ref but also implements `hashCode()`. Useful as a key into a kj::HashTable. // // T must v8::Object or a subclass (or anything that implements GetIdentityHash()). template class HashableV8Ref: public V8Ref { public: HashableV8Ref(decltype(nullptr)): V8Ref(nullptr), identityHash(0) {} HashableV8Ref(v8::Isolate* isolate, v8::Local handle) // TODO(perf): It's not clear if V8's `GetIdentityHash()` is intended to return uniform // results as required for KJ hashing, so we pass it to `kj::hashCode()` to further hash // it. This may be unnecessary. Note that there are several other call sites of // `GetIdentityHash()` which do the same -- if we decide we don't need this we should fix // all of them. : V8Ref(isolate, handle), identityHash(kj::hashCode(handle->GetIdentityHash())) {} HashableV8Ref(HashableV8Ref&& other) = default; HashableV8Ref& operator=(HashableV8Ref&& other) = default; KJ_DISALLOW_COPY(HashableV8Ref); HashableV8Ref addRef(v8::Isolate* isolate) { return HashableV8Ref(isolate, this->getHandle(isolate), identityHash); } HashableV8Ref addRef(jsg::Lock& js); int hashCode() const { return identityHash; } private: int identityHash; HashableV8Ref(v8::Isolate* isolate, v8::Local handle, int identityHash) : V8Ref(isolate, handle), identityHash(identityHash) {} }; template void MemoryTracker::trackField( kj::StringPtr edgeName, const V8Ref& value, kj::Maybe nodeName) { // Even though we're passing in a template T, casting to a v8::Value is sufficient here. trackField(edgeName, value.handle.Get(isolate_).template As(), nodeName); } // A value of type T, or `undefined`. // // In C++, this has the same usage as kj::Maybe. However, a null kj::Maybe corresponds to // `null` in JavaScript, whereas a null Optional corresponds to `undefined` in JavaScript. // // Note: Due to Web IDL's undefined-to-nullable coercion rule, a null Maybe can also unwrap // from an `undefined` value explicitly passed to a non-optional nullable. // // There are two main use cases for Optional: optional function/method parameters and optional // JSG_STRUCT members. In both cases, a null value in C++ corresponds to the parameter/field not // being present at all in JavaScript, or explicitly set to `undefined`. // // In Web IDL, function parameters are considered required unless marked `optional`, while // dictionary (JSG_STRUCT) members are considered optional unless marked `required`. So, if you // were implementing an API specified in Web IDL like so: // // dictionary Data { // double number; // required DOMString string; // }; // void foo(optional Data data); // // An appropriate representation in C++ would be: // // struct Data { // Optional number; // kj::String string; // JSG_STRUCT(number, string); // }; // void foo(Optional data); template class Optional: public kj::Maybe { public: // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding // constructor instead. template Optional(Params&&... params): kj::Maybe(kj::fwd(params)...) {} }; // Identical to Optional, but rather than treating failures to unwrap a JS value to type T as an // error, it just results in an unset LenientOptional. template class LenientOptional: public kj::Maybe { public: // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding // constructor instead. template LenientOptional(Params&&... params): kj::Maybe(kj::fwd(params)...) {} }; // Use this type in a JSG_STRUCT to define a special field that will be filled in with a // reference to the original struct's JavaScript representation. This is useful e.g. if you // may need to pull additional fields out of the struct. // // Another option is to use jsg::Identified, but sometimes storing the reference // into a field of the unwrapped struct is more convenient. class SelfRef: public V8Ref { public: using V8Ref::V8Ref; // Convert the V8Ref to a V8Ref inline Value asValue(Lock& js) const; }; template static constexpr bool isUsableStructField = !kj::isSameType() && !kj::isSameType() && !kj::isSameType(); template void jsgAddToStructNames(auto& names) { constexpr const char* exportedName = name + prefix; if constexpr (isUsableStructField) names.add(exportedName); } // A USVString has the exact same representation as a kj::String, but we guarantee that it meets // the WHATWG definition of a "scalar value string". Particularly, a USVString will never contain // invalid surrogate characters. A USVString should be used when implementing a Web API that // requires this behaviour. // See class USVString: public kj::String { public: // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding // constructor instead. template explicit USVString(Params&&... params): kj::String(kj::fwd(params)...) { KJ_DASSERT(isValidUtf8()); } private: // This is a seperate method to avoid including simdutf in the header file. bool isValidUtf8() const; }; // A DOMString has the exact same representation as a kj::String, but may contain WTF-8 encoded // data like unpaired surrogate characters, that are not strictly valid in UTF-8. A DOMString // should be used when implementing a Web API that requires this behaviour, or when an explicit // decision is made to accept potentially invalid strings. class DOMString: public kj::String { public: // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding // constructor instead. template explicit DOMString(Params&&... params): kj::String(kj::fwd(params)...) {} }; // A Dict in C++ corresponds to a JavaScript object that is being used as a string -> value // map, where all the values are of type T. // // Note: A Dict corresponds to a record in the Web IDL language. template struct Dict { // TODO(someday): Maybe make this a map and not an array? Current use case doesn't care, though. // Field of an object. struct Field { Key name; Value value; JSG_MEMORY_INFO(Field) { tracker.trackField("name", name); tracker.trackField("value", value); } }; kj::Array fields; JSG_MEMORY_INFO(Dict) { for (const auto& field: fields) { tracker.trackField(nullptr, field); } } }; template class TypeHandler; // When used as a function argument type, captures all remaining arguments passed to the method, // unwrapping them all as type T. template class Arguments: public kj::Array { public: Arguments(kj::Array&& value): kj::Array(kj::mv(value)) {} using ElementType = T; }; // Is `T` some specialization of `Arguments`? template struct IsArguments_ { static constexpr bool value = false; }; template struct IsArguments_> { static constexpr bool value = true; }; template constexpr bool isArguments() { return IsArguments_::value; } template constexpr bool resourceNeedsGcTracing(); template void visitSubclassForGc(T* obj, GcVisitor& visitor); // All resource types must inherit from this. class Object: private Wrappable { public: using jsgThis = Object; // Objects that extend from jsg::Object should never be copied or moved // independently of their owning jsg::Ref so we explicitly delete the // copy and move constructors and assignment operators to be safe. KJ_DISALLOW_COPY_AND_MOVE(Object); // Since we explicitly delete the copy and move constructors, we have // to explicitly declare the default constructor. Object() = default; inline void jsgVisitForGc(GcVisitor& visitor) override {} // Subclasses should override these to provide appropriate information for // the heap snapshot process. inline kj::StringPtr jsgGetMemoryName() const override { return "Object"; } inline size_t jsgGetMemorySelfSize() const override { return sizeof(Object); } inline void jsgGetMemoryInfo(MemoryTracker& tracker) const override { Wrappable::jsgGetMemoryInfo(tracker); } inline v8::Local jsgGetMemoryInfoWrapperObject(v8::Isolate* isolate) override { return Wrappable::jsgGetMemoryInfoWrapperObject(isolate); } inline bool jsgGetMemoryInfoIsRootNode() const override { return Wrappable::jsgGetMemoryInfoIsRootNode(); } static constexpr bool jsgHasReflection = false; template inline void jsgInitReflection(TypeWrapper& wrapper) {} // Dummy invalid serialization tag. This is only used to detect when a subclass has defined their // own tag. static constexpr uint jsgSerializeTag = kj::maxValue; private: inline void visitForMemoryInfo(MemoryTracker& tracker) const {} inline void visitForGc(GcVisitor& visitor) {} template friend constexpr bool ::workerd::jsg::resourceNeedsGcTracing(); template friend void visitSubclassForGc(T* obj, GcVisitor& visitor); template friend void visitSubclassForMemoryInfo(const T* obj, MemoryTracker& visitor); template friend class Ref; friend class kj::Refcounted; template friend kj::Own kj::addRef(T& object); template friend kj::Own kj::refcounted(Params&&... params); friend class GcVisitor; template friend class TypeWrapper; template friend class ResourceWrapper; template friend class ObjectWrapper; template friend class SelfPropertyReader; friend class MemoryTracker; }; // Ref is a reference to a resource type (a type with a JSG_RESOURCE_TYPE block) living on // the V8 heap. // // Use Ref when you want a long-lived reference to such a type. If you only need a reference // that lasts until your method returns, you can specify the parameter type `T&` instead, which // is more efficient. Use Ref when you need to keep the reference longer than that. // // WARNING: When storing Ref in a C++ object that itself is referenced from the JS heap, // you must implement GC visitation; see GcVisitor, below. // // It is safe to destroy a jsg::Ref object outside of the isolate lock. In this case, // the underlying V8 handles will be added to a queue, to be destroyed the next time a thread // locks the isolate. This means their destruction is non-deterministic, but that is true of V8 // objects anyway, due to the GC. // // Move construction and move assignment of strong jsg::Refs is well-defined even without // holding the isolate lock. That is, it is safe to move Refs unless you have implemented GC // visitation for them. Moving jsg::Refs which are reachable via GC visitation is undefined // behavior outside of an isolate lock. template class Ref { public: Ref(decltype(nullptr)): strong(false) {} Ref(Ref&& other) noexcept: inner(kj::mv(other.inner)), strong(true) { if (other.strong) { other.strong = false; } else { inner->addStrongRef(); } } // Upgrade a KJ allocation to a Ref. This is useful if you want to allocate the object outside // the isolate lock and then bring it in later. The object must be allocated with // kj::refcounted. Once the Ref is constructed, the refcount is protected by the isolate lock // going forward; you can no longer add or remove refs outside the lock. explicit Ref(kj::Own innerParam): inner(kj::mv(innerParam)), strong(true) { inner->addStrongRef(); } template ()>> Ref(Ref&& other) noexcept: inner(kj::mv(other.inner)), strong(true) { if (other.strong) { other.strong = false; } else { inner->addStrongRef(); } } template Ref& operator=(Ref&& other) { destroy(); inner = kj::mv(other.inner); strong = true; if (other.strong) { other.strong = false; } else { inner->addStrongRef(); } return *this; } ~Ref() noexcept(false) { destroy(); } KJ_DISALLOW_COPY(Ref); T& operator*() { return *inner; } T* operator->() { return inner.get(); } T* get() { return inner.get(); } const T& operator*() const { return *inner; } const T* operator->() const { return inner.get(); } const T* get() const { return inner.get(); } Ref addRef() & { return Ref(kj::addRef(*inner)); } Ref addRef() && = delete; // would be redundant // If the object has a JS wrapper, return it. Note that the JS wrapper is initialized lazily // when the object is first passed to JS, so you can't be sure that it exists. To reliably // get a handle (creating it on-demand if necessary), use a TypeHandler>. kj::Maybe> tryGetHandle(v8::Isolate* isolate) { return inner->tryGetHandle(isolate); } kj::Maybe> tryGetHandle(Lock& js); // Attach a JavaScript object which implements the JS interface for this C++ object. Normally, // this happens automatically the first time the Ref is passed across the FFI barrier into JS. // This method may be useful in order to use a different wrapper type than the one that would // be used automatically. This method is also useful when implementing TypeWrapperExtensions. // // It is an error to attach a wrapper when another wrapper is already attached. Hence, // typically this should only be called on a newly-allocated object. void attachWrapper(v8::Isolate* isolate, v8::Local object) { inner->Wrappable::attachWrapper(isolate, object, resourceNeedsGcTracing()); } private: kj::Own inner; // If this has ever been traced, the parent object from which the trace originated. This is kept // for debugging purposes only -- there should only ever be one parent for a particular ref. // // This field does NOT move when the Ref moves, because it's a property of the specific Ref // location. kj::Maybe parent; // True if the ref is currently counted in the target's strong refcount. bool strong; void destroy() { if (auto ptr = inner.get(); ptr != nullptr) { inner->maybeDeferDestruction(strong, kj::mv(inner), static_cast(ptr)); } } template friend class Ref; template friend Ref alloc(Params&&... params); friend class Lock; template friend Ref _jsgThis(U* obj); template friend class ResourceWrapper; template friend class ObjectWrapper; friend class GcVisitor; }; template void MemoryTracker::trackField( kj::StringPtr edgeName, const Ref& value, kj::Maybe nodeName) { trackField(edgeName, value.get(), nodeName); } template // TODO(js.alloc): When most of the jsg::alloc users are updated we can uncomment // the deprecation here. When all uses are updated to use js.alloc, we can remove // this method entirely. //[[deprecated("Use js.alloc(...) instead")]] Ref alloc(Params&&... params) { return Ref(kj::refcounted(kj::fwd(params)...)); } template Ref _jsgThis(T* obj) { return Ref(kj::addRef(*obj)); } #define JSG_THIS (::workerd::jsg::_jsgThis(this)) // Holds a value of type `T` and allows it to be passed to JavaScript multiple times, resulting // in exactly the same JavaScript object each time (will compare equal using `===`). You may // pass `MemoizedIdentity` by reference, e.g. you could define a method of a JSG_RESOURCE_TYPE // which returns `MemoizedIdentity&`, returning a reference to a member of the object. // // Note that you don't need to wrap `jsg::Ref` this way, as it already has the property that // only one wrapper will be created. `MemoizedIdentity` can wrap any type that is convertible to // JavaScript, including types that are otherwise pass-by-value. template class MemoizedIdentity { public: inline MemoizedIdentity(T value): value(kj::mv(value)) {} inline MemoizedIdentity& operator=(T value) { this->value = kj::mv(value); return *this; } void visitForGc(GcVisitor& visitor); JSG_MEMORY_INFO(MemoizedIdentity) { KJ_SWITCH_ONEOF(value) { KJ_CASE_ONEOF(val, T) { if constexpr (MemoryRetainer) { tracker.trackField("value", val); } else { tracker.trackFieldWithSize("value", sizeof(T)); } } KJ_CASE_ONEOF(val, Value) { tracker.trackField("value", val); } } } private: kj::OneOf value; template friend class MemoizedIdentityWrapper; friend class MemoryTracker; }; // Accept this type from JavaScript when you want to receive an object's identity in addition to // unwrapping it. This is useful, for example, if you need to be able to recognize when the // application passes in the same object again later. // // `T` must be a type whose JavaScript representation is an Object (including Functions), since // other types do not have a notion of identity-equality. template struct Identified { // Handle to the original object. HashableV8Ref identity; // The object's unwrapped value. T unwrapped; JSG_MEMORY_INFO(Identified) { tracker.trackField("identity", identity); if constexpr (MemoryRetainer) { tracker.trackField("unwrapped", unwrapped); } else { tracker.trackFieldWithSize("unwrapped", sizeof(T)); } } }; // jsg::Name represents a value that is either a string or a v8::Symbol. It is most useful for // use in APIs that can accept both interchangeably. // // Name implements hashCode() so it is suitable for use as a key in kj::HashMap, etc. class Name final { public: explicit Name(kj::String string); explicit Name(kj::StringPtr string); explicit Name(Lock& js, v8::Local symbol); KJ_DISALLOW_COPY(Name); Name(Name&&) = default; Name& operator=(Name&&) = default; inline int hashCode() const { return hash; } Name clone(jsg::Lock& js); kj::String toString(jsg::Lock& js); JSG_MEMORY_INFO(Name) { KJ_SWITCH_ONEOF(inner) { KJ_CASE_ONEOF(str, kj::String) { tracker.trackField("inner", str); } KJ_CASE_ONEOF(sym, V8Ref) { tracker.trackField("inner", sym); } } } private: int hash; kj::OneOf> inner; kj::OneOf> getUnwrapped(v8::Isolate* isolate); friend class NameWrapper; void visitForGc(GcVisitor& visitor); friend class MemoryTracker; }; // jsg::Function behaves much like kj::Function, but can be passed to/from JS. It works in // both directions: you can receive a jsg::Function from JavaScript and call it from C++, and you // can also initialize a jsg::Function from a C++ lambda and pass it back to JavaScript. // // Since the function could be backed by JavaScript, when calling it, you must always pass // `jsg::Lock&` as the first parameter. When implementing a `jsg::Function` using a C++ lambda, // the lambda should similarly take `jsg::Lock&` as the first parameter. Note that this first // parameter is not declared in the function's signature. For example, `jsg::Function` // declares a function that accepts a parameter of type int and returns an int. However, when // actually calling it, you must still pass `jsg::Lock&`, with the `int` as the second parameter. // (Of course, from the JavaScript side, the lock parameter is hidden, and the `int` is in fact // the first parameter.) // // jsg::Function can be visited using a GcVisitor. If a jsg::Function is initialized from a // C++ functor object that happens to have a public method `visitForGc(jsg::GcVisitor&)`, then // it will arrange for that method to be called during GC tracing. // // Note that, obviously, a normal C++ lambda cannot have a `visitForGc()` method. So when writing // a visitable function in C++, you have to write out a struct or class with an `operator()` // method and a `visitForGc()` method. That's a bit of a pain, so the macro JSG_VISITABLE_LAMBDA() // is provided to assist. This lets you write something like a lambda expression where some of the // captured variables can be GC visited. Example: // // jsg::Function myFunc = // JSG_VISITABLE_LAMBDA((foo = getFoo(), bar, &baz), // (foo, baz.handle), // (jsg::Lock& js, int param) { // // ... body of function ... // }); // // The first parameter to JSG_VISITABLE_LAMBDA is your capture list, in exactly the syntax that a // regular lambda would use, except in parentheses instead of square brackets. The second // parameter is a parenthesized list of visitation expressions. This will literally be used as a // parameter list to `gcVisitor.visit()`, e.g. in the above example // `gcVisitor.visit(foo, baz.handle)` will be called when visited. Finally, the third parameter // is the rest of the lambda expression -- parameter list followed by body block. template class Function; // Use this to unwrap a JavaScript function that should be called as a constructor (with `new`). // The return type in this case is the constructed type. `Constructor` is a subclass of `Function`; // it can be used in all the same ways. template class Constructor; // jsg::Promise wraps a JavaScript promise. Use it when you want to pass Promises to or from // JavaScript. // // jsg::Promise offers a `.then()` method which looks a lot like kj::Promise's similar // function, except that you must pass `Lock&` to it, and it passes `Lock&` back to the callback: // // Promise promise = ...; // Promise promise2 = promise.then(js, // [](Lock& js, int val) { return kj::str(val); }) // // Unlike kj::Promise, jsg::Promises run on the V8 microtask loop, NOT on the KJ event loop. That // implies that the isolate is already locked and active during callbacks, and control does not // return to the KJ event loop at all if a promise continuation is immediately runnable. // // `.catch_()` and two-argument `.then()` are supported. Thrown exceptions are represented using // `jsg::Value`, since technically JavaScript allows throwing any type. // // The type T does not have to be convertible to/from JavaScript unless a Promise is actually // passed to/from JavaScript. That is, you can have an intermediate Promise where U is a type // that has no JavaScript representation. What actually happens is, when a Promise is passed // from JS into C++, JSG adds a .then() which unwraps the value T, and when a Promise is // passed back to JS, JSG adds a .then() to wrap the value again. // // If the type T is GC visitable (i.e. it is a type that you could pass to GcVisitor::visit()), // then the system will arrange to correctly visit it when the T is wrapped in a Promise. // Additionally, if a continuation function passed to `.then()` is GC-visitable, it will similarly // be visited. JSG_VISITABLE_LAMBDA is a useful in conjunction with `.then()` (see jsg::Function, // above). // // Unlike KJ promises, dropping a jsg::Promise does not cancel it. However, like a KJ promise, // a jsg::Promise can only have `.then()` called on it once; the continuation consumes the value. // This is so that pass-by-move C++ types can safely be passed through jsg::Promises. Of course, // once returned to JavaScript, JS code is free to call `.then()` as many times as it wants; this // restriction only applies to calling `.then()` in C++. // // When a JSG method returns a Promise, the system ensures that the object on which the method // was called will not be GC'ed until the Promise resolves (or is itself GC'ed, indicating it will // never resolve). This is a convenience so that method implementations that return promises do // not need to carefully capture a reference to `JSG_THIS`. // // You can construct an immediate Promise value using js.resolvedPromise() and // js.rejectedPromise() (see below). // // You can also create a promise/resolver pair: // // auto [promise, resolver] = js.newPromiseAndResolver(); // resolver.resolve(js, kj::str(foo)); // // The Promise exposes a markAsHandled() API that will mark JavaScript Promise such that rejections // are not reported to the isolate's unhandled rejection tracking mechanisms. Importantly, any then // then() or catch_() continuation on either type will return an unhandled Promise. But, any // whenResolved() continuation, and any type handler continuations added internally will be // automatically marked handled. Use of markAsHandled() should be rare. It is largely used by Web // Platform APIs in certain cases where consumption of a promise is optional, or where a promise // rejection is likely to be surfaced via multiple promises (and therefore only needs to be handled // once). template class Promise; template struct PromiseResolverPair; // Convenience template to detect a `jsg::Promise` type. template struct IsPromise_ { static constexpr bool value = false; }; template struct IsPromise_> { static constexpr bool value = true; }; template constexpr bool isPromise() { return IsPromise_::value; } // Convenience template to strip off `jsg::Promise`. template struct RemovePromise_ { using Type = T; }; template struct RemovePromise_> { using Type = T; }; template using RemovePromise = RemovePromise_::Type; // Convenience template to add `jsg::Promise` if it is not present. template struct MaintainPromise_ { using Type = Promise; }; template struct MaintainPromise_> { using Type = Promise; }; template using MaintainPromise = MaintainPromise_::Type; // Convenience template to calculate the return type of a function when passed parameter type T. // `T = void` is understood to mean no parameters. template struct ReturnType_; template struct ReturnType_ { using Type = decltype(kj::instance()(kj::instance())); }; template struct ReturnType_ { using Type = decltype(kj::instance()(kj::instance(), kj::instance())); }; template struct ReturnType_ { using Type = decltype(kj::instance()()); }; template struct ReturnType_ { using Type = decltype(kj::instance()(kj::instance())); }; template using ReturnType = ReturnType_::Type; // Convenience template to produce a promise for the result of calling a function with the given // parameter type. This wraps the function's result type in `jsg::Promise` UNLESS the function // already returns a `jsg::Promise`, in which case the type is unchanged. // TODO(cleanup): The passLock = false variation is currently only used for js.evalNow(). // It would be nice to refactor that a bit so we can clean up this template and simplify. template using PromiseForResult = MaintainPromise>; // All types declared with JSG_RESOURCE_TYPE which are intended to be used as the global object // must inherit jsg::ContextGlobal, in addition to inheriting jsg::Object // (or a subclass of jsg::Object). // jsg::Object should always be the first inherited class, and jsg::ContextGlobal second. // The lifetime of the global object matches the lifetime of the JavaScript context. class ContextGlobal { public: ContextGlobal() {} KJ_DISALLOW_COPY_AND_MOVE(ContextGlobal); const capnp::SchemaLoader& getSchemaLoader(); private: // This opaque owner is used to keep the ModuleRegistry alive as long as the ContextGlobal // object is alive. This may be the legacy or new module registry, depending which one is // in use. We don't care about the actual type here, just that it is kept alive. kj::Own moduleRegistryBackingOwner; kj::Maybe schemaLoader; void setModuleRegistryBackingOwner(kj::Own registry) { moduleRegistryBackingOwner = kj::mv(registry); } void setSchemaLoader(const capnp::SchemaLoader& schemaLoader); template friend class ResourceWrapper; }; // Reference to a JavaScript context whose global object wraps a C++ object of type T. This is // similar to Ref but not the same, since JsContext provides access to the Context itself, // which is more than just the global object. template class JsContext { public: static_assert( std::is_base_of_v, "context global type must extend jsg::ContextGlobal"); JsContext(v8::Local handle, Ref object) : handle(v8::Isolate::GetCurrent(), handle), object(kj::mv(object)) {} JsContext(JsContext&&) = default; KJ_DISALLOW_COPY(JsContext); T& operator*() { return *object; } T* operator->() { return object.get(); } v8::Local getHandle(v8::Isolate* isolate) const { return handle.Get(isolate); } v8::Local getHandle(Lock& js) const; private: v8::Global handle; Ref object; }; class BufferSource; constexpr bool hasPublicVisitForGc_(...) { return false; } template constexpr bool hasPublicVisitForGc_(T*) { return true; } template constexpr bool hasPublicVisitForGc() { return hasPublicVisitForGc_(static_cast(nullptr)); } // Visitor used during garbage collection. Any resource class that holds `Ref`s should // implement GC visitation by declaring a private method like: // // private: // void visitForGc(GcVisitor& visitor); // // In this method, call visitor.visit() on each `Ref` owned by the object. // // A `visitForGc()` method does NOT need to handle visiting superclasses. The JSG framework will // automatically discover the presence of `visitForGc()` in each class in the hierarchy and will // arrange for them all to be called. (Thus, when adding a new `visitForGc()` method to a class // that has many subclasses, there is no need to update the subclasses.) // // Functors (freestanding functions/callbacks/lambdas, not declared as resources) can also // implement GC visitation. To do so, implement the function as a struct with `operator()`, and // also give the function a `visitForGc()` method. In this case, `visitForGc()` must be public. // // GC visitation is optional. If your type owns no `Ref`s, it can skip implementing // `visitForGc()`. You can also omit `visitForGc()` if you don't care about the possibility of // reference cycles. Any `Ref` which is not explicitly visited will not be eligible for // garbage collection at all. Hence, failure to implement proper visitation may lead to memory // leaks, but NOT to use-after-free. // // Note that GC visitation technically only collects JavaScript objects, including wrapper // objects. C++ objects will not be collected if they contain reference cycles entirely in C++ // land. That is, if you have two C++ objects that contain `Ref`s to each other, and you // implement GC visitation, the JavaScript wrapper objects wrapping these C++ objects will be // collected, but the C++ objects will not -- a `Ref` can never becomes "dangling", and // therefore the C++ objects cannot be destroyed because there's no correct order in which to // destroy them. To avoid this situation, make sure your C++ objects have clear ownership, so // that the reference graph is a DAG, just like you always would in C++. class GcVisitor { public: template void visit(Ref& ref) { ref.inner->visitRef(*this, ref.parent, ref.strong); } template void visit(kj::Maybe>& maybeRef) { KJ_IF_SOME(ref, maybeRef) { visit(ref); } } void visit(Data& data); /// Visit a raw `v8::Global` + `v8::TracedReference` pair, /// implementing the same strong↔traced dual-mode switching as `visit(Data&)`. /// /// Used by the Rust JSG FFI to support `v8::Global` fields on Rust /// resources without a full `jsg::Data` wrapper. void visit(v8::Global& strong, v8::TracedReference& traced); void visit(kj::Maybe& maybeData) { KJ_IF_SOME(data, maybeData) { visit(data); } } template void visit(V8Ref& value) { visit(static_cast(value)); } template void visit(kj::Maybe>& maybeValue) { KJ_IF_SOME(value, maybeValue) { visit(value); } } void visit(BufferSource& bufferSource); template ()>()> void visit(T& supportsVisit) { supportsVisit.visitForGc(*this); } template ()>()> void visit(kj::Maybe& maybeSupportsVisit) { KJ_IF_SOME(supportsVisit, maybeSupportsVisit) { supportsVisit.visitForGc(*this); } } void visit() {} template void visit(T& t, U& u, Args&... remaining) { visit(t); visit(u, kj::fwd(remaining)...); } void visitAll(auto& collection) { for (auto& item: collection) { visit(item); } } private: Wrappable& parent; kj::Maybe cppgcVisitor; explicit GcVisitor(Wrappable& parent, kj::Maybe cppgcVisitor) : parent(parent), cppgcVisitor(cppgcVisitor) {} KJ_DISALLOW_COPY_AND_MOVE(GcVisitor); friend class Wrappable; friend class Object; friend class HeapTracer; }; constexpr bool isGcVisitable_(...) { return false; } template ().visit(kj::instance()))> constexpr bool isGcVisitable_(T*) { return true; } template constexpr bool isGcVisitable() { return isGcVisitable_(static_cast(nullptr)); } // TypeHandler translates between V8 values and local values for a particular type T. // // When you define a function or method that is to be wrapped by V8, you can append TypeHandler // references to your argument list, and they will automatically be filled in by the caller. // This allows you to manually manage objects of this type in your code. For example, you could // use this to manually test two different possible input types: // // void myMethod(v8::Local handle, // const TypeHandler& wrapper, // const TypeHandler& wrapper) { // KJ_IF_SOME(value1, wrapper.tryUnwrap(handle)) { // value1.someMyType1Method(); // } KJ_IF_SOME(value2, wrapper.tryUnwrap(handle)) { // value2.someMyType2Method(); // } // } // // To use a JSG_RESOURCE_TYPE in the TypeHandler, it must be listed in your isolate type's // JSG_DECLARE_ISOLATE_TYPE declaration. See JSG_DECLARE_ISOLATE_TYPE in setup.h for info. // For resource types, also need to wrap in Ref, i.e. `TypeHandler>`. template class TypeHandler { public: // --------------------------------------------------------------------------- // Interface for value types (i.e. types not declared using JSG_RESOURCE_TYPE). // // This includes builtin types, e.g. `double` or `kj::String`. // // These methods will fail for resource types. // Wrap by value. virtual v8::Local wrap(Lock& js, T value) const = 0; // Unwrap by value. Returns null if not the right type. virtual kj::Maybe tryUnwrap(Lock& js, v8::Local handle) const = 0; }; // Utility that allows C++ code in a resource type to examine properties that have been added to // its JavaScript wrapper. // // To use this, add a member of type `PropertyReflection` to your resource type, then after // your JSG_RESOURCE_TYPE block (NOT inside it; at the class scope), write // `JSG_REFLECTION(name)`. You will then be able to use the reflection to read properties // set on the JavaScript side, interpreting them as the type `T`. // // class Foo: public jsg::Object { // public: // ... // JSG_RESOURCE_TYPE(EventTarget) { // ... // } // JSG_REFLECTION(intReader, stringReader); // private: // PropertyReflection intReader; // PropertyReflection stringReader; // } // // PropertyReflection's trick is that it isn't initialized until the JavaScript wrapper is // created. Until that point, get() just always returns nullptr. // // PropertyReflection's main use case is reading event handler `onfoo` properties. That is, // traditionally, instead of using `obj.addEventListener("foo", func)` to register an event // handler, you can also do `obj.onfoo = func`. template class PropertyReflection { public: // Read the property of this object called `name`, unwrapping it as type `T`. kj::Maybe get(Lock& js, kj::StringPtr name); // Read the property of this object called `name`, unwrapping it as type `T`. kj::Maybe get(v8::Isolate* isolate, kj::StringPtr name) { v8::HandleScope scope(isolate); KJ_IF_SOME(s, self) { KJ_IF_SOME(h, s.tryGetHandle(isolate)) { return unwrapper(isolate, h, name); } } return kj::none; } // TODO(someday): Support for reading Symbols and Privates? private: kj::Maybe self; using Unwrapper = kj::Maybe(v8::Isolate*, v8::Local object, kj::StringPtr name); Unwrapper* unwrapper = nullptr; template friend class TypeWrapper; }; template concept CoercibleType = kj::isSameType() || kj::isSameType() || kj::isSameType() || kj::isSameType() || kj::isSameType(); // When updating this list, be sure to keep the corresponding checks in the NonCoercibleWrapper // class in value.h updated as well. // By default types in JavaScript can be implicitly converted to other types as needed. This // can lead to surprising results. For instance, passing null into an API method that accepts // string will have the null coerced into the string value "null". The NonCoercible type can // be used to disable automatic type coercion in APIs. For instance, NonCoercible // will ensure that any value other than a string will be rejected with a TypeError. // // Here, T can be only one of several types that support coercion: // // * kj::String, jsg::USVString, jsg::DOMString (value must be a string) // * bool (value must be a boolean) // * double (value must be a number) // // It should be pointed out that using NonCoercible runs counter to Web IDL and general // Web Platform API best practices, which use type coercion fairly often. However, in certain // Cloudflare-specific APIs, automatic coercion can cause surprising developer experience // issues. Only use NonCoercible if you have a good reason to disable coercion. When in // doubt, don't use it. template struct NonCoercible { T value; }; // ----------------------------------------------------------------------------- // A Sequence in C++ corresponds to a Sequence IDL type. A sequence is a list of values // that may or may not be an array. The key difference between the kj::Array mapping in // JSG and a jsg::Sequence, is that the jsg::Sequence can be initialized from any object // that exposes an @@iterable symbol. However, when a Sequence is surfaced back up to // JavaScript, it will always be an array. // // At the C++ level, the Sequence itself is just a kj::Array. // // Both jsg::Sequence and jsg::Generator provide the ability to work with synchronous // iterable/generator objects. The key difference is that jsg::Sequence will always // produce a kj::Array of the elements, does not allow for early termination of the // iteration, and does not provide access to the return value. jsg::Generator, on the // other hand, allows performing an action on each individual item, terminating the // iteration early, and retrieving the generators final return value, if any. template struct Sequence; // jsg::Generator wraps a JavaScript synchronous generator. // // jsg::Generator offers a `.forEach()` method that will invoke a callback function for // each individual item produced by the generator: // // Generator generator = ...; // generator.forEach(js, [](Lock& js, int val, GeneratorContext context) { // // Do something with val. // // To exit early from the iteration, either call `context.return_()`, // // which will call the `.return()` method on the underlying generator, // // or throw a JavaScript exception, which will call the `.throw()` // // method on the underlying generator. // }); // // The Generator is intended only to be used when receiving a Generator object as // a parameter. Instances of Generator cannot be passed back out to JavaScript. Refer // to the documentation for JSG_ITERATOR to see how to create and pass Generator/Iterable // objects back out to JavaScript. // // The `.forEach()` method is fully synchronous and will fully consume the generator // before it returns. Calling `.forEach()` a second time on the generator will return // immediately as a non-op. template class Generator; // The jsg::AsyncGenerator wraps a JavaScript asynchronous generator. // // The jsg::AsyncGenerator is similar to jsg::Generator except that it supports // async iteration over the individual elements produced by the generator. The // `.forEach()` method returns a `Promise>>` that is resolved once the // generator as been fully consumed. The callback passed in to `.forEach()` must // also return a `Promise` that is resolved whenever the item has been consumed // and the iterator should advance to the next item. // // AsyncGenerator generator = ...; // generator.forEach(js, [](Lock& js, int val, GeneratorContext context) { // // Do something with val. // // To exit early from the iteration, either call `context.return_()`, // // which will call the `.return()` method on the underlying generator, // // or throw a JavaScript exception, which will call the `.throw()` // // method on the underlying generator. // return js.resolvedPromise(); // }).then(js, [](Lock&, kj::Maybe) { KJ_DBG("DONE!"); }); // // The `.forEach()` method will fully consume the generator, returning a Promise // that is resolved once the generator completes. Calling `.forEach()` a second // time on the generator will return an immediately resolved promise. template class AsyncGenerator; // The jsg::GeneratorContext is used with both jsg::Generator and jsg::AsyncGenerator // to allow for early termination of the generator iteration. template class GeneratorContext; // ----------------------------------------------------------------------------- struct JsgConfig { bool noSubstituteNull = false; bool unwrapCustomThenables = false; bool fetchIterableTypeSupport = false; bool fetchIterableTypeSupportOverrideAdjustment = false; bool fastApiEnabled = false; }; static constexpr JsgConfig DEFAULT_JSG_CONFIG = {}; template static const JsgConfig& getConfig(const Config& config) { if constexpr (kj::isSameType() || kj::canConvert()) { // Returning a reference to a parameter is harmless here since call sites pass in a reference to // config, which they can continue to use if returned here. // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) return config; } else { return DEFAULT_JSG_CONFIG; } } // ----------------------------------------------------------------------------- class IsolateBase; template class Isolate; // Defined in setup.h -- most code doesn't need to use these directly. template constexpr bool isV8Ref(T*) { return false; } template constexpr bool isV8Ref(V8Ref*) { return true; } template constexpr bool isV8Ref() { return isV8Ref(static_cast(nullptr)); } template constexpr bool isV8Local(T*) { return false; } template constexpr bool isV8Local(v8::Local*) { return true; } template constexpr bool isV8Local() { return isV8Local(static_cast(nullptr)); } template constexpr bool isV8MaybeLocal(T*) { return false; } template constexpr bool isV8MaybeLocal(v8::MaybeLocal*) { return true; } template constexpr bool isV8MaybeLocal() { return isV8MaybeLocal(static_cast(nullptr)); } class AsyncContextFrame; template class JsRef; #define JS_V8_SYMBOLS(V) \ V(AsyncIterator) \ V(HasInstance) \ V(IsConcatSpreadable) \ V(Iterator) \ V(Match) \ V(Replace) \ V(Search) \ V(Split) \ V(ToPrimitive) \ V(ToStringTag) \ V(Unscopables) \ V(Dispose) \ V(AsyncDispose) class JsValue; class JsMessage; #define JS_TYPE_CLASSES(V) \ V(Object) \ V(Boolean) \ V(Array) \ V(String) \ V(Symbol) \ V(BigInt) \ V(Number) \ V(Int32) \ V(Uint32) \ V(Date) \ V(RegExp) \ V(Map) \ V(Set) \ V(Promise) \ V(Proxy) \ V(Function) \ V(Uint8Array) \ V(ArrayBuffer) \ V(ArrayBufferView) #define V(Name) class Js##Name; JS_TYPE_CLASSES(V) #undef V // JsBufferSource is not in JS_TYPE_CLASSES because there is no v8::BufferSource // type (and hence no v8::Value::IsBufferSource() check). It is instead handled // with special-case logic in JsValue::tryCast and JsValueWrapper. class JsBufferSource; #define V(Name) || kj::isSameType() template concept IsJsValue = kj::isSameType() || kj::isSameType() JS_TYPE_CLASSES(V) || kj::isSameType(); #undef V class DOMException; class ExternalMemoryAdjustment; // Used to save a reference to an isolate that is responsible for external memory usage. // getAdjustment() can be invoked at any time to create a new RAII adjustment object // pointing to this isolate. // // Each isolate has a singleton `ExternalMemoryTarget`, which all `ExternalMemoryAdjustment`s // point to. The only purpose of this object is to hold a weak reference back to the isolate; the // reference is nulled out when the isolate is destroyed. class ExternalMemoryTarget: public kj::AtomicRefcounted { public: ExternalMemoryTarget(v8::Isolate* isolate): isolate(isolate) {} ExternalMemoryAdjustment getAdjustment(size_t amount) const; // Apply any deferred external memory updates. Must be called with isolate locked. void applyDeferredMemoryUpdate() const; // Disconnects the ExternalMemoryTarget from the isolate (called just before destroying the // isolate). void detach() const; // These two methods are for tests only. bool isIsolateAliveForTest() const; int64_t getPendingMemoryUpdateForTest() const; private: void maybeDeferAdjustment(ssize_t amount) const; void adjustNow(Lock& js, ssize_t amount) const; // Mutable so that it can be set null when the isolate is destroyed. mutable std::atomic isolate; static_assert(std::atomic::is_always_lock_free); // Tracks changes to external memory that were applied from a thread that did not hold the // isolate lock. These will be applied the next time the lock is taken. mutable std::atomic pendingExternalMemoryUpdate = {0}; static_assert(std::atomic::is_always_lock_free); friend class ExternalMemoryAdjustment; }; // RAII class to adjust the amount of external memory attributed to an isolate. // The adjustment will be automatically decremented when the object is destroyed. // The allocation amount can be adjusted up or down during the lifetime of an object. class ExternalMemoryAdjustment final { public: ExternalMemoryAdjustment(kj::Arc externalMemory, size_t amount); ExternalMemoryAdjustment(ExternalMemoryAdjustment&& other) noexcept; ExternalMemoryAdjustment& operator=(ExternalMemoryAdjustment&& other); KJ_DISALLOW_COPY(ExternalMemoryAdjustment); ~ExternalMemoryAdjustment() noexcept(false); // Adjust the amount of external memory report up or down. void adjust(ssize_t amount); // Like adjust, except that the adjustment is applied immediately with no deferral. void adjustNow(Lock& js, ssize_t amount); // Set a specific amount of external memory to be attributed, overriding // the previous amount. void set(size_t amount); // Like set(), except that the adjustment is applied immediately with no deferral. void setNow(Lock& js, size_t amount); inline size_t getAmount() const { return amount; } private: kj::Arc externalMemory; size_t amount = 0; // If the isolate is locked, adjust the external memory immediately. // Otherwise, if we don't have the isolate locked, defer the adjustment to the next // time that we do. void maybeDeferAdjustment(ssize_t amount); }; // If memory protection keys are enabled, provides the ability to run a function // within the scope of a particular protection key associated with the isolate lock. // This class is designed to be movable. class MemoryProtectionKeyScope final { public: KJ_DISALLOW_COPY(MemoryProtectionKeyScope); MemoryProtectionKeyScope(MemoryProtectionKeyScope&&) = default; MemoryProtectionKeyScope& operator=(MemoryProtectionKeyScope&&) = default; auto runWithKey(auto func) { #ifdef V8_ENABLE_SANDBOX PkeyScope scope(pkey); #endif return func(); } private: #ifdef V8_ENABLE_SANDBOX int pkey; MemoryProtectionKeyScope(Lock&); struct PkeyScope { int key; int saved; PkeyScope(int pkey); ~PkeyScope(); }; #else MemoryProtectionKeyScope(Lock&) { // No-op if sandboxing is not enabled. } #endif friend class Lock; }; // Represents an isolate lock, which allows the current thread to execute JavaScript code within // an isolate. A thread must lock an isolate -- obtaining an instance of `Lock` -- before it can // manipulate JavaScript objects or execute JavaScript code inside the isolate. // // The `Lock` interface also provides access to basic JavaScript functionality, such as the // ability to construct basic JS values, throw and catch errors, etc. // // By convention, all functions which manipulate JavaScript take `Lock& js` as their first // parameter. A `Lock&` reference must never be stored as an object member nor captured in a // lambda, as `Lock`s are always constructed on the stack and so their lifetime is never // guaranteed beyond the end of the function call. // // Methods declared with JSG_METHOD and similar macros may optionally take a `Lock&` as the // first parameter. Template magic will automatically discover if the parameter is present and // will populate it. Such methods are always invoked under lock whether or not they have a // `Lock&` parameter, but it is recommended that you declare the parameter if the function // touches the JS heap in any way. This way, if someone wants to call the method directly from // C++, they know whether a lock is required. // // To create a lock in the first place, you have to create a specific instance of // Isolate::Lock. Usually this is only done in top-level code, and the Lock is // passed down to everyone else from there. See setup.h for details. class Lock { public: // The underlying V8 isolate, useful for directly calling V8 APIs. Hopefully, this is rarely // needed outside JSG itself. v8::Isolate* const v8Isolate; template Ref alloc(Params&&... params) { // TODO(soon): While it is possible to create jsg::Object instances outside of the // isolate lock, we intend to change that in order to improve memory accounting and // tracking of objects created while under lock. As such, all instances of jsg::alloc(...) // are to be replaced by js.alloc(...). For now, these are functionally equivalent. return Ref(kj::refcounted(kj::fwd(params)...)); } // Like alloc() but attaches an external memory adjustment of size indicated by `accountedSize`. template Ref allocAccounted(size_t accountedSize, Params&&... params) { return Ref(kj::refcounted(kj::fwd(params)...) .attach(getExternalMemoryAdjustment(accountedSize))); } // When you want to temporarily use a memory allocation that is protected // by the isolate's memory protection key, use this to get a utility that // will capture the key and allow you to run a function with the key enabled. // The key use case is to allow tempporary access outside of the isolate lock // for things like ArrayBuffer backing stores. MemoryProtectionKeyScope getMemoryProtectionKeyScope() { return MemoryProtectionKeyScope(*this); } v8::Local v8Context() { auto context = v8Isolate->GetCurrentContext(); KJ_ASSERT(!context.IsEmpty(), "Isolate has no currently active v8::Context::Scope"); return context; } // Get the current Lock for the given V8 isolate. Segfaults if the isolate is not locked. // // This method is intended to be used in callbacks from V8 that pass an isolate pointer but // don't provide any further context. Most code should rely on the caller passing in a `Lock&`. static Lock& from(v8::Isolate* v8Isolate) { return *reinterpret_cast(v8Isolate->GetData(SET_DATA_LOCK)); } // TODO(someday): A clang-tidy rule to enforce use of Lock::current over // v8::Isolate::GetCurrent would be helpful. static Lock& current() { return from(v8::Isolate::GetCurrent()); } // RAII construct that reports amount of external memory to be manually attributed to // the isolate. When the returned ExtrernalMemoryAdjuster is dropped, the amount will // be subtracted from the isolate's external memory accounting. If the adjuster is // dropped while the isolate lock is not being held, the adjustment will be deferred // until the next time the lock is held. The ExternalMemoryAdjustment itself can be // moved and can be used to increment or decrement the amount of external memory // held. ExternalMemoryAdjustment getExternalMemoryAdjustment(int64_t amount = 0); // Used to save a reference to an isolate that is responsible for external memory usage. // getAdjustment() can be invoked at any time to create a new RAII adjustment object // pointing to this isolate kj::Arc getExternalMemoryTarget(); Value parseJson(kj::ArrayPtr data); Value parseJson(v8::Local text); template kj::String serializeJson(V8Ref& value) { return serializeJson(value.getHandle(*this)); } template kj::String serializeJson(V8Ref&& value) { // Callers expect the rvalue-reference to be consumed, and to ensure // that, explicitly move it into a local variable auto moved = kj::mv(value); return serializeJson(moved.getHandle(*this)); } void recursivelyFreeze(Value& value); // --------------------------------------------------------------------------- // Exception-related stuff // Converts the KJ exception to a JS exception. If the KJ exception is a tunneled JavaScript // error, this reproduces the original error. If it is not a tunneled error, then it is treated // as an internal error: the KJ exception message is logged to stderr, and a JavaScript error // is returned with a generic description. Value exceptionToJs(kj::Exception&& exception, ExceptionToJsOptions options = {}); JsRef exceptionToJsValue(kj::Exception&& exception, ExceptionToJsOptions options = {}); // Encodes the given JavaScript exception into a KJ exception, formatting the description in // such a way that hopefully exceptionToJs() can reproduce something equivalent to the original // JavaScript error. kj::Exception exceptionToKj(const JsValue& exception); // Encodes the given JavaScript exception into a KJ exception, formatting the description in // such a way that hopefully exceptionToJs() can reproduce something equivalent to the original // JavaScript error. kj::Exception exceptionToKj(Value&& exception); // Throws a JavaScript exception. The exception is scheduled on the isolate, and then an // instance of `JsExceptionThrown` is thrown in C++. All places where JavaScript calls into C++ // via JSG understand how to handle this and propagate the exception back to JavaScript. [[noreturn]] void throwException(Value&& exception); [[noreturn]] void throwException(kj::Exception&& exception, ExceptionToJsOptions options = {}) { throwException(exceptionToJs(kj::mv(exception), options)); } [[noreturn]] void throwException(const JsValue& exception); // Invokes `func()` synchronously, catching exceptions. In the event of an exception, // `errorHandler()` will be called, passing the exception as type `jsg::Value`. // // KJ exceptions are also caught and will be converted to JS exceptions using exceptionToJs(). // // Some kinds of exceptions explicitly will not be caught: // - Exceptions where JavaScript execution cannot continue, such as the "uncatchable exception" // produced by IsolateBase::TerminateExecution(). // - C++ exceptions other than `kj::Exception`, e.g. `std::bad_alloc`. These exceptions are // assumed to be serious enough that they cannot be caught as if they were JavaScript errors, // and instead unwind must continue until C++ catches them. // // func() and errorHandler() must return the same type; the value they return will be returned // from `tryCatch()` itself. template auto tryCatch(Func&& func, ErrorHandler&& errorHandler, // If an exception occurs, convert KJ exceptions to JS exceptions // using these options. ExceptionToJsOptions options = {}) -> decltype(func()) { Value error = nullptr; { v8::TryCatch tryCatch(v8Isolate); try { return func(); } catch (JsExceptionThrown&) { // If tryCatch.HasCaught() is false, it typically means that JsExceptionThrown // was thrown without an exception actually being scheduled on the isolate. // This may happen in particular when the JsExceptionThrown was the result of // TerminateExecution() but V8 has since cleared the terminate flag because all // JavaScript call frames have been unwound. Hence, we want to treat this the // same as if `CanContinue()` returned false. // TODO(cleanup): Do more investigation, maybe explicitly check for the termination // flag or arrange to maintain our own separate termination flag to avoid confusion. if (!tryCatch.CanContinue() || !tryCatch.HasCaught() || tryCatch.Exception().IsEmpty()) { tryCatch.ReThrow(); throw; } error = Value(v8Isolate, tryCatch.Exception()); } catch (kj::Exception& e) { error = exceptionToJs(kj::mv(e), options); } } // We have to make sure the `v8::TryCatch` is off the stack before invoking `errorHandler`, // otherwise the same `TryCatch` will catch any exceptions the error handler throws, ugh. return errorHandler(kj::mv(error)); } // Like tryCatch() but returns a Promise that resolves to the result of func() or // rejects with the result of errorHandler() if an exception is thrown. template Promise tryOrReject(Func&& func) { return tryCatch([&]() -> Promise { return toPromise(func()); }, [&](Value&& error) -> Promise { return rejectedPromise(kj::mv(error)); }); } // --------------------------------------------------------------------------- // Promise-related stuff // Get a pair of a Promise and a Promise::Resolver that resolves the promise. You should // call this like: // // auto [promise, resolver] = js.newPromiseAndResolver(); template PromiseResolverPair newPromiseAndResolver(); // Construct an immediately-resolved promise resolving to the given value. template Promise resolvedPromise(T&& value); // Construct an immediately-resolved promise resolving to the given value. Promise resolvedPromise(); // Construct an immediately-rejected promise throwing the given exception. template Promise rejectedPromise(v8::Local exception); // Construct an immediately-rejected promise throwing the given exception. template Promise rejectedPromise(jsg::Value exception); // Construct an immediately-rejected promise throwing the given exception. template Promise rejectedPromise(kj::Exception&& exception, ExceptionToJsOptions options = {}); // Like above, but return a pure-JS promise, not a typed Promise. JsPromise rejectedJsPromise(jsg::JsValue exception); JsPromise rejectedJsPromise(kj::Exception&& exception, ExceptionToJsOptions options = {}); JsPromise resolvedJsPromise(jsg::JsValue value); // Like `kj::evalNow()`, but returns a jsg::Promise for the result. Synchronous exceptions are // caught and returned as a rejected promise. // // If an exception is caught as a result of TerminateExecution() being called, it is rethrown // to the caller, not encapsulated in a promise. // // Note `func` is NOT expected to take `Lock&` as a parameter, as normally func should be a lambda // that captures `[&]`, so will capture the caller's lock reference. Capturing the lock here is // allowed since `func` is invoked synchronously. template PromiseForResult evalNow(Func&& func); // --------------------------------------------------------------------------- // Name/Symbol stuff // Creates a Name encapsulating a new unique v8::Symbol. Name newSymbol(kj::StringPtr symbol); // Creates a Name encapsulating a name from the global symbol registry. // Equivalent to Symbol.for(symbol) in JavaScript. Name newSharedSymbol(kj::StringPtr symbol); // Similar to newSharedSymbol except that it uses a separate isolate registry // that is not accessible by JavaScript. Name newApiSymbol(kj::StringPtr symbol); // --------------------------------------------------------------------------- // Logging stuff inline bool areWarningsLogged() const { return warningsLogged; } // Emits the warning only if there is anywhere for the log to go (for instance, // if debug logging is enabled or the inspector is being used). void logWarning(kj::StringPtr message); // TODO(later): Add the other log variants from IoContext? eg. logWarningOnce, // logErrorOnce, logUncaughtException, etc. // --------------------------------------------------------------------------- // v8 Local handle related stuff // TODO(cleanup): Direct use of v8::Local handles is discouraged and is something we are trying // to move away from. However, there are still plenty of cases where we need to do so. The // methods here help avoid directly using v8::Isolate and serve as an interim until we can // eliminate direct use as much as possible. // Convenience methods to unwrap various types of V8 values. All of these could be done manually // via the V8 API, but these methods are much easier. v8::Local v8Undefined(); v8::Local v8Null(); v8::Local v8Error(kj::StringPtr message); v8::Local v8TypeError(kj::StringPtr message); void v8Set(v8::Local obj, V8Ref& name, Value& value); void v8Set(v8::Local obj, kj::StringPtr name, v8::Local value); void v8Set(v8::Local obj, kj::StringPtr name, Value& value); v8::Local v8Get(v8::Local obj, kj::StringPtr name); v8::Local v8Get(v8::Local obj, uint idx); bool v8Has(v8::Local obj, kj::StringPtr name); bool v8HasOwn(v8::Local obj, kj::StringPtr name); template V8Ref v8Ref(v8::Local local); Data v8Data(v8::Local data); kj::String serializeJson(v8::Local value); v8::Local wrapString(kj::StringPtr text); virtual v8::Local wrapBytes(kj::Array data) = 0; virtual v8::Local wrapSimpleFunction(v8::Local context, jsg::Function& info)> simpleFunction) = 0; // A variation on wrapSimpleFunction that allows for a return value. While the wrapSimpleFunction // implementation passes the FunctionCallbackInfo into the called function, any call to // GetReturnValue().Set(...) to specify a return value will be ignored by the FunctorCallback // wrapper. The wrapReturningFunction variation forces the wrapper to use the version that // pays attention to the return value. virtual v8::Local wrapReturningFunction(v8::Local context, jsg::Function(const v8::FunctionCallbackInfo& info)> returningFunction) = 0; virtual v8::Local wrapPromiseReturningFunction(v8::Local context, jsg::Function(const v8::FunctionCallbackInfo& info)> returningFunction) = 0; // TODO(later): See if we can easily combine wrapSimpleFunction and wrapReturningFunction // into one. virtual v8::Local wrapSimplePromise(Promise promise) = 0; bool toBool(v8::Local value); virtual kj::String toString(v8::Local value) = 0; virtual jsg::Dict> toDict(v8::Local value) = 0; virtual jsg::Dict toDict(const jsg::JsValue& value) = 0; virtual Promise toPromise(v8::Local promise) = 0; // --------------------------------------------------------------------------- // Setup stuff // Use to enable/disable dynamic code evaluation (via eval(), new Function(), or WebAssembly). void setAllowEval(bool allow); void setCaptureThrowsAsRejections(bool capture); void setUsingEnhancedErrorSerialization(); void setUsingFastJsgStruct(); bool isUsingFastJsgStruct() const; bool isUsingEnhancedErrorSerialization() const; void setNodeJsCompatEnabled(); void setNodeJsProcessV2Enabled(); void setRequireReturnsDefaultExportEnabled(); void setThrowOnUnrecognizedImportAssertion(); bool getThrowOnUnrecognizedImportAssertion() const; void setToStringTag(); void setImmutablePrototype(); void setSpecCompliantPropertyAttributes(); void disableTopLevelAwait(); using Logger = void(Lock&, kj::StringPtr); void setLoggerCallback(kj::Function&& logger); using ErrorReporter = void(Lock&, kj::String, const JsValue&, const JsMessage&); void setErrorReporterCallback(kj::Function&& errorReporter); // --------------------------------------------------------------------------- // Misc. Stuff // Sends an immediate request for full GC, this function is to ONLY be used in testing, otherwise // it will throw. If a need for a minor GC is needed look at the call in jsg.c++ and the // implementation in setup.c++. Use responsibly. void requestGcForTesting() const; // Runs the given function synchronously with a v8::HandleScope on the stack. // If the fn returns a v8::Local or v8::MaybeLocal type, then // v8::EscapableHandleScope is used ensuring that the v8::Local return // value is properly handled. auto withinHandleScope(auto&& fn) { using Ret = decltype(fn()); if constexpr (IsJsValue) { v8::EscapableHandleScope scope(v8Isolate); v8::Local value = fn(); return Ret(scope.Escape(value)); } else if constexpr (isV8Local()) { v8::EscapableHandleScope scope(v8Isolate); return scope.Escape(fn()); } else if constexpr (isV8MaybeLocal()) { v8::EscapableHandleScope scope(v8Isolate); return scope.EscapeMaybe(fn()); } else { v8::HandleScope scope(v8Isolate); return fn(); } } virtual Ref domException( kj::String name, kj::String message, kj::Maybe stackValue = kj::none) = 0; // Get the prototype object for the given C++ type (which must be a JSG_RESOURCE_TYPE). // // WARNING: A malicious script can tamper with this by overwriting the `prototype` property // of the class object. template JsObject getPrototypeFor(); // ==================================================================================== JsObject global() KJ_WARN_UNUSED_RESULT; JsValue undefined() KJ_WARN_UNUSED_RESULT; JsValue null() KJ_WARN_UNUSED_RESULT; JsBoolean boolean(bool val) KJ_WARN_UNUSED_RESULT; JsNumber num(double) KJ_WARN_UNUSED_RESULT; JsNumber num(float) KJ_WARN_UNUSED_RESULT; JsInt32 num(int8_t) KJ_WARN_UNUSED_RESULT; JsInt32 num(int16_t) KJ_WARN_UNUSED_RESULT; JsInt32 num(int32_t) KJ_WARN_UNUSED_RESULT; JsUint32 num(uint8_t) KJ_WARN_UNUSED_RESULT; JsUint32 num(uint16_t) KJ_WARN_UNUSED_RESULT; JsUint32 num(uint32_t) KJ_WARN_UNUSED_RESULT; JsBigInt bigInt(int64_t) KJ_WARN_UNUSED_RESULT; JsBigInt bigInt(uint64_t) KJ_WARN_UNUSED_RESULT; JsString str() KJ_WARN_UNUSED_RESULT; JsString str(kj::ArrayPtr) KJ_WARN_UNUSED_RESULT; JsString str(kj::ArrayPtr) KJ_WARN_UNUSED_RESULT; JsString str(kj::ArrayPtr) KJ_WARN_UNUSED_RESULT; JsString str(kj::ArrayPtr) KJ_WARN_UNUSED_RESULT; JsString strIntern(kj::StringPtr) KJ_WARN_UNUSED_RESULT; JsString strExtern(kj::ArrayPtr) KJ_WARN_UNUSED_RESULT; JsString strExtern(kj::ArrayPtr) KJ_WARN_UNUSED_RESULT; JsSymbol symbol(kj::StringPtr) KJ_WARN_UNUSED_RESULT; JsSymbol symbolShared(kj::StringPtr) KJ_WARN_UNUSED_RESULT; JsSymbol symbolInternal(kj::StringPtr) KJ_WARN_UNUSED_RESULT; JsObject obj() KJ_WARN_UNUSED_RESULT; JsObject obj(kj::ArrayPtr keys, kj::ArrayPtr values) KJ_WARN_UNUSED_RESULT; JsObject objNoProto() KJ_WARN_UNUSED_RESULT; JsObject objNoProto( kj::ArrayPtr keys, kj::ArrayPtr values) KJ_WARN_UNUSED_RESULT; JsMap map() KJ_WARN_UNUSED_RESULT; JsValue external(void*) KJ_WARN_UNUSED_RESULT; JsValue error(kj::StringPtr message) KJ_WARN_UNUSED_RESULT; JsValue typeError(kj::StringPtr message) KJ_WARN_UNUSED_RESULT; JsValue rangeError(kj::StringPtr message) KJ_WARN_UNUSED_RESULT; JsDate date(double timestamp) KJ_WARN_UNUSED_RESULT; JsDate date(kj::Date date) KJ_WARN_UNUSED_RESULT; JsDate date(kj::StringPtr date) KJ_WARN_UNUSED_RESULT; // Returns a JsObject that is backed internally by a v8::External object that // takes ownership over the inner. template JsObject opaque(T&& inner) KJ_WARN_UNUSED_RESULT; // Returns a jsg::BufferSource whose underlying JavaScript handle is a Uint8Array. BufferSource bytes(kj::Array data) KJ_WARN_UNUSED_RESULT; // Returns a jsg::BufferSource whose underlying JavaScript handle is an ArrayBuffer // as opposed to the default Uint8Array. May copy and move the bytes if they are // not in the right sandbox. BufferSource arrayBuffer(kj::Array data) KJ_WARN_UNUSED_RESULT; enum class AllocOption { ZERO_INITIALIZED, UNINITIALIZED }; // Utility method to safely allocate a v8::BackingStore with allocation failure handling. // Throws a javascript error if allocation fails. // // IMPORTANT: This method can trigger garbage collection, which may move or invalidate V8 // objects. Do NOT call this method while: // - A v8::String::ValueView is alive (it holds internal V8 heap locks) // - You have raw pointers to V8 heap data (e.g., from view.data8(), view.data16()) // // Safe pattern: Copy V8 string data to off-heap memory FIRST (e.g., via JsString::writeInto() // into kj::SmallArray), THEN call allocBackingStore(). See TextEncoder::encode() for example. std::unique_ptr allocBackingStore( size_t size, AllocOption init_mode = AllocOption::ZERO_INITIALIZED) KJ_WARN_UNUSED_RESULT; enum RegExpFlags { kNONE = v8::RegExp::Flags::kNone, kGLOBAL = v8::RegExp::Flags::kGlobal, kIGNORE_CASE = v8::RegExp::Flags::kIgnoreCase, kMULTILINE = v8::RegExp::Flags::kMultiline, kSTICKY = v8::RegExp::Flags::kSticky, kUNICODE = v8::RegExp::Flags::kUnicode, kDOTALL = v8::RegExp::Flags::kDotAll, kLINEAR = v8::RegExp::Flags::kLinear, kHAS_INDICES = v8::RegExp::Flags::kHasIndices, kUNICODE_SETS = v8::RegExp::Flags::kUnicodeSets, }; JsRegExp regexp(kj::StringPtr pattern, RegExpFlags flags = RegExpFlags::kNONE, kj::Maybe backtrackLimit = kj::none) KJ_WARN_UNUSED_RESULT; template requires(std::assignable_from && ...) JsArray arr(const Args&... args) KJ_WARN_UNUSED_RESULT; JsArray arr(kj::ArrayPtr values) KJ_WARN_UNUSED_RESULT; // Create a JavaScript array from the given kj::ArrayPtr, passing each // item through the given transformation function to create the appropriate // JsValue. template JsArray arr(kj::ArrayPtr values, Func fn) KJ_WARN_UNUSED_RESULT; template requires(std::assignable_from && ...) JsSet set(const Args&... args) KJ_WARN_UNUSED_RESULT; #define V(Name) JsSymbol symbol##Name() KJ_WARN_UNUSED_RESULT; JS_V8_SYMBOLS(V) #undef V void runMicrotasks(); // Request an extra microtask checkpoint after the current one completes. void requestExtraMicrotaskCheckpoint(); // Sets the terminate-execution flag on the isolate so that the next time code tries to run, it // will be terminated. (But note that V8 only checks the flag at certain times, so it's possible // some code will actually execute before termination kicks in.) void terminateNextExecution(); // Terminates exution immediately, forcing V8 to see the flag and react to it before returning. // Always throws JsExceptionThrown. [[noreturn]] void terminateExecutionNow(); bool pumpMsgLoop(); // Logs and reports the error to tail workers (if called within an request), // the inspector (if attached), or to KJ_LOG(Info). virtual void reportError(const JsValue& value) = 0; // Store the worker environment. virtual void setWorkerEnv(V8Ref value) = 0; // Retrieve the worker environment. virtual kj::Maybe> getWorkerEnv() = 0; // Store the worker exports. virtual void setWorkerExports(V8Ref value) = 0; // Retrieve the worker exports. virtual kj::Maybe> getWorkerExports() = 0; // Resolve an internal module namespace from the given specifier. // This variation can be used only for internal built-ins. kj::Maybe resolveInternalModule(kj::StringPtr specifier); // Resolve a user-importable built-in module namespace from the given specifier. // Unlike resolveInternalModule, this only searches user-importable built-ins // (PUBLIC_BUILTIN context), excluding internal-only modules and worker bundle // modules. Use this for user-facing APIs like process.getBuiltinModule() that // must not expose internal modules or return user bundle overrides. // Only valid when the new module registry is in use. kj::Maybe resolvePublicBuiltinModule(kj::StringPtr specifier); // Resolve a module namespace from the given specifier. // This variation includes modules from the worker bundle. kj::Maybe resolveModule( kj::StringPtr specifier, RequireEsm requireEsm = RequireEsm::NO); // Returns the capnp::SchemaLoader for this isolate/context template const capnp::SchemaLoader& getCapnpSchemaLoader() const { return KJ_ASSERT_NONNULL( jsg::getAlignedPointerFromEmbedderData( v8Isolate->GetCurrentContext(), ContextPointerSlot::GLOBAL_WRAPPER)) .getSchemaLoader(); } private: // Mark the jsg::Lock as being disallowed from being passed as a parameter into // a kj promise coroutine. Note that this only blocks directly passing the Lock // in. Types that have the Lock included as a member field won't be caught and // should themselves be marked with KJ_DISALLOW_AS_COROUTINE_PARAM. Note also // that this would not stop someone from passing the v8::Isolate reference into // the coroutine and using `Lock::from(...)` to get the Lock. Don't do that. // jsg::Lock should NOT be used within a kj promise coroutine. KJ_DISALLOW_AS_COROUTINE_PARAM; friend class IsolateBase; template friend class Isolate; Lock(v8::Isolate* v8Isolate); ~Lock() noexcept(false); v8::Locker locker; v8::Isolate::Scope isolateScope; void* previousData; bool warningsLogged; friend class JsObject; virtual kj::Maybe getInstance(v8::Local obj, const std::type_info& type) = 0; virtual v8::Local getPrototypeFor(const std::type_info& type) = 0; }; // Ensures that the given fn is run within both a handlescope and the context scope. // The lock must be assignable to a jsg::Lock, and the context must be or be assignable // to a v8::Local. The context will be evaluated within the handle scope. #define JSG_WITHIN_CONTEXT_SCOPE(lock, context, fn) \ (static_cast(lock)).withinHandleScope([&]() -> auto { \ v8::Local ctx = context; \ KJ_ASSERT(!ctx.IsEmpty(), "unable to enter invalid v8::Context"); \ v8::Context::Scope scope(ctx); \ return fn(static_cast(lock)); \ }) // The V8StackScope is used only as a marker to prove that we are running in the V8 stack // established by calling runInV8Stack(...) class V8StackScope final { public: KJ_DISALLOW_COPY_AND_MOVE(V8StackScope); private: V8StackScope() = default; KJ_DISALLOW_AS_COROUTINE_PARAM; static auto runInV8StackImpl(void* pos, auto callback) __attribute__((noinline)) { #if V8_HAS_STACK_START_MARKER // This currently depends on a V8 patch which hasn't been upstreamed. Note that workerd does // not use this patch; it's only used internally. The patch is needed in order to work around // oddities of our internal environment which do not apply to workerd. For workerd, V8's default // behavior is just fine. v8::StackStartMarker marker(pos); #endif // We create a V8StackScope only as proof that we are running in the V8 stack. V8StackScope stackScope; return callback(stackScope); } friend auto runInV8Stack(auto callback); }; // Ensures that a v8::StackStartMarker is allocated on the stack before calling the callback. // This must be used, for instance, before taking an isolate lock. // The reason why Isolate::Lock doesn't take care of this automatically is because it is often // allocated on the heap. The purpose of using runInV8Stack is to capture the start of the stack // range that V8 must scan when performing conservative stack-scanning garbage collection. auto runInV8Stack(auto callback) { return V8StackScope::runInV8StackImpl(__builtin_frame_address(0), kj::mv(callback)); }; // Returns true if we are currently executing C++ destructors as a result of garbage collection // occurring. bool isInGcDestructor(); // ======================================================================================= // inline implementation details template template V8Ref V8Ref::cast(jsg::Lock& js) { return js.v8Ref(getHandle(js).template As()); } template inline kj::Maybe PropertyReflection::get(Lock& js, kj::StringPtr name) { return get(js.v8Isolate, name); } template inline V8Ref Lock::v8Ref(v8::Local local) { return V8Ref(v8Isolate, local); } inline Data Lock::v8Data(v8::Local local) { return Data(v8Isolate, local); } inline v8::Local Lock::v8Undefined() { return v8::Undefined(v8Isolate); } inline v8::Local Lock::v8Null() { return v8::Null(v8Isolate); } inline Data Data::addRef(jsg::Lock& js) { return Data(js.v8Isolate, getHandle(js)); } template kj::Maybe> Ref::tryGetHandle(Lock& js) { return tryGetHandle(js.v8Isolate); } template inline V8Ref V8Ref::addRef(jsg::Lock& js) { return js.v8Ref(getHandle(js)); } template V8Ref V8Ref::deepClone(jsg::Lock& js) { return js.v8Ref(jsg::deepClone(js.v8Context(), getHandle(js)).template As()); } template inline HashableV8Ref HashableV8Ref::addRef(jsg::Lock& js) { return HashableV8Ref(js.v8Isolate, this->getHandle(js), identityHash); } template inline v8::Local V8Ref::getHandle(jsg::Lock& js) const { return getHandle(js.v8Isolate); } inline v8::Local Data::getHandle(jsg::Lock& js) const { return getHandle(js.v8Isolate); } template inline v8::Local JsContext::getHandle(Lock& js) const { return handle.Get(js.v8Isolate); } inline Value SelfRef::asValue(Lock& js) const { return Value(js.v8Isolate, getHandle(js).As()); } namespace _ { // Helper class for JSG_TRY / JSG_CATCH macros. // // Sets up a v8::TryCatch on construction and converts caught exceptions to jsg::Value. // Handles both JsExceptionThrown (returns V8 exception directly) and kj::Exception // (converts via Lock::exceptionToJs()). // // This class is an implementation detail of the JSG_TRY / JSG_CATCH macros and should // not be used directly. class JsgCatchScope { public: explicit JsgCatchScope(Lock& js); // Converts the in-flight exception to a jsg::Value and stores it. // Called by JSG_CATCH macro. void catchException(ExceptionToJsOptions options = {}); // Returns the caught exception. Must be called after catchException(). Value& getCaughtException() { return KJ_ASSERT_NONNULL(caughtException); } private: Lock& js; // Simple wrapper to work around v8::TryCatch's deleted operator new. struct Holder { v8::TryCatch tryCatch; explicit Holder(v8::Isolate* isolate): tryCatch(isolate) {} }; // We use two separate Maybe members rather than kj::OneOf because v8::TryCatch // has deleted copy/move constructors, making it incompatible with OneOf's internal storage. // The tryCatchHolder is active during the try block and released by catchException(), which // then populates caughtException. // Active during the try block, consumed by catchException(). kj::Maybe tryCatchHolder; // Populated by catchException(), returned by getCaughtException(). kj::Maybe caughtException; }; } // namespace _ // JSG_TRY / JSG_CATCH macros for exception handling in JSG code. // // These macros provide clean exception handling that automatically converts both JavaScript // exceptions (JsExceptionThrown) and KJ exceptions (kj::Exception) to jsg::Value. This is // the recommended way to handle exceptions in JSG code. // // Usage: // JSG_TRY(js) { // someCodeThatMightThrow(); // } JSG_CATCH(exception) { // // `exception` is a jsg::Value& containing the caught exception // return js.rejectedPromise(kj::mv(exception)); // } // // With ExceptionToJsOptions: // JSG_TRY(js) { // someCodeThatMightThrow(); // } JSG_CATCH(exception, {.ignoreDetail = true}) { // // Handle exception with custom conversion options // } // // JSG_TRY(js): Sets up exception handling with the given jsg::Lock. The `js` parameter makes // the isolate explicit and enables future coroutine support. // // JSG_CATCH(name, ...): Catches any exception and converts it to a jsg::Value. The `name` // parameter is a user-chosen identifier that will be a `jsg::Value&` in the handler block. // Optional ExceptionToJsOptions can be passed as a second argument. // // IMPORTANT: The code block following JSG_CATCH is NOT a true catch handler: // - You CANNOT rethrow with `throw` (there is no current exception) // // To rethrow the exception, use: js.throwException(kj::mv(exception)); // Since we have two macros -- JSG_TRY and JSG_CATCH -- which must both access the same state, // we use a hard-coded variable name. This causes benign shadowing in nested JSG_TRY/JSG_CATCHes, // so we disable shadowing warnings. The `_jsg` prefix makes name collision unlikely. #define JSG_TRY(js) \ KJ_SILENCE_SHADOWING_BEGIN \ if (::workerd::jsg::_::JsgCatchScope _jsgTryCatch(js); true) try KJ_SILENCE_SHADOWING_END #define JSG_CATCH(exception, ...) \ catch (...) { \ _jsgTryCatch.catchException(__VA_ARGS__); \ goto KJ_UNIQUE_NAME(_jsgTryCatchHandler); \ } \ else KJ_UNIQUE_NAME(_jsgTryCatchHandler) \ : if (auto& exception = _jsgTryCatch.getCaughtException(); false) {} \ else } // namespace workerd::jsg // clang-format off // These includes are needed for the JSG type glue macros to work. #include "promise.h" #include "modules.h" #include "resource.h" // JSG has very entrenched include cycles // NOLINTNEXTLINE(misc-header-include-cycle) #include "jsvalue.h" // clang-format on // The main JSG API no longer depends on the Type Wrapper, but to avoid extensive changes in // external code using JSG we still want it to be available when including jsg.h. This technically // violates Bazel's encapsulation philosophy (type-wrapper.h should not be visible from jsg.h), so // we only make jsg.h available for external code as part of the main jsg target including type-wrapper.h. #ifndef JSG_IMPLEMENTATION #include #endif // JSG_IMPLEMENTATION