// 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 // Public API for setting up JavaScript context. Only high-level code needs to include this file. #include "async-context.h" #include "jsg.h" #include "v8-platform-wrapper.h" #include #include #include #include #include #include #include #include namespace workerd::jsg { class Deserializer; class Serializer; // Construct a default V8 platform, with the given background thread pool size. // // Passing zero for `backgroundThreadCount` causes V8 to ask glibc how many processors there are. // Now, glibc *could* answer this problem easily by calling `sched_getaffinity()`, which would // not only tell it how many cores exist, but also how many cores are available to this specific // process. But does glibc do that? No, it does not. Instead, it frantically tries to open // `/sys/devices/system/cpu/online`, then `/proc/stat`, then `/proc/cpuinfo`, and parses the text // it reads from whichever file successfully opens to find out the number of processors. Of course, // if you're in a sandbox, that probably won't work. And anyway, you probably don't actually want // V8 to consume all available cores with background work. So, please specify a thread pool size. kj::Own defaultPlatform(uint backgroundThreadCount); // In order to use any part of the JSG API, you must first construct a V8System. You can only // construct one of these per process. This performs process-wide initialization of the V8 // library. class V8System { using PumpMsgLoopType = kj::Function; using ShutdownIsolateType = kj::Function; public: // Uses the default v8::Platform implementation, as if by: // auto v8Platform = jsg::defaultPlatform(); // auto v8System = V8System(*v8Platform, flags); // (Optional) `flags` is a list of command-line flags to pass to V8, like "--expose-gc" or // "--single_threaded_gc". An exception will be thrown if any flags are not recognized. explicit V8System(kj::ArrayPtr flags = nullptr); // Use a possibly-custom v8::Platform wrapper over default v8::Platform, and apply flags. explicit V8System(v8::Platform& platform, kj::ArrayPtr flags, v8::Platform* defaultPlatformPtr); // Use a possibly-custom v8::Platform implementation with custom task queue, and apply flags. explicit V8System(v8::Platform& platform, kj::ArrayPtr flags, PumpMsgLoopType, ShutdownIsolateType); ~V8System() noexcept(false); using FatalErrorCallback = void(kj::StringPtr location, kj::StringPtr message); static void setFatalErrorCallback(FatalErrorCallback* callback); private: kj::Own platformInner; kj::Own platformWrapper; PumpMsgLoopType pumpMsgLoop; ShutdownIsolateType shutdownIsolate; friend class IsolateBase; void init(kj::Own, kj::ArrayPtr, PumpMsgLoopType, ShutdownIsolateType); }; // Base class of Isolate containing parts that don't need to be templated, to avoid code // bloat. class IsolateBase { public: static IsolateBase& from(v8::Isolate* isolate); // Unwraps a JavaScript exception as a kj::Exception. virtual kj::Exception unwrapException( Lock& js, v8::Local context, v8::Local exception) = 0; // Wraps a kj::Exception as a JavaScript Exception. virtual v8::Local wrapException( Lock& js, v8::Local context, kj::Exception&& exception) = 0; // Used by Serializer/Deserializer implementations, calls into DynamicResourceTypeMap // serializerMap and deserializerMap. virtual bool serialize( Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) = 0; virtual kj::Maybe> deserialize( Lock& js, uint tag, Deserializer& deserializer) = 0; // Immediately cancels JavaScript execution in this isolate, causing an uncatchable exception to // be thrown. Safe to call across threads, without holding the lock. void terminateExecution() const; using Logger = Lock::Logger; inline void setLoggerCallback(kj::Badge, kj::Function&& logger) { maybeLogger = kj::mv(logger); } using ErrorReporter = Lock::ErrorReporter; inline void setErrorReporterCallback(kj::Badge, kj::Function&& reporter) { maybeErrorReporter = kj::mv(reporter); } using ModuleFallbackCallback = kj::Maybe>( jsg::Lock&, kj::StringPtr, kj::Maybe, jsg::CompilationObserver&, jsg::ModuleRegistry::ResolveMethod, kj::Maybe); inline void setModuleFallbackCallback(kj::Function&& callback) { maybeModuleFallbackCallback = kj::mv(callback); } inline kj::Maybe&> tryGetModuleFallback() { KJ_IF_SOME(moduleFallbackCallback, maybeModuleFallbackCallback) { return moduleFallbackCallback; } return kj::none; } // Requests an extra microtask checkpoint after the current one completes. inline void requestExtraMicrotaskCheckpoint(kj::Badge) { extraMicrotaskCheckpointRequested = true; } // Returns true if an extra microtask checkpoint was requested since the last // call, and clears the flag. inline bool takeExtraMicrotaskCheckpointRequested(kj::Badge) { bool requested = extraMicrotaskCheckpointRequested; extraMicrotaskCheckpointRequested = false; return requested; } inline void setAllowEval(kj::Badge, bool allow) { if (alwaysAllowEval) return; evalAllowed = allow; } inline void setAllowsAllowEval() { alwaysAllowEval = true; evalAllowed = true; } inline void setCaptureThrowsAsRejections(kj::Badge, bool capture) { captureThrowsAsRejections = capture; } inline void setNodeJsCompatEnabled(kj::Badge, bool enabled) { nodeJsCompatEnabled = enabled; } inline void setNodeJsProcessV2Enabled(kj::Badge, bool enabled) { nodeJsProcessV2Enabled = enabled; } inline void setRequireReturnsDefaultExportEnabled(kj::Badge, bool enabled) { requireReturnsDefaultExportEnabled = enabled; } inline bool areWarningsLogged() const { return maybeLogger != kj::none; } inline bool areErrorsReported() const { return maybeErrorReporter != kj::none; } inline bool isNodeJsCompatEnabled() const { return nodeJsCompatEnabled; } inline bool isNodeJsProcessV2Enabled() const { return nodeJsProcessV2Enabled; } inline bool isRequireReturnsDefaultExportEnabled() const { return requireReturnsDefaultExportEnabled; } inline bool shouldSetToStringTag() const { return setToStringTag; } void enableSetToStringTag() { setToStringTag = true; } inline bool shouldSetImmutablePrototype() const { return shouldSetImmutablePrototypeFlag; } void enableSetImmutablePrototype() { shouldSetImmutablePrototypeFlag = true; } inline bool shouldUseSpecCompliantPropertyAttributes() const { return specCompliantPropertyAttributesFlag; } void enableSpecCompliantPropertyAttributes() { specCompliantPropertyAttributesFlag = true; } inline void disableTopLevelAwait() { allowTopLevelAwait = false; } inline bool isTopLevelAwaitEnabled() const { return allowTopLevelAwait; } // The logger will be optionally set by the isolate setup logic if there is anywhere // for the log to go (for instance, if debug logging is enabled or the inspector is // being used). inline void logWarning(Lock& js, kj::StringPtr message) { KJ_IF_SOME(logger, maybeLogger) { logger(js, message); } } inline void reportError( Lock& js, kj::String desc, const JsValue& error, const JsMessage& message) { KJ_IF_SOME(reporter, maybeErrorReporter) { reporter(js, kj::mv(desc), error, message); } } IsolateObserver& getObserver() { return *observer; } ExternalStringAllocator& getExternalStringAllocator() { return *externalStringAllocator; } // Implementation of MemoryRetainer void jsgGetMemoryInfo(MemoryTracker& tracker) const; kj::StringPtr jsgGetMemoryName() const { return "IsolateBase"_kjc; } size_t jsgGetMemorySelfSize() const { return sizeof(IsolateBase); } bool jsgGetMemoryInfoIsRootNode() const { return true; } // Get an object referencing this isolate that can be used to adjust external memory usage later kj::Arc getExternalMemoryTarget(); // Equivalent to getExternalMemoryTarget()->getAdjustment(amount), but saves an atomic refcount // increment and decrement. ExternalMemoryAdjustment getExternalMemoryAdjustment(int64_t amount) { return externalMemoryTarget->getAdjustment(amount); } AsyncContextFrame::StorageKey& getEnvAsyncContextKey() { return *envAsyncContextKey; } AsyncContextFrame::StorageKey& getExportsAsyncContextKey() { return *exportsAsyncContextKey; } void setUsingNewModuleRegistry() { usingNewModuleRegistry = true; } bool isUsingNewModuleRegistry() const { return usingNewModuleRegistry; } void setThrowOnUnrecognizedImportAssertion() { throwOnUnrecognizedImportAssertion = true; } bool getThrowOnUnrecognizedImportAssertion() const { return throwOnUnrecognizedImportAssertion; } void setUsingEnhancedErrorSerialization() { usingEnhancedErrorSerialization = true; } bool getUsingEnhancedErrorSerialization() const { return usingEnhancedErrorSerialization; } void setUsingFastJsgStruct() { usingFastJsgStruct = true; } bool getUsingFastJsgStruct() const { return usingFastJsgStruct; } bool pumpMsgLoop() { return v8System.pumpMsgLoop(ptr); } // Allows an object to register an that will be dropped when the destroy // queue is drained under the isolate lock. void destroyUnderLock(kj::Own item) { deferDestruction(kj::mv(item)); } v8::Isolate* getIsolate() const { return ptr; } private: template friend class Isolate; static void buildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data); // The internals of a jsg::Ref to be deleted. class RefToDelete { public: RefToDelete(bool strong, kj::Own ownWrappable, Wrappable* wrappable) : strong(strong), ownWrappable(kj::mv(ownWrappable)), wrappable(wrappable) {} ~RefToDelete() noexcept(false) { if (ownWrappable.get() != nullptr && strong) { wrappable->removeStrongRef(); } } RefToDelete(RefToDelete&&) noexcept = default; // Default move ctor okay because ownWrappable.get() will be null if moved-from. KJ_DISALLOW_COPY(RefToDelete); private: bool strong; // Keeps the `wrappable` pointer below valid. kj::Own ownWrappable; Wrappable* wrappable; }; class GlobalToDelete { // wrapper around v8::Global with noexcept move constructor. public: GlobalToDelete(v8::Global handle) noexcept: handle(kj::mv(handle)) {} GlobalToDelete(GlobalToDelete&&) noexcept = default; KJ_DISALLOW_COPY(GlobalToDelete); private: v8::Global handle; }; using Item = kj::OneOf>; V8System& v8System; // TODO(cleanup): After v8 13.4 is fully released we can inline this into `newIsolate` // and remove this member. std::unique_ptr cppHeap; v8::Isolate* ptr; // When true, evalAllowed is true and switching it to false is a no-op. bool alwaysAllowEval = false; bool evalAllowed = false; // The Web Platform API specifications require that any API that returns a JavaScript Promise // should never throw errors synchronously. Rather, they are supposed to capture any synchronous // throws and return a rejected Promise. Historically, Workers did not follow that guideline // and there are a number of async APIs that currently throw. When the captureThrowsAsRejections // flag is set, that old behavior is changed to be correct. bool captureThrowsAsRejections = false; bool asyncContextTrackingEnabled = false; bool nodeJsCompatEnabled = false; bool nodeJsProcessV2Enabled = false; bool requireReturnsDefaultExportEnabled = false; bool setToStringTag = false; bool shouldSetImmutablePrototypeFlag = false; bool specCompliantPropertyAttributesFlag = false; bool allowTopLevelAwait = true; bool usingNewModuleRegistry = false; bool usingEnhancedErrorSerialization = false; bool usingFastJsgStruct = false; bool extraMicrotaskCheckpointRequested = false; // Only used when the original module registry is used. bool throwOnUnrecognizedImportAssertion = false; kj::Maybe> maybeLogger; kj::Maybe> maybeErrorReporter; kj::Maybe> maybeModuleFallbackCallback; // FunctionTemplate used by Wrappable::attachOpaqueWrapper(). Just a constructor for an empty // object with 2 internal fields. v8::Global opaqueTemplate; // Object used as the underlying storage for a workers environment. v8::Global workerEnvObj; // Object used as the underlying storage for a workers exports. v8::Global workerExportsObj; /* *** External Memory accounting *** */ // ExternalMemoryTarget holds a weak reference back to the isolate. ExternalMemoryAjustments // hold references to the ExternalMemoryTarget. This allows the ExternalMemoryAjustments to // outlive the isolate. kj::Arc externalMemoryTarget; // A shared async context key for accessing env kj::Own envAsyncContextKey; // A shared async context key for accessing exports kj::Own exportsAsyncContextKey; // We expect queues to remain relatively small -- 8 is the largest size I have observed from local // testing. static constexpr auto DESTRUCTION_QUEUE_INITIAL_SIZE = 8; // If a queue grows larger than this, we reset it back to the initial size. static constexpr auto DESTRUCTION_QUEUE_MAX_CAPACITY = 10'000; // We use a double buffer for our deferred destruction queue. This allows us to avoid any // allocations in the general, steady state case, and forces us to clear the vector (a O(n) // operation) outside of the queue lock. const kj::MutexGuarded> queue{ DESTRUCTION_QUEUE_INITIAL_SIZE, DESTRUCTION_QUEUE_MAX_CAPACITY}; enum QueueState { ACTIVE, DROPPING, DROPPED }; QueueState queueState = ACTIVE; struct CodeBlockInfo { size_t size = 0; kj::Maybe type; kj::String name; struct PositionMapping { uint instructionOffset; uint sourceOffset; }; kj::Array mapping; // Sorted }; // Maps instructions to source code locations. kj::TreeMap codeMap; explicit IsolateBase(V8System& system, v8::Isolate::CreateParams&& createParams, kj::Own observer, kj::Own externalStringAllocator, v8::IsolateGroup group); ~IsolateBase() noexcept(false); KJ_DISALLOW_COPY_AND_MOVE(IsolateBase); void dropWrappers(kj::FunctionParam drop); bool getCaptureThrowsAsRejections() const { return captureThrowsAsRejections; } // Add an item to the deferred destruction queue. Safe to call from any thread at any time. void deferDestruction(v8::Global item); void deferDestruction(Item item); // Destroy everything in the deferred destruction queue and apply deferred external memory // updates. Called each time a lock is taken. Must be called under the isolate lock. void applyDeferredActions(); static void fatalError(const char* location, const char* message); static void oomError(const char* location, const v8::OOMDetails& details); static v8::ModifyCodeGenerationFromStringsResult modifyCodeGenCallback( v8::Local context, v8::Local source, bool isCodeLike); static bool allowWasmCallback(v8::Local context, v8::Local source); static bool jspiEnabledCallback(v8::Local context); static void jitCodeEvent(const v8::JitCodeEvent* event) noexcept; friend kj::Maybe getJsStackTrace(void* ucontext, kj::ArrayPtr scratch); HeapTracer heapTracer; kj::Own observer; kj::Own externalStringAllocator; friend class Data; friend class Wrappable; friend class HeapTracer; friend class ExternalMemoryTarget; friend bool getCaptureThrowsAsRejections(v8::Isolate* isolate); friend kj::Maybe getJsStackTrace(void* ucontext, kj::ArrayPtr scratch); friend kj::Exception createTunneledException( v8::Isolate* isolate, v8::Local exception); // Get a singleton ObjectTemplate used for opaque wrappers (which have an empty-object interface // in JavaScript). (Called by Wrappable::attachOpaqueWrapper().) // // This returns a FunctionTemplate which should be used as a constructor. That is, you can use // use `->InstanceTemplate()->NewInstance()` to construct an object, and you can pass this to // `FindInstanceInPrototypeChain()` on an existing object to check whether it was created using // this template. static v8::Local getOpaqueTemplate(v8::Isolate* isolate); }; // If JavaScript frames are currently on the stack, returns a string representing a stack trace // through it. The trace is built inside `scratch` without performing any allocation. This is // intended to be invoked from a signal handler. kj::Maybe getJsStackTrace(void* ucontext, kj::ArrayPtr scratch); // Set the location of the pointer cage base for the current isolate. This is only // used by getJsCageBase(). void setJsCageBase(void* cageBase); // Get the location previously set by setJsCageBase() for the current isolate. Returns // a null pointer if there is no current isolate. void* getJsCageBase(); // Class representing a JavaScript execution engine, with the ability to wrap some set of API // classes which you specify. // // To use this, you must declare your own custom specialization listing all of the API types that // you want to support in this JavaScript context. API types are types which have // JSG_RESOURCE_TYPE or JSG_STRUCT declarations, as well as TypeWrapperExtensions. // // To declare a specialization, do: // // JSG_DECLARE_ISOLATE_TYPE(MyIsolateType, MyApiType1, MyApiType2, ...); // // This declares a class `MyIsolateType` which is a subclass of Isolate. You can then // instantiate this class to begin executing JavaScript. // // You can instantiate multiple Isolates which can run on separate threads simultaneously. // // Example usage: // // // Create once per process, probably in main(). // V8System system; // // // Create an isolate with the ability to wrap MyType and MyContextType. // JSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyApiType, MyContextApiType); // MyIsolate isolate(system); // // // Lock the isolate in this thread (creates a v8::Isolate::Scope). // isolate.runInLockScope([&] (MyIsolate::Lock& lock) { // // Create a context based on MyContextType. // v8::Local context = lock.newContext(lock.isolate, MyContextType()); // // // Create an instance of MyType. // v8::Local obj = lock.getTypeHandler().wrap(lock, context, MyType()); // }); // template class Isolate: public IsolateBase { public: // Construct an isolate that requires configuration. `configuration` is a value that all // individual wrappers' configurations must be able to be constructed from. For example, if all // wrappers use the same configuration type, then `MetaConfiguration` should just be that type. // If different wrappers use different types, then `MetaConfiguration` should be some value that // inherits or defines conversion operators to each required type -- or the individual // configuration types must declare constructors from `MetaConfiguration`. // If `instantiateTypeWrapper` is false, then the default wrapper will not be instantiated // and should be instantiated with `instantiateTypeWrapper` before `newContext` is called on // a jsg::Lock of this Isolate. // // If using v8 sandboxing, the group argument controls which isolates share a // sandbox, and which are isolated (as much as possible) in the event of a // heap corruption attack. Note: The isolates in a group are limited to at // most 4Gbytes of V8 heap in all. Groups can be created with // v8::IsolateGroup::Create(). (If using V8 pointer compression, this // requires the enable_pointer_compression_multiple_cages build flag for V8.) // Pass v8::IsolateGroup::Default() as the group to put all isolates in the // same group. template explicit Isolate(V8System& system, v8::IsolateGroup group, MetaConfiguration&& configuration, kj::Own observer, kj::Own externalStringAllocator = defaultExternalStringAllocator(), v8::Isolate::CreateParams createParams = {}, bool instantiateTypeWrapper = true) : IsolateBase(system, kj::mv(createParams), kj::mv(observer), kj::mv(externalStringAllocator), group) { wrappers.resize(1); if (instantiateTypeWrapper) { instantiateDefaultWrapper(kj::fwd(configuration)); } } // Legacy isolate constructor that creates a new IsolateGroup for the new // Isolate. Currently used by non-sandboxing edgeworker, but deprecated. template explicit Isolate(V8System& system, MetaConfiguration&& configuration, kj::Own observer, v8::Isolate::CreateParams createParams = {}, bool instantiateTypeWrapper = true) : IsolateBase(system, kj::mv(createParams), kj::mv(observer), defaultExternalStringAllocator(), v8::IsolateGroup::Create()) { wrappers.resize(1); if (instantiateTypeWrapper) { instantiateDefaultWrapper(kj::fwd(configuration)); } } // Use this constructor when no wrappers have any required configuration. explicit Isolate(V8System& system, kj::Own observer, v8::Isolate::CreateParams createParams = {}) : Isolate(system, v8::IsolateGroup::GetDefault(), nullptr, kj::mv(observer), defaultExternalStringAllocator(), kj::mv(createParams)) {} template void instantiateDefaultWrapper(MetaConfiguration&& configuration) { KJ_DASSERT(wrappers[0].get() == nullptr); auto wrapper = wrapperSpace.construct(ptr, kj::fwd(configuration)); wrapper->initTypeWrapper(); wrappers[0] = kj::mv(wrapper); } ~Isolate() noexcept(false) { dropWrappers([this]() { wrappers.clear(); }); } kj::Exception unwrapException( Lock& js, v8::Local context, v8::Local exception) override { return getWrapperByContext(context)->template unwrap( js, context, exception, jsg::TypeErrorContext::other()); } v8::Local wrapException( Lock& js, v8::Local context, kj::Exception&& exception) override { return getWrapperByContext(context)->wrap( js, context, kj::none, kj::fwd(exception)); } bool serialize( Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) override { auto* wrapper = getWrapperByContext(js); KJ_IF_SOME(func, wrapper->serializerMap.find(type)) { func(*wrapper, js, instance, serializer); return true; } else { return false; } } kj::Maybe> deserialize( Lock& js, uint tag, Deserializer& deserializer) override { auto* wrapper = getWrapperByContext(js); KJ_IF_SOME(func, wrapper->deserializerMap.find(tag)) { return func(*wrapper, js, tag, deserializer); } else { return kj::none; } } // Before you can execute code in your Isolate you must lock it to the current thread by // constructing a `Lock` on the stack. class Lock final: public jsg::Lock { public: // `V8StackScope` must be provided to prove that one has been created on the stack before // taking a lock. Any GC'ed pointers stored on the stack must be kept within this scope in // order for V8's stack-scanning GC to find them. Lock(const Isolate& isolate, V8StackScope&) : jsg::Lock(isolate.ptr), jsgIsolate(const_cast(isolate)) { jsgIsolate.applyDeferredActions(); } KJ_DISALLOW_COPY_AND_MOVE(Lock); KJ_DISALLOW_AS_COROUTINE_PARAM; // Creates a `TypeHandler` for the given type. You can use this to convert between the type // and V8 handles, as well as to allocate instances of the type on the V8 heap (if it is // a resource type). template const TypeHandler& getTypeHandler() { return TypeWrapper::template TYPE_HANDLER_INSTANCE; } // Wrap a C++ value, returning a v8::Local (possibly of a specific type). template auto wrap(v8::Local context, T&& value) { return jsgIsolate.getWrapperByContext(context)->wrap( *this, context, kj::none, kj::fwd(value)); } // Wrap a context-independent value. Only a few built-in types, like numbers and strings, // can be wrapped without a context. template auto wrapNoContext(T&& value) { return jsgIsolate.getWrapperByContext(*this)->wrap(v8Isolate, kj::none, kj::fwd(value)); } // Convert a JavaScript value to a C++ value, or throw a JS exception if the type doesn't // match. template auto unwrap(v8::Local context, v8::Local handle) { return jsgIsolate.getWrapperByContext(context)->template unwrap( *this, context, handle, jsg::TypeErrorContext::other()); } Ref domException( kj::String name, kj::String message, kj::Maybe maybeStack) override { return withinHandleScope([&] { v8::Local tmpl = jsgIsolate.getWrapperByContext(*this)->getTemplate( v8Isolate, static_cast(nullptr)); KJ_DASSERT(!tmpl.IsEmpty()); v8::Local obj = check(tmpl->InstanceTemplate()->NewInstance(v8Context())); v8::Local stackName = str("stack"_kjc); KJ_IF_SOME(stack, maybeStack) { v8::PropertyDescriptor prop(str(stack), true); prop.set_enumerable(true); jsg::check(obj->DefineProperty(v8Context(), stackName, prop)); } else { v8::Exception::CaptureStackTrace(v8Context(), obj); v8::PropertyDescriptor prop; prop.set_enumerable(true); jsg::check(obj->DefineProperty(v8Context(), stackName, prop)); } auto de = alloc(kj::mv(message), kj::mv(name)); de.attachWrapper(v8Isolate, obj); return kj::mv(de); }); } // Returns the constructor function for a given type declared as JSG_RESOURCE_TYPE. // // Note there's a useful property of class constructor functions: A constructor's __proto__ // is set to the parent type's constructor. Thus you can discover whether one class is a // subclass of another by following the __proto__ chain. // // TODO(cleanup): This should return `JsFunction`, but there is no such type. We only have // `jsg::Function<...>` (or perhaps more appropriately, `jsg::Constructor<...>`), but we // don't actually know the function signature so that's not useful here. Should we add a // `JsFunction` that has no signature? template jsg::JsObject getConstructor(v8::Local context) { v8::EscapableHandleScope scope(v8Isolate); v8::Local tpl = jsgIsolate.getWrapperByContext(context)->getTemplate(v8Isolate, static_cast(nullptr)); v8::Local prototype = check(tpl->GetFunction(context)); return jsg::JsObject(scope.Escape(prototype)); } v8::Local wrapBytes(kj::Array data) override { return jsgIsolate.getWrapperByContext(*this)->wrap(v8Isolate, kj::none, kj::mv(data)); } v8::Local wrapSimpleFunction(v8::Local context, jsg::Function& info)> simpleFunction) override { return jsgIsolate.getWrapperByContext(context)->wrap( *this, context, kj::none, kj::mv(simpleFunction)); } v8::Local wrapReturningFunction(v8::Local context, jsg::Function(const v8::FunctionCallbackInfo& info)> returningFunction) override { return jsgIsolate.getWrapperByContext(context)->wrap( *this, context, kj::none, kj::mv(returningFunction)); } v8::Local wrapPromiseReturningFunction(v8::Local context, jsg::Function(const v8::FunctionCallbackInfo& info)> returningFunction) override { return jsgIsolate.getWrapperByContext(context)->wrap( *this, context, kj::none, kj::mv(returningFunction)); } kj::String toString(v8::Local value) override { return jsgIsolate.getWrapperByContext(*this)->template unwrap( *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other()); } jsg::Dict> toDict(v8::Local value) override { return jsgIsolate.getWrapperByContext(*this) ->template unwrap>>( *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other()); } jsg::Dict toDict(const jsg::JsValue& value) override { return jsgIsolate.getWrapperByContext(*this)->template unwrap>( *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other()); } v8::Local wrapSimplePromise(jsg::Promise promise) override { return jsgIsolate.getWrapperByContext(*this)->wrap( *this, v8Context(), kj::none, kj::mv(promise)); } jsg::Promise toPromise(v8::Local promise) override { return jsgIsolate.getWrapperByContext(*this)->template unwrap>( *this, v8Isolate->GetCurrentContext(), promise, jsg::TypeErrorContext::other()); } template JsContext newContextWithWrapper( TypeWrapper* wrapper, NewContextOptions options, Args&&... args) { // TODO(soon): Requiring move semantics for the global object is awkward. This should instead // allocate the object (forwarding arguments to the constructor) and return something like // a Ref. auto context = wrapper->newContext(*this, options, jsgIsolate.getObserver(), static_cast(nullptr), kj::fwd(args)...); jsg::setAlignedPointerInEmbedderData( context.getHandle(v8Isolate), jsg::ContextPointerSlot::EXTENDED_CONTEXT_WRAPPER, wrapper); return context; } // Creates a new JavaScript "context", i.e. the global object. This is the first step to // executing JavaScript code. T should be one of your API types which you want to use as the // global object. `args...` are passed to the type's constructor. template JsContext newContext(NewContextOptions options, Args&&... args) { KJ_DASSERT(!jsgIsolate.wrappers.empty()); KJ_DASSERT(jsgIsolate.wrappers[0].get() != nullptr); return newContextWithWrapper( jsgIsolate.wrappers[0].get(), options, kj::fwd(args)...); } // Creates a new JavaScript "context", i.e. the global object. This is the first step to // executing JavaScript code. T should be one of your API types which you want to use as the // global object. `args...` are passed to the type's constructor. template JsContext newContext(Args&&... args) { return newContext(NewContextOptions{}, kj::fwd(args)...); } template JsContext newContextWithConfiguration( MetaConfiguration&& configuration, NewContextOptions options, Args&&... args) { jsgIsolate.hasExtraWrappers = true; auto& wrapper = jsgIsolate.wrappers.add( kj::heap(jsgIsolate.ptr, kj::fwd(configuration))); return newContextWithWrapper(wrapper.get(), options, kj::fwd(args)...); } void reportError(const JsValue& value) override { auto& js = Lock::from(v8Isolate); KJ_IF_SOME(domException, jsgIsolate.getWrapperByContext(*this)->tryUnwrap( js, v8Context(), value, static_cast(nullptr), kj::none)) { auto desc = kj::str("DOMException(", domException.getName(), "): ", domException.getMessage()); jsgIsolate.reportError(*this, kj::mv(desc), value, JsMessage::create(*this, value)); } else { jsgIsolate.reportError( *this, value.toString(*this), value, JsMessage::create(*this, value)); } } void setWorkerEnv(V8Ref value) override { jsgIsolate.workerEnvObj.Reset(v8Isolate, value.getHandle(*this)); } kj::Maybe> getWorkerEnv() override { if (jsgIsolate.workerEnvObj.IsEmpty()) return kj::none; return v8Ref(jsgIsolate.workerEnvObj.Get(v8Isolate)); } void setWorkerExports(V8Ref value) override { jsgIsolate.workerExportsObj.Reset(v8Isolate, value.getHandle(*this)); } kj::Maybe> getWorkerExports() override { if (jsgIsolate.workerExportsObj.IsEmpty()) return kj::none; return v8Ref(jsgIsolate.workerExportsObj.Get(v8Isolate)); } private: Isolate& jsgIsolate; virtual kj::Maybe getInstance( v8::Local obj, const std::type_info& type) override { auto instance = v8::Local(obj)->FindInstanceInPrototypeChain( jsgIsolate.getWrapperByContext(*this)->getDynamicTypeInfo(v8Isolate, type).tmpl); if (instance.IsEmpty()) { return kj::none; } else { return *reinterpret_cast( instance->GetAlignedPointerFromInternalField(Wrappable::WRAPPED_OBJECT_FIELD_INDEX, static_cast(Wrappable::WRAPPED_OBJECT_FIELD_INDEX))); } } virtual v8::Local getPrototypeFor(const std::type_info& type) override { v8::EscapableHandleScope scope(v8Isolate); auto tmpl = jsgIsolate.getWrapperByContext(*this)->getDynamicTypeInfo(v8Isolate, type).tmpl; auto constructor = JsObject(check(tmpl->GetFunction(v8Context()))); // Note that `constructor.getPrototype()` returns the prototype of the constructor itself, // which is NOT the same as the prototype of the object it constructs. For the latter we // need to access the `prototype` property. auto proto = constructor.get(*this, "prototype"); KJ_ASSERT(proto.isObject()); return scope.Escape(v8::Local(proto).As()); } }; // The func must be a callback with the signature: T(jsg::Lock&) // Be careful not to leak v8 objects outside of the scope. auto runInLockScope(auto func) { return runInV8Stack([&](V8StackScope& stackScope) { Lock lock(*this, stackScope); return lock.withinHandleScope([&] { return func(lock); }); }); } protected: inline TypeWrapper* getWrapperByContext(jsg::Lock& js) { if (KJ_LIKELY(!hasExtraWrappers)) { return wrappers[0].get(); } else { return getWrapperByContext(js.v8Context()); } } inline TypeWrapper* getWrapperByContext(v8::Local context) { if (KJ_LIKELY(!hasExtraWrappers)) { return wrappers[0].get(); } else { KJ_IF_SOME(data, jsg::getAlignedPointerFromEmbedderData( context, ContextPointerSlot::EXTENDED_CONTEXT_WRAPPER)) { return &data; } return wrappers[0].get(); } } private: kj::SpaceFor wrapperSpace; kj::Vector> wrappers; // Needs to be destroyed under lock... // This is just an optimization boolean, when we only have one wrapper we can skip calling // GetAlignedPointerFromEmbedderData and just return wrappers[0]. bool hasExtraWrappers = false; }; } // namespace workerd::jsg