Skip to content

Commit b34512e

Browse files
authored
src: preload function for Environment
This PR adds a |preload| arg to the node::LoadEnvironment to allow embedders to set a preload function for the environment, which will run after the environment is loaded and before the main script runs. This is similiar to the --require CLI option, but runs a C++ function, and can only be set by embedders. The preload function can be used by embedders to inject scripts before running the main script, for example: 1. In Electron it is used to initialize the ASAR virtual filesystem, inject custom process properties, etc. 2. In VS Code it can be used to reset the module search paths for extensions. PR-URL: #51539 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
1 parent 151d365 commit b34512e

10 files changed

+108
-9
lines changed

lib/internal/process/pre_execution.js

+7
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ function setupUserModules(forceDefaultLoader = false) {
197197
} = require('internal/modules/helpers');
198198
assert(!hasStartedUserCJSExecution());
199199
assert(!hasStartedUserESMExecution());
200+
if (getEmbedderOptions().hasEmbedderPreload) {
201+
runEmbedderPreload();
202+
}
200203
// Do not enable preload modules if custom loaders are disabled.
201204
// For example, loader workers are responsible for doing this themselves.
202205
// And preload modules are not supported in ShadowRealm as well.
@@ -725,6 +728,10 @@ function initializeFrozenIntrinsics() {
725728
}
726729
}
727730

731+
function runEmbedderPreload() {
732+
internalBinding('mksnapshot').runEmbedderPreload(process, require);
733+
}
734+
728735
function loadPreloadModules() {
729736
// For user code, we preload modules if `-r` is passed
730737
const preloadModules = getOptionValue('--require');

src/api/environment.cc

+12-6
Original file line numberDiff line numberDiff line change
@@ -538,25 +538,31 @@ NODE_EXTERN std::unique_ptr<InspectorParentHandle> GetInspectorParentHandle(
538538
#endif
539539
}
540540

541-
MaybeLocal<Value> LoadEnvironment(
542-
Environment* env,
543-
StartExecutionCallback cb) {
541+
MaybeLocal<Value> LoadEnvironment(Environment* env,
542+
StartExecutionCallback cb,
543+
EmbedderPreloadCallback preload) {
544544
env->InitializeLibuv();
545545
env->InitializeDiagnostics();
546+
if (preload) {
547+
env->set_embedder_preload(std::move(preload));
548+
}
546549

547550
return StartExecution(env, cb);
548551
}
549552

550553
MaybeLocal<Value> LoadEnvironment(Environment* env,
551-
std::string_view main_script_source_utf8) {
554+
std::string_view main_script_source_utf8,
555+
EmbedderPreloadCallback preload) {
552556
CHECK_NOT_NULL(main_script_source_utf8.data());
553557
return LoadEnvironment(
554-
env, [&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
558+
env,
559+
[&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
555560
Local<Value> main_script =
556561
ToV8Value(env->context(), main_script_source_utf8).ToLocalChecked();
557562
return info.run_cjs->Call(
558563
env->context(), Null(env->isolate()), 1, &main_script);
559-
});
564+
},
565+
std::move(preload));
560566
}
561567

562568
Environment* GetCurrentEnvironment(Local<Context> context) {

src/env-inl.h

+8
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,14 @@ inline builtins::BuiltinLoader* Environment::builtin_loader() {
430430
return &builtin_loader_;
431431
}
432432

433+
inline const EmbedderPreloadCallback& Environment::embedder_preload() const {
434+
return embedder_preload_;
435+
}
436+
437+
inline void Environment::set_embedder_preload(EmbedderPreloadCallback fn) {
438+
embedder_preload_ = std::move(fn);
439+
}
440+
433441
inline double Environment::new_async_id() {
434442
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
435443
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];

src/env.h

+4
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,9 @@ class Environment : public MemoryRetainer {
10021002

10031003
#endif // HAVE_INSPECTOR
10041004

1005+
inline const EmbedderPreloadCallback& embedder_preload() const;
1006+
inline void set_embedder_preload(EmbedderPreloadCallback fn);
1007+
10051008
inline void set_process_exit_handler(
10061009
std::function<void(Environment*, ExitCode)>&& handler);
10071010

@@ -1207,6 +1210,7 @@ class Environment : public MemoryRetainer {
12071210
std::unique_ptr<PrincipalRealm> principal_realm_ = nullptr;
12081211

12091212
builtins::BuiltinLoader builtin_loader_;
1213+
EmbedderPreloadCallback embedder_preload_;
12101214

12111215
// Used by allocate_managed_buffer() and release_managed_buffer() to keep
12121216
// track of the BackingStore for a given pointer.

src/node.h

+23-2
Original file line numberDiff line numberDiff line change
@@ -731,12 +731,33 @@ struct StartExecutionCallbackInfo {
731731

732732
using StartExecutionCallback =
733733
std::function<v8::MaybeLocal<v8::Value>(const StartExecutionCallbackInfo&)>;
734+
using EmbedderPreloadCallback =
735+
std::function<void(Environment* env,
736+
v8::Local<v8::Value> process,
737+
v8::Local<v8::Value> require)>;
734738

739+
// Run initialization for the environment.
740+
//
741+
// The |preload| function, usually used by embedders to inject scripts,
742+
// will be run by Node.js before Node.js executes the entry point.
743+
// The function is guaranteed to run before the user land module loader running
744+
// any user code, so it is safe to assume that at this point, no user code has
745+
// been run yet.
746+
// The function will be executed with preload(process, require), and the passed
747+
// require function has access to internal Node.js modules. There is no
748+
// stability guarantee about the internals exposed to the internal require
749+
// function. Expect breakages when updating Node.js versions if the embedder
750+
// imports internal modules with the internal require function.
751+
// Worker threads created in the environment will also respect The |preload|
752+
// function, so make sure the function is thread-safe.
735753
NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
736754
Environment* env,
737-
StartExecutionCallback cb);
755+
StartExecutionCallback cb,
756+
EmbedderPreloadCallback preload = nullptr);
738757
NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
739-
Environment* env, std::string_view main_script_source_utf8);
758+
Environment* env,
759+
std::string_view main_script_source_utf8,
760+
EmbedderPreloadCallback preload = nullptr);
740761
NODE_EXTERN void FreeEnvironment(Environment* env);
741762

742763
// Set a callback that is called when process.exit() is called from JS,

src/node_options.cc

+6
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,12 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
13101310
.IsNothing())
13111311
return;
13121312

1313+
if (ret->Set(context,
1314+
FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload"),
1315+
Boolean::New(isolate, env->embedder_preload() != nullptr))
1316+
.IsNothing())
1317+
return;
1318+
13131319
args.GetReturnValue().Set(ret);
13141320
}
13151321

src/node_snapshotable.cc

+13
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,17 @@ void SerializeSnapshotableObjects(Realm* realm,
14181418
});
14191419
}
14201420

1421+
void RunEmbedderPreload(const FunctionCallbackInfo<Value>& args) {
1422+
Environment* env = Environment::GetCurrent(args);
1423+
CHECK(env->embedder_preload());
1424+
CHECK_EQ(args.Length(), 2);
1425+
Local<Value> process_obj = args[0];
1426+
Local<Value> require_fn = args[1];
1427+
CHECK(process_obj->IsObject());
1428+
CHECK(require_fn->IsFunction());
1429+
env->embedder_preload()(env, process_obj, require_fn);
1430+
}
1431+
14211432
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
14221433
CHECK(args[0]->IsString());
14231434
Local<String> filename = args[0].As<String>();
@@ -1541,6 +1552,7 @@ void CreatePerContextProperties(Local<Object> target,
15411552
void CreatePerIsolateProperties(IsolateData* isolate_data,
15421553
Local<ObjectTemplate> target) {
15431554
Isolate* isolate = isolate_data->isolate();
1555+
SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload);
15441556
SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain);
15451557
SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback);
15461558
SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback);
@@ -1553,6 +1565,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
15531565
}
15541566

15551567
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
1568+
registry->Register(RunEmbedderPreload);
15561569
registry->Register(CompileSerializeMain);
15571570
registry->Register(SetSerializeCallback);
15581571
registry->Register(SetDeserializeCallback);

src/node_worker.cc

+6-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Worker::Worker(Environment* env,
6262
thread_id_(AllocateEnvironmentThreadId()),
6363
name_(name),
6464
env_vars_(env_vars),
65+
embedder_preload_(env->embedder_preload()),
6566
snapshot_data_(snapshot_data) {
6667
Debug(this, "Creating new worker instance with thread id %llu",
6768
thread_id_.id);
@@ -386,8 +387,12 @@ void Worker::Run() {
386387
}
387388

388389
Debug(this, "Created message port for worker %llu", thread_id_.id);
389-
if (LoadEnvironment(env_.get(), StartExecutionCallback{}).IsEmpty())
390+
if (LoadEnvironment(env_.get(),
391+
StartExecutionCallback{},
392+
std::move(embedder_preload_))
393+
.IsEmpty()) {
390394
return;
395+
}
391396

392397
Debug(this, "Loaded environment for worker %llu", thread_id_.id);
393398
}

src/node_worker.h

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class Worker : public AsyncWrap {
114114

115115
std::unique_ptr<MessagePortData> child_port_data_;
116116
std::shared_ptr<KVStore> env_vars_;
117+
EmbedderPreloadCallback embedder_preload_;
117118

118119
// A raw flag that is used by creator and worker threads to
119120
// sync up on pre-mature termination of worker - while in the

test/cctest/test_environment.cc

+28
Original file line numberDiff line numberDiff line change
@@ -778,3 +778,31 @@ TEST_F(EnvironmentTest, RequestInterruptAtExit) {
778778

779779
context->Exit();
780780
}
781+
782+
TEST_F(EnvironmentTest, EmbedderPreload) {
783+
v8::HandleScope handle_scope(isolate_);
784+
v8::Local<v8::Context> context = node::NewContext(isolate_);
785+
v8::Context::Scope context_scope(context);
786+
787+
node::EmbedderPreloadCallback preload = [](node::Environment* env,
788+
v8::Local<v8::Value> process,
789+
v8::Local<v8::Value> require) {
790+
CHECK(process->IsObject());
791+
CHECK(require->IsFunction());
792+
process.As<v8::Object>()
793+
->Set(env->context(),
794+
v8::String::NewFromUtf8Literal(env->isolate(), "prop"),
795+
v8::String::NewFromUtf8Literal(env->isolate(), "preload"))
796+
.Check();
797+
};
798+
799+
std::unique_ptr<node::Environment, decltype(&node::FreeEnvironment)> env(
800+
node::CreateEnvironment(isolate_data_, context, {}, {}),
801+
node::FreeEnvironment);
802+
803+
v8::Local<v8::Value> main_ret =
804+
node::LoadEnvironment(env.get(), "return process.prop;", preload)
805+
.ToLocalChecked();
806+
node::Utf8Value main_ret_str(isolate_, main_ret);
807+
EXPECT_EQ(std::string(*main_ret_str), "preload");
808+
}

0 commit comments

Comments
 (0)