| // Copyright 2026 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "src/dawn/node/napi_v8/napi_v8.h" | |
| #include <algorithm> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <limits> | |
| #include <utility> | |
| #include "src/utils/assert.h" | |
| #include "src/utils/compiler.h" | |
| namespace { | |
| // Hands any exception recorded by napi_throw() to V8 when a native callback returns to JavaScript, | |
| // and clears env->last_exception so it does not stay pending for later calls. Returns true if an | |
| // exception was delivered. | |
| bool DeliverPendingException(napi_env env) { | |
| if (env->last_exception.IsEmpty()) { | |
| return false; | |
| } | |
| v8::Local<v8::Value> exception = env->last_exception.Get(env->isolate); | |
| env->last_exception.Reset(); | |
| if (!env->isolate->IsExecutionTerminating()) { | |
| env->isolate->ThrowException(exception); | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| napi_ref__::napi_ref__(napi_env e, | |
| v8::Local<v8::Value> val, | |
| uint32_t count, | |
| void* native_obj, | |
| napi_finalize fin_cb, | |
| void* fin_hint, | |
| bool wrap_ref, | |
| bool userland_ref) | |
| : env(e), | |
| handle(e->isolate, val), | |
| ref_count(count), | |
| native_object(native_obj), | |
| finalize_cb(fin_cb), | |
| finalize_hint(fin_hint), | |
| is_wrap_ref(wrap_ref), | |
| is_userland_ref(userland_ref) { | |
| if (finalize_cb != nullptr) { | |
| env->finalizable_references.insert(this); | |
| } | |
| if (ref_count == 0) { | |
| SetWeak(); | |
| } | |
| } | |
| napi_ref__::~napi_ref__() { | |
| env->pending_finalizers.erase(this); | |
| ClearFinalizer(); | |
| handle.Reset(); | |
| } | |
| void napi_ref__::SetWeak() { | |
| handle.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); | |
| } | |
| void napi_ref__::ClearWeak() { | |
| handle.ClearWeak<void>(); | |
| } | |
| void napi_env__::DrainFinalizers() { | |
| while (!pending_finalizers.empty()) { | |
| auto it = pending_finalizers.begin(); | |
| napi_ref__* ref = *it; | |
| pending_finalizers.erase(it); | |
| if (ref->finalize_cb == nullptr) { | |
| continue; | |
| } | |
| napi_finalize cb = ref->finalize_cb; | |
| void* native_object = ref->native_object; | |
| void* finalize_hint = ref->finalize_hint; | |
| ref->ClearFinalizer(); | |
| ClearLastError(); | |
| cb(this, native_object, finalize_hint); | |
| if (DeliverPendingException(this)) { | |
| break; | |
| } | |
| } | |
| } | |
| namespace { | |
| void DrainFinalizersV8Callback(const v8::FunctionCallbackInfo<v8::Value>& info) { | |
| auto* env = static_cast<napi_env>( | |
| info.DataV2().As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault)); | |
| env->finalizer_drain_scheduled = false; | |
| env->DrainFinalizers(); | |
| } | |
| } // namespace | |
| // Node-API `napi_finalize` callbacks (https://nodejs.org/api/n-api.html#napi_finalize) receive a | |
| // `napi_env` and are permitted to allocate JavaScript objects and call into the engine. However, | |
| // V8's weak handle callback (`v8::PersistentBase::SetWeak`, | |
| // https://chromium.googlesource.com/v8/v8/+/refs/heads/main/include/v8-persistent-handle.h) runs | |
| // synchronously inside V8's atomic garbage collection pause while the heap is being swept. As | |
| // documented on `v8::WeakCallbackInfo::SetSecondPassCallback` | |
| // (https://chromium.googlesource.com/v8/v8/+/refs/heads/main/include/v8-weak-callback-info.h), the | |
| // initial callback passed to `SetWeak` must call `Reset()` on the weak handle and is prohibited | |
| // from making any other V8 API calls or allocating on the heap; any further work must be deferred | |
| // via `SetSecondPassCallback()`. | |
| // | |
| // Finalization therefore runs in three stages: | |
| // 1. `WeakCallback` (during the GC atomic pause): resets `self->handle`, appends `self` to | |
| // `env->pending_finalizers`, and registers `PostGarbageCollectionCallback` via | |
| // `SetSecondPassCallback()` once per GC cycle. | |
| // 2. `PostGarbageCollectionCallback` (after the GC pause finishes): runs once V8's heap is | |
| // consistent again (`AllowGarbageCollection`). It schedules a single | |
| // `DrainFinalizersV8Callback` task onto `globalThis.setImmediate` (matching Node.js's | |
| // `v8impl::Reference::WeakCallback` -> `SetImmediate` ordering so finalizers run in the event | |
| // loop's check phase rather than inside `Heap::CollectGarbage()`, where V8 prohibits | |
| // arbitrary JavaScript execution). | |
| // 3. `DrainFinalizers` (on the event loop): invokes each queued `napi_finalize` callback in FIFO | |
| // order. | |
| void napi_ref__::WeakCallback(const v8::WeakCallbackInfo<napi_ref__>& data) { | |
| napi_ref__* self = data.GetParameter(); | |
| self->handle.Reset(); | |
| if (self->finalize_cb != nullptr) { | |
| napi_env env = self->env; | |
| env->pending_finalizers.insert(self); | |
| if (!env->post_gc_callback_scheduled && !env->finalizer_drain_scheduled) { | |
| env->post_gc_callback_scheduled = true; | |
| data.SetSecondPassCallback(&napi_ref__::PostGarbageCollectionCallback); | |
| } | |
| } | |
| } | |
| // Invoked by V8 (`GlobalHandles::InvokeSecondPassPhantomCallbacks`) after the garbage collection | |
| // cycle completes (`Heap::NOT_IN_GC`), when V8 heap allocations are permitted again. Schedules | |
| // `DrainFinalizers` onto `globalThis.setImmediate`. | |
| void napi_ref__::PostGarbageCollectionCallback(const v8::WeakCallbackInfo<napi_ref__>& data) { | |
| napi_env env = data.GetParameter()->env; | |
| env->post_gc_callback_scheduled = false; | |
| if (env->pending_finalizers.empty() || env->finalizer_drain_scheduled) { | |
| return; | |
| } | |
| // V8 lifts `DisallowGarbageCollection` before invoking second-pass callbacks, but leaves | |
| // `DisallowJavascriptExecution` active on the isolate; re-enable JS execution so we can call | |
| // `globalThis.setImmediate` to enqueue `DrainFinalizersV8Callback`. | |
| v8::Isolate::AllowJavascriptExecutionScope allow_js(env->isolate); | |
| v8::HandleScope handle_scope(env->isolate); | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::Context::Scope context_scope(ctx); | |
| // `napi_v8` does not depend on `standalone::EventLoop`, so it schedules the batch drain once | |
| // per GC cycle through `globalThis.setImmediate`, and then drains all queued references in C++ | |
| // inside `DrainFinalizers()`. | |
| v8::Local<v8::String> key = v8::String::NewFromUtf8Literal(env->isolate, "setImmediate"); | |
| v8::Local<v8::Value> set_immediate_val; | |
| [[maybe_unused]] bool has_set_immediate = | |
| ctx->Global()->Get(ctx, key).ToLocal(&set_immediate_val) && set_immediate_val->IsFunction(); | |
| DAWN_ASSERT(has_set_immediate); | |
| v8::Local<v8::External> ext = | |
| v8::External::New(env->isolate, env, v8::kExternalPointerTypeTagDefault); | |
| v8::Local<v8::Function> drain_fn; | |
| [[maybe_unused]] bool has_drain_fn = | |
| v8::Function::New(ctx, DrainFinalizersV8Callback, ext).ToLocal(&drain_fn); | |
| DAWN_ASSERT(has_drain_fn); | |
| env->finalizer_drain_scheduled = true; | |
| v8::Local<v8::Value> arg = drain_fn; | |
| (void)set_immediate_val.As<v8::Function>()->Call(ctx, ctx->Global(), 1, &arg); | |
| } | |
| namespace { | |
| // Validates that the environment pointer and all required argument pointers are non-null. | |
| // Clears any previous error on the environment if the environment is valid. | |
| template <typename... Args> | |
| bool ValidateArgs(napi_env env, Args... args) { | |
| if (env == nullptr) { | |
| return false; | |
| } | |
| env->ClearLastError(); | |
| if (((args == nullptr) || ...)) { | |
| env->SetLastError(napi_invalid_arg, "Invalid argument: null pointer"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Converts a JavaScript value into a numeric primitive type using a conversion callback. | |
| template <typename V8Type, typename OutType, typename ConvertFn> | |
| napi_status ExtractNumber(napi_env env, | |
| napi_value value, | |
| OutType* result, | |
| const char* error_message, | |
| ConvertFn&& convert) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::MaybeLocal<V8Type> maybe_num = convert(dawn::napi_v8::ToV8(value), env->GetContext()); | |
| if (maybe_num.IsEmpty()) { | |
| return env->SetLastError(napi_number_expected, error_message); | |
| } | |
| *result = static_cast<OutType>(maybe_num.ToLocalChecked()->Value()); | |
| return napi_ok; | |
| } | |
| // Coerces a JavaScript value into another JavaScript value type. | |
| template <typename V8Type, typename ConvertFn> | |
| napi_status CoerceTo(napi_env env, | |
| napi_value value, | |
| napi_value* result, | |
| const char* error_message, | |
| ConvertFn&& convert) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::MaybeLocal<V8Type> maybe_val = convert(dawn::napi_v8::ToV8(value), env->GetContext()); | |
| if (maybe_val.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, error_message); | |
| } | |
| *result = dawn::napi_v8::ToNapi(maybe_val.ToLocalChecked()); | |
| return napi_ok; | |
| } | |
| // Validates and extracts a v8::Object and its associated Context. | |
| napi_status UnwrapObject(napi_env env, | |
| napi_value object, | |
| v8::Local<v8::Object>* out_obj, | |
| v8::Local<v8::Context>* out_ctx) { | |
| *out_ctx = env->GetContext(); | |
| v8::MaybeLocal<v8::Object> maybe_obj = dawn::napi_v8::ToV8(object)->ToObject(*out_ctx); | |
| if (maybe_obj.IsEmpty()) { | |
| return env->SetLastError(napi_object_expected, "An object was expected"); | |
| } | |
| *out_obj = maybe_obj.ToLocalChecked(); | |
| return napi_ok; | |
| } | |
| // Creates an internalized V8 string from a UTF-8 C string. | |
| napi_status CreateInternalizedName(napi_env env, | |
| const char* utf8name, | |
| v8::Local<v8::String>* out_name) { | |
| if (!ValidateArgs(env, utf8name)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::MaybeLocal<v8::String> maybe_name = | |
| v8::String::NewFromUtf8(env->isolate, utf8name, v8::NewStringType::kInternalized); | |
| if (maybe_name.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create property name"); | |
| } | |
| *out_name = maybe_name.ToLocalChecked(); | |
| return napi_ok; | |
| } | |
| // Generic property setter supporting Local<Value> names or uint32_t indices. | |
| template <typename NameType> | |
| napi_status WriteProperty(napi_env env, | |
| napi_value object, | |
| NameType name, | |
| napi_value value, | |
| const char* error_message) { | |
| if (!ValidateArgs(env, object, value)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Object> obj; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapObject(env, object, &obj, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Maybe<bool> res = obj->Set(ctx, name, dawn::napi_v8::ToV8(value)); | |
| if (!res.FromMaybe(false)) { | |
| return env->SetLastError(napi_generic_failure, error_message); | |
| } | |
| return napi_ok; | |
| } | |
| // Generic property getter supporting Local<Value> names or uint32_t indices. | |
| template <typename NameType> | |
| napi_status ReadProperty(napi_env env, | |
| napi_value object, | |
| NameType name, | |
| napi_value* result, | |
| const char* error_message) { | |
| if (!ValidateArgs(env, object, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Object> obj; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapObject(env, object, &obj, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::MaybeLocal<v8::Value> val = obj->Get(ctx, name); | |
| if (val.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, error_message); | |
| } | |
| *result = dawn::napi_v8::ToNapi(val.ToLocalChecked()); | |
| return napi_ok; | |
| } | |
| // Generic property existence check supporting Local<Value> names or uint32_t indices. | |
| template <typename NameType> | |
| napi_status QueryProperty(napi_env env, | |
| napi_value object, | |
| NameType name, | |
| bool* result, | |
| const char* error_message) { | |
| if (!ValidateArgs(env, object, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Object> obj; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapObject(env, object, &obj, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Maybe<bool> has = obj->Has(ctx, name); | |
| if (has.IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, error_message); | |
| } | |
| *result = has.FromJust(); | |
| return napi_ok; | |
| } | |
| // V8 and Node-API use different callback conventions: | |
| // - V8 callbacks use `void (const v8::FunctionCallbackInfo<v8::Value>&)` and pass user context via | |
| // an attached `v8::External` value in `v8_info.DataV2()`. | |
| // - Node-API callbacks use `napi_value (*)(napi_env, napi_callback_info)` and retrieve user context | |
| // via `napi_get_cb_info()`. | |
| // | |
| // `NativeCallbackTrampoline` acts as the universal bridge: it unpacks the persistent | |
| // `CallbackBinding` from the `v8::External`, constructs a lightweight `napi_callback_info__` stack | |
| // wrapper referencing the active V8 callback arguments, executes the target `napi_callback`, and | |
| // forwards the returned `napi_value` back to V8. | |
| void NativeCallbackTrampoline(const v8::FunctionCallbackInfo<v8::Value>& v8_info) { | |
| v8::Local<v8::External> ext = v8_info.DataV2().As<v8::External>(); | |
| auto* binding = static_cast<CallbackBinding*>(ext->Value(v8::kExternalPointerTypeTagDefault)); | |
| napi_env env = binding->env; | |
| napi_callback_info__ invocation_info; | |
| invocation_info.v8_info = &v8_info; | |
| invocation_info.data = binding->user_data; | |
| v8::HandleScope scope(env->isolate); | |
| env->ClearLastError(); | |
| napi_value return_val = binding->callback(env, &invocation_info); | |
| if (!DeliverPendingException(env) && return_val != nullptr) { | |
| v8_info.GetReturnValue().Set(dawn::napi_v8::ToV8(return_val)); | |
| } | |
| } | |
| // Base helper to create a `v8::FunctionTemplate` from a v8::Name. | |
| // To ensure the callback function pointer and user data remain valid across the environment's | |
| // lifetime, an internal `CallbackBinding` structure is allocated and stored in | |
| // `env->callback_bindings`. A pointer to this binding is attached to the `v8::FunctionTemplate` as | |
| // a `v8::External`. | |
| napi_status CreateFunctionTemplate(napi_env env, | |
| v8::Local<v8::Name> name, | |
| napi_callback callback, | |
| void* user_data, | |
| v8::Local<v8::FunctionTemplate>* out_template) { | |
| auto binding = std::make_unique<CallbackBinding>(); | |
| binding->env = env; | |
| binding->callback = callback; | |
| binding->user_data = user_data; | |
| CallbackBinding* binding_ptr = binding.get(); | |
| env->callback_bindings.push_back(std::move(binding)); | |
| v8::Local<v8::External> ext = | |
| v8::External::New(env->isolate, binding_ptr, v8::kExternalPointerTypeTagDefault); | |
| auto function_template = v8::FunctionTemplate::New(env->isolate, NativeCallbackTrampoline, ext); | |
| if (!name.IsEmpty() && name->IsString()) { | |
| function_template->SetClassName(name.As<v8::String>()); | |
| } | |
| *out_template = function_template; | |
| return napi_ok; | |
| } | |
| // UTF-8 string wrapper for CreateFunctionTemplate. | |
| napi_status CreateFunctionTemplate(napi_env env, | |
| const char* utf8name, | |
| size_t length, | |
| napi_callback callback, | |
| void* user_data, | |
| v8::Local<v8::FunctionTemplate>* out_template) { | |
| v8::Local<v8::Name> name; | |
| if (utf8name != nullptr) { | |
| int len = (length == NAPI_AUTO_LENGTH) ? -1 : static_cast<int>(length); | |
| v8::MaybeLocal<v8::String> v8_name = | |
| v8::String::NewFromUtf8(env->isolate, utf8name, v8::NewStringType::kInternalized, len); | |
| if (v8_name.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create function name"); | |
| } | |
| name = v8_name.ToLocalChecked(); | |
| } | |
| return CreateFunctionTemplate(env, name, callback, user_data, out_template); | |
| } | |
| // Extracts a property name from a Node-API property descriptor. | |
| napi_status GetDescriptorName(napi_env env, | |
| const napi_property_descriptor& desc, | |
| v8::Local<v8::Name>* out_name) { | |
| if (desc.utf8name != nullptr) { | |
| v8::MaybeLocal<v8::String> name = | |
| v8::String::NewFromUtf8(env->isolate, desc.utf8name, v8::NewStringType::kInternalized); | |
| if (name.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create property name"); | |
| } | |
| *out_name = name.ToLocalChecked(); | |
| return napi_ok; | |
| } | |
| if (desc.name != nullptr) { | |
| v8::Local<v8::Value> v8_name = dawn::napi_v8::ToV8(desc.name); | |
| if (!v8_name->IsName()) { | |
| return env->SetLastError(napi_name_expected, "A name or symbol was expected"); | |
| } | |
| *out_name = v8_name.As<v8::Name>(); | |
| return napi_ok; | |
| } | |
| return env->SetLastError(napi_invalid_arg, "Property descriptor missing name"); | |
| } | |
| // Validates and extracts a v8::Function and its associated Context. | |
| napi_status UnwrapFunction(napi_env env, | |
| napi_value function_value, | |
| v8::Local<v8::Function>* out_fn, | |
| v8::Local<v8::Context>* out_ctx) { | |
| v8::Local<v8::Value> v8_func = dawn::napi_v8::ToV8(function_value); | |
| if (!v8_func->IsFunction()) { | |
| return env->SetLastError(napi_function_expected, "A function was expected"); | |
| } | |
| *out_ctx = env->GetContext(); | |
| *out_fn = v8_func.As<v8::Function>(); | |
| return napi_ok; | |
| } | |
| // Unpacks and validates a Node-API argument array into a vector of V8 values. | |
| napi_status UnpackArgs(napi_env env, | |
| size_t argc, | |
| const napi_value* argv, | |
| std::vector<v8::Local<v8::Value>>* out_args) { | |
| if (argc > 0 && argv == nullptr) { | |
| return env->SetLastError(napi_invalid_arg, "Invalid argument: null argv"); | |
| } | |
| out_args->resize(argc); | |
| for (size_t i = 0; i < argc; ++i) { | |
| // SAFETY: The caller guarantees argv points to an array with at least argc elements. | |
| napi_value arg = DAWN_UNSAFE_BUFFERS(argv[i]); | |
| if (arg == nullptr) { | |
| return env->SetLastError(napi_invalid_arg, "Invalid argument in argv"); | |
| } | |
| (*out_args)[i] = dawn::napi_v8::ToV8(arg); | |
| } | |
| return napi_ok; | |
| } | |
| // Evaluates the result of a V8 function or constructor invocation within TryCatch. | |
| template <typename T> | |
| napi_status ProcessCallResult(napi_env env, | |
| v8::TryCatch& try_catch, | |
| v8::MaybeLocal<T> maybe_result, | |
| napi_value* out_result) { | |
| if (try_catch.HasTerminated()) { | |
| return env->SetLastError(napi_generic_failure, "Execution was terminated"); | |
| } | |
| if (try_catch.HasCaught()) { | |
| env->last_exception.Reset(env->isolate, try_catch.Exception()); | |
| return env->SetLastError(napi_pending_exception, | |
| "An exception was thrown during execution"); | |
| } | |
| if (maybe_result.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Function invocation failed"); | |
| } | |
| if (out_result != nullptr) { | |
| *out_result = dawn::napi_v8::ToNapi(maybe_result.ToLocalChecked()); | |
| } | |
| return napi_ok; | |
| } | |
| // Instantiates a JavaScript function from a native Node-API callback. | |
| napi_status CreateCallbackFunction(napi_env env, | |
| v8::Local<v8::Context> ctx, | |
| v8::Local<v8::Name> name, | |
| napi_callback callback, | |
| void* data, | |
| v8::Local<v8::Function>* out_fn) { | |
| if (callback == nullptr) { | |
| return napi_ok; | |
| } | |
| v8::Local<v8::FunctionTemplate> function_template; | |
| napi_status status = CreateFunctionTemplate(env, name, callback, data, &function_template); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::MaybeLocal<v8::Function> maybe_fn = function_template->GetFunction(ctx); | |
| if (maybe_fn.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create callback function"); | |
| } | |
| *out_fn = maybe_fn.ToLocalChecked(); | |
| return napi_ok; | |
| } | |
| // Converts Node-API property attributes to V8 PropertyAttribute. | |
| v8::PropertyAttribute ToV8PropertyAttribute(napi_property_attributes attributes) { | |
| int v8_attr = v8::None; | |
| if ((attributes & napi_writable) == 0) { | |
| v8_attr |= v8::ReadOnly; | |
| } | |
| if ((attributes & napi_enumerable) == 0) { | |
| v8_attr |= v8::DontEnum; | |
| } | |
| if ((attributes & napi_configurable) == 0) { | |
| v8_attr |= v8::DontDelete; | |
| } | |
| return static_cast<v8::PropertyAttribute>(v8_attr); | |
| } | |
| // Attaches a single property descriptor to an existing JavaScript object. | |
| napi_status AttachObjectProperty(napi_env env, | |
| v8::Local<v8::Context> ctx, | |
| v8::Local<v8::Object> obj, | |
| const napi_property_descriptor& desc) { | |
| v8::Local<v8::Name> name; | |
| napi_status status = GetDescriptorName(env, desc, &name); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::PropertyAttribute v8_attr = ToV8PropertyAttribute(desc.attributes); | |
| if (desc.method != nullptr) { | |
| v8::Local<v8::Function> method_fn; | |
| status = CreateCallbackFunction(env, ctx, name, desc.method, desc.data, &method_fn); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| if (obj->DefineOwnProperty(ctx, name, method_fn, v8_attr).IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to define method"); | |
| } | |
| } else if (desc.getter != nullptr || desc.setter != nullptr) { | |
| v8::Local<v8::Function> getter_fn; | |
| status = CreateCallbackFunction(env, ctx, {}, desc.getter, desc.data, &getter_fn); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Local<v8::Function> setter_fn; | |
| status = CreateCallbackFunction(env, ctx, {}, desc.setter, desc.data, &setter_fn); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| obj->SetAccessorProperty(name, getter_fn, setter_fn, v8_attr); | |
| } else if (desc.value != nullptr) { | |
| if (obj->DefineOwnProperty(ctx, name, dawn::napi_v8::ToV8(desc.value), v8_attr) | |
| .IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to set property value"); | |
| } | |
| } | |
| return napi_ok; | |
| } | |
| enum class ErrorType { | |
| Error, | |
| TypeError, | |
| RangeError, | |
| }; | |
| // Helper to create JavaScript Error objects. | |
| napi_status CreateError(napi_env env, | |
| ErrorType error_type, | |
| napi_value code, | |
| napi_value msg, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, msg, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_msg = dawn::napi_v8::ToV8(msg); | |
| if (!v8_msg->IsString()) { | |
| return env->SetLastError(napi_string_expected, "A string was expected for error message"); | |
| } | |
| v8::Local<v8::String> str_msg = v8_msg.As<v8::String>(); | |
| v8::Local<v8::Value> err; | |
| switch (error_type) { | |
| case ErrorType::Error: | |
| err = v8::Exception::Error(str_msg); | |
| break; | |
| case ErrorType::TypeError: | |
| err = v8::Exception::TypeError(str_msg); | |
| break; | |
| case ErrorType::RangeError: | |
| err = v8::Exception::RangeError(str_msg); | |
| break; | |
| } | |
| if (code != nullptr) { | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::Local<v8::String> code_name; | |
| napi_status status = CreateInternalizedName(env, "code", &code_name); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| if (err.As<v8::Object>()->Set(ctx, code_name, dawn::napi_v8::ToV8(code)).IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to set error code"); | |
| } | |
| } | |
| *result = dawn::napi_v8::ToNapi(err); | |
| return napi_ok; | |
| } | |
| napi_status GetWrapObject(napi_env env, napi_value js_object, v8::Local<v8::Object>* out_obj) { | |
| if (!ValidateArgs(env, js_object)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(js_object); | |
| if (!v8_val->IsObject()) { | |
| return env->SetLastError(napi_object_expected, "An object was expected"); | |
| } | |
| *out_obj = v8_val.As<v8::Object>(); | |
| return napi_ok; | |
| } | |
| // Returns the key used to retrieve the private member of a wrapper object. | |
| // The name "node:napi:wrapper" is used for the key. The namespace should ensure | |
| // no conflict with user names. | |
| v8::Local<v8::Private> GetWrapperKey(napi_env env) { | |
| if (env->wrapper_key.IsEmpty()) { | |
| v8::Local<v8::String> name = v8::String::NewFromUtf8Literal( | |
| env->isolate, "node:napi:wrapper", v8::NewStringType::kInternalized); | |
| env->wrapper_key.Reset(env->isolate, v8::Private::New(env->isolate, name)); | |
| } | |
| return env->wrapper_key.Get(env->isolate); | |
| } | |
| // Retrieves the `napi_ref__` previously attached to `js_object` by napi_wrap(). | |
| // Returns napi_invalid_arg if the object is not wrapped. | |
| napi_status GetWrapReference(napi_env env, | |
| napi_value js_object, | |
| v8::Local<v8::Object>* out_obj, | |
| napi_ref__** out_ref) { | |
| napi_status status = GetWrapObject(env, js_object, out_obj); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Local<v8::Value> val; | |
| if (!(*out_obj)->GetPrivate(env->GetContext(), GetWrapperKey(env)).ToLocal(&val)) { | |
| return env->SetLastError(napi_generic_failure, "Failed to read the object's wrapper"); | |
| } | |
| if (!val->IsExternal()) { | |
| return env->SetLastError(napi_invalid_arg, "Object is not wrapped"); | |
| } | |
| *out_ref = | |
| static_cast<napi_ref__*>(val.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault)); | |
| return napi_ok; | |
| } | |
| napi_status GetArrayBuffer(napi_env env, napi_value value, v8::Local<v8::ArrayBuffer>* out_ab) { | |
| if (!ValidateArgs(env, value)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| if (!v8_val->IsArrayBuffer()) { | |
| return env->SetLastError(napi_arraybuffer_expected, "An ArrayBuffer was expected"); | |
| } | |
| *out_ab = v8_val.As<v8::ArrayBuffer>(); | |
| return napi_ok; | |
| } | |
| napi_status ConcludeDeferred(napi_env env, | |
| napi_deferred deferred, | |
| napi_value value, | |
| bool is_resolve) { | |
| if (!ValidateArgs(env, deferred, value)) { | |
| return napi_invalid_arg; | |
| } | |
| if (!env->last_exception.IsEmpty()) { | |
| return env->SetLastError(napi_pending_exception, "An exception is pending"); | |
| } | |
| auto it = std::ranges::find_if(env->deferreds.begin(), env->deferreds.end(), | |
| [deferred](const auto& d) { return d.get() == deferred; }); | |
| if (it == env->deferreds.end()) { | |
| return env->SetLastError(napi_invalid_arg, "Deferred not found"); | |
| } | |
| v8::Local<v8::Promise::Resolver> resolver = deferred->resolver.Get(env->isolate); | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::Maybe<bool> success = is_resolve ? resolver->Resolve(ctx, dawn::napi_v8::ToV8(value)) | |
| : resolver->Reject(ctx, dawn::napi_v8::ToV8(value)); | |
| env->deferreds.erase(it); | |
| if (success.IsNothing() || !success.FromJust()) { | |
| return env->SetLastError(napi_generic_failure, is_resolve ? "Failed to resolve promise" | |
| : "Failed to reject promise"); | |
| } | |
| return napi_ok; | |
| } | |
| #ifndef V8_ENABLE_SANDBOX | |
| struct ExternalArrayBufferFinalizer { | |
| napi_env env; | |
| node_api_nogc_finalize finalize_cb; | |
| void* finalize_hint; | |
| }; | |
| void ExternalArrayBufferDeleter(void* data, size_t, void* deleter_data) { | |
| auto* fin = static_cast<ExternalArrayBufferFinalizer*>(deleter_data); | |
| if (fin != nullptr) { | |
| if (fin->finalize_cb != nullptr) { | |
| fin->finalize_cb(fin->env, data, fin->finalize_hint); | |
| } | |
| delete fin; | |
| } | |
| } | |
| #endif | |
| size_t ElementSizeForTypedArray(napi_typedarray_type type) { | |
| switch (type) { | |
| case napi_int8_array: | |
| case napi_uint8_array: | |
| case napi_uint8_clamped_array: | |
| return 1; | |
| case napi_int16_array: | |
| case napi_uint16_array: | |
| return 2; | |
| case napi_int32_array: | |
| case napi_uint32_array: | |
| case napi_float32_array: | |
| return 4; | |
| case napi_float64_array: | |
| case napi_bigint64_array: | |
| case napi_biguint64_array: | |
| return 8; | |
| } | |
| return 0; | |
| } | |
| } // namespace | |
| using dawn::napi_v8::ToNapi; | |
| using dawn::napi_v8::ToV8; | |
| extern "C" { | |
| // ============================================================================ | |
| // Error Handling & Environment Info | |
| // ============================================================================ | |
| napi_status napi_get_last_error_info(node_api_nogc_env env, | |
| const napi_extended_error_info** result) { | |
| if (env == nullptr || result == nullptr) { | |
| return napi_invalid_arg; | |
| } | |
| *result = &(env->last_error); | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Handle Scopes | |
| // ============================================================================ | |
| napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| env->open_handle_scopes.push_back(std::make_unique<napi_standard_handle_scope__>(env->isolate)); | |
| *result = env->open_handle_scopes.back().get(); | |
| return napi_ok; | |
| } | |
| napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { | |
| if (!ValidateArgs(env, scope)) { | |
| return napi_invalid_arg; | |
| } | |
| if (env->open_handle_scopes.empty() || env->open_handle_scopes.back().get() != scope) { | |
| return env->SetLastError(napi_handle_scope_mismatch, "Handle scope closed out of order"); | |
| } | |
| env->open_handle_scopes.pop_back(); | |
| return napi_ok; | |
| } | |
| napi_status napi_open_escapable_handle_scope(napi_env env, napi_escapable_handle_scope* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| auto scope = std::make_unique<napi_escapable_handle_scope__>(env->isolate); | |
| *result = scope.get(); | |
| env->open_handle_scopes.push_back(std::move(scope)); | |
| return napi_ok; | |
| } | |
| napi_status napi_close_escapable_handle_scope(napi_env env, napi_escapable_handle_scope scope) { | |
| if (!ValidateArgs(env, scope)) { | |
| return napi_invalid_arg; | |
| } | |
| if (env->open_handle_scopes.empty() || env->open_handle_scopes.back().get() != scope) { | |
| return env->SetLastError(napi_handle_scope_mismatch, "Handle scope closed out of order"); | |
| } | |
| env->open_handle_scopes.pop_back(); | |
| return napi_ok; | |
| } | |
| napi_status napi_escape_handle(napi_env env, | |
| napi_escapable_handle_scope scope, | |
| napi_value src, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, scope, src, result)) { | |
| return napi_invalid_arg; | |
| } | |
| if (scope->escape_called) { | |
| return env->SetLastError(napi_escape_called_twice, | |
| "Handle already escaped from this scope"); | |
| } | |
| scope->escape_called = true; | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(src); | |
| *result = dawn::napi_v8::ToNapi(scope->scope.Escape(v8_val)); | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Primitives & Singletons | |
| // ============================================================================ | |
| napi_status napi_get_undefined(napi_env env, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Undefined(env->isolate)); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_null(napi_env env, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Null(env->isolate)); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_global(napi_env env, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(env->GetContext()->Global()); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Boolean::New(env->isolate, value)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_double(napi_env env, double value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Number::New(env->isolate, value)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Integer::New(env->isolate, value)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Integer::NewFromUnsigned(env->isolate, value)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Number::New(env->isolate, static_cast<double>(value))); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_bigint_int64(napi_env env, int64_t value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = dawn::napi_v8::ToNapi(v8::BigInt::New(env->isolate, value)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = dawn::napi_v8::ToNapi(v8::BigInt::NewFromUnsigned(env->isolate, value)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_string_utf8(napi_env env, | |
| const char* str, | |
| size_t length, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| int len = (length == NAPI_AUTO_LENGTH) ? -1 : static_cast<int>(length); | |
| v8::MaybeLocal<v8::String> v8_str = | |
| v8::String::NewFromUtf8(env->isolate, str ? str : "", v8::NewStringType::kNormal, len); | |
| if (v8_str.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create UTF-8 string"); | |
| } | |
| *result = ToNapi(v8_str.ToLocalChecked()); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_symbol(napi_env env, napi_value description, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::String> desc; | |
| if (description != nullptr) { | |
| desc = ToV8(description).As<v8::String>(); | |
| } | |
| *result = ToNapi(v8::Symbol::New(env->isolate, desc)); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_value_double(napi_env env, napi_value value, double* result) { | |
| return ExtractNumber<v8::Number>(env, value, result, "A number was expected", | |
| [](auto v, auto ctx) { return v->ToNumber(ctx); }); | |
| } | |
| napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result) { | |
| return ExtractNumber<v8::Int32>(env, value, result, "A 32-bit integer was expected", | |
| [](auto v, auto ctx) { return v->ToInt32(ctx); }); | |
| } | |
| napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* result) { | |
| return ExtractNumber<v8::Uint32>(env, value, result, "An unsigned 32-bit integer was expected", | |
| [](auto v, auto ctx) { return v->ToUint32(ctx); }); | |
| } | |
| napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result) { | |
| return ExtractNumber<v8::Integer>(env, value, result, "An integer was expected", | |
| [](auto v, auto ctx) { return v->ToInteger(ctx); }); | |
| } | |
| napi_status napi_get_value_bigint_int64(napi_env env, | |
| napi_value value, | |
| int64_t* result, | |
| bool* lossless) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| if (!v8_val->IsBigInt()) { | |
| return env->SetLastError(napi_bigint_expected, "BigInt expected"); | |
| } | |
| v8::Local<v8::BigInt> bi = v8_val.As<v8::BigInt>(); | |
| *result = bi->Int64Value(lossless); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_value_bigint_uint64(napi_env env, | |
| napi_value value, | |
| uint64_t* result, | |
| bool* lossless) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| if (!v8_val->IsBigInt()) { | |
| return env->SetLastError(napi_bigint_expected, "BigInt expected"); | |
| } | |
| v8::Local<v8::BigInt> bi = v8_val.As<v8::BigInt>(); | |
| *result = bi->Uint64Value(lossless); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToV8(value)->BooleanValue(env->isolate); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_value_string_utf8(napi_env env, | |
| napi_value value, | |
| char* buf, | |
| size_t bufsize, | |
| size_t* result) { | |
| if (!ValidateArgs(env, value)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> val = ToV8(value); | |
| if (!val->IsString()) { | |
| return env->SetLastError(napi_string_expected, "A string was expected"); | |
| } | |
| v8::Local<v8::String> str = val.As<v8::String>(); | |
| size_t utf8_len = static_cast<size_t>(str->Utf8Length(env->isolate)); | |
| if (buf == nullptr) { | |
| if (result == nullptr) { | |
| return env->SetLastError(napi_invalid_arg, "Invalid argument: null pointer"); | |
| } | |
| *result = utf8_len; | |
| } else if (bufsize == 0) { | |
| if (result != nullptr) { | |
| *result = 0; | |
| } | |
| } else { | |
| size_t written = str->WriteUtf8(env->isolate, buf, bufsize - 1, | |
| v8::String::WriteFlags::kReplaceInvalidUtf8); | |
| // SAFETY: written is guaranteed to be <= bufsize - 1 by WriteUtf8, so written is a valid | |
| // index in buf. | |
| DAWN_UNSAFE_BUFFERS(buf[written] = '\0'); | |
| if (result != nullptr) { | |
| *result = written; | |
| } | |
| } | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Type Coercions, Type Checking & Equality | |
| // ============================================================================ | |
| napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> val = ToV8(value); | |
| if (val->IsNumber()) { | |
| *result = napi_number; | |
| } else if (val->IsString()) { | |
| *result = napi_string; | |
| } else if (val->IsFunction()) { | |
| *result = napi_function; | |
| } else if (val->IsArray()) { | |
| *result = napi_object; | |
| } else if (val->IsBoolean()) { | |
| *result = napi_boolean; | |
| } else if (val->IsUndefined()) { | |
| *result = napi_undefined; | |
| } else if (val->IsNull()) { | |
| *result = napi_null; | |
| } else if (val->IsSymbol()) { | |
| *result = napi_symbol; | |
| } else if (val->IsBigInt()) { | |
| *result = napi_bigint; | |
| } else if (val->IsExternal()) { | |
| *result = napi_external; | |
| } else if (val->IsObject()) { | |
| *result = napi_object; | |
| } else { | |
| *result = napi_undefined; | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_coerce_to_bool(napi_env env, napi_value value, napi_value* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(ToV8(value)->ToBoolean(env->isolate)); | |
| return napi_ok; | |
| } | |
| napi_status napi_coerce_to_number(napi_env env, napi_value value, napi_value* result) { | |
| return CoerceTo<v8::Number>(env, value, result, "Failed to coerce value to number", | |
| [](auto v, auto ctx) { return v->ToNumber(ctx); }); | |
| } | |
| napi_status napi_coerce_to_string(napi_env env, napi_value value, napi_value* result) { | |
| return CoerceTo<v8::String>(env, value, result, "Failed to coerce value to string", | |
| [](auto v, auto ctx) { return v->ToString(ctx); }); | |
| } | |
| napi_status napi_coerce_to_object(napi_env env, napi_value value, napi_value* result) { | |
| return CoerceTo<v8::Object>(env, value, result, "Failed to coerce value to object", | |
| [](auto v, auto ctx) { return v->ToObject(ctx); }); | |
| } | |
| napi_status napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, bool* result) { | |
| if (!ValidateArgs(env, lhs, rhs, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToV8(lhs)->StrictEquals(ToV8(rhs)); | |
| return napi_ok; | |
| } | |
| napi_status napi_instanceof(napi_env env, napi_value object, napi_value constructor, bool* result) { | |
| if (!ValidateArgs(env, object, constructor, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_obj = dawn::napi_v8::ToV8(object); | |
| v8::Local<v8::Value> v8_ctor = dawn::napi_v8::ToV8(constructor); | |
| if (!v8_ctor->IsObject()) { | |
| return env->SetLastError(napi_object_expected, "Constructor must be an object"); | |
| } | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::TryCatch try_catch(env->isolate); | |
| v8::Maybe<bool> res = v8_obj->InstanceOf(ctx, v8_ctor.As<v8::Object>()); | |
| if (res.IsNothing()) { | |
| if (try_catch.HasCaught()) { | |
| env->last_exception.Reset(env->isolate, try_catch.Exception()); | |
| return env->SetLastError(napi_pending_exception, | |
| "An exception was thrown during the InstanceOf check"); | |
| } | |
| return env->SetLastError(napi_generic_failure, "InstanceOf check failed"); | |
| } | |
| *result = res.FromJust(); | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Objects & Properties | |
| // ============================================================================ | |
| napi_status napi_create_object(napi_env env, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Object::New(env->isolate)); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_property_names(napi_env env, napi_value object, napi_value* result) { | |
| if (!ValidateArgs(env, object, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Object> obj; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapObject(env, object, &obj, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::MaybeLocal<v8::Array> names = obj->GetPropertyNames(ctx); | |
| if (names.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to get property names"); | |
| } | |
| *result = ToNapi(names.ToLocalChecked()); | |
| return napi_ok; | |
| } | |
| napi_status napi_set_property(napi_env env, napi_value object, napi_value name, napi_value value) { | |
| if (!ValidateArgs(env, name)) { | |
| return napi_invalid_arg; | |
| } | |
| return WriteProperty(env, object, ToV8(name), value, "Failed to set property"); | |
| } | |
| napi_status napi_get_property(napi_env env, | |
| napi_value object, | |
| napi_value name, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, name)) { | |
| return napi_invalid_arg; | |
| } | |
| return ReadProperty(env, object, ToV8(name), result, "Failed to get property"); | |
| } | |
| napi_status napi_has_property(napi_env env, napi_value object, napi_value name, bool* result) { | |
| if (!ValidateArgs(env, name)) { | |
| return napi_invalid_arg; | |
| } | |
| return QueryProperty(env, object, ToV8(name), result, "Failed to check property"); | |
| } | |
| napi_status napi_set_named_property(napi_env env, | |
| napi_value object, | |
| const char* utf8name, | |
| napi_value value) { | |
| v8::Local<v8::String> name; | |
| napi_status status = CreateInternalizedName(env, utf8name, &name); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| return WriteProperty(env, object, name, value, "Failed to set named property"); | |
| } | |
| napi_status napi_get_named_property(napi_env env, | |
| napi_value object, | |
| const char* utf8name, | |
| napi_value* result) { | |
| v8::Local<v8::String> name; | |
| napi_status status = CreateInternalizedName(env, utf8name, &name); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| return ReadProperty(env, object, name, result, "Failed to get named property"); | |
| } | |
| napi_status napi_has_named_property(napi_env env, | |
| napi_value object, | |
| const char* utf8name, | |
| bool* result) { | |
| v8::Local<v8::String> name; | |
| napi_status status = CreateInternalizedName(env, utf8name, &name); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| return QueryProperty(env, object, name, result, "Failed to check named property"); | |
| } | |
| // ============================================================================ | |
| // Arrays & Indexed Properties | |
| // ============================================================================ | |
| napi_status napi_create_array(napi_env env, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Array::New(env->isolate)); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_array_with_length(napi_env env, size_t length, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = ToNapi(v8::Array::New(env->isolate, static_cast<int>(length))); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_array_length(napi_env env, napi_value value, uint32_t* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = ToV8(value); | |
| if (!v8_val->IsArray()) { | |
| return env->SetLastError(napi_array_expected, "An array was expected"); | |
| } | |
| *result = v8_val.As<v8::Array>()->Length(); | |
| return napi_ok; | |
| } | |
| napi_status napi_is_array(napi_env env, napi_value value, bool* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| *result = v8_val->IsArray(); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value* result) { | |
| return ReadProperty(env, object, index, result, "Failed to get element"); | |
| } | |
| napi_status napi_set_element(napi_env env, napi_value object, uint32_t index, napi_value value) { | |
| return WriteProperty(env, object, index, value, "Failed to set element"); | |
| } | |
| // ============================================================================ | |
| // Functions, Classes & Callbacks | |
| // ============================================================================ | |
| napi_status napi_create_function(napi_env env, | |
| const char* utf8name, | |
| size_t length, | |
| napi_callback cb, | |
| void* data, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, cb, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::FunctionTemplate> function_template; | |
| napi_status status = | |
| CreateFunctionTemplate(env, utf8name, length, cb, data, &function_template); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::MaybeLocal<v8::Function> fn = function_template->GetFunction(env->GetContext()); | |
| if (fn.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create function"); | |
| } | |
| *result = dawn::napi_v8::ToNapi(fn.ToLocalChecked()); | |
| return napi_ok; | |
| } | |
| napi_status napi_call_function(napi_env env, | |
| napi_value recv, | |
| napi_value func, | |
| size_t argc, | |
| const napi_value* argv, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, func)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Function> fn; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapFunction(env, func, &fn, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| std::vector<v8::Local<v8::Value>> args; | |
| status = UnpackArgs(env, argc, argv, &args); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Local<v8::Value> v8_recv = (recv != nullptr) | |
| ? dawn::napi_v8::ToV8(recv) | |
| : v8::Local<v8::Value>(v8::Undefined(env->isolate)); | |
| v8::TryCatch try_catch(env->isolate); | |
| v8::MaybeLocal<v8::Value> ret = fn->Call(ctx, v8_recv, static_cast<int>(argc), args.data()); | |
| return ProcessCallResult(env, try_catch, ret, result); | |
| } | |
| napi_status napi_get_cb_info(napi_env env, | |
| napi_callback_info cbinfo, | |
| size_t* argc, | |
| napi_value* argv, | |
| napi_value* this_arg, | |
| void** data) { | |
| if (!ValidateArgs(env, cbinfo)) { | |
| return napi_invalid_arg; | |
| } | |
| if (data != nullptr) { | |
| *data = cbinfo->data; | |
| } | |
| const auto& info = *(cbinfo->v8_info); | |
| if (this_arg != nullptr) { | |
| *this_arg = dawn::napi_v8::ToNapi(info.This()); | |
| } | |
| if (argc != nullptr) { | |
| size_t available_argc = static_cast<size_t>(info.Length()); | |
| if (argv != nullptr) { | |
| size_t copy_count = std::min(*argc, available_argc); | |
| for (size_t i = 0; i < copy_count; ++i) { | |
| // SAFETY: When argv is non-null, the caller guarantees it points to a buffer of at | |
| // least *argc elements. | |
| DAWN_UNSAFE_BUFFERS(argv[i]) = dawn::napi_v8::ToNapi(info[static_cast<int>(i)]); | |
| } | |
| for (size_t i = copy_count; i < *argc; ++i) { | |
| // SAFETY: When argv is non-null, the caller guarantees it points to a buffer of at | |
| // least *argc elements. | |
| DAWN_UNSAFE_BUFFERS(argv[i]) = dawn::napi_v8::ToNapi(v8::Undefined(env->isolate)); | |
| } | |
| } | |
| *argc = available_argc; | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_get_new_target(napi_env env, napi_callback_info cbinfo, napi_value* result) { | |
| if (!ValidateArgs(env, cbinfo, result)) { | |
| return napi_invalid_arg; | |
| } | |
| if (cbinfo->v8_info == nullptr) { | |
| return env->SetLastError(napi_invalid_arg, "Callback info has no V8 info"); | |
| } | |
| v8::Local<v8::Value> new_target = cbinfo->v8_info->NewTarget(); | |
| if (new_target.IsEmpty()) { | |
| new_target = v8::Undefined(env->isolate); | |
| } | |
| *result = dawn::napi_v8::ToNapi(new_target); | |
| return napi_ok; | |
| } | |
| napi_status napi_new_instance(napi_env env, | |
| napi_value constructor, | |
| size_t argc, | |
| const napi_value* argv, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, constructor, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Function> ctor; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapFunction(env, constructor, &ctor, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| std::vector<v8::Local<v8::Value>> args; | |
| status = UnpackArgs(env, argc, argv, &args); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::TryCatch try_catch(env->isolate); | |
| v8::MaybeLocal<v8::Object> ret = ctor->NewInstance(ctx, static_cast<int>(argc), args.data()); | |
| return ProcessCallResult(env, try_catch, ret, result); | |
| } | |
| napi_status napi_define_class(napi_env env, | |
| const char* utf8name, | |
| size_t length, | |
| napi_callback constructor, | |
| void* data, | |
| size_t property_count, | |
| const napi_property_descriptor* properties, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, constructor, result)) { | |
| return napi_invalid_arg; | |
| } | |
| if (property_count > 0 && properties == nullptr) { | |
| return env->SetLastError(napi_invalid_arg, "Invalid argument: null properties"); | |
| } | |
| v8::Local<v8::FunctionTemplate> class_template; | |
| napi_status status = | |
| CreateFunctionTemplate(env, utf8name, length, constructor, data, &class_template); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::MaybeLocal<v8::Function> maybe_ctor = class_template->GetFunction(ctx); | |
| if (maybe_ctor.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create class constructor"); | |
| } | |
| v8::Local<v8::Function> ctor_fn = maybe_ctor.ToLocalChecked(); | |
| v8::Local<v8::Object> proto_obj; | |
| if (property_count > 0) { | |
| v8::Local<v8::String> proto_name; | |
| status = CreateInternalizedName(env, "prototype", &proto_name); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Local<v8::Value> proto_val; | |
| if (!ctor_fn->Get(ctx, proto_name).ToLocal(&proto_val) || !proto_val->IsObject()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to get class prototype object"); | |
| } | |
| proto_obj = proto_val.As<v8::Object>(); | |
| } | |
| for (size_t i = 0; i < property_count; ++i) { | |
| // SAFETY: The caller guarantees properties points to an array of at least property_count | |
| // descriptors. | |
| const napi_property_descriptor& prop = DAWN_UNSAFE_BUFFERS(properties[i]); | |
| bool is_static = (prop.attributes & napi_static) != 0; | |
| v8::Local<v8::Object> target = is_static ? ctor_fn : proto_obj; | |
| status = AttachObjectProperty(env, ctx, target, prop); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| } | |
| *result = dawn::napi_v8::ToNapi(ctor_fn); | |
| return napi_ok; | |
| } | |
| napi_status napi_define_properties(napi_env env, | |
| napi_value object, | |
| size_t property_count, | |
| const napi_property_descriptor* properties) { | |
| if (!ValidateArgs(env, object)) { | |
| return napi_invalid_arg; | |
| } | |
| if (property_count > 0 && properties == nullptr) { | |
| return env->SetLastError(napi_invalid_arg, "Invalid argument: null properties"); | |
| } | |
| v8::Local<v8::Object> obj; | |
| v8::Local<v8::Context> ctx; | |
| napi_status status = UnwrapObject(env, object, &obj, &ctx); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| for (size_t i = 0; i < property_count; ++i) { | |
| // SAFETY: The caller guarantees properties points to an array of at least property_count | |
| // descriptors. | |
| const napi_property_descriptor& prop = DAWN_UNSAFE_BUFFERS(properties[i]); | |
| status = AttachObjectProperty(env, ctx, obj, prop); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| } | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Exceptions & Errors | |
| // ============================================================================ | |
| napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { | |
| return CreateError(env, ErrorType::Error, code, msg, result); | |
| } | |
| napi_status napi_create_type_error(napi_env env, | |
| napi_value code, | |
| napi_value msg, | |
| napi_value* result) { | |
| return CreateError(env, ErrorType::TypeError, code, msg, result); | |
| } | |
| napi_status napi_create_range_error(napi_env env, | |
| napi_value code, | |
| napi_value msg, | |
| napi_value* result) { | |
| return CreateError(env, ErrorType::RangeError, code, msg, result); | |
| } | |
| napi_status napi_throw(napi_env env, napi_value error) { | |
| if (!ValidateArgs(env, error)) { | |
| return napi_invalid_arg; | |
| } | |
| // Record the exception rather than calling Isolate::ThrowException() immediately so native | |
| // callers can inspect or clear it with napi_is_exception_pending() and | |
| // napi_get_and_clear_last_exception() before DeliverPendingException() hands it to V8. | |
| env->last_exception.Reset(env->isolate, dawn::napi_v8::ToV8(error)); | |
| return napi_ok; | |
| } | |
| napi_status napi_is_exception_pending(napi_env env, bool* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = !env->last_exception.IsEmpty(); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_and_clear_last_exception(napi_env env, napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| if (!env->last_exception.IsEmpty()) { | |
| *result = dawn::napi_v8::ToNapi(env->last_exception.Get(env->isolate)); | |
| env->last_exception.Reset(); | |
| } else { | |
| *result = dawn::napi_v8::ToNapi(v8::Undefined(env->isolate)); | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_is_error(napi_env env, napi_value value, bool* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| *result = dawn::napi_v8::ToV8(value)->IsNativeError(); | |
| return napi_ok; | |
| } | |
| void napi_fatal_error(const char* location, | |
| size_t location_len, | |
| const char* message, | |
| size_t message_len) { | |
| std::string loc = | |
| (location != nullptr) | |
| ? (location_len == NAPI_AUTO_LENGTH ? location : std::string(location, location_len)) | |
| : ""; | |
| std::string msg = | |
| (message != nullptr) | |
| ? (message_len == NAPI_AUTO_LENGTH ? message : std::string(message, message_len)) | |
| : ""; | |
| fprintf(stderr, "FATAL ERROR in %s: %s\n", loc.c_str(), msg.c_str()); | |
| fflush(stderr); | |
| std::abort(); | |
| } | |
| // ============================================================================ | |
| // References, ObjectWrap & Instance Data | |
| // ============================================================================ | |
| napi_status napi_create_reference(napi_env env, | |
| napi_value value, | |
| uint32_t initial_refcount, | |
| napi_ref* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| auto ref = std::make_unique<napi_ref__>(env, v8_val, initial_refcount); | |
| napi_ref__* key = ref.get(); | |
| *result = key; | |
| env->references.insert({key, std::move(ref)}); | |
| return napi_ok; | |
| } | |
| napi_status napi_delete_reference(napi_env env, napi_ref ref) { | |
| if (!ValidateArgs(env, ref)) { | |
| return napi_invalid_arg; | |
| } | |
| if (env->references.erase(ref) > 0) { | |
| return napi_ok; | |
| } | |
| return env->SetLastError(napi_invalid_arg, "Reference not found"); | |
| } | |
| napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) { | |
| if (!ValidateArgs(env, ref)) { | |
| return napi_invalid_arg; | |
| } | |
| if (ref->ref_count == 0) { | |
| ref->ClearWeak(); | |
| } | |
| ref->ref_count++; | |
| if (result != nullptr) { | |
| *result = ref->ref_count; | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) { | |
| if (!ValidateArgs(env, ref)) { | |
| return napi_invalid_arg; | |
| } | |
| if (ref->ref_count == 0) { | |
| return env->SetLastError(napi_generic_failure, "Cannot unref a reference with refcount 0"); | |
| } | |
| ref->ref_count--; | |
| if (ref->ref_count == 0) { | |
| ref->SetWeak(); | |
| } | |
| if (result != nullptr) { | |
| *result = ref->ref_count; | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value* result) { | |
| if (!ValidateArgs(env, ref, result)) { | |
| return napi_invalid_arg; | |
| } | |
| if (ref->handle.IsEmpty()) { | |
| *result = nullptr; | |
| } else { | |
| *result = dawn::napi_v8::ToNapi(ref->handle.Get(env->isolate)); | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_wrap(napi_env env, | |
| napi_value js_object, | |
| void* native_object, | |
| napi_finalize finalize_cb, | |
| void* finalize_hint, | |
| napi_ref* result) { | |
| v8::Local<v8::Object> obj; | |
| napi_status status = GetWrapObject(env, js_object, &obj); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::Local<v8::Private> key = GetWrapperKey(env); | |
| v8::Maybe<bool> has_wrapper = obj->HasPrivate(ctx, key); | |
| if (has_wrapper.IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to read the object's wrapper"); | |
| } | |
| if (has_wrapper.FromJust()) { | |
| return env->SetLastError(napi_invalid_arg, "Object is already wrapped"); | |
| } | |
| // A reference is created even when the caller asks for neither a finalizer nor a napi_ref: | |
| // it is what holds the native pointer for napi_unwrap() to read back. | |
| auto ref = std::make_unique<napi_ref__>(env, obj, 0, native_object, finalize_cb, finalize_hint, | |
| /*wrap_ref=*/true, | |
| /*userland_ref=*/result != nullptr); | |
| napi_ref__* key_ref = ref.get(); | |
| v8::Local<v8::External> ext = | |
| v8::External::New(env->isolate, key_ref, v8::kExternalPointerTypeTagDefault); | |
| if (obj->SetPrivate(ctx, key, ext).IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to attach the object's wrapper"); | |
| } | |
| if (result != nullptr) { | |
| *result = key_ref; | |
| } | |
| env->references.insert({key_ref, std::move(ref)}); | |
| return napi_ok; | |
| } | |
| napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Object> obj; | |
| napi_ref__* ref = nullptr; | |
| napi_status status = GetWrapReference(env, js_object, &obj, &ref); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| *result = ref->native_object; | |
| return napi_ok; | |
| } | |
| napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result) { | |
| v8::Local<v8::Object> obj; | |
| napi_ref__* ref = nullptr; | |
| napi_status status = GetWrapReference(env, js_object, &obj, &ref); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| if (result != nullptr) { | |
| *result = ref->native_object; | |
| } | |
| if (obj->DeletePrivate(env->GetContext(), GetWrapperKey(env)).IsNothing()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to detach the object's wrapper"); | |
| } | |
| // Detach the reference, clearing the finalizer so it never runs on the removed native object. | |
| ref->ClearFinalizer(); | |
| ref->native_object = nullptr; | |
| ref->is_wrap_ref = false; | |
| if (!ref->is_userland_ref) { | |
| // Internal runtime reference: erasing it destroys the unique_ptr, whose | |
| // destructor calls handle.Reset(), deregistering the weak callback in V8. | |
| env->references.erase(ref); | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_add_finalizer(napi_env env, | |
| napi_value js_object, | |
| void* finalize_data, | |
| napi_finalize finalize_cb, | |
| void* finalize_hint, | |
| napi_ref* result) { | |
| if (!ValidateArgs(env, js_object)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> val = dawn::napi_v8::ToV8(js_object); | |
| if (!val->IsObject()) { | |
| return env->SetLastError(napi_object_expected, "Object expected"); | |
| } | |
| auto ref = std::make_unique<napi_ref__>(env, val, 0, finalize_data, finalize_cb, finalize_hint, | |
| /*wrap_ref=*/false, | |
| /*userland_ref=*/result != nullptr); | |
| napi_ref__* key_ref = ref.get(); | |
| if (result != nullptr) { | |
| *result = key_ref; | |
| } | |
| env->references.insert({key_ref, std::move(ref)}); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_external(napi_env env, | |
| void* data, | |
| napi_finalize finalize_cb, | |
| void* finalize_hint, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::External> ext = | |
| v8::External::New(env->isolate, data, v8::kExternalPointerTypeTagDefault); | |
| if (finalize_cb != nullptr) { | |
| auto ref = std::make_unique<napi_ref__>(env, ext, 0, data, finalize_cb, finalize_hint, | |
| /*wrap_ref=*/false, /*userland_ref=*/false); | |
| napi_ref__* key_ref = ref.get(); | |
| env->references.insert({key_ref, std::move(ref)}); | |
| } | |
| *result = dawn::napi_v8::ToNapi(ext); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_value_external(napi_env env, napi_value value, void** result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| if (!v8_val->IsExternal()) { | |
| return env->SetLastError(napi_invalid_arg, "Value is not an external"); | |
| } | |
| *result = v8_val.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault); | |
| return napi_ok; | |
| } | |
| napi_status napi_set_instance_data(napi_env env, | |
| void* data, | |
| napi_finalize finalize_cb, | |
| void* finalize_hint) { | |
| if (!ValidateArgs(env)) { | |
| return napi_invalid_arg; | |
| } | |
| if (env->instance_data.finalize_cb != nullptr) { | |
| env->instance_data.finalize_cb(env, env->instance_data.data, | |
| env->instance_data.finalize_hint); | |
| } | |
| env->instance_data.data = data; | |
| env->instance_data.finalize_cb = finalize_cb; | |
| env->instance_data.finalize_hint = finalize_hint; | |
| return napi_ok; | |
| } | |
| napi_status napi_get_instance_data(napi_env env, void** data) { | |
| if (!ValidateArgs(env, data)) { | |
| return napi_invalid_arg; | |
| } | |
| *data = env->instance_data.data; | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // ArrayBuffer, TypedArray & DataView | |
| // ============================================================================ | |
| napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| *result = v8_val->IsArrayBuffer(); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_arraybuffer(napi_env env, | |
| size_t byte_length, | |
| void** data, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(env->isolate, byte_length); | |
| if (data != nullptr) { | |
| *data = ab->Data(); | |
| } | |
| *result = dawn::napi_v8::ToNapi(ab); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_arraybuffer_info(napi_env env, | |
| napi_value arraybuffer, | |
| void** data, | |
| size_t* byte_length) { | |
| v8::Local<v8::ArrayBuffer> ab; | |
| napi_status status = GetArrayBuffer(env, arraybuffer, &ab); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| if (data != nullptr) { | |
| *data = ab->Data(); | |
| } | |
| if (byte_length != nullptr) { | |
| *byte_length = ab->ByteLength(); | |
| } | |
| return napi_ok; | |
| } | |
| napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer) { | |
| v8::Local<v8::ArrayBuffer> ab; | |
| napi_status status = GetArrayBuffer(env, arraybuffer, &ab); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| if (!ab->IsDetachable()) { | |
| return env->SetLastError(napi_detachable_arraybuffer_expected, | |
| "ArrayBuffer is not detachable"); | |
| } | |
| ab->Detach(v8::Local<v8::Value>()).Check(); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_external_arraybuffer(napi_env env, | |
| void* external_data, | |
| size_t byte_length, | |
| node_api_nogc_finalize finalize_cb, | |
| void* finalize_hint, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| #ifdef V8_ENABLE_SANDBOX | |
| return env->SetLastError(napi_no_external_buffers_allowed, | |
| "External buffers are not allowed when V8 Sandbox is enabled"); | |
| #else | |
| ExternalArrayBufferFinalizer* fin = nullptr; | |
| if (finalize_cb != nullptr) { | |
| fin = new ExternalArrayBufferFinalizer{env, finalize_cb, finalize_hint}; | |
| } | |
| auto backing_store = v8::ArrayBuffer::NewBackingStore(external_data, byte_length, | |
| ExternalArrayBufferDeleter, fin); | |
| v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New( | |
| env->isolate, std::shared_ptr<v8::BackingStore>(std::move(backing_store))); | |
| *result = dawn::napi_v8::ToNapi(ab); | |
| return napi_ok; | |
| #endif | |
| } | |
| napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) { | |
| if (!ValidateArgs(env, value, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| *result = v8_val->IsTypedArray(); | |
| return napi_ok; | |
| } | |
| napi_status napi_create_typedarray(napi_env env, | |
| napi_typedarray_type type, | |
| size_t length, | |
| napi_value arraybuffer, | |
| size_t byte_offset, | |
| napi_value* result) { | |
| if (!ValidateArgs(env, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::ArrayBuffer> ab; | |
| napi_status status = GetArrayBuffer(env, arraybuffer, &ab); | |
| if (status != napi_ok) { | |
| return status; | |
| } | |
| size_t element_size = ElementSizeForTypedArray(type); | |
| if (element_size == 0) { | |
| return env->SetLastError(napi_invalid_arg, "Invalid typedarray type"); | |
| } | |
| if (byte_offset % element_size != 0) { | |
| return env->SetLastError(napi_invalid_arg, "Offset must be aligned to element size"); | |
| } | |
| if (length > (std::numeric_limits<size_t>::max() - byte_offset) / element_size || | |
| byte_offset + length * element_size > ab->ByteLength()) { | |
| return env->SetLastError(napi_invalid_arg, "Range out of bounds"); | |
| } | |
| v8::Local<v8::TypedArray> ta; | |
| switch (type) { | |
| case napi_int8_array: | |
| ta = v8::Int8Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_uint8_array: | |
| ta = v8::Uint8Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_uint8_clamped_array: | |
| ta = v8::Uint8ClampedArray::New(ab, byte_offset, length); | |
| break; | |
| case napi_int16_array: | |
| ta = v8::Int16Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_uint16_array: | |
| ta = v8::Uint16Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_int32_array: | |
| ta = v8::Int32Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_uint32_array: | |
| ta = v8::Uint32Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_float32_array: | |
| ta = v8::Float32Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_float64_array: | |
| ta = v8::Float64Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_bigint64_array: | |
| ta = v8::BigInt64Array::New(ab, byte_offset, length); | |
| break; | |
| case napi_biguint64_array: | |
| ta = v8::BigUint64Array::New(ab, byte_offset, length); | |
| break; | |
| } | |
| *result = dawn::napi_v8::ToNapi(ta); | |
| return napi_ok; | |
| } | |
| napi_status napi_get_typedarray_info(napi_env env, | |
| napi_value typedarray, | |
| napi_typedarray_type* type, | |
| size_t* length, | |
| void** data, | |
| napi_value* arraybuffer, | |
| size_t* byte_offset) { | |
| if (!ValidateArgs(env, typedarray)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(typedarray); | |
| if (!v8_val->IsTypedArray()) { | |
| return env->SetLastError(napi_invalid_arg, "A TypedArray was expected"); | |
| } | |
| v8::Local<v8::TypedArray> ta = v8_val.As<v8::TypedArray>(); | |
| if (type != nullptr) { | |
| if (v8_val->IsInt8Array()) { | |
| *type = napi_int8_array; | |
| } else if (v8_val->IsUint8Array()) { | |
| *type = napi_uint8_array; | |
| } else if (v8_val->IsUint8ClampedArray()) { | |
| *type = napi_uint8_clamped_array; | |
| } else if (v8_val->IsInt16Array()) { | |
| *type = napi_int16_array; | |
| } else if (v8_val->IsUint16Array()) { | |
| *type = napi_uint16_array; | |
| } else if (v8_val->IsInt32Array()) { | |
| *type = napi_int32_array; | |
| } else if (v8_val->IsUint32Array()) { | |
| *type = napi_uint32_array; | |
| } else if (v8_val->IsFloat32Array()) { | |
| *type = napi_float32_array; | |
| } else if (v8_val->IsFloat64Array()) { | |
| *type = napi_float64_array; | |
| } else if (v8_val->IsBigInt64Array()) { | |
| *type = napi_bigint64_array; | |
| } else if (v8_val->IsBigUint64Array()) { | |
| *type = napi_biguint64_array; | |
| } | |
| } | |
| if (length != nullptr) { | |
| *length = ta->Length(); | |
| } | |
| if (byte_offset != nullptr) { | |
| *byte_offset = ta->ByteOffset(); | |
| } | |
| if (arraybuffer != nullptr) { | |
| *arraybuffer = dawn::napi_v8::ToNapi(ta->GetBuffer()); | |
| } | |
| if (data != nullptr) { | |
| // SAFETY: ta->Buffer() provides data storage of at least ta->ByteOffset() + | |
| // ta->ByteLength() bytes. | |
| *data = | |
| DAWN_UNSAFE_BUFFERS(static_cast<uint8_t*>(ta->GetBuffer()->Data()) + ta->ByteOffset()); | |
| } | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Promises & Scripts | |
| // ============================================================================ | |
| napi_status napi_create_promise(napi_env env, napi_deferred* deferred, napi_value* promise) { | |
| if (!ValidateArgs(env, deferred, promise)) { | |
| return napi_invalid_arg; | |
| } | |
| if (!env->last_exception.IsEmpty()) { | |
| return env->SetLastError(napi_pending_exception, "An exception is pending"); | |
| } | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::MaybeLocal<v8::Promise::Resolver> maybe_resolver = v8::Promise::Resolver::New(ctx); | |
| if (maybe_resolver.IsEmpty()) { | |
| return env->SetLastError(napi_generic_failure, "Failed to create promise resolver"); | |
| } | |
| v8::Local<v8::Promise::Resolver> resolver = maybe_resolver.ToLocalChecked(); | |
| auto def = std::make_unique<napi_deferred__>(); | |
| def->resolver.Reset(env->isolate, resolver); | |
| *deferred = def.get(); | |
| *promise = dawn::napi_v8::ToNapi(resolver->GetPromise()); | |
| env->deferreds.push_back(std::move(def)); | |
| return napi_ok; | |
| } | |
| napi_status napi_resolve_deferred(napi_env env, napi_deferred deferred, napi_value resolution) { | |
| return ConcludeDeferred(env, deferred, resolution, /*is_resolve=*/true); | |
| } | |
| napi_status napi_reject_deferred(napi_env env, napi_deferred deferred, napi_value rejection) { | |
| return ConcludeDeferred(env, deferred, rejection, /*is_resolve=*/false); | |
| } | |
| napi_status napi_is_promise(napi_env env, napi_value value, bool* is_promise) { | |
| if (!ValidateArgs(env, value, is_promise)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_val = dawn::napi_v8::ToV8(value); | |
| *is_promise = v8_val->IsPromise(); | |
| return napi_ok; | |
| } | |
| napi_status napi_run_script(napi_env env, napi_value script, napi_value* result) { | |
| if (!ValidateArgs(env, script, result)) { | |
| return napi_invalid_arg; | |
| } | |
| v8::Local<v8::Value> v8_script = dawn::napi_v8::ToV8(script); | |
| if (!v8_script->IsString()) { | |
| return env->SetLastError(napi_string_expected, "A string was expected"); | |
| } | |
| v8::Local<v8::Context> ctx = env->GetContext(); | |
| v8::TryCatch try_catch(env->isolate); | |
| v8::MaybeLocal<v8::Script> compiled = v8::Script::Compile(ctx, v8_script.As<v8::String>()); | |
| if (compiled.IsEmpty()) { | |
| return ProcessCallResult(env, try_catch, v8::MaybeLocal<v8::Value>(), nullptr); | |
| } | |
| v8::MaybeLocal<v8::Value> eval_result = compiled.ToLocalChecked()->Run(ctx); | |
| return ProcessCallResult(env, try_catch, eval_result, result); | |
| } | |
| napi_status napi_get_version(node_api_nogc_env env, uint32_t* result) { | |
| if (env == nullptr || result == nullptr) { | |
| return napi_invalid_arg; | |
| } | |
| *result = NAPI_VERSION; | |
| return napi_ok; | |
| } | |
| // ============================================================================ | |
| // Node-API Stubs | |
| // | |
| // The following functions are not used by dawn.node or the CTS runner. | |
| // However, node-addon-api (<napi-inl.h>) defines inline and virtual destructors | |
| // for Napi::AsyncContext, Napi::CallbackScope, and Napi::AsyncWorker that | |
| // reference these symbols. In debug or unoptimized builds, the compiler emits | |
| // these unused destructors into every translation unit including <napi.h>, | |
| // requiring these symbols to exist at link time to avoid undefined reference errors. | |
| // ============================================================================ | |
| napi_status napi_async_destroy(napi_env env, napi_async_context async_context) { | |
| if (!ValidateArgs(env, async_context)) { | |
| return napi_invalid_arg; | |
| } | |
| return env->SetLastError(napi_generic_failure, "napi_async_destroy is not supported"); | |
| } | |
| napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) { | |
| if (!ValidateArgs(env, scope)) { | |
| return napi_invalid_arg; | |
| } | |
| return env->SetLastError(napi_generic_failure, "napi_close_callback_scope is not supported"); | |
| } | |
| napi_status napi_delete_async_work(napi_env env, napi_async_work work) { | |
| if (!ValidateArgs(env, work)) { | |
| return napi_invalid_arg; | |
| } | |
| return env->SetLastError(napi_generic_failure, "napi_delete_async_work is not supported"); | |
| } | |
| } // extern "C" |