diff --git a/node.gyp b/node.gyp index c1a9b61b3d9eac..35a44be345a61e 100644 --- a/node.gyp +++ b/node.gyp @@ -91,6 +91,7 @@ 'src/module_wrap.cc', 'src/node.cc', 'src/node_api.cc', + 'src/node_api_embedding.cc', 'src/node_binding.cc', 'src/node_blob.cc', 'src/node_buffer.cc', diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 3ea6d0641108d2..005382f173fee9 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -28,7 +28,6 @@ typedef struct napi_handle_scope__* napi_handle_scope; typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; typedef struct napi_callback_info__* napi_callback_info; typedef struct napi_deferred__* napi_deferred; -typedef struct napi_platform__* napi_platform; typedef enum { napi_default = 0, diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 833ced6d296026..823799a901ad83 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -5,9 +5,6 @@ #include "env-inl.h" #include "js_native_api.h" #include "js_native_api_v8.h" -#include "node_api_embedding.h" -#include "node_api_internals.h" -#include "simdutf.h" #include "util-inl.h" #define CHECK_MAYBE_NOTHING(env, maybe, status) \ @@ -240,42 +237,6 @@ inline v8impl::Persistent* NodePersistentFromJsDeferred( return reinterpret_cast*>(local); } -class EmbeddedEnvironment : public node::EmbeddedEnvironment { - public: - explicit EmbeddedEnvironment( - std::unique_ptr&& setup, - const std::shared_ptr& main_resource) - : main_resource_(main_resource), - setup_(std::move(setup)), - locker_(setup_->isolate()), - isolate_scope_(setup_->isolate()), - handle_scope_(setup_->isolate()), - context_scope_(setup_->context()), - seal_scope_(nullptr) {} - - inline node::CommonEnvironmentSetup* setup() { return setup_.get(); } - inline void seal() { - seal_scope_ = - std::make_unique(setup_->isolate()); - } - - private: - // The pointer to the UTF-16 main script convertible to V8 UnionBytes resource - // This must be constructed first and destroyed last because the isolate - // references it - std::shared_ptr main_resource_; - - std::unique_ptr setup_; - v8::Locker locker_; - v8::Isolate::Scope isolate_scope_; - v8::HandleScope handle_scope_; - v8::Context::Scope context_scope_; - // As this handle scope will remain open for the lifetime - // of the environment, we seal it to prevent it from - // becoming everyone's favorite trash bin - std::unique_ptr seal_scope_; -}; - class HandleScopeWrapper { public: explicit HandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} @@ -876,206 +837,6 @@ napi_status NAPI_CDECL napi_get_last_error_info( return napi_ok; } -napi_status NAPI_CDECL -napi_create_platform(int argc, - char** argv, - napi_error_message_handler err_handler, - napi_platform* result) { - argv = uv_setup_args(argc, argv); - std::vector args(argv, argv + argc); - if (args.size() < 1) args.push_back("libnode"); - - std::unique_ptr node_platform = - node::InitializeOncePerProcess( - args, - {node::ProcessInitializationFlags::kNoInitializeV8, - node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); - - for (const std::string& error : node_platform->errors()) { - if (err_handler != nullptr) { - err_handler(error.c_str()); - } else { - fprintf(stderr, "%s\n", error.c_str()); - } - } - if (node_platform->early_return() != 0) { - return napi_generic_failure; - } - - auto thread_pool_size = node::per_process::cli_options->v8_thread_pool_size; - std::unique_ptr v8_platform = - node::MultiIsolatePlatform::Create(thread_pool_size); - v8::V8::InitializePlatform(v8_platform.get()); - v8::V8::Initialize(); - reinterpret_cast(node_platform.get()) - ->platform_ = v8_platform.release(); - *result = reinterpret_cast(node_platform.release()); - return napi_ok; -} - -napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform) { - auto wrapper = reinterpret_cast(platform); - v8::V8::Dispose(); - v8::V8::DisposePlatform(); - node::TearDownOncePerProcess(); - delete wrapper->platform(); - delete wrapper; - return napi_ok; -} - -napi_status NAPI_CDECL -napi_create_environment(napi_platform platform, - napi_error_message_handler err_handler, - const char* main_script, - int32_t api_version, - napi_env* result) { - auto wrapper = reinterpret_cast(platform); - std::vector errors_vec; - - auto setup = node::CommonEnvironmentSetup::Create( - wrapper->platform(), &errors_vec, wrapper->args(), wrapper->exec_args()); - - for (const std::string& error : errors_vec) { - if (err_handler != nullptr) { - err_handler(error.c_str()); - } else { - fprintf(stderr, "%s\n", error.c_str()); - } - } - if (setup == nullptr) { - return napi_generic_failure; - } - - std::shared_ptr main_resource = nullptr; - if (main_script != nullptr) { - // We convert the user-supplied main_script to a UTF-16 resource - // and we store its shared_ptr in the environment - size_t u8_length = strlen(main_script); - size_t expected_u16_length = - simdutf::utf16_length_from_utf8(main_script, u8_length); - auto out = std::make_shared>(expected_u16_length); - size_t u16_length = simdutf::convert_utf8_to_utf16( - main_script, u8_length, reinterpret_cast(out->data())); - out->resize(u16_length); - main_resource = std::make_shared( - out->data(), out->size(), out); - } - - auto emb_env = - new v8impl::EmbeddedEnvironment(std::move(setup), main_resource); - - std::string filename = - wrapper->args().size() > 1 ? wrapper->args()[1] : ""; - auto env__ = - new node_napi_env__(emb_env->setup()->context(), filename, api_version); - emb_env->setup()->env()->set_embedded(emb_env); - env__->node_env()->AddCleanupHook( - [](void* arg) { static_cast(arg)->Unref(); }, - static_cast(env__)); - *result = env__; - - auto env = emb_env->setup()->env(); - - auto ret = node::LoadEnvironment( - env, - [env, resource = main_resource.get()]( - const node::StartExecutionCallbackInfo& info) - -> v8::MaybeLocal { - node::Realm* realm = env->principal_realm(); - auto ret = realm->ExecuteBootstrapper( - "internal/bootstrap/switches/is_embedded_env"); - if (ret.IsEmpty()) return ret; - - std::string name = - "embedder_main_napi_" + std::to_string(env->thread_id()); - if (resource != nullptr) { - env->builtin_loader()->Add(name.c_str(), node::UnionBytes(resource)); - return realm->ExecuteBootstrapper(name.c_str()); - } else { - return v8::Undefined(env->isolate()); - } - }); - if (ret.IsEmpty()) return napi_pending_exception; - - emb_env->seal(); - - return napi_ok; -} - -napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { - CHECK_ARG(env, env); - node_napi_env node_env = reinterpret_cast(env); - - int r = node::SpinEventLoop(node_env->node_env()).FromMaybe(1); - if (exit_code != nullptr) *exit_code = r; - node::Stop(node_env->node_env()); - - auto emb_env = reinterpret_cast( - node_env->node_env()->get_embedded()); - node_env->node_env()->set_embedded(nullptr); - // This deletes the uniq_ptr to node::CommonEnvironmentSetup - // and the v8::locker - delete emb_env; - - cppgc::ShutdownProcess(); - - return napi_ok; -} - -napi_status NAPI_CDECL napi_run_environment(napi_env env) { - CHECK_ARG(env, env); - node_napi_env node_env = reinterpret_cast(env); - - if (node::SpinEventLoopWithoutCleanup(node_env->node_env()).IsNothing()) - return napi_closing; - - return napi_ok; -} - -static void napi_promise_error_handler( - const v8::FunctionCallbackInfo& info) { - return; -} - -napi_status napi_await_promise(napi_env env, - napi_value promise, - napi_value* result) { - NAPI_PREAMBLE(env); - CHECK_ARG(env, result); - - v8::EscapableHandleScope scope(env->isolate); - node_napi_env node_env = reinterpret_cast(env); - - v8::Local promise_value = v8impl::V8LocalValueFromJsValue(promise); - if (promise_value.IsEmpty() || !promise_value->IsPromise()) - return napi_invalid_arg; - v8::Local promise_object = promise_value.As(); - - v8::Local rejected = v8::Boolean::New(env->isolate, false); - v8::Local err_handler = - v8::Function::New(env->context(), napi_promise_error_handler, rejected) - .ToLocalChecked(); - - if (promise_object->Catch(env->context(), err_handler).IsEmpty()) - return napi_pending_exception; - - if (node::SpinEventLoopWithoutCleanup( - node_env->node_env(), - [&promise_object]() { - return promise_object->State() == - v8::Promise::PromiseState::kPending; - }) - .IsNothing()) - return napi_closing; - - *result = - v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); - if (promise_object->State() == v8::Promise::PromiseState::kRejected) - return napi_pending_exception; - - return napi_ok; -} - napi_status NAPI_CDECL napi_create_function(napi_env env, const char* utf8name, size_t length, diff --git a/src/node_api_embedding.cc b/src/node_api_embedding.cc new file mode 100644 index 00000000000000..0c5e4c3c59379a --- /dev/null +++ b/src/node_api_embedding.cc @@ -0,0 +1,254 @@ +#include +#include // INT_MAX +#include +#define NAPI_EXPERIMENTAL +#include "env-inl.h" +#include "js_native_api.h" +#include "js_native_api_v8.h" +#include "node_api_embedding.h" +#include "node_api_internals.h" +#include "simdutf.h" +#include "util-inl.h" + +namespace v8impl { +namespace { + +class EmbeddedEnvironment : public node::EmbeddedEnvironment { + public: + explicit EmbeddedEnvironment( + std::unique_ptr&& setup, + const std::shared_ptr& main_resource) + : main_resource_(main_resource), + setup_(std::move(setup)), + locker_(setup_->isolate()), + isolate_scope_(setup_->isolate()), + handle_scope_(setup_->isolate()), + context_scope_(setup_->context()), + seal_scope_(nullptr) {} + + inline node::CommonEnvironmentSetup* setup() { return setup_.get(); } + inline void seal() { + seal_scope_ = + std::make_unique(setup_->isolate()); + } + + private: + // The pointer to the UTF-16 main script convertible to V8 UnionBytes resource + // This must be constructed first and destroyed last because the isolate + // references it + std::shared_ptr main_resource_; + + std::unique_ptr setup_; + v8::Locker locker_; + v8::Isolate::Scope isolate_scope_; + v8::HandleScope handle_scope_; + v8::Context::Scope context_scope_; + // As this handle scope will remain open for the lifetime + // of the environment, we seal it to prevent it from + // becoming everyone's favorite trash bin + std::unique_ptr seal_scope_; +}; + +} // end of anonymous namespace +} // end of namespace v8impl + +napi_status NAPI_CDECL +napi_create_platform(int argc, + char** argv, + napi_error_message_handler err_handler, + napi_platform* result) { + argv = uv_setup_args(argc, argv); + std::vector args(argv, argv + argc); + if (args.size() < 1) args.push_back("libnode"); + + std::unique_ptr node_platform = + node::InitializeOncePerProcess( + args, + {node::ProcessInitializationFlags::kNoInitializeV8, + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform}); + + for (const std::string& error : node_platform->errors()) { + if (err_handler != nullptr) { + err_handler(error.c_str()); + } else { + fprintf(stderr, "%s\n", error.c_str()); + } + } + if (node_platform->early_return() != 0) { + return napi_generic_failure; + } + + int thread_pool_size = + static_cast(node::per_process::cli_options->v8_thread_pool_size); + std::unique_ptr v8_platform = + node::MultiIsolatePlatform::Create(thread_pool_size); + v8::V8::InitializePlatform(v8_platform.get()); + v8::V8::Initialize(); + reinterpret_cast(node_platform.get()) + ->platform_ = v8_platform.release(); + *result = reinterpret_cast(node_platform.release()); + return napi_ok; +} + +napi_status NAPI_CDECL napi_destroy_platform(napi_platform platform) { + auto wrapper = reinterpret_cast(platform); + v8::V8::Dispose(); + v8::V8::DisposePlatform(); + node::TearDownOncePerProcess(); + delete wrapper->platform(); + delete wrapper; + return napi_ok; +} + +napi_status NAPI_CDECL +napi_create_environment(napi_platform platform, + napi_error_message_handler err_handler, + const char* main_script, + int32_t api_version, + napi_env* result) { + auto wrapper = reinterpret_cast(platform); + std::vector errors_vec; + + auto setup = node::CommonEnvironmentSetup::Create( + wrapper->platform(), &errors_vec, wrapper->args(), wrapper->exec_args()); + + for (const std::string& error : errors_vec) { + if (err_handler != nullptr) { + err_handler(error.c_str()); + } else { + fprintf(stderr, "%s\n", error.c_str()); + } + } + if (setup == nullptr) { + return napi_generic_failure; + } + + std::shared_ptr main_resource = nullptr; + if (main_script != nullptr) { + // We convert the user-supplied main_script to a UTF-16 resource + // and we store its shared_ptr in the environment + size_t u8_length = strlen(main_script); + size_t expected_u16_length = + simdutf::utf16_length_from_utf8(main_script, u8_length); + auto out = std::make_shared>(expected_u16_length); + size_t u16_length = simdutf::convert_utf8_to_utf16( + main_script, u8_length, reinterpret_cast(out->data())); + out->resize(u16_length); + main_resource = std::make_shared( + out->data(), out->size(), out); + } + + auto emb_env = + new v8impl::EmbeddedEnvironment(std::move(setup), main_resource); + + std::string filename = + wrapper->args().size() > 1 ? wrapper->args()[1] : ""; + auto env__ = + new node_napi_env__(emb_env->setup()->context(), filename, api_version); + emb_env->setup()->env()->set_embedded(emb_env); + env__->node_env()->AddCleanupHook( + [](void* arg) { static_cast(arg)->Unref(); }, + static_cast(env__)); + *result = env__; + + auto env = emb_env->setup()->env(); + + auto ret = node::LoadEnvironment( + env, + [env, resource = main_resource.get()]( + const node::StartExecutionCallbackInfo& info) + -> v8::MaybeLocal { + node::Realm* realm = env->principal_realm(); + auto ret = realm->ExecuteBootstrapper( + "internal/bootstrap/switches/is_embedded_env"); + if (ret.IsEmpty()) return ret; + + std::string name = + "embedder_main_napi_" + std::to_string(env->thread_id()); + if (resource != nullptr) { + env->builtin_loader()->Add(name.c_str(), node::UnionBytes(resource)); + return realm->ExecuteBootstrapper(name.c_str()); + } else { + return v8::Undefined(env->isolate()); + } + }); + if (ret.IsEmpty()) return napi_pending_exception; + + emb_env->seal(); + + return napi_ok; +} + +napi_status NAPI_CDECL napi_destroy_environment(napi_env env, int* exit_code) { + CHECK_ARG(env, env); + node_napi_env node_env = reinterpret_cast(env); + + int r = node::SpinEventLoop(node_env->node_env()).FromMaybe(1); + if (exit_code != nullptr) *exit_code = r; + node::Stop(node_env->node_env()); + + auto emb_env = reinterpret_cast( + node_env->node_env()->get_embedded()); + node_env->node_env()->set_embedded(nullptr); + // This deletes the uniq_ptr to node::CommonEnvironmentSetup + // and the v8::locker + delete emb_env; + + cppgc::ShutdownProcess(); + + return napi_ok; +} + +napi_status NAPI_CDECL napi_run_environment(napi_env env) { + CHECK_ARG(env, env); + node_napi_env node_env = reinterpret_cast(env); + + if (node::SpinEventLoopWithoutCleanup(node_env->node_env()).IsNothing()) + return napi_closing; + + return napi_ok; +} + +static void napi_promise_error_handler( + const v8::FunctionCallbackInfo& info) { + return; +} + +napi_status napi_await_promise(napi_env env, + napi_value promise, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + v8::EscapableHandleScope scope(env->isolate); + node_napi_env node_env = reinterpret_cast(env); + + v8::Local promise_value = v8impl::V8LocalValueFromJsValue(promise); + if (promise_value.IsEmpty() || !promise_value->IsPromise()) + return napi_invalid_arg; + v8::Local promise_object = promise_value.As(); + + v8::Local rejected = v8::Boolean::New(env->isolate, false); + v8::Local err_handler = + v8::Function::New(env->context(), napi_promise_error_handler, rejected) + .ToLocalChecked(); + + if (promise_object->Catch(env->context(), err_handler).IsEmpty()) + return napi_pending_exception; + + if (node::SpinEventLoopWithoutCleanup( + node_env->node_env(), + [&promise_object]() { + return promise_object->State() == + v8::Promise::PromiseState::kPending; + }) + .IsNothing()) + return napi_closing; + + *result = + v8impl::JsValueFromV8LocalValue(scope.Escape(promise_object->Result())); + if (promise_object->State() == v8::Promise::PromiseState::kRejected) + return napi_pending_exception; + + return napi_ok; +} diff --git a/src/node_api_embedding.h b/src/node_api_embedding.h index 4550ce958a3d19..2d2afcab8749a3 100644 --- a/src/node_api_embedding.h +++ b/src/node_api_embedding.h @@ -12,6 +12,8 @@ #define EXTERN_C_END #endif +typedef struct napi_platform__* napi_platform; + EXTERN_C_START typedef void (*napi_error_message_handler)(const char* msg);