Skip to content

Commit bc1e621

Browse files
committed
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.
1 parent f04abdb commit bc1e621

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
@@ -999,6 +999,9 @@ class Environment : public MemoryRetainer {
999999

10001000
#endif // HAVE_INSPECTOR
10011001

1002+
inline const EmbedderPreloadCallback& embedder_preload() const;
1003+
inline void set_embedder_preload(EmbedderPreloadCallback fn);
1004+
10021005
inline void set_process_exit_handler(
10031006
std::function<void(Environment*, ExitCode)>&& handler);
10041007

@@ -1204,6 +1207,7 @@ class Environment : public MemoryRetainer {
12041207
std::unique_ptr<PrincipalRealm> principal_realm_ = nullptr;
12051208

12061209
builtins::BuiltinLoader builtin_loader_;
1210+
EmbedderPreloadCallback embedder_preload_;
12071211

12081212
// Used by allocate_managed_buffer() and release_managed_buffer() to keep
12091213
// 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 will run before executing the entry point, which
742+
// is usually used by embedders to inject scripts.
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
@@ -1304,6 +1304,12 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
13041304
.IsNothing())
13051305
return;
13061306

1307+
if (ret->Set(context,
1308+
FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload"),
1309+
Boolean::New(isolate, env->embedder_preload() != nullptr))
1310+
.IsNothing())
1311+
return;
1312+
13071313
args.GetReturnValue().Set(ret);
13081314
}
13091315

src/node_snapshotable.cc

+13
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,17 @@ void SerializeSnapshotableObjects(Realm* realm,
14101410
});
14111411
}
14121412

1413+
void RunEmbedderPreload(const FunctionCallbackInfo<Value>& args) {
1414+
Environment* env = Environment::GetCurrent(args);
1415+
CHECK(env->embedder_preload());
1416+
CHECK_EQ(args.Length(), 2);
1417+
Local<Value> process_obj = args[0];
1418+
Local<Value> require_fn = args[1];
1419+
CHECK(process_obj->IsObject());
1420+
CHECK(require_fn->IsFunction());
1421+
env->embedder_preload()(env, process_obj, require_fn);
1422+
}
1423+
14131424
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
14141425
CHECK(args[0]->IsString());
14151426
Local<String> filename = args[0].As<String>();
@@ -1533,6 +1544,7 @@ void CreatePerContextProperties(Local<Object> target,
15331544
void CreatePerIsolateProperties(IsolateData* isolate_data,
15341545
Local<ObjectTemplate> target) {
15351546
Isolate* isolate = isolate_data->isolate();
1547+
SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload);
15361548
SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain);
15371549
SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback);
15381550
SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback);
@@ -1545,6 +1557,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
15451557
}
15461558

15471559
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
1560+
registry->Register(RunEmbedderPreload);
15481561
registry->Register(CompileSerializeMain);
15491562
registry->Register(SetSerializeCallback);
15501563
registry->Register(SetDeserializeCallback);

src/node_worker.cc

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

389390
Debug(this, "Created message port for worker %llu", thread_id_.id);
390-
if (LoadEnvironment(env_.get(), StartExecutionCallback{}).IsEmpty())
391+
if (LoadEnvironment(env_.get(),
392+
StartExecutionCallback{},
393+
std::move(embedder_preload_))
394+
.IsEmpty()) {
391395
return;
396+
}
392397

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

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)