#include "shell/common/node_bindings.h"
#include <algorithm>
#include <iostream>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "base/allocator/partition_allocator/src/partition_alloc/oom.h"
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/containers/fixed_flat_set.h"
#include "base/environment.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "chrome/common/chrome_version.h"
#include "content/public/common/content_paths.h"
#include "content/public/renderer/render_frame.h"
#include "electron/buildflags/buildflags.h"
#include "electron/electron_version.h"
#include "electron/fuses.h"
#include "electron/mas.h"
#include "gin/per_context_data.h"
#include "shell/browser/api/electron_api_app.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/electron_command_line.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/event.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/gin_helper/function_template.h"
#include "shell/common/js2c_bundle_ids.h"
#include "shell/common/mac/main_application_bundle.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/platform_util.h"
#include "shell/common/process_util.h"
#include "shell/common/world_ids.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"
#include "third_party/electron_node/src/debug_utils.h"
#include "third_party/electron_node/src/module_wrap.h"
#include "third_party/electron_node/src/node_realm-inl.h"
#include "third_party/electron_node/src/node_snapshot_builder.h"
#include "v8/include/v8-statistics.h"
#if !IS_MAS_BUILD()
#include "shell/common/crash_keys.h"
#include "shell/common/v8_oom_diagnostics.h"
#endif
#define ELECTRON_BROWSER_BINDINGS(V) \
V(electron_browser_app) \
V(electron_browser_auto_updater) \
V(electron_browser_content_tracing) \
V(electron_browser_crash_reporter) \
V(electron_browser_desktop_capturer) \
V(electron_browser_dialog) \
V(electron_browser_event_emitter) \
V(electron_browser_ipc_dispatch) \
V(electron_browser_global_shortcut) \
V(electron_browser_image_view) \
V(electron_browser_in_app_purchase) \
V(electron_browser_menu) \
V(electron_browser_message_port) \
V(electron_browser_msix_updater) \
V(electron_browser_native_theme) \
V(electron_browser_notification) \
V(electron_browser_power_monitor) \
V(electron_browser_power_save_blocker) \
V(electron_browser_protocol) \
V(electron_browser_push_notifications) \
V(electron_browser_safe_storage) \
V(electron_browser_service_worker_main) \
V(electron_browser_session) \
V(electron_browser_screen) \
V(electron_browser_system_preferences) \
V(electron_browser_base_window) \
V(electron_browser_clipboard) \
V(electron_browser_clipboard_item) \
V(electron_browser_tray) \
V(electron_browser_utility_process) \
V(electron_browser_view) \
V(electron_browser_web_contents) \
V(electron_browser_web_contents_view) \
V(electron_browser_web_frame_main) \
V(electron_browser_web_view_manager) \
V(electron_browser_window) \
V(electron_common_net)
#define ELECTRON_COMMON_BINDINGS(V) \
V(electron_common_asar) \
V(electron_common_command_line) \
V(electron_common_crashpad_support) \
V(electron_common_events) \
V(electron_common_features) \
V(electron_common_native_image) \
V(electron_common_shared_texture) \
V(electron_common_shell) \
V(electron_common_v8_util)
#define ELECTRON_RENDERER_BINDINGS(V) \
V(electron_renderer_web_utils) \
V(electron_renderer_context_bridge) \
V(electron_renderer_crash_reporter) \
V(electron_renderer_ipc) \
V(electron_renderer_web_frame)
#if BUILDFLAG(ENABLE_PROMPT_API)
#define ELECTRON_UTILITY_PROMPT_API_BINDINGS(V) \
V(electron_utility_local_ai_handler)
#else
#define ELECTRON_UTILITY_PROMPT_API_BINDINGS(V)
#endif
#define ELECTRON_UTILITY_BINDINGS(V) \
V(electron_browser_event_emitter) \
V(electron_browser_system_preferences) \
V(electron_common_net) \
ELECTRON_UTILITY_PROMPT_API_BINDINGS(V) \
V(electron_utility_parent_port)
#define ELECTRON_TESTING_BINDINGS(V) V(electron_common_testing)
#define V(modname) \
void _register_##modname(); \
node::node_module* get_linked_module_##modname();
ELECTRON_BROWSER_BINDINGS(V)
ELECTRON_COMMON_BINDINGS(V)
ELECTRON_RENDERER_BINDINGS(V)
ELECTRON_UTILITY_BINDINGS(V)
#if DCHECK_IS_ON()
ELECTRON_TESTING_BINDINGS(V)
#endif
#undef V
using node::loader::ModuleWrap;
namespace {
void stop_and_close_uv_loop(uv_loop_t* loop) {
uv_stop(loop);
auto const ensure_closing = [](uv_handle_t* handle, void*) {
DCHECK(uv_is_closing(handle));
if (!uv_is_closing(handle)) {
uv_close(handle, nullptr);
}
};
uv_walk(loop, ensure_closing, nullptr);
for (;;)
if (uv_run(loop, UV_RUN_DEFAULT) == 0)
break;
DCHECK_EQ(0, uv_loop_alive(loop));
node::CheckedUvLoopClose(loop);
}
bool g_is_initialized = false;
void V8FatalErrorCallback(const char* location, const char* message) {
LOG(ERROR) << "Fatal error in V8: " << location << " " << message;
#if !IS_MAS_BUILD()
electron::crash_keys::SetCrashKey("electron.v8-fatal.message", message);
electron::crash_keys::SetCrashKey("electron.v8-fatal.location", location);
#endif
volatile int* zero = nullptr;
*zero = 0;
}
void V8OOMErrorCallback(const char* location, const v8::OOMDetails& details) {
const char* message =
details.is_heap_oom ? "Allocation failed - JavaScript heap out of memory"
: "Allocation failed - process out of memory";
if (location) {
LOG(ERROR) << "OOM error in V8: " << location << " " << message;
} else {
LOG(ERROR) << "OOM error in V8: " << message;
}
if (details.detail) {
LOG(ERROR) << "OOM detail: " << details.detail;
}
#if !IS_MAS_BUILD()
v8::Isolate* isolate = v8::Isolate::TryGetCurrent();
electron::v8_oom::RecordErrorDetails(isolate, location, details);
electron::v8_oom::RecordHeapDiagnostics(isolate);
#endif
OOM_CRASH(0);
}
bool AllowWasmCodeGenerationCallback(v8::Local<v8::Context> context,
v8::Local<v8::String> source) {
if (node::Environment::GetCurrent(context) == nullptr) {
if (!electron::IsRendererProcess())
return false;
return blink::V8Initializer::WasmCodeGenerationCheckCallback(context,
source);
}
return node::AllowWasmCodeGenerationCallback(context, source);
}
enum ESMHandlerPlatform {
kNone,
kNodeJS,
kBlink,
};
static ESMHandlerPlatform SelectESMHandlerPlatform(
v8::Local<v8::Context> context,
v8::Local<v8::Data> raw_host_defined_options) {
if (node::Environment::GetCurrent(context) == nullptr) {
if (electron::IsBrowserProcess() || electron::IsUtilityProcess())
return ESMHandlerPlatform::kNone;
return ESMHandlerPlatform::kBlink;
}
if (!electron::IsRendererProcess())
return ESMHandlerPlatform::kNodeJS;
blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForContext(context);
if (frame == nullptr)
return ESMHandlerPlatform::kBlink;
auto prefs = content::RenderFrame::FromWebFrame(frame)->GetBlinkPreferences();
if (prefs.context_isolation) {
return frame->GetScriptContextWorldId(context) ==
electron::WorldIDs::ISOLATED_WORLD_ID
? ESMHandlerPlatform::kNodeJS
: ESMHandlerPlatform::kBlink;
}
if (raw_host_defined_options.IsEmpty() ||
!raw_host_defined_options->IsFixedArray()) {
return ESMHandlerPlatform::kBlink;
}
static_assert(
static_cast<size_t>(node::loader::HostDefinedOptions::kLength) !=
blink::ReferrerScriptInfo::HostDefinedOptionsIndex::kLength);
auto options = v8::Local<v8::FixedArray>::Cast(raw_host_defined_options);
if (options->Length() == node::loader::HostDefinedOptions::kLength) {
return ESMHandlerPlatform::kNodeJS;
}
return ESMHandlerPlatform::kBlink;
}
v8::MaybeLocal<v8::Promise> HostImportModuleWithPhaseDynamically(
v8::Local<v8::Context> context,
v8::Local<v8::Data> v8_host_defined_options,
v8::Local<v8::Value> v8_referrer_resource_url,
v8::Local<v8::String> v8_specifier,
v8::ModuleImportPhase import_phase,
v8::Local<v8::FixedArray> v8_import_attributes) {
switch (SelectESMHandlerPlatform(context, v8_host_defined_options)) {
case ESMHandlerPlatform::kBlink:
return blink::V8Initializer::HostImportModuleWithPhaseDynamically(
context, v8_host_defined_options, v8_referrer_resource_url,
v8_specifier, import_phase, v8_import_attributes);
case ESMHandlerPlatform::kNodeJS:
CHECK(import_phase == v8::ModuleImportPhase::kEvaluation);
return node::loader::ImportModuleDynamicallyWithPhase(
context, v8_host_defined_options, v8_referrer_resource_url,
v8_specifier, import_phase, v8_import_attributes);
case ESMHandlerPlatform::kNone:
default:
return {};
}
}
v8::MaybeLocal<v8::Promise> HostImportModuleDynamically(
v8::Local<v8::Context> context,
v8::Local<v8::Data> v8_host_defined_options,
v8::Local<v8::Value> v8_referrer_resource_url,
v8::Local<v8::String> v8_specifier,
v8::Local<v8::FixedArray> v8_import_attributes) {
return HostImportModuleWithPhaseDynamically(
context, v8_host_defined_options, v8_referrer_resource_url, v8_specifier,
v8::ModuleImportPhase::kEvaluation, v8_import_attributes);
}
void HostInitializeImportMetaObject(v8::Local<v8::Context> context,
v8::Local<v8::Module> module,
v8::Local<v8::Object> meta) {
node::Environment* env = node::Environment::GetCurrent(context);
if (env == nullptr) {
if (electron::IsBrowserProcess() || electron::IsUtilityProcess())
return;
return blink::V8Initializer::HostGetImportMetaProperties(context, module,
meta);
}
if (electron::IsRendererProcess()) {
if (env != nullptr) {
ModuleWrap* wrap = ModuleWrap::GetFromModule(env, module);
if (wrap)
return ModuleWrap::HostInitializeImportMetaObjectCallback(context,
module, meta);
}
blink::WebLocalFrame* frame =
blink::WebLocalFrame::FrameForContext(context);
if (!frame || frame->GetScriptContextWorldId(context) !=
electron::WorldIDs::ISOLATED_WORLD_ID) {
return blink::V8Initializer::HostGetImportMetaProperties(context, module,
meta);
}
}
return ModuleWrap::HostInitializeImportMetaObjectCallback(context, module,
meta);
}
v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
v8::Local<v8::Context> context,
v8::Local<v8::Value> source,
bool is_code_like) {
if (node::Environment::GetCurrent(context) == nullptr) {
if (!electron::IsRendererProcess()) {
NOTREACHED();
}
return blink::V8Initializer::CodeGenerationCheckCallbackInMainThread(
context, source, is_code_like);
}
if (electron::IsRendererProcess()) {
v8::ModifyCodeGenerationFromStringsResult result =
blink::V8Initializer::CodeGenerationCheckCallbackInMainThread(
context, source, is_code_like);
if (!result.codegen_allowed)
return result;
}
return node::ModifyCodeGenerationFromStrings(context, source, is_code_like);
}
void ErrorMessageListener(v8::Local<v8::Message> message,
v8::Local<v8::Value> data) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
node::Environment* env = node::Environment::GetCurrent(isolate);
if (env) {
v8::MicrotasksScope microtasks_scope(
env->context(), v8::MicrotasksScope::kDoNotRunMicrotasks);
if (env->async_hooks()->fields()[node::AsyncHooks::kAfter]) {
while (env->async_hooks()->fields()[node::AsyncHooks::kStackLength]) {
double id = env->execution_async_id();
if (id != 0)
node::AsyncWrap::EmitAfter(env, id);
env->async_hooks()->pop_async_context(id);
}
}
env->async_hooks()->clear_async_id_stack();
}
}
bool IsAllowedOption(const std::string_view option) {
static constexpr auto debug_options =
base::MakeFixedFlatSet<std::string_view>({
"--debug",
"--debug-brk",
"--debug-port",
"--inspect",
"--inspect-brk",
"--inspect-brk-node",
"--inspect-port",
"--inspect-publish-uid",
"--experimental-network-inspection",
"--experimental-inspector-network-resource",
"--experimental-transform-types",
});
static constexpr auto options = base::MakeFixedFlatSet<std::string_view>({
"--diagnostic-dir",
"--dns-result-order",
"--no-deprecation",
"--throw-deprecation",
"--trace-deprecation",
"--trace-warnings",
"--no-experimental-global-navigator",
});
static constexpr auto unpacked_options =
base::MakeFixedFlatSet<std::string_view>({
"--expose-internals",
});
if (debug_options.contains(option))
return electron::fuses::IsNodeCliInspectEnabled();
if (unpacked_options.contains(option))
return !electron::api::App::IsPackaged();
return options.contains(option);
}
void SetNodeOptions(base::Environment* env) {
static constexpr auto disallowed = base::MakeFixedFlatSet<std::string_view>({
"--enable-fips",
"--experimental-policy",
"--force-fips",
"--openssl-config",
"--use-bundled-ca",
"--use-openssl-ca",
});
static constexpr auto pkg_opts = base::MakeFixedFlatSet<std::string_view>({
"--http-parser",
"--max-http-header-size",
});
if (env->HasVar("NODE_EXTRA_CA_CERTS")) {
if (!electron::fuses::IsNodeOptionsEnabled()) {
LOG(WARNING) << "NODE_OPTIONS ignored due to disabled nodeOptions fuse.";
env->UnSetVar("NODE_EXTRA_CA_CERTS");
}
}
if (env->HasVar("NODE_OPTIONS")) {
if (electron::fuses::IsNodeOptionsEnabled()) {
std::string result_options;
std::string options = env->GetVar("NODE_OPTIONS").value();
const std::vector<std::string_view> parts = base::SplitStringPiece(
options, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
bool is_packaged_app = electron::api::App::IsPackaged();
for (const std::string_view part : parts) {
const std::string_view option = part.substr(0, part.find('='));
if (is_packaged_app && !pkg_opts.contains(option)) {
LOG(ERROR) << "Most NODE_OPTIONs are not supported in packaged apps."
<< " See documentation for more details.";
continue;
} else if (disallowed.contains(option)) {
LOG(ERROR) << "The NODE_OPTION " << option
<< " is not supported in Electron";
continue;
}
result_options.append(part);
result_options.append(" ");
}
env->SetVar("NODE_OPTIONS", result_options);
} else {
LOG(WARNING) << "NODE_OPTIONS ignored due to disabled nodeOptions fuse.";
env->UnSetVar("NODE_OPTIONS");
}
}
}
}
namespace electron {
base::FilePath GetResourcesPath() {
#if BUILDFLAG(IS_MAC)
return MainApplicationBundlePath().Append("Contents").Append("Resources");
#else
base::FilePath assets_path;
base::PathService::Get(base::DIR_ASSETS, &assets_path);
return assets_path.Append(FILE_PATH_LITERAL("resources"));
#endif
}
NodeBindings::NodeBindings(BrowserEnvironment browser_env, uv_loop_t* loop)
: browser_env_{browser_env},
uv_loop_{loop ? loop : &owned_loop_.emplace()} {
if (owned_loop_)
CHECK_EQ(0, uv_loop_init(uv_loop_));
uv_loop_configure(uv_loop_, UV_LOOP_INTERRUPT_ON_IO_CHANGE);
}
NodeBindings::~NodeBindings() {
StopPolling();
if (embed_thread_prepared_) {
uv_sem_destroy(&embed_sem_);
dummy_uv_handle_.reset();
}
if (owned_loop_)
stop_and_close_uv_loop(uv_loop_);
if (initialized_node_per_process_)
TearDownOncePerProcess();
}
void NodeBindings::TearDownOncePerProcess() {
if (!g_is_initialized)
return;
g_is_initialized = false;
node::TearDownOncePerProcess();
}
void NodeBindings::StopPolling() {
if (!initialized_)
return;
embed_closed_ = true;
uv_sem_post(&embed_sem_);
WakeupEmbedThread();
uv_thread_join(&embed_thread_);
while (uv_sem_trywait(&embed_sem_) == 0) {
}
embed_closed_ = false;
initialized_ = false;
}
void NodeBindings::RegisterBuiltinBindings() {
#define V(modname) _register_##modname();
if (IsBrowserProcess()) {
ELECTRON_BROWSER_BINDINGS(V)
}
ELECTRON_COMMON_BINDINGS(V)
if (IsRendererProcess()) {
ELECTRON_RENDERER_BINDINGS(V)
}
if (IsUtilityProcess()) {
ELECTRON_UTILITY_BINDINGS(V)
}
#if DCHECK_IS_ON()
ELECTRON_TESTING_BINDINGS(V)
#endif
#undef V
}
node::node_module* NodeBindings::GetLinkedBinding(std::string_view name) {
#define V(modname) \
if (name == #modname) \
return get_linked_module_##modname();
if (IsBrowserProcess()) {
ELECTRON_BROWSER_BINDINGS(V)
}
ELECTRON_COMMON_BINDINGS(V)
if (IsRendererProcess()) {
ELECTRON_RENDERER_BINDINGS(V)
}
if (IsUtilityProcess()) {
ELECTRON_UTILITY_BINDINGS(V)
}
#if DCHECK_IS_ON()
ELECTRON_TESTING_BINDINGS(V)
#endif
#undef V
return nullptr;
}
bool NodeBindings::IsInitialized() {
return g_is_initialized;
}
std::vector<std::string> NodeBindings::ParseNodeCliFlags() {
const auto argv = base::CommandLine::ForCurrentProcess()->argv();
std::vector<std::string> args;
args.reserve(argv.size() + 2);
args.emplace_back("electron");
args.emplace_back("--no-js-source-phase-imports");
for (const auto& arg : argv) {
#if BUILDFLAG(IS_WIN)
const auto& option = base::WideToUTF8(arg);
#else
const auto& option = arg;
#endif
const auto stripped = std::string_view{option}.substr(0, option.find('='));
if (IsAllowedOption(stripped) || stripped == "--")
args.push_back(option);
}
if (browser_env_ == BrowserEnvironment::kRenderer ||
browser_env_ == BrowserEnvironment::kWorker) {
args.push_back("--experimental-eventsource");
}
return args;
}
void NodeBindings::Initialize(v8::Isolate* const isolate,
v8::Local<v8::Context> context) {
TRACE_EVENT0("electron", "NodeBindings::Initialize");
#if BUILDFLAG(IS_LINUX)
if (browser_env_ != BrowserEnvironment::kBrowser)
ElectronCommandLine::InitializeFromCommandLine();
#endif
RegisterBuiltinBindings();
electron::util::InstallProcessCodeCache();
auto env = base::Environment::Create();
SetNodeOptions(env.get());
std::vector<std::string> args = ParseNodeCliFlags();
node::per_process::cli_options->disable_wasm_trap_handler = true;
uint64_t process_flags =
node::ProcessInitializationFlags::kNoInitializeCppgc |
node::ProcessInitializationFlags::kNoInitializeV8 |
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform;
if (browser_env_ != BrowserEnvironment::kUtility)
process_flags |= node::ProcessInitializationFlags::kEnableStdioInheritance;
if (browser_env_ == BrowserEnvironment::kRenderer)
process_flags |= node::ProcessInitializationFlags::kNoDefaultSignalHandling;
if (!fuses::IsNodeOptionsEnabled())
process_flags |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv;
#if BUILDFLAG(IS_WIN)
if (!platform_util::IsNulDeviceEnabled())
process_flags |= node::ProcessInitializationFlags::kNoStdioInitialization;
#endif
std::shared_ptr<node::InitializationResult> result =
node::InitializeOncePerProcess(
args,
static_cast<node::ProcessInitializationFlags::Flags>(process_flags));
for (const std::string& error : result->errors())
std::cerr << args[0] << ": " << error << '\n';
if (result->early_return() != 0)
exit(result->exit_code());
#if BUILDFLAG(IS_WIN)
SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX);
#endif
g_is_initialized = true;
initialized_node_per_process_ = true;
}
void NodeBindings::SetUpIsolate(v8::Isolate* const isolate) {
node::IsolateSettings is;
is.fatal_error_callback = V8FatalErrorCallback;
is.oom_error_callback = V8OOMErrorCallback;
if (browser_env_ != BrowserEnvironment::kUtility) {
is.should_abort_on_uncaught_exception_callback = [](v8::Isolate*) {
return false;
};
}
is.allow_wasm_code_generation_callback = AllowWasmCodeGenerationCallback;
is.flags |= node::IsolateSettingsFlags::
ALLOW_MODIFY_CODE_GENERATION_FROM_STRINGS_CALLBACK;
is.modify_code_generation_from_strings_callback =
ModifyCodeGenerationFromStrings;
if (browser_env_ == BrowserEnvironment::kBrowser ||
browser_env_ == BrowserEnvironment::kUtility) {
is.policy = v8::MicrotasksPolicy::kExplicit;
} else {
is.policy = isolate->GetMicrotasksPolicy();
is.flags &= ~node::IsolateSettingsFlags::MESSAGE_LISTENER_WITH_ERROR_LEVEL;
isolate->AddMessageListenerWithErrorLevel(ErrorMessageListener,
v8::Isolate::kMessageError);
is.flags |=
node::IsolateSettingsFlags::SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK;
is.flags |=
node::IsolateSettingsFlags::SHOULD_NOT_SET_PREPARE_STACK_TRACE_CALLBACK;
}
node::SetIsolateUpForNode(isolate, is);
isolate->SetHostImportModuleDynamicallyCallback(HostImportModuleDynamically);
isolate->SetHostImportModuleWithPhaseDynamicallyCallback(
HostImportModuleWithPhaseDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObject);
}
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
v8::Isolate* isolate,
v8::Local<v8::Context> context,
node::MultiIsolatePlatform* platform,
size_t max_young_generation_size,
std::vector<std::string> args,
std::vector<std::string> exec_args,
std::optional<base::RepeatingCallback<void()>> on_app_code_ready) {
const std::string process_type(ProcessType());
const bool from_snapshot = context.IsEmpty();
std::unique_ptr<gin::ContextHolder> gin_context_holder;
auto set_up_context = [&](v8::Local<v8::Context> ctx,
node::IsolateData* iso_data) {
ctx->SetAlignedPointerInEmbedderData(kElectronContextEmbedderDataIndex,
static_cast<void*>(iso_data),
v8::kEmbedderDataTypeTagDefault);
if (!gin::PerContextData::From(ctx)) {
gin_context_holder = std::make_unique<gin::ContextHolder>(isolate);
gin_context_holder->SetContext(ctx);
}
};
auto snapshot_wrapper =
from_snapshot ? node::EmbedderSnapshotData::BuiltinSnapshotData()
: node::EmbedderSnapshotData::Pointer{};
auto* isolate_data =
node::CreateIsolateData(isolate, uv_loop_, platform,
nullptr, snapshot_wrapper.get());
isolate_data->max_young_gen_size = max_young_generation_size;
if (!from_snapshot) {
set_up_context(context, isolate_data);
}
uint64_t env_flags = node::EnvironmentFlags::kDefaultFlags |
node::EnvironmentFlags::kHideConsoleWindows |
node::EnvironmentFlags::kNoGlobalSearchPaths |
node::EnvironmentFlags::kNoRegisterESMLoader;
if (browser_env_ == BrowserEnvironment::kRenderer ||
browser_env_ == BrowserEnvironment::kWorker) {
env_flags |= node::EnvironmentFlags::kNoBrowserGlobals |
node::EnvironmentFlags::kNoCreateInspector;
}
if (!electron::fuses::IsNodeCliInspectEnabled()) {
env_flags |= node::EnvironmentFlags::kNoStartDebugSignalHandler;
}
node::Environment* env = electron::util::CreateEnvironment(
isolate, static_cast<node::IsolateData*>(isolate_data), context, args,
exec_args, static_cast<node::EnvironmentFlags::Flags>(env_flags),
process_type);
DCHECK(env);
std::optional<v8::Context::Scope> snapshot_context_scope;
if (from_snapshot) {
context = env->context();
DCHECK(!context.IsEmpty());
snapshot_context_scope.emplace(context);
set_up_context(context, isolate_data);
}
gin_helper::internal::Event::GetConstructor(
isolate, context, &gin_helper::internal::Event::kWrapperInfo);
gin_helper::Dictionary process(isolate, env->process_object());
process.SetReadOnly("type", process_type);
if (browser_env_ == BrowserEnvironment::kBrowser ||
browser_env_ == BrowserEnvironment::kRenderer) {
if (on_app_code_ready) {
process.SetMethod("appCodeLoaded", *on_app_code_ready);
} else {
process.SetMethod("appCodeLoaded",
base::BindRepeating(&NodeBindings::SetAppCodeLoaded,
base::Unretained(this)));
}
}
auto env_deleter = [isolate, isolate_data,
gin_context_holder = std::move(gin_context_holder),
context = v8::Global<v8::Context>{isolate, context}](
node::Environment* nenv) mutable {
v8::HandleScope handle_scope{isolate};
gin_context_holder.reset();
context.Get(isolate)->SetAlignedPointerInEmbedderData(
kElectronContextEmbedderDataIndex, nullptr,
v8::kEmbedderDataTypeTagDefault);
context.Reset();
node::FreeEnvironment(nenv);
node::FreeIsolateData(isolate_data);
};
return {env, std::move(env_deleter)};
}
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
v8::Isolate* const isolate,
v8::Local<v8::Context> context,
node::MultiIsolatePlatform* platform,
size_t max_young_generation_size,
std::optional<base::RepeatingCallback<void()>> on_app_code_ready) {
return CreateEnvironment(
isolate, context, platform, max_young_generation_size,
ElectronCommandLine::AsUtf8(), {}, on_app_code_ready);
}
namespace {
void RefreshCachedNodeOptions(v8::Local<v8::Context> context,
v8::Local<v8::Function> require) {
v8::Isolate* const isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Value> id = gin::StringToV8(isolate, "internal/options");
v8::Local<v8::Value> options;
v8::Local<v8::Value> refresh;
if (require->Call(context, v8::Null(isolate), 1, &id).ToLocal(&options) &&
options->IsObject() &&
options.As<v8::Object>()
->Get(context, gin::StringToV8(isolate, "refreshOptions"))
.ToLocal(&refresh) &&
refresh->IsFunction()) {
std::ignore =
refresh.As<v8::Function>()->Call(context, options, 0, nullptr);
}
}
}
void NodeBindings::LoadEnvironment(node::Environment* env) {
electron::util::FeedEnvironmentCodeCache(env);
node::DebugOptions* const debug_options = env->options()->get_debug_options();
const bool break_first_line = debug_options->break_first_line;
debug_options->break_first_line = false;
const std::string bundle_id =
"internal/electron/js2c/" + std::string(ProcessType()) + "_init";
node::LoadEnvironment(
env,
[&](const node::StartExecutionCallbackInfo& info) {
v8::Isolate* const isolate = env->isolate();
v8::Local<v8::Context> context = env->context();
v8::Local<v8::Function> require =
env->principal_realm()->builtin_module_require();
if (break_first_line) {
debug_options->break_first_line = true;
RefreshCachedNodeOptions(context, require);
}
v8::LocalVector<v8::String> params =
js2c::MakeBundleParams(isolate, js2c::kInitBundleParams);
v8::Local<v8::Value> args[] = {info.process_object, require};
v8::Local<v8::Function> bundle;
if (!electron::util::CompileBundle(context, bundle_id.c_str(), ¶ms)
.ToLocal(&bundle)) {
return v8::MaybeLocal<v8::Value>();
}
return bundle->Call(context, v8::Null(isolate), std::size(args), args);
},
&OnNodePreload);
debug_options->break_first_line = break_first_line;
gin_helper::EmitEvent(env->isolate(), env->process_object(), "loaded");
}
std::string_view NodeBindings::ProcessType() const {
switch (browser_env_) {
case BrowserEnvironment::kBrowser:
return "browser";
case BrowserEnvironment::kRenderer:
return "renderer";
case BrowserEnvironment::kWorker:
return "worker";
case BrowserEnvironment::kUtility:
return "utility";
}
}
void NodeBindings::PrepareEmbedThread() {
if (initialized_)
return;
if (!embed_thread_prepared_) {
uv_async_init(uv_loop_, dummy_uv_handle_.get(), nullptr);
uv_sem_init(&embed_sem_, 0);
embed_thread_prepared_ = true;
}
uv_thread_create(&embed_thread_, EmbedThreadRunner, this);
}
void NodeBindings::StartPolling() {
if (initialized_)
return;
initialized_ = true;
task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
UvRunOnce();
}
void NodeBindings::SetAppCodeLoaded() {
app_code_loaded_ = true;
}
void NodeBindings::JoinAppCode() {
if (browser_env_ != BrowserEnvironment::kBrowser) {
return;
}
auto* browser = Browser::Get();
node::Environment* env = uv_env();
if (!env)
return;
v8::HandleScope handle_scope(env->isolate());
v8::Context::Scope context_scope(env->context());
while (!app_code_loaded_ && !browser->is_shutting_down()) {
int r = uv_run(uv_loop_, UV_RUN_ONCE);
if (r == 0) {
base::RunLoop().QuitWhenIdle();
break;
}
}
}
void NodeBindings::UvRunOnce() {
node::Environment* env = uv_env();
if (!env)
return;
v8::HandleScope handle_scope(env->isolate());
v8::Context::Scope context_scope(env->context());
{
util::ExplicitMicrotasksScope microtasks_scope(
env->context()->GetMicrotaskQueue());
if (browser_env_ != BrowserEnvironment::kBrowser)
TRACE_EVENT_BEGIN0("devtools.timeline", "FunctionCall");
uv_loop_interrupt_suspend(uv_loop_);
int r = uv_run(uv_loop_, UV_RUN_NOWAIT);
uv_loop_interrupt_resume(uv_loop_);
if (browser_env_ != BrowserEnvironment::kBrowser)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
if (r == 0)
base::RunLoop().QuitWhenIdle();
}
uv_sem_post(&embed_sem_);
}
void NodeBindings::WakeupMainThread() {
DCHECK(task_runner_);
task_runner_->PostTask(FROM_HERE, base::BindOnce(&NodeBindings::UvRunOnce,
weak_factory_.GetWeakPtr()));
}
void NodeBindings::WakeupEmbedThread() {
uv_async_send(dummy_uv_handle_.get());
}
void NodeBindings::EmbedThreadRunner(void* arg) {
auto* self = static_cast<NodeBindings*>(arg);
while (true) {
uv_sem_wait(&self->embed_sem_);
if (self->embed_closed_)
break;
self->PollEvents();
if (self->embed_closed_)
break;
self->WakeupMainThread();
}
}
void OnNodePreload(node::Environment* env,
v8::Local<v8::Value> process,
v8::Local<v8::Value> require) {
if (node::Realm::GetCurrent(env->isolate()->GetCurrentContext()) !=
env->principal_realm()) {
return;
}
gin_helper::Dictionary dict(env->isolate(), process.As<v8::Object>());
dict.SetReadOnly("resourcesPath", GetResourcesPath());
base::FilePath helper_exec_path;
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
dict.SetReadOnly("helperExecPath", helper_exec_path);
gin_helper::Dictionary versions;
if (dict.Get("versions", &versions)) {
versions.SetReadOnly(ELECTRON_PROJECT_NAME, ELECTRON_VERSION_STRING);
versions.SetReadOnly("chrome", CHROME_VERSION_STRING);
#if BUILDFLAG(HAS_VENDOR_VERSION)
versions.SetReadOnly(BUILDFLAG(VENDOR_VERSION_NAME),
BUILDFLAG(VENDOR_VERSION_VALUE));
#endif
}
v8::LocalVector<v8::String> bundle_params =
js2c::MakeBundleParams(env->isolate(), js2c::kInitBundleParams);
v8::LocalVector<v8::Value> bundle_args(env->isolate(), {process, require});
electron::util::CompileAndCall(env->isolate(), env->context(),
js2c::kNodeInitId, &bundle_params,
&bundle_args);
}
}