From 9faf9ac9b8232fc9e6cda91d90615449d2236bd4 Mon Sep 17 00:00:00 2001 From: Cheng Date: Mon, 6 Jan 2025 17:14:11 +0900 Subject: [PATCH] Use official APIs for loading bootstrap script --- node | 2 +- src/bootstrap.js | 46 +++++++-------- src/yode.cc | 148 ++++++++++++++++++++++++++++++----------------- yode.gyp | 3 + 4 files changed, 118 insertions(+), 81 deletions(-) diff --git a/node b/node index 6372b05..e69d602 160000 --- a/node +++ b/node @@ -1 +1 @@ -Subproject commit 6372b0551929196e3d7dca615fa4a680932a0326 +Subproject commit e69d602ffa5c740136e073dedd0ea1e0fd22980a diff --git a/src/bootstrap.js b/src/bootstrap.js index f107be1..b290a5c 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -11,61 +11,55 @@ function wrapWithActivateUvLoop(func) { } } -(function bootstrap(process, global, internalRequire, execPath) { - delete process.bootstrap - +(function bootstrap(process, internalRequire, exports) { // The |require| here is actually |nativeModuleRequire|. const {BuiltinModule, internalBinding, require} = internalRequire('internal/bootstrap/realm') + const {compileFunctionForCJSLoader} = internalBinding('contextify') // Make async method work. const timers = require('timers') process.nextTick = wrapWithActivateUvLoop(process.nextTick) - global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate) - global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout) - global.setInterval = wrapWithActivateUvLoop(timers.setInterval) + this.setImmediate = wrapWithActivateUvLoop(timers.setImmediate) + this.setTimeout = wrapWithActivateUvLoop(timers.setTimeout) + this.setInterval = wrapWithActivateUvLoop(timers.setInterval) - // Wrap the source code like Module.wrapSafe. - const {compileFunction} = internalBinding('contextify') - function wrapSafe(filename, content) { - const compiled = compileFunction( - content, filename, 0, 0, undefined, false, undefined, [], - [ 'exports', 'require', 'module', '__filename', '__dirname', 'execPath' ]) - return compiled.function - } + // Use a virtual "asar" directory as root. + const dirname = require('path').join(process.execPath, 'asar') // Implemented to be loaded by nativeModuleRequire. class YodeModule { constructor(id, source) { this.id = id this.source = source + this.exports = {} + this.loaded = false + this.loading = false } compileForInternalLoader() { - if (!this.exports) { - this.exports = {} - const filename = this.id + '.js' - const compiledWrapper = wrapSafe(filename, this.source) - compiledWrapper.call(this.exports, this.exports, require, this, filename, '', execPath); - } + if (this.loaded || this.loading) + return this.exports + const filename = this.id + '.js' + const {function: compiledWrapper} = compileFunctionForCJSLoader(this.source, filename) + compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname); return this.exports } } // Turn our modules into built-in modules. - for (const id in this) - BuiltinModule.map.set(id, new YodeModule(id, this[id], require)) + for (const id in exports) + BuiltinModule.map.set(id, new YodeModule(id, exports[id], require)) try { // Is the executable concatenated with ASAR archive? const AsarArchive = require('asar_archive') - process.asarArchive = new AsarArchive(execPath/* REPLACE_WITH_OFFSET */) + process.asarArchive = new AsarArchive(process.execPath/* REPLACE_WITH_OFFSET */) // Monkey patch built-in modules. require('asar_monkey_patch').wrapFsWithAsar(require('fs')) - // Redirect Node to execute from current ASAR archive, using a virtual - // "asar" directory as root. - return require('path').join(execPath, 'asar') + // Redirect Node to execute from current ASAR archive. + return dirname } catch (error) { // Not an ASAR archive, continue to Node's default routine. if (error.message != 'Not an ASAR archive') diff --git a/src/yode.cc b/src/yode.cc index f3dbfd1..345bb15 100644 --- a/src/yode.cc +++ b/src/yode.cc @@ -23,9 +23,6 @@ namespace { // The global instance of NodeIntegration. std::unique_ptr g_node_integration; -// Has we run message loop before. -bool g_first_runloop = true; - // Untility function to create a V8 string. inline v8::Local ToV8(node::Environment* env, const char* str) { return v8::String::NewFromUtf8( @@ -34,15 +31,23 @@ inline v8::Local ToV8(node::Environment* env, const char* str) { // Force running uv loop. void ActivateUvLoop(const v8::FunctionCallbackInfo& args) { - g_node_integration->CallNextTick(); + if (g_node_integration) + g_node_integration->CallNextTick(); } // Invoke our bootstrap script. -void Bootstrap(const v8::FunctionCallbackInfo& args) { - node::Environment* env = node::Environment::GetCurrent(args); - CHECK(env); +void Bootstrap(node::Environment* env, + v8::Local process, + v8::Local require) { + // Set native methods. + node::SetMethod( + env->context(), env->process_object(), "activateUvLoop", &ActivateUvLoop); + // Set process.versions.yode. + v8::Local versions = env->process_object()->Get( + env->context(), ToV8(env, "versions")).ToLocalChecked(); + versions.As()->Set( + env->context(), ToV8(env, "yode"), ToV8(env, "0.11.1")).ToChecked(); // Initialize GUI after Node gets initialized. - v8::HandleScope handle_scope(env->isolate()); Init(env); // Put our scripts into |exports|. v8::Local exports = v8::Object::New(env->isolate()); @@ -58,14 +63,12 @@ void Bootstrap(const v8::FunctionCallbackInfo& args) { v8::Local bootstrap = v8::Local::Cast(result.ToLocalChecked()); // Invoke the |bootstrap| with |exports|. - std::vector> bootstrap_args(args.Length()); - for (int i = 0; i < args.Length(); ++i) - bootstrap_args[i] = args[i]; - bootstrap_args.push_back(ToV8(env, env->exec_path().c_str())); + std::vector> args = { process, require, exports }; TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal); - v8::MaybeLocal ret = - bootstrap->Call(env->context(), exports, - bootstrap_args.size(), bootstrap_args.data()); + v8::MaybeLocal ret = bootstrap->Call(env->context(), + env->context()->Global(), + args.size(), + args.data()); // Change process.argv if the binary starts itself. v8::Local r; if (ret.ToLocal(&r) && r->IsString()) { @@ -74,59 +77,96 @@ void Bootstrap(const v8::FunctionCallbackInfo& args) { } } -// Inject custom bindings. -bool InitWrapper(node::Environment* env) { - // Native methods. - node::SetMethod( - env->context(), env->process_object(), "bootstrap", &Bootstrap); - node::SetMethod( - env->context(), env->process_object(), "activateUvLoop", &ActivateUvLoop); - // process.versions.yode - v8::Local versions = env->process_object()->Get( - env->context(), ToV8(env, "versions")).ToLocalChecked(); - versions.As()->Set( - env->context(), ToV8(env, "yode"), ToV8(env, "0.11.0")).ToChecked(); - env->process_object()->DefineOwnProperty( - env->context(), ToV8(env, "versions"), versions, v8::ReadOnly).Check(); - return true; -} +// Like SpinEventLoop but replaces the uv_run with RunLoop. +int SpinGUIEventLoop(node::Environment* env) { + env->set_trace_sync_io(env->options()->trace_sync_io); -bool RunLoopWrapper(node::Environment* env) { - // Run uv loop for once before entering GUI message loop. - if (g_first_runloop) { - g_node_integration->UvRunOnce(); - g_first_runloop = false; - } // Run GUI message loop. RunLoop(env); // No need to keep uv loop alive. g_node_integration->ReleaseHandleRef(); // Enter uv loop to handle unfinished uv tasks. - return uv_run(env->event_loop(), UV_RUN_DEFAULT); + uv_run(env->event_loop(), UV_RUN_DEFAULT); + + node::EmitProcessBeforeExit(env); + env->set_trace_sync_io(false); + env->ForEachRealm([](auto* realm) { realm->VerifyNoStrongBaseObjects(); }); + auto exit_code = node::EmitProcessExitInternal(env); + return static_cast( + exit_code.FromMaybe(node::ExitCode::kGenericUserError)); } } // namespace int Start(int argc, char* argv[]) { - const char* run_as_node = getenv("YODE_RUN_AS_NODE"); - if (!run_as_node || strcmp(run_as_node, "1")) { - // Prepare node integration. - g_node_integration.reset(NodeIntegration::Create()); - g_node_integration->Init(); - - // Make Node use our message loop. - node::SetRunLoop(&InitWrapper, &RunLoopWrapper); - } - // Always enable GC this app is almost always running on desktop. v8::V8::SetFlagsFromString("--expose_gc", 11); - // Start node and enter message loop. - int code = node::Start(argc, argv); - - // Clean up node integration and quit. - g_node_integration.reset(); - return code; + // Set up per-process state. + std::vector args(argv, argv + argc); + auto init = node::InitializeOncePerProcess(args); + + // Initialize V8. + auto* platform = init->platform(); + std::unique_ptr array_buffer_allocator( + node::ArrayBufferAllocator::Create()); + auto isolate_params = std::make_unique(); + isolate_params->array_buffer_allocator = array_buffer_allocator.get(); + v8::Isolate* isolate = node::NewIsolate(isolate_params.get(), + uv_default_loop(), + platform); + std::unique_ptr isolate_data(node::CreateIsolateData( + isolate, + uv_default_loop(), + platform, + array_buffer_allocator.get(), + nullptr)); + isolate_data->max_young_gen_size = + isolate_params->constraints.max_young_generation_size_in_bytes(); + + int exit_code = 0; + { + // Create environment. + v8::Locker locker(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + v8::Local context = node::NewContext(isolate); + v8::Context::Scope context_scope(context); + node::DeleteFnPtr env( + node::CreateEnvironment(isolate_data.get(), + context, + init->args(), + init->exec_args())); + + // Check if this process should run GUI event loop. + const char* run_as_node = getenv("YODE_RUN_AS_NODE"); + if (!run_as_node || strcmp(run_as_node, "1")) { + g_node_integration.reset(NodeIntegration::Create()); + g_node_integration->Init(); + } + + // Load bootstrap script. + if (g_node_integration) + env->set_embedder_preload(&Bootstrap); + + // Load node. + { + node::LoadEnvironment(env.get(), node::StartExecutionCallback{}); + // Enter event loop. + if (g_node_integration) { + g_node_integration->UvRunOnce(); + exit_code = SpinGUIEventLoop(env.get()); + } else { + exit_code = node::SpinEventLoop(env.get()).FromMaybe(1); + } + } + node::Stop(env.get()); + } + isolate_data.reset(); + platform->UnregisterIsolate(isolate); + isolate->Dispose(); + node::TearDownOncePerProcess(); + return exit_code; } } // namespace yode diff --git a/yode.gyp b/yode.gyp index 78aa319..aed11de 100644 --- a/yode.gyp +++ b/yode.gyp @@ -64,6 +64,9 @@ }, }], ['OS=="win"', { + 'defines': [ + 'NOMINMAX', + ], 'sources': [ 'src/yode.rc', 'deps/node.def',