diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 98533b7828d3ff..fd632be10099b0 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -186,6 +186,9 @@ function setupUserModules(forceDefaultLoader = false) { initializeESMLoader(forceDefaultLoader); const CJSLoader = require('internal/modules/cjs/loader'); assert(!CJSLoader.hasLoadedAnyUserCJSModule); + if (getEmbedderOptions().hasEmbedderPreload) { + runEmbedderPreload(); + } // Do not enable preload modules if custom loaders are disabled. // For example, loader workers are responsible for doing this themselves. // And preload modules are not supported in ShadowRealm as well. @@ -754,6 +757,10 @@ function initializeFrozenIntrinsics() { } } +function runEmbedderPreload() { + internalBinding('mksnapshot').runEmbedderPreload(process, require); +} + function loadPreloadModules() { // For user code, we preload modules if `-r` is passed const preloadModules = getOptionValue('--require'); diff --git a/src/api/environment.cc b/src/api/environment.cc index 8d7ecad56175ff..aedaf055225e5f 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -431,7 +431,8 @@ Environment* CreateEnvironment( const std::vector& exec_args, EnvironmentFlags::Flags flags, ThreadId thread_id, - std::unique_ptr inspector_parent_handle) { + std::unique_ptr inspector_parent_handle, + EmbedderPreloadCallback preload) { Isolate* isolate = isolate_data->isolate(); Isolate::Scope isolate_scope(isolate); @@ -452,7 +453,8 @@ Environment* CreateEnvironment( exec_args, env_snapshot_info, flags, - thread_id); + thread_id, + std::move(preload)); CHECK_NOT_NULL(env); if (use_snapshot) { diff --git a/src/env-inl.h b/src/env-inl.h index 56a96def344194..1c31aab45cd500 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -438,6 +438,10 @@ inline void Environment::set_embedder_entry_point(StartExecutionCallback&& fn) { embedder_entry_point_ = std::move(fn); } +inline const EmbedderPreloadCallback& Environment::embedder_preload() const { + return embedder_preload_; +} + inline double Environment::new_async_id() { async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1; return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter]; diff --git a/src/env.cc b/src/env.cc index 424f400b8f0f8f..7fc4d68f0ea5cc 100644 --- a/src/env.cc +++ b/src/env.cc @@ -776,7 +776,8 @@ Environment::Environment(IsolateData* isolate_data, const std::vector& exec_args, const EnvSerializeInfo* env_info, EnvironmentFlags::Flags flags, - ThreadId thread_id) + ThreadId thread_id, + EmbedderPreloadCallback preload) : isolate_(isolate), isolate_data_(isolate_data), async_hooks_(isolate, MAYBE_FIELD_PTR(env_info, async_hooks)), @@ -802,7 +803,8 @@ Environment::Environment(IsolateData* isolate_data, flags_(flags), thread_id_(thread_id.id == static_cast(-1) ? AllocateEnvironmentThreadId().id - : thread_id.id) { + : thread_id.id), + embedder_preload_(std::move(preload)) { constexpr bool is_shared_ro_heap = #ifdef NODE_V8_SHARED_RO_HEAP true; diff --git a/src/env.h b/src/env.h index 59ee108e5e8cc1..1c737ecaf59a31 100644 --- a/src/env.h +++ b/src/env.h @@ -649,7 +649,8 @@ class Environment : public MemoryRetainer { const std::vector& exec_args, const EnvSerializeInfo* env_info, EnvironmentFlags::Flags flags, - ThreadId thread_id); + ThreadId thread_id, + EmbedderPreloadCallback preload); void InitializeMainContext(v8::Local context, const EnvSerializeInfo* env_info); ~Environment() override; @@ -1002,6 +1003,8 @@ class Environment : public MemoryRetainer { inline const StartExecutionCallback& embedder_entry_point() const; inline void set_embedder_entry_point(StartExecutionCallback&& fn); + inline const EmbedderPreloadCallback& embedder_preload() const; + inline void set_process_exit_handler( std::function&& handler); @@ -1208,6 +1211,7 @@ class Environment : public MemoryRetainer { builtins::BuiltinLoader builtin_loader_; StartExecutionCallback embedder_entry_point_; + EmbedderPreloadCallback embedder_preload_; // Used by allocate_managed_buffer() and release_managed_buffer() to keep // track of the BackingStore for a given pointer. diff --git a/src/node.h b/src/node.h index bf3382f4c952ca..7b6cf300ffea53 100644 --- a/src/node.h +++ b/src/node.h @@ -691,11 +691,23 @@ struct InspectorParentHandle { virtual ~InspectorParentHandle() = default; }; +using EmbedderPreloadCallback = + std::function process, + v8::Local require)>; + // TODO(addaleax): Maybe move per-Environment options parsing here. // Returns nullptr when the Environment cannot be created e.g. there are // pending JavaScript exceptions. // `context` may be empty if an `EmbedderSnapshotData` instance was provided // to `NewIsolate()` and `CreateIsolateData()`. +// +// The `preload` function will run before executing the entry point, which +// is usually used by embedders to inject scripts. The function is executed +// with preload(process, require), and the passed require function has access +// to internal Node.js modules. The `preload` function is inherited by worker +// threads and thus will run in work threads, so make sure the function is +// thread-safe. NODE_EXTERN Environment* CreateEnvironment( IsolateData* isolate_data, v8::Local context, @@ -703,7 +715,8 @@ NODE_EXTERN Environment* CreateEnvironment( const std::vector& exec_args, EnvironmentFlags::Flags flags = EnvironmentFlags::kDefaultFlags, ThreadId thread_id = {} /* allocates a thread id automatically */, - std::unique_ptr inspector_parent_handle = {}); + std::unique_ptr inspector_parent_handle = {}, + EmbedderPreloadCallback preload = nullptr); // Returns a handle that can be passed to `LoadEnvironment()`, making the // child Environment accessible to the inspector as if it were a Node.js Worker. diff --git a/src/node_options.cc b/src/node_options.cc index 7b5152172c5ce7..f4bd5e16402928 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1304,6 +1304,12 @@ void GetEmbedderOptions(const FunctionCallbackInfo& args) { .IsNothing()) return; + if (ret->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload"), + Boolean::New(isolate, env->embedder_preload() != nullptr)) + .IsNothing()) + return; + args.GetReturnValue().Set(ret); } diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index c59a4cdccb9c8a..05abc3d418e4b7 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1453,6 +1453,13 @@ static void RunEmbedderEntryPoint(const FunctionCallbackInfo& args) { } } +static void RunEmbedderPreload(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->embedder_preload()); + CHECK_EQ(args.Length(), 2); + env->embedder_preload()(env, args[0], args[1]); +} + void CompileSerializeMain(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Local filename = args[0].As(); @@ -1577,6 +1584,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); SetMethod(isolate, target, "runEmbedderEntryPoint", RunEmbedderEntryPoint); + SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload); SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain); SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback); SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback); @@ -1590,6 +1598,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(RunEmbedderEntryPoint); + registry->Register(RunEmbedderPreload); registry->Register(CompileSerializeMain); registry->Register(SetSerializeCallback); registry->Register(SetDeserializeCallback); diff --git a/src/node_worker.cc b/src/node_worker.cc index 552fdc438a0895..b44a66cfcec8d8 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -63,6 +63,7 @@ Worker::Worker(Environment* env, thread_id_(AllocateEnvironmentThreadId()), name_(name), env_vars_(env_vars), + embedder_preload_(env->embedder_preload()), snapshot_data_(snapshot_data) { Debug(this, "Creating new worker instance with thread id %llu", thread_id_.id); @@ -366,7 +367,8 @@ void Worker::Run() { std::move(exec_argv_), static_cast(environment_flags_), thread_id_, - std::move(inspector_parent_handle_))); + std::move(inspector_parent_handle_), + std::move(embedder_preload_))); if (is_stopped()) return; CHECK_NOT_NULL(env_); env_->set_env_vars(std::move(env_vars_)); diff --git a/src/node_worker.h b/src/node_worker.h index 531e2b5287010f..07fd7b460654e1 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -114,6 +114,7 @@ class Worker : public AsyncWrap { std::unique_ptr child_port_data_; std::shared_ptr env_vars_; + EmbedderPreloadCallback embedder_preload_; // A raw flag that is used by creator and worker threads to // sync up on pre-mature termination of worker - while in the diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 9b812408154287..958cbe6dff48b1 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -778,3 +778,37 @@ TEST_F(EnvironmentTest, RequestInterruptAtExit) { context->Exit(); } + +TEST_F(EnvironmentTest, EmbedderPreload) { + v8::HandleScope handle_scope(isolate_); + v8::Local context = node::NewContext(isolate_); + v8::Context::Scope context_scope(context); + + node::EmbedderPreloadCallback preload = [](node::Environment* env, + v8::Local process, + v8::Local require) { + CHECK(process->IsObject()); + CHECK(require->IsFunction()); + process.As() + ->Set(env->context(), + v8::String::NewFromUtf8Literal(env->isolate(), "prop"), + v8::String::NewFromUtf8Literal(env->isolate(), "preload")) + .Check(); + }; + + std::unique_ptr env( + node::CreateEnvironment(isolate_data_, + context, + {}, + {}, + node::EnvironmentFlags::kDefaultFlags, + {}, + {}, + preload), + node::FreeEnvironment); + + v8::Local main_ret = + node::LoadEnvironment(env.get(), "return process.prop;").ToLocalChecked(); + node::Utf8Value main_ret_str(isolate_, main_ret); + EXPECT_EQ(std::string(*main_ret_str), "preload"); +}