blob: 495ab01ea0d42a15d0964c81077a9beeb78b1a24 [file] [edit]
// 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.
#ifndef SRC_DAWN_NODE_NAPI_V8_NAPI_V8_H_
#define SRC_DAWN_NODE_NAPI_V8_NAPI_V8_H_
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <node_api.h>
#include <node_api_types.h>
#include <algorithm>
#include <bit>
#include <cstring>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/container/linked_hash_set.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundef"
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
#pragma clang diagnostic ignored "-Wcast-function-type-mismatch"
#pragma clang diagnostic ignored "-Wsuggest-destructor-override"
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#pragma clang diagnostic ignored "-Wunique-object-duplication"
#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast"
#pragma clang diagnostic ignored "-Wsign-conversion"
#include <v8.h>
#pragma clang diagnostic pop
// Internal structs representing handle scopes
struct napi_handle_scope__ {
virtual ~napi_handle_scope__() = default;
};
struct napi_standard_handle_scope__ : public napi_handle_scope__ {
v8::HandleScope scope;
explicit napi_standard_handle_scope__(v8::Isolate* isolate) : scope(isolate) {}
};
struct napi_escapable_handle_scope__ : public napi_handle_scope__ {
v8::EscapableHandleScope scope;
// A scope can promote at most one handle. V8 only guards this with a DCHECK, so the second
// request would silently overwrite the first in a release build.
bool escape_called = false;
explicit napi_escapable_handle_scope__(v8::Isolate* isolate) : scope(isolate) {}
};
// Internal struct representing callback metadata passed to a native callback
struct napi_callback_info__ {
const v8::FunctionCallbackInfo<v8::Value>* v8_info = nullptr;
void* data = nullptr;
};
// Persistent binding metadata for a registered native function/method/accessor callback
struct CallbackBinding {
napi_env env = nullptr;
napi_callback callback = nullptr;
void* user_data = nullptr;
};
// Internal struct representing a Node-API reference (napi_ref)
struct napi_ref__ {
napi_env env = nullptr;
v8::Global<v8::Value> handle;
uint32_t ref_count = 0;
void* native_object = nullptr;
napi_finalize finalize_cb = nullptr;
void* finalize_hint = nullptr;
bool is_wrap_ref = false;
bool is_userland_ref = false;
napi_ref__(napi_env e,
v8::Local<v8::Value> val,
uint32_t count,
void* native_obj = nullptr,
napi_finalize fin_cb = nullptr,
void* fin_hint = nullptr,
bool wrap_ref = false,
bool userland_ref = false);
~napi_ref__();
void SetWeak();
void ClearWeak();
void ClearFinalizer();
static void WeakCallback(const v8::WeakCallbackInfo<napi_ref__>& data);
static void PostGarbageCollectionCallback(const v8::WeakCallbackInfo<napi_ref__>& data);
};
// Internal struct representing a Node-API deferred promise (napi_deferred)
struct napi_deferred__ {
v8::Global<v8::Promise::Resolver> resolver;
};
// Internal structs representing Node-API types stubbed for link compatibility
struct napi_async_context__ {};
struct napi_callback_scope__ {};
struct napi_async_work__ {};
// Instance data stored in napi_env
struct InstanceData {
void* data = nullptr;
napi_finalize finalize_cb = nullptr;
void* finalize_hint = nullptr;
};
// Internal struct representing a Node-API environment (napi_env)
struct napi_env__ {
v8::Isolate* isolate = nullptr;
v8::Global<v8::Context> context;
v8::Global<v8::Value> last_exception;
napi_extended_error_info last_error{};
std::vector<std::unique_ptr<napi_handle_scope__>> open_handle_scopes;
std::vector<std::unique_ptr<CallbackBinding>> callback_bindings;
// Owns all live references, keyed by address for O(1) lookup and deletion.
absl::flat_hash_map<napi_ref__*, std::unique_ptr<napi_ref__>> references;
// Subset of `references` that have a non-null `finalize_cb`, in creation order so ~napi_env__
// can run remaining finalizers in reverse creation order in O(1) per finalizer.
absl::linked_hash_set<napi_ref__*> finalizable_references;
// References collected by V8 GC whose `napi_finalize` callbacks are waiting to run outside the
// GC atomic pause, in FIFO order.
absl::linked_hash_set<napi_ref__*> pending_finalizers;
bool post_gc_callback_scheduled = false;
bool finalizer_drain_scheduled = false;
std::vector<std::unique_ptr<napi_deferred__>> deferreds;
InstanceData instance_data{};
// Private key under which napi_wrap() stores the `napi_ref__` binding a JavaScript object to
// its native object. Lazily created by GetWrapperKey(). Being a v8::Private, the property is
// invisible to JavaScript, so it cannot be observed, enumerated or tampered with by scripts.
v8::Global<v8::Private> wrapper_key;
void DrainFinalizers();
napi_env__(v8::Isolate* iso, v8::Local<v8::Context> ctx) : isolate(iso), context(iso, ctx) {
ClearLastError();
}
~napi_env__();
v8::Local<v8::Context> GetContext() const { return context.Get(isolate); }
void ClearLastError() {
last_error.error_code = napi_ok;
last_error.engine_error_code = 0;
last_error.engine_reserved = nullptr;
last_error.error_message = nullptr;
}
napi_status SetLastError(napi_status status,
const char* error_message = nullptr,
uint32_t engine_error_code = 0) {
last_error.error_code = status;
last_error.engine_error_code = engine_error_code;
last_error.engine_reserved = nullptr;
last_error.error_message = error_message;
return status;
}
};
inline void napi_ref__::ClearFinalizer() {
finalize_cb = nullptr;
env->finalizable_references.erase(this);
}
inline napi_env__::~napi_env__() {
if (instance_data.finalize_cb != nullptr) {
instance_data.finalize_cb(this, instance_data.data, instance_data.finalize_hint);
instance_data.finalize_cb = nullptr;
}
pending_finalizers.clear();
// Finalize all remaining references that have an active finalizer, most recently created
// first. References stay in `references` until ~napi_env__ completes unless a finalizer
// explicitly calls napi_delete_reference.
while (!finalizable_references.empty()) {
napi_ref__* ref = *finalizable_references.rbegin();
napi_finalize cb = ref->finalize_cb;
void* native_object = ref->native_object;
void* finalize_hint = ref->finalize_hint;
// Reset the handle and clear finalize_cb BEFORE calling user code.
ref->handle.Reset();
ref->ClearFinalizer();
cb(this, native_object, finalize_hint);
}
references.clear();
}
// Inline handle conversion functions between V8 and Node-API.
// v8::Local<v8::Value> is guaranteed by V8 to be a trivially copyable, pointer-sized
// V8_TRIVIAL_ABI handle pointing to an active HandleScope slot.
namespace dawn::napi_v8 {
static_assert(sizeof(napi_value) == sizeof(v8::Local<v8::Value>));
static_assert(std::is_trivially_copyable_v<v8::Local<v8::Value>>);
inline napi_value ToNapi(v8::Local<v8::Value> val) {
return std::bit_cast<napi_value>(val);
}
inline v8::Local<v8::Value> ToV8(napi_value val) {
return std::bit_cast<v8::Local<v8::Value>>(val);
}
// Environment factory helpers
inline napi_env CreateEnv(v8::Isolate* isolate, v8::Local<v8::Context> context) {
return new napi_env__(isolate, context);
}
inline void DestroyEnv(napi_env env) {
delete env;
}
} // namespace dawn::napi_v8
#endif // SRC_DAWN_NODE_NAPI_V8_NAPI_V8_H_