electron/shell/common/node_bindings.cc
electron-roller[bot] 5d5e672f17
chore: bump chromium to 141.0.7361.0 (main) (#48054)
* chore: bump chromium in DEPS to 141.0.7352.0

* chore: update patches

* 6830573: Revert 'Migrate WrappableWithNamedPropertyInterceptor to gin::Wrappable' | https://chromium-review.googlesource.com/c/chromium/src/+/6830573

* chore: bump chromium in DEPS to 141.0.7354.0

* chore: bump chromium in DEPS to 141.0.7356.0

* chore: bump chromium in DEPS to 141.0.7357.0

* chore: bump chromium in DEPS to 141.0.7359.0

* chore: bump chromium in DEPS to 141.0.7361.0

* 6838518: [Mac] Correctly deallocate sandbox error buffers and prevent crash resulting from nullptr assignment | https://chromium-review.googlesource.com/c/chromium/src/+/6838518

* 6850973: Reland "Use base::ByteCount in base::SysInfo." | https://chromium-review.googlesource.com/c/chromium/src/+/6850973

* 6506565: [FPF-CI] Create initial NoiseHash in the browser. | https://chromium-review.googlesource.com/c/chromium/src/+/6506565

* chore: update patches

* fixup! 6850973: Reland "Use base::ByteCount in base::SysInfo." | https://chromium-review.googlesource.com/c/chromium/src/+/6850973

* fixup! 6506565: [FPF-CI] Create initial NoiseHash in the browser. | https://chromium-review.googlesource.com/c/chromium/src/+/6506565

* fix: unsafe buffer warning in fix_properly_honor_printing_page_ranges.patch

* fix: FTBFS in src_remove_dependency_on_wrapper-descriptor-based_cppheap.patch

This change should be upstreamed.

Fixes this error:

../../third_party/electron_node/src/env.cc:606:3: error: no matching function for call to 'Wrap'
  606 |   v8::Object::Wrap<v8::CppHeapPointerTag::kDefaultTag>(
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../v8/include/v8-object.h:1076:14: note: candidate function template not viable: cannot convert argument of incomplete type 'void *' to 'v8::Object::Wrappable *' for 3rd argument
 1076 | void Object::Wrap(v8::Isolate* isolate, const v8::Local<v8::Object>& wrapper,
      |              ^
 1077 |                   v8::Object::Wrappable* wrappable) {
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../v8/include/v8-object.h:1084:14: note: candidate function template not viable: no known conversion from 'Local<Object>' to 'const PersistentBase<Object>' for 2nd argument
 1084 | void Object::Wrap(v8::Isolate* isolate, const PersistentBase<Object>& wrapper,
      |              ^                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../v8/include/v8-object.h:1093:14: note: candidate function template not viable: no known conversion from 'Local<Object>' to 'const BasicTracedReference<Object>' for 2nd argument
 1093 | void Object::Wrap(v8::Isolate* isolate,
      |              ^
 1094 |                   const BasicTracedReference<Object>& wrapper,
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

* [v8-init] Access crash key only from main thread | https://chromium-review.googlesource.com/c/chromium/src/+/6827167

* chore: e patches all

* chore: remove chore_restore_some_deprecated_wrapper_utility_in_gin.patch from patches

this remove line got re-added when rebasing roller/chromium/main

* chore: e patches all

* fix: include base/time/time.h when using base::Time

* chore: update patches

* Make --host-rules an alias for --host-resolver-rules.

Refs https://chromium-review.googlesource.com/c/chromium/src/+/4867872

* ci: update BUILD_TOOLS_SHA

Refs https://github.com/electron/build-tools/pull/746

* [Fontations] Remove Fontations suffix from font names

Refs https://chromium-review.googlesource.com/c/chromium/src/+/6835930

* temp: debug macOS addon build failure

* Revert "temp: debug macOS addon build failure"

This reverts commit 40bc8abab65dc83e17c4ab97cb6e7522a193fb44.

* test: run tests with Xcode 16.4

* ci: fix tccdb update for macOS 15

* spec: disable opening external application for loadURL

on macOS opening unknown external application will bring
up dialog to choose apps from application store which will
break our other test suites that want to capture screen
for pixel matching.

The loadURL spec that tests bad-scheme://foo is sufficient
that we hit the permission handler for openExternal since
at that point we already know the runtime gave up on handling
the scheme.

* chore: rebase patches

* chore: disable codesiging tests

* ci: update ScreenCaptureApprovals.plist for /bin/bash

* ci: try updating tcc permissions

* ci: update TCC permissions

Refs https://www.rainforestqa.com/blog/macos-tcc-db-deep-dive

* chore: test with 1st quadrant of the window

* chore: adjust for macOS 15 menubar height

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-08-29 12:31:47 +09:00

1024 lines
37 KiB
C++

// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/node_bindings.h"
#include <algorithm>
#include <iostream>
#include <string>
#include <string_view>
#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_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 "electron/buildflags/buildflags.h"
#include "electron/electron_version.h"
#include "electron/fuses.h"
#include "electron/mas.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/mac/main_application_bundle.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/process_util.h"
#include "shell/common/world_ids.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h" // nogncheck
#include "third_party/electron_node/src/debug_utils.h"
#include "third_party/electron_node/src/module_wrap.h"
#if !IS_MAS_BUILD()
#include "shell/common/crash_keys.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_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_native_theme) \
V(electron_browser_notification) \
V(electron_browser_power_monitor) \
V(electron_browser_power_save_blocker) \
V(electron_browser_protocol) \
V(electron_browser_printing) \
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_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_clipboard) \
V(electron_common_command_line) \
V(electron_common_crashpad_support) \
V(electron_common_environment) \
V(electron_common_features) \
V(electron_common_native_image) \
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)
#define ELECTRON_UTILITY_BINDINGS(V) \
V(electron_browser_event_emitter) \
V(electron_browser_system_preferences) \
V(electron_common_net) \
V(electron_utility_parent_port)
#define ELECTRON_TESTING_BINDINGS(V) V(electron_common_testing)
// This is used to load built-in bindings. Instead of using
// __attribute__((constructor)), we call the _register_<modname>
// function for each built-in bindings explicitly. This is only
// forward declaration. The definitions are in each binding's
// implementation when calling the NODE_LINKED_BINDING_CONTEXT_AWARE.
#define V(modname) void _register_##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*) {
// We should be using the UvHandle wrapper everywhere, in which case
// all handles should already be in a closing state...
DCHECK(uv_is_closing(handle));
// ...but if a raw handle got through, through, do the right thing anyway
if (!uv_is_closing(handle)) {
uv_close(handle, nullptr);
}
};
uv_walk(loop, ensure_closing, nullptr);
// All remaining handles are in a closing state now.
// Pump the event loop so that they can finish closing.
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()
electron::crash_keys::SetCrashKey("electron.v8-oom.is_heap_oom",
std::to_string(details.is_heap_oom));
if (location) {
electron::crash_keys::SetCrashKey("electron.v8-oom.location", location);
}
if (details.detail) {
electron::crash_keys::SetCrashKey("electron.v8-oom.detail", details.detail);
}
#endif
OOM_CRASH(0);
}
bool AllowWasmCodeGenerationCallback(v8::Local<v8::Context> context,
v8::Local<v8::String> source) {
// If we're running with contextIsolation enabled in the renderer process,
// fall back to Blink's logic.
if (node::Environment::GetCurrent(context) == nullptr) {
if (!electron::IsRendererProcess())
return false;
return blink::V8Initializer::WasmCodeGenerationCheckCallback(context,
source);
}
return node::AllowWasmCodeGenerationCallback(context, source);
}
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) {
if (node::Environment::GetCurrent(context) == nullptr) {
if (electron::IsBrowserProcess() || electron::IsUtilityProcess())
return {};
return blink::V8Initializer::HostImportModuleWithPhaseDynamically(
context, v8_host_defined_options, v8_referrer_resource_url,
v8_specifier, import_phase, v8_import_attributes);
}
// If we're running with contextIsolation enabled in the renderer process,
// fall back to Blink's logic.
if (electron::IsRendererProcess()) {
blink::WebLocalFrame* frame =
blink::WebLocalFrame::FrameForContext(context);
if (!frame || frame->GetScriptContextWorldId(context) !=
electron::WorldIDs::ISOLATED_WORLD_ID) {
return blink::V8Initializer::HostImportModuleWithPhaseDynamically(
context, v8_host_defined_options, v8_referrer_resource_url,
v8_specifier, import_phase, v8_import_attributes);
}
}
// TODO: Switch to node::loader::ImportModuleDynamicallyWithPhase
// once we land the Node.js version that has it in upstream.
CHECK(import_phase == v8::ModuleImportPhase::kEvaluation);
return node::loader::ImportModuleDynamically(
context, v8_host_defined_options, v8_referrer_resource_url, v8_specifier,
v8_import_attributes);
}
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 the module is created by Node.js, use Node.js' handling.
if (env != nullptr) {
ModuleWrap* wrap = ModuleWrap::GetFromModule(env, module);
if (wrap)
return ModuleWrap::HostInitializeImportMetaObjectCallback(context,
module, meta);
}
// If contextIsolation is enabled, fall back to Blink's handling.
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) {
// No node environment means we're in the renderer process, either in a
// sandboxed renderer or in an unsandboxed renderer with context isolation
// enabled.
if (!electron::IsRendererProcess()) {
NOTREACHED();
}
return blink::V8Initializer::CodeGenerationCheckCallbackInMainThread(
context, source, is_code_like);
}
// If we get here then we have a node environment, so either a) we're in the
// non-renderer process, or b) we're in the renderer process in a context that
// has both node and blink, i.e. contextIsolation disabled.
// If we're in the renderer with contextIsolation disabled, ask blink first
// (for CSP), and iff that allows codegen, delegate to node.
if (electron::IsRendererProcess()) {
v8::ModifyCodeGenerationFromStringsResult result =
blink::V8Initializer::CodeGenerationCheckCallbackInMainThread(
context, source, is_code_like);
if (!result.codegen_allowed)
return result;
}
// If we're in the main process or utility process, delegate to node.
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);
// Emit the after() hooks now that the exception has been handled.
// Analogous to node/lib/internal/process/execution.js#L176-L180
if (env->async_hooks()->fields()[node::AsyncHooks::kAfter]) {
while (env->async_hooks()->fields()[node::AsyncHooks::kStackLength]) {
double id = env->execution_async_id();
// Do not call EmitAfter for asyncId 0.
if (id != 0)
node::AsyncWrap::EmitAfter(env, id);
env->async_hooks()->pop_async_context(id);
}
}
// Ensure that the async id stack is properly cleared so the async
// hook stack does not become corrupted.
env->async_hooks()->clear_async_id_stack();
}
}
// Only allow a specific subset of options in non-ELECTRON_RUN_AS_NODE mode.
// If node CLI inspect support is disabled, allow no debug options.
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",
});
// This should be aligned with what's possible to set via the process object.
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",
});
// This should be aligned with what's possible to set via the process object.
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);
}
// Initialize NODE_OPTIONS to pass to Node.js
// See https://nodejs.org/api/cli.html#cli_node_options_options
void SetNodeOptions(base::Environment* env) {
// Options that are expressly disallowed
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) {
// Strip off values passed to individual NODE_OPTIONs
const std::string_view option = part.substr(0, part.find('='));
if (is_packaged_app && !pkg_opts.contains(option)) {
// Explicitly disallow majority of NODE_OPTIONS in packaged apps
LOG(ERROR) << "Most NODE_OPTIONs are not supported in packaged apps."
<< " See documentation for more details.";
continue;
} else if (disallowed.contains(option)) {
// Remove NODE_OPTIONS specifically disallowed for use in Node.js
// through Electron owing to constraints like BoringSSL.
LOG(ERROR) << "The NODE_OPTION " << option
<< " is not supported in Electron";
continue;
}
result_options.append(part);
result_options.append(" ");
}
// overwrite new NODE_OPTIONS without unsupported variables
env->SetVar("NODE_OPTIONS", result_options);
} else {
LOG(WARNING) << "NODE_OPTIONS ignored due to disabled nodeOptions fuse.";
env->UnSetVar("NODE_OPTIONS");
}
}
}
} // namespace
namespace electron {
namespace {
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
}
} // namespace
NodeBindings::NodeBindings(BrowserEnvironment browser_env)
: browser_env_{browser_env},
uv_loop_{InitEventLoop(browser_env, &worker_loop_)} {}
NodeBindings::~NodeBindings() {
// Quit the embed thread.
embed_closed_ = true;
uv_sem_post(&embed_sem_);
WakeupEmbedThread();
// Wait for everything to be done.
uv_thread_join(&embed_thread_);
// Clear uv.
uv_sem_destroy(&embed_sem_);
dummy_uv_handle_.reset();
// Clean up worker loop
if (in_worker_loop())
stop_and_close_uv_loop(uv_loop_);
}
node::IsolateData* NodeBindings::isolate_data(
v8::Local<v8::Context> context) const {
if (context->GetNumberOfEmbedderDataFields() <=
kElectronContextEmbedderDataIndex) {
return nullptr;
}
auto* isolate_data = static_cast<node::IsolateData*>(
context->GetAlignedPointerFromEmbedderData(
kElectronContextEmbedderDataIndex));
CHECK(isolate_data);
CHECK(isolate_data->event_loop());
return isolate_data;
}
// static
uv_loop_t* NodeBindings::InitEventLoop(BrowserEnvironment browser_env,
uv_loop_t* worker_loop) {
uv_loop_t* event_loop = nullptr;
if (browser_env == BrowserEnvironment::kWorker) {
uv_loop_init(worker_loop);
event_loop = worker_loop;
} else {
event_loop = uv_default_loop();
}
// Interrupt embed polling when a handle is started.
uv_loop_configure(event_loop, UV_LOOP_INTERRUPT_ON_IO_CHANGE);
return event_loop;
}
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
}
bool NodeBindings::IsInitialized() {
return g_is_initialized;
}
// Initialize Node.js cli options to pass to Node.js
// See https://nodejs.org/api/cli.html#cli_options
std::vector<std::string> NodeBindings::ParseNodeCliFlags() {
const auto argv = base::CommandLine::ForCurrentProcess()->argv();
std::vector<std::string> args;
// TODO(codebytere): We need to set the first entry in args to the
// process name owing to src/node_options-inl.h#L286-L290 but this is
// redundant and so should be refactored upstream.
args.reserve(argv.size() + 1);
args.emplace_back("electron");
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('='));
// Only allow no-op or a small set of debug/trace related options.
if (IsAllowedOption(stripped) || stripped == "--")
args.push_back(option);
}
// We need to disable Node.js' fetch implementation to prevent
// conflict with Blink's in renderer and worker processes.
if (browser_env_ == BrowserEnvironment::kRenderer ||
browser_env_ == BrowserEnvironment::kWorker) {
args.push_back("--no-experimental-fetch");
}
return args;
}
void NodeBindings::Initialize(v8::Isolate* const isolate,
v8::Local<v8::Context> context) {
TRACE_EVENT0("electron", "NodeBindings::Initialize");
// Open node's error reporting system for browser process.
#if BUILDFLAG(IS_LINUX)
// Get real command line in renderer process forked by zygote.
if (browser_env_ != BrowserEnvironment::kBrowser)
ElectronCommandLine::InitializeFromCommandLine();
#endif
// Explicitly register electron's builtin bindings.
RegisterBuiltinBindings();
auto env = base::Environment::Create();
SetNodeOptions(env.get());
// Parse and set Node.js cli flags.
std::vector<std::string> args = ParseNodeCliFlags();
// V8::EnableWebAssemblyTrapHandler can be called only once or it will
// hard crash. We need to prevent Node.js calling it in the event it has
// already been called.
node::per_process::cli_options->disable_wasm_trap_handler = true;
uint64_t process_flags =
node::ProcessInitializationFlags::kNoInitializeCppgc |
node::ProcessInitializationFlags::kNoInitializeV8 |
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform;
// We do not want the child processes spawned from the utility process
// to inherit the custom stdio handles created for the parent.
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;
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)
// uv_init overrides error mode to suppress the default crash dialog, bring
// it back if user wants to show it.
if (browser_env_ == BrowserEnvironment::kBrowser ||
env->HasVar("ELECTRON_DEFAULT_ERROR_MODE"))
SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX);
#endif
gin_helper::internal::Event::GetConstructor(
isolate, context, &gin_helper::internal::Event::kWrapperInfo);
g_is_initialized = true;
}
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) {
// Feed node the path to initialization script.
std::string process_type;
switch (browser_env_) {
case BrowserEnvironment::kBrowser:
process_type = "browser";
break;
case BrowserEnvironment::kRenderer:
process_type = "renderer";
break;
case BrowserEnvironment::kWorker:
process_type = "worker";
break;
case BrowserEnvironment::kUtility:
process_type = "utility";
break;
}
gin_helper::Dictionary global(isolate, context->Global());
if (browser_env_ == BrowserEnvironment::kBrowser) {
const std::vector<std::string> search_paths = {"app.asar", "app",
"default_app.asar"};
const std::vector<std::string> app_asar_search_paths = {"app.asar"};
context->Global()->SetPrivate(
context,
v8::Private::ForApi(
isolate,
gin::ConvertToV8(isolate, "appSearchPaths").As<v8::String>()),
gin::ConvertToV8(isolate,
electron::fuses::IsOnlyLoadAppFromAsarEnabled()
? app_asar_search_paths
: search_paths));
context->Global()->SetPrivate(
context,
v8::Private::ForApi(
isolate, gin::ConvertToV8(isolate, "appSearchPathsOnlyLoadASAR")
.As<v8::String>()),
gin::ConvertToV8(isolate,
electron::fuses::IsOnlyLoadAppFromAsarEnabled()));
}
std::string init_script = "electron/js2c/" + process_type + "_init";
args.insert(args.begin() + 1, init_script);
auto* isolate_data = node::CreateIsolateData(isolate, uv_loop_, platform);
isolate_data->max_young_gen_size = max_young_generation_size;
context->SetAlignedPointerInEmbedderData(kElectronContextEmbedderDataIndex,
static_cast<void*>(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) {
// Only one ESM loader can be registered per isolate -
// in renderer processes this should be blink. We need to tell Node.js
// not to register its handler (overriding blinks) in non-browser processes.
// We also avoid overriding globals like setImmediate, clearImmediate
// queueMicrotask etc during the bootstrap phase of Node.js
// for processes that already have these defined by DOM.
// Check //third_party/electron_node/lib/internal/bootstrap/node.js
// for the list of overrides on globalThis.
env_flags |= node::EnvironmentFlags::kNoBrowserGlobals |
node::EnvironmentFlags::kNoCreateInspector;
}
if (!electron::fuses::IsNodeCliInspectEnabled()) {
// If --inspect and friends are disabled we also shouldn't listen for
// SIGUSR1
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);
node::IsolateSettings is;
// Use a custom fatal error callback to allow us to add
// crash message and location to CrashReports.
is.fatal_error_callback = V8FatalErrorCallback;
is.oom_error_callback = V8OOMErrorCallback;
// We don't want to abort either in the renderer or browser processes.
// We already listen for uncaught exceptions and handle them there.
// For utility process we expect the process to behave as standard
// Node.js runtime and abort the process with appropriate exit
// code depending on a handler being set for `uncaughtException` event.
if (browser_env_ != BrowserEnvironment::kUtility) {
is.should_abort_on_uncaught_exception_callback = [](v8::Isolate*) {
return false;
};
}
// Use a custom callback here to allow us to leverage Blink's logic in the
// renderer process.
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) {
// Node.js requires that microtask checkpoints be explicitly invoked.
is.policy = v8::MicrotasksPolicy::kExplicit;
} else {
// Blink expects the microtasks policy to be kScoped, but Node.js expects it
// to be kExplicit. In the renderer, there can be many contexts within the
// same isolate, so we don't want to change the existing policy here, which
// could be either kExplicit or kScoped depending on whether we're executing
// from within a Node.js or a Blink entrypoint. Instead, the policy is
// toggled to kExplicit when entering Node.js through UvRunOnce.
is.policy = isolate->GetMicrotasksPolicy();
// We do not want to use Node.js' message listener as it interferes with
// Blink's.
is.flags &= ~node::IsolateSettingsFlags::MESSAGE_LISTENER_WITH_ERROR_LEVEL;
// Isolate message listeners are additive (you can add multiple), so instead
// we add an extra one here to ensure that the async hook stack is properly
// cleared when errors are thrown.
isolate->AddMessageListenerWithErrorLevel(ErrorMessageListener,
v8::Isolate::kMessageError);
// We do not want to use the promise rejection callback that Node.js uses,
// because it does not send PromiseRejectionEvents to the global script
// context. We need to use the one Blink already provides.
is.flags |=
node::IsolateSettingsFlags::SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK;
// We do not want to use the stack trace callback that Node.js uses,
// because it relies on Node.js being aware of the current Context and
// that's not always the case. We need to use the one Blink already
// provides.
is.flags |=
node::IsolateSettingsFlags::SHOULD_NOT_SET_PREPARE_STACK_TRACE_CALLBACK;
}
node::SetIsolateUpForNode(isolate, is);
isolate->SetHostImportModuleDynamicallyCallback(HostImportModuleDynamically);
isolate->SetHostImportModuleWithPhaseDynamicallyCallback(
HostImportModuleWithPhaseDynamically);
isolate->SetHostInitializeImportMetaObjectCallback(
HostInitializeImportMetaObject);
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", std::move(*on_app_code_ready));
} else {
process.SetMethod("appCodeLoaded",
base::BindRepeating(&NodeBindings::SetAppCodeLoaded,
base::Unretained(this)));
}
}
auto env_deleter = [isolate, isolate_data,
context = v8::Global<v8::Context>{isolate, context}](
node::Environment* nenv) mutable {
// When `isolate_data` was created above, a pointer to it was kept
// in context's embedder_data[kElectronContextEmbedderDataIndex].
// Since we're about to free `isolate_data`, clear that entry
v8::HandleScope handle_scope{isolate};
context.Get(isolate)->SetAlignedPointerInEmbedderData(
kElectronContextEmbedderDataIndex, nullptr);
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);
}
void NodeBindings::LoadEnvironment(node::Environment* env) {
node::LoadEnvironment(env, node::StartExecutionCallback{}, &OnNodePreload);
gin_helper::EmitEvent(env->isolate(), env->process_object(), "loaded");
}
void NodeBindings::PrepareEmbedThread() {
// IOCP does not change for the process until the loop is recreated,
// we ensure that there is only a single polling thread satisfying
// the concurrency limit set from CreateIoCompletionPort call by
// uv_loop_init for the lifetime of this process.
// More background can be found at:
// https://github.com/microsoft/vscode/issues/142786#issuecomment-1061673400
if (initialized_)
return;
// Add dummy handle for libuv, otherwise libuv would quit when there is
// nothing to do.
uv_async_init(uv_loop_, dummy_uv_handle_.get(), nullptr);
// Start worker that will interrupt main loop when having uv events.
uv_sem_init(&embed_sem_, 0);
uv_thread_create(&embed_thread_, EmbedThreadRunner, this);
}
void NodeBindings::StartPolling() {
// Avoid calling UvRunOnce if the loop is already active,
// otherwise it can lead to situations were the number of active
// threads processing on IOCP is greater than the concurrency limit.
if (initialized_)
return;
initialized_ = true;
// The MessageLoop should have been created, remember the one in main thread.
task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
// Run uv loop for once to give the uv__io_poll a chance to add all events.
UvRunOnce();
}
void NodeBindings::SetAppCodeLoaded() {
app_code_loaded_ = true;
}
void NodeBindings::JoinAppCode() {
// We can only "join" app code to the main thread in the browser process
if (browser_env_ != BrowserEnvironment::kBrowser) {
return;
}
auto* browser = Browser::Get();
node::Environment* env = uv_env();
if (!env)
return;
v8::HandleScope handle_scope(env->isolate());
// Enter node context while dealing with uv events.
v8::Context::Scope context_scope(env->context());
// Pump the event loop until we get the signal that the app code has finished
// loading
while (!app_code_loaded_ && !browser->is_shutting_down()) {
int r = uv_run(uv_loop_, UV_RUN_ONCE);
if (r == 0) {
base::RunLoop().QuitWhenIdle(); // Quit from uv.
break;
}
}
}
void NodeBindings::UvRunOnce() {
node::Environment* env = uv_env();
// When doing navigation without restarting renderer process, it may happen
// that the node environment is destroyed but the message loop is still there.
// In this case we should not run uv loop.
if (!env)
return;
v8::HandleScope handle_scope(env->isolate());
// Enter node context while dealing with uv events.
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");
// Deal with uv events.
int r = uv_run(uv_loop_, UV_RUN_NOWAIT);
if (browser_env_ != BrowserEnvironment::kBrowser)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
if (r == 0)
base::RunLoop().QuitWhenIdle(); // Quit from uv.
}
// Tell the worker thread to continue polling.
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());
}
// static
void NodeBindings::EmbedThreadRunner(void* arg) {
auto* self = static_cast<NodeBindings*>(arg);
while (true) {
// Wait for the main loop to deal with events.
uv_sem_wait(&self->embed_sem_);
if (self->embed_closed_)
break;
// Wait for something to happen in uv loop.
// Note that the PollEvents() is implemented by derived classes, so when
// this class is being destructed the PollEvents() would not be available
// anymore. Because of it we must make sure we only invoke PollEvents()
// when this class is alive.
self->PollEvents();
if (self->embed_closed_)
break;
// Deal with event in main thread.
self->WakeupMainThread();
}
}
void OnNodePreload(node::Environment* env,
v8::Local<v8::Value> process,
v8::Local<v8::Value> require) {
// Set custom process properties.
gin_helper::Dictionary dict(env->isolate(), process.As<v8::Object>());
dict.SetReadOnly("resourcesPath", GetResourcesPath());
base::FilePath helper_exec_path; // path to the helper app.
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
}
// Execute lib/node/init.ts.
v8::LocalVector<v8::String> bundle_params(
env->isolate(), {node::FIXED_ONE_BYTE_STRING(env->isolate(), "process"),
node::FIXED_ONE_BYTE_STRING(env->isolate(), "require")});
v8::LocalVector<v8::Value> bundle_args(env->isolate(), {process, require});
electron::util::CompileAndCall(env->isolate(), env->context(),
"electron/js2c/node_init", &bundle_params,
&bundle_args);
}
} // namespace electron