Skip to content

Commit c319459

Browse files
committed
src: preload function for Environment
This PR adds a |preload| arg to the node::CreateEnvironment 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 f820efe commit c319459

File tree

11 files changed

+91
-7
lines changed

11 files changed

+91
-7
lines changed

lib/internal/process/pre_execution.js

Lines changed: 7 additions & 0 deletions
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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,8 @@ Environment* CreateEnvironment(
431431
const std::vector<std::string>& exec_args,
432432
EnvironmentFlags::Flags flags,
433433
ThreadId thread_id,
434-
std::unique_ptr<InspectorParentHandle> inspector_parent_handle) {
434+
std::unique_ptr<InspectorParentHandle> inspector_parent_handle,
435+
EmbedderPreloadCallback preload) {
435436
Isolate* isolate = isolate_data->isolate();
436437

437438
Isolate::Scope isolate_scope(isolate);
@@ -452,7 +453,8 @@ Environment* CreateEnvironment(
452453
exec_args,
453454
env_snapshot_info,
454455
flags,
455-
thread_id);
456+
thread_id,
457+
std::move(preload));
456458
CHECK_NOT_NULL(env);
457459

458460
if (use_snapshot) {

src/env-inl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ 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+
441445
inline double Environment::new_async_id() {
442446
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
443447
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];

src/env.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,8 @@ Environment::Environment(IsolateData* isolate_data,
776776
const std::vector<std::string>& exec_args,
777777
const EnvSerializeInfo* env_info,
778778
EnvironmentFlags::Flags flags,
779-
ThreadId thread_id)
779+
ThreadId thread_id,
780+
EmbedderPreloadCallback preload)
780781
: isolate_(isolate),
781782
isolate_data_(isolate_data),
782783
async_hooks_(isolate, MAYBE_FIELD_PTR(env_info, async_hooks)),
@@ -802,7 +803,8 @@ Environment::Environment(IsolateData* isolate_data,
802803
flags_(flags),
803804
thread_id_(thread_id.id == static_cast<uint64_t>(-1)
804805
? AllocateEnvironmentThreadId().id
805-
: thread_id.id) {
806+
: thread_id.id),
807+
embedder_preload_(std::move(preload)) {
806808
constexpr bool is_shared_ro_heap =
807809
#ifdef NODE_V8_SHARED_RO_HEAP
808810
true;

src/env.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,8 @@ class Environment : public MemoryRetainer {
649649
const std::vector<std::string>& exec_args,
650650
const EnvSerializeInfo* env_info,
651651
EnvironmentFlags::Flags flags,
652-
ThreadId thread_id);
652+
ThreadId thread_id,
653+
EmbedderPreloadCallback preload);
653654
void InitializeMainContext(v8::Local<v8::Context> context,
654655
const EnvSerializeInfo* env_info);
655656
~Environment() override;
@@ -1002,6 +1003,8 @@ class Environment : public MemoryRetainer {
10021003
inline const StartExecutionCallback& embedder_entry_point() const;
10031004
inline void set_embedder_entry_point(StartExecutionCallback&& fn);
10041005

1006+
inline const EmbedderPreloadCallback& embedder_preload() const;
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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,19 +691,32 @@ struct InspectorParentHandle {
691691
virtual ~InspectorParentHandle() = default;
692692
};
693693

694+
using EmbedderPreloadCallback =
695+
std::function<void(Environment* env,
696+
v8::Local<v8::Value> process,
697+
v8::Local<v8::Value> require)>;
698+
694699
// TODO(addaleax): Maybe move per-Environment options parsing here.
695700
// Returns nullptr when the Environment cannot be created e.g. there are
696701
// pending JavaScript exceptions.
697702
// `context` may be empty if an `EmbedderSnapshotData` instance was provided
698703
// to `NewIsolate()` and `CreateIsolateData()`.
704+
//
705+
// The `preload` function will run before executing the entry point, which
706+
// is usually used by embedders to inject scripts. The function is executed
707+
// with preload(process, require), and the passed require function has access
708+
// to internal Node.js modules. The `preload` function is inherited by worker
709+
// threads and thus will run in work threads, so make sure the function is
710+
// thread-safe.
699711
NODE_EXTERN Environment* CreateEnvironment(
700712
IsolateData* isolate_data,
701713
v8::Local<v8::Context> context,
702714
const std::vector<std::string>& args,
703715
const std::vector<std::string>& exec_args,
704716
EnvironmentFlags::Flags flags = EnvironmentFlags::kDefaultFlags,
705717
ThreadId thread_id = {} /* allocates a thread id automatically */,
706-
std::unique_ptr<InspectorParentHandle> inspector_parent_handle = {});
718+
std::unique_ptr<InspectorParentHandle> inspector_parent_handle = {},
719+
EmbedderPreloadCallback preload = nullptr);
707720

708721
// Returns a handle that can be passed to `LoadEnvironment()`, making the
709722
// child Environment accessible to the inspector as if it were a Node.js Worker.

src/node_options.cc

Lines changed: 6 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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);
@@ -366,7 +367,8 @@ void Worker::Run() {
366367
std::move(exec_argv_),
367368
static_cast<EnvironmentFlags::Flags>(environment_flags_),
368369
thread_id_,
369-
std::move(inspector_parent_handle_)));
370+
std::move(inspector_parent_handle_),
371+
std::move(embedder_preload_)));
370372
if (is_stopped()) return;
371373
CHECK_NOT_NULL(env_);
372374
env_->set_env_vars(std::move(env_vars_));

src/node_worker.h

Lines changed: 1 addition & 0 deletions
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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,3 +778,37 @@ 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_,
801+
context,
802+
{},
803+
{},
804+
node::EnvironmentFlags::kDefaultFlags,
805+
{},
806+
{},
807+
preload),
808+
node::FreeEnvironment);
809+
810+
v8::Local<v8::Value> main_ret =
811+
node::LoadEnvironment(env.get(), "return process.prop;").ToLocalChecked();
812+
node::Utf8Value main_ret_str(isolate_, main_ret);
813+
EXPECT_EQ(std::string(*main_ret_str), "preload");
814+
}

0 commit comments

Comments
 (0)