Skip to content

Commit 289d86f

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 0f461aa commit 289d86f

10 files changed

+98
-9
lines changed

lib/internal/process/pre_execution.js

+7
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ function setupUserModules(forceDefaultLoader = false) {
186186
initializeESMLoader(forceDefaultLoader);
187187
const CJSLoader = require('internal/modules/cjs/loader');
188188
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
189+
if (getEmbedderOptions().hasEmbedderPreload) {
190+
runEmbedderPreload();
191+
}
189192
// Do not enable preload modules if custom loaders are disabled.
190193
// For example, loader workers are responsible for doing this themselves.
191194
// And preload modules are not supported in ShadowRealm as well.
@@ -754,6 +757,10 @@ function initializeFrozenIntrinsics() {
754757
}
755758
}
756759

760+
function runEmbedderPreload() {
761+
internalBinding('mksnapshot').runEmbedderPreload(process, require);
762+
}
763+
757764
function loadPreloadModules() {
758765
// For user code, we preload modules if `-r` is passed
759766
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
@@ -438,6 +438,14 @@ inline void Environment::set_embedder_entry_point(StartExecutionCallback&& fn) {
438438
embedder_entry_point_ = std::move(fn);
439439
}
440440

441+
inline const EmbedderPreloadCallback& Environment::embedder_preload() const {
442+
return embedder_preload_;
443+
}
444+
445+
inline void Environment::set_embedder_preload(EmbedderPreloadCallback fn) {
446+
embedder_preload_ = std::move(fn);
447+
}
448+
441449
inline double Environment::new_async_id() {
442450
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
443451
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
inline const StartExecutionCallback& embedder_entry_point() const;
10031003
inline void set_embedder_entry_point(StartExecutionCallback&& fn);
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

@@ -1208,6 +1211,7 @@ class Environment : public MemoryRetainer {
12081211

12091212
builtins::BuiltinLoader builtin_loader_;
12101213
StartExecutionCallback embedder_entry_point_;
1214+
EmbedderPreloadCallback embedder_preload_;
12111215

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

src/node.h

+17-2
Original file line numberDiff line numberDiff line change
@@ -731,12 +731,27 @@ 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. The function is executed
743+
// with preload(process, require), and the passed require function has access
744+
// to internal Node.js modules. The |preload| function is inherited by worker
745+
// threads and thus will run in work threads, so make sure the function is
746+
// thread-safe.
735747
NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
736748
Environment* env,
737-
StartExecutionCallback cb);
749+
StartExecutionCallback cb,
750+
EmbedderPreloadCallback preload = nullptr);
738751
NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
739-
Environment* env, std::string_view main_script_source_utf8);
752+
Environment* env,
753+
std::string_view main_script_source_utf8,
754+
EmbedderPreloadCallback preload = nullptr);
740755
NODE_EXTERN void FreeEnvironment(Environment* env);
741756

742757
// 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

+9
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,13 @@ static void RunEmbedderEntryPoint(const FunctionCallbackInfo<Value>& args) {
14531453
}
14541454
}
14551455

1456+
static void RunEmbedderPreload(const FunctionCallbackInfo<Value>& args) {
1457+
Environment* env = Environment::GetCurrent(args);
1458+
CHECK(env->embedder_preload());
1459+
CHECK_EQ(args.Length(), 2);
1460+
env->embedder_preload()(env, args[0], args[1]);
1461+
}
1462+
14561463
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
14571464
CHECK(args[0]->IsString());
14581465
Local<String> filename = args[0].As<String>();
@@ -1577,6 +1584,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
15771584
Local<ObjectTemplate> target) {
15781585
Isolate* isolate = isolate_data->isolate();
15791586
SetMethod(isolate, target, "runEmbedderEntryPoint", RunEmbedderEntryPoint);
1587+
SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload);
15801588
SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain);
15811589
SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback);
15821590
SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback);
@@ -1590,6 +1598,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
15901598

15911599
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
15921600
registry->Register(RunEmbedderEntryPoint);
1601+
registry->Register(RunEmbedderPreload);
15931602
registry->Register(CompileSerializeMain);
15941603
registry->Register(SetSerializeCallback);
15951604
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)