diff --git a/docs/api/command-line-switches.md b/docs/api/command-line-switches.md index f836ac5548..42597bc163 100644 --- a/docs/api/command-line-switches.md +++ b/docs/api/command-line-switches.md @@ -16,80 +16,66 @@ app.whenReady().then(() => { }) ``` -## --ignore-connections-limit=`domains` +## Electron CLI Flags -Ignore the connections limit for `domains` list separated by `,`. +### --auth-server-whitelist=`url` -## --disable-http-cache - -Disables the disk cache for HTTP requests. - -## --disable-http2 - -Disable HTTP/2 and SPDY/3.1 protocols. - -## --lang - -Set a custom locale. - -## --inspect=`port` and --inspect-brk=`port` - -Debug-related flags, see the [Debugging the Main Process][debugging-main-process] guide for details. - -## --remote-debugging-port=`port` - -Enables remote debugging over HTTP on the specified `port`. - -## --disk-cache-size=`size` - -Forces the maximum disk space to be used by the disk cache, in bytes. - -## --js-flags=`flags` - -Specifies the flags passed to the Node.js engine. It has to be passed when starting -Electron if you want to enable the `flags` in the main process. - -```sh -$ electron --js-flags="--harmony_proxies --harmony_collections" your-app -``` - -See the [Node.js documentation][node-cli] or run `node --help` in your terminal for a list of available flags. Additionally, run `node --v8-options` to see a list of flags that specifically refer to Node.js's V8 JavaScript engine. - -## --proxy-server=`address:port` - -Use a specified proxy server, which overrides the system setting. This switch -only affects requests with HTTP protocol, including HTTPS and WebSocket -requests. It is also noteworthy that not all proxy servers support HTTPS and -WebSocket requests. The proxy URL does not support username and password -authentication [per Chromium issue](https://bugs.chromium.org/p/chromium/issues/detail?id=615947). - -## --proxy-bypass-list=`hosts` - -Instructs Electron to bypass the proxy server for the given semi-colon-separated -list of hosts. This flag has an effect only if used in tandem with -`--proxy-server`. +A comma-separated list of servers for which integrated authentication is enabled. For example: -```javascript -const { app } = require('electron') -app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') +```sh +--auth-server-whitelist='*example.com, *foobar.com, *baz' ``` -Will use the proxy server for all hosts except for local addresses (`localhost`, -`127.0.0.1` etc.), `google.com` subdomains, hosts that contain the suffix -`foo.com` and anything at `1.2.3.4:5678`. +then any `url` ending with `example.com`, `foobar.com`, `baz` will be considered +for integrated authentication. Without `*` prefix the URL has to match exactly. -## --proxy-pac-url=`url` +### --auth-negotiate-delegate-whitelist=`url` -Uses the PAC script at the specified `url`. +A comma-separated list of servers for which delegation of user credentials is required. +Without `*` prefix the URL has to match exactly. -## --no-proxy-server +### --disable-http-cache -Don't use a proxy server and always make direct connections. Overrides any other -proxy server flags that are passed. +Disables the disk cache for HTTP requests. -## --host-rules=`rules` +### --disable-http2 + +Disable HTTP/2 and SPDY/3.1 protocols. + +### --disable-renderer-backgrounding + +Prevents Chromium from lowering the priority of invisible pages' renderer +processes. + +This flag is global to all renderer processes, if you only want to disable +throttling in one window, you can take the hack of +[playing silent audio][play-silent-audio]. + +### --disk-cache-size=`size` + +Forces the maximum disk space to be used by the disk cache, in bytes. + +### --enable-api-filtering-logging + +Enables caller stack logging for the following APIs (filtering events): +- `desktopCapturer.getSources()` / `desktop-capturer-get-sources` +- `remote.require()` / `remote-require` +- `remote.getGlobal()` / `remote-get-builtin` +- `remote.getBuiltin()` / `remote-get-global` +- `remote.getCurrentWindow()` / `remote-get-current-window` +- `remote.getCurrentWebContents()` / `remote-get-current-web-contents` + +### --enable-logging + +Prints Chromium's logging into console. + +This switch can not be used in `app.commandLine.appendSwitch` since it is parsed +earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING` +environment variable to achieve the same effect. + +### --host-rules=`rules` A comma-separated list of `rules` that control how hostnames are mapped. @@ -107,69 +93,96 @@ These mappings apply to the endpoint host in a net request (the TCP connect and host resolver in a direct connection, and the `CONNECT` in an HTTP proxy connection, and the endpoint host in a `SOCKS` proxy connection). -## --host-resolver-rules=`rules` +### --host-resolver-rules=`rules` Like `--host-rules` but these `rules` only apply to the host resolver. -## --auth-server-whitelist=`url` - -A comma-separated list of servers for which integrated authentication is enabled. - -For example: - -```sh ---auth-server-whitelist='*example.com, *foobar.com, *baz' -``` - -then any `url` ending with `example.com`, `foobar.com`, `baz` will be considered -for integrated authentication. Without `*` prefix the URL has to match exactly. - -## --auth-negotiate-delegate-whitelist=`url` - -A comma-separated list of servers for which delegation of user credentials is required. -Without `*` prefix the URL has to match exactly. - -## --ignore-certificate-errors +### --ignore-certificate-errors Ignores certificate related errors. -## --ppapi-flash-path=`path` +### --ignore-connections-limit=`domains` -Sets the `path` of the pepper flash plugin. +Ignore the connections limit for `domains` list separated by `,`. -## --ppapi-flash-version=`version` +### --js-flags=`flags` -Sets the `version` of the pepper flash plugin. +Specifies the flags passed to the Node.js engine. It has to be passed when starting +Electron if you want to enable the `flags` in the main process. -## --log-net-log=`path` +```sh +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` + +See the [Node.js documentation][node-cli] or run `node --help` in your terminal for a list of available flags. Additionally, run `node --v8-options` to see a list of flags that specifically refer to Node.js's V8 JavaScript engine. + +### --lang + +Set a custom locale. + +### --log-net-log=`path` Enables net log events to be saved and writes them to `path`. -## --disable-renderer-backgrounding +### --no-proxy-server -Prevents Chromium from lowering the priority of invisible pages' renderer -processes. +Don't use a proxy server and always make direct connections. Overrides any other +proxy server flags that are passed. -This flag is global to all renderer processes, if you only want to disable -throttling in one window, you can take the hack of -[playing silent audio][play-silent-audio]. +### --no-sandbox -## --enable-logging +Disables Chromium sandbox, which is now enabled by default. +Should only be used for testing. -Prints Chromium's logging into console. +### --proxy-bypass-list=`hosts` -This switch can not be used in `app.commandLine.appendSwitch` since it is parsed -earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING` -environment variable to achieve the same effect. +Instructs Electron to bypass the proxy server for the given semi-colon-separated +list of hosts. This flag has an effect only if used in tandem with +`--proxy-server`. -## --v=`log_level` +For example: + +```javascript +const { app } = require('electron') +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') +``` + +Will use the proxy server for all hosts except for local addresses (`localhost`, +`127.0.0.1` etc.), `google.com` subdomains, hosts that contain the suffix +`foo.com` and anything at `1.2.3.4:5678`. + +### --proxy-pac-url=`url` + +Uses the PAC script at the specified `url`. + +### --proxy-server=`address:port` + +Use a specified proxy server, which overrides the system setting. This switch +only affects requests with HTTP protocol, including HTTPS and WebSocket +requests. It is also noteworthy that not all proxy servers support HTTPS and +WebSocket requests. The proxy URL does not support username and password +authentication [per Chromium issue](https://bugs.chromium.org/p/chromium/issues/detail?id=615947). + +### --remote-debugging-port=`port` + +Enables remote debugging over HTTP on the specified `port`. + +### --ppapi-flash-path=`path` + +Sets the `path` of the pepper flash plugin. + +### --ppapi-flash-version=`version` + +Sets the `version` of the pepper flash plugin. + +### --v=`log_level` Gives the default maximal active V-logging level; 0 is the default. Normally positive values are used for V-logging levels. This switch only works when `--enable-logging` is also passed. -## --vmodule=`pattern` +### --vmodule=`pattern` Gives the per-module maximal V-logging levels to override the value given by `--v`. E.g. `my_module=2,foo*=3` would change the logging level for all code in @@ -181,20 +194,38 @@ logging level for all code in the source files under a `foo/bar` directory. This switch only works when `--enable-logging` is also passed. -## --enable-api-filtering-logging +## Node.js Flags -Enables caller stack logging for the following APIs (filtering events): -- `desktopCapturer.getSources()` / `desktop-capturer-get-sources` -- `remote.require()` / `remote-require` -- `remote.getGlobal()` / `remote-get-builtin` -- `remote.getBuiltin()` / `remote-get-global` -- `remote.getCurrentWindow()` / `remote-get-current-window` -- `remote.getCurrentWebContents()` / `remote-get-current-web-contents` +Electron supports some of the [CLI flags][node-cli] supported by Node.js. -## --no-sandbox +**Note:** Passing unsupported command line switches to Electron when it is not running in `ELECTRON_RUN_AS_NODE` will have no effect. -Disables Chromium sandbox, which is now enabled by default. -Should only be used for testing. +### --inspect-brk[=[host:]port] + +Activate inspector on host:port and break at start of user script. Default host:port is 127.0.0.1:9229. + +Aliased to `--debug-brk=[host:]port`. + +### --inspect-port=[host:]port + +Set the `host:port` to be used when the inspector is activated. Useful when activating the inspector by sending the SIGUSR1 signal. Default host is `127.0.0.1`. + +Aliased to `--debug-port=[host:]port`. + +### --inspect[=[host:]port] + +Activate inspector on `host:port`. Default is `127.0.0.1:9229`. + +V8 inspector integration allows tools such as Chrome DevTools and IDEs to debug and profile Electron instances. The tools attach to Electron instances via a tcp port and communicate using the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). + +See the [Debugging the Main Process][debugging-main-process] guide for more details. + +Aliased to `--debug[=[host:]port`. + +### --inspect-publish-uid=stderr,http +Specify ways of the inspector web socket url exposure. + +By default inspector websocket url is available in stderr and under /json/list endpoint on http://host:port/json/list. [app]: app.md [append-switch]: app.md#appcommandlineappendswitchswitch-value diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md index 016463a045..cae7df9f68 100644 --- a/docs/api/environment-variables.md +++ b/docs/api/environment-variables.md @@ -80,6 +80,18 @@ and spawned child processes that set `ELECTRON_RUN_AS_NODE`. Starts the process as a normal Node.js process. +In this mode, you will be able to pass [cli options](https://nodejs.org/api/cli.html) to Node.js as +you would when running the normal Node.js executable, with the exception of the following flags: + +* "--openssl-config" +* "--use-bundled-ca" +* "--use-openssl-ca", +* "--force-fips" +* "--enable-fips" + +These flags are disabled owing to the fact that Electron uses BoringSSL instead of OpenSSL when building Node.js' +`crypto` module, and so will not work as designed. + ### `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ Don't attach to the current console session. diff --git a/patches/node/.patches b/patches/node/.patches index 7e0b9277a0..b57a0b4214 100644 --- a/patches/node/.patches +++ b/patches/node/.patches @@ -36,3 +36,4 @@ remove_serialization_deserialization_of_wasmmoduleobject.patch 64bit_bump_typedarray_max_length_to_2_32-1_elements.patch test_use_tmpdir_refresh_in_test-esm-windows_js.patch override_existing_v8_reallocate.patch +feat_enable_passing_cli_flags.patch diff --git a/patches/node/feat_enable_passing_cli_flags.patch b/patches/node/feat_enable_passing_cli_flags.patch new file mode 100644 index 0000000000..f865d68b22 --- /dev/null +++ b/patches/node/feat_enable_passing_cli_flags.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shelley Vohr +Date: Wed, 13 Nov 2019 15:39:48 +0000 +Subject: feat: enable passing cli flags + +This patches enables passing safelisted cli flags to Node.js. +Upstreamed in https://github.com/nodejs/node/pull/30466. + +diff --git a/src/node.cc b/src/node.cc +index 461f736beacec67b35c89a42319f99178a1e38e9..c00bfb6ccbe603604fca29268d15d67b208b2e6a 100644 +--- a/src/node.cc ++++ b/src/node.cc +@@ -113,8 +113,6 @@ + namespace node { + + using native_module::NativeModuleEnv; +-using options_parser::kAllowedInEnvironment; +-using options_parser::kDisallowedInEnvironment; + + using v8::Boolean; + using v8::EscapableHandleScope; +@@ -670,7 +668,7 @@ void ResetStdio() { + int ProcessGlobalArgs(std::vector* args, + std::vector* exec_args, + std::vector* errors, +- bool is_env) { ++ OptionEnvvarSettings settings) { + // Parse a few arguments which are specific to Node. + std::vector v8_args; + +@@ -680,7 +678,7 @@ int ProcessGlobalArgs(std::vector* args, + exec_args, + &v8_args, + per_process::cli_options.get(), +- is_env ? kAllowedInEnvironment : kDisallowedInEnvironment, ++ settings, + errors); + + if (!errors->empty()) return 9; +@@ -851,7 +849,7 @@ int InitializeNodeWithArgs(std::vector* argv, + return 9; + } + +- const int exit_code = ProcessGlobalArgs(&env_argv, nullptr, errors, true); ++ const int exit_code = ProcessGlobalArgs(&env_argv, nullptr, errors, kAllowedInEnvironment); + if (exit_code != 0) return exit_code; + } + #endif +@@ -859,7 +857,7 @@ int InitializeNodeWithArgs(std::vector* argv, + if (g_upstream_node_mode) { + // NOTE(jeremy): indentation is intentionally wrong here, to ease rebasing. + +- const int exit_code = ProcessGlobalArgs(argv, exec_argv, errors, false); ++ const int exit_code = ProcessGlobalArgs(argv, exec_argv, errors, kDisallowedInEnvironment); + if (exit_code != 0) return exit_code; + + // Set the process.title immediately after processing argv if --title is set. +diff --git a/src/node.h b/src/node.h +index 9c6dcbf7014f7cf87f7f66886cbf255978c244fa..4f2da9a46966199465a33c1fa275d0116d395a56 100644 +--- a/src/node.h ++++ b/src/node.h +@@ -223,6 +223,16 @@ NODE_EXTERN void Init(int* argc, + int* exec_argc, + const char*** exec_argv); + ++enum OptionEnvvarSettings { ++ kAllowedInEnvironment, ++ kDisallowedInEnvironment ++}; ++ ++NODE_EXTERN int ProcessGlobalArgs(std::vector* args, ++ std::vector* exec_args, ++ std::vector* errors, ++ OptionEnvvarSettings settings); ++ + class NodeArrayBufferAllocator; + + // An ArrayBuffer::Allocator class with some Node.js-specific tweaks. If you do +diff --git a/src/node_options.h b/src/node_options.h +index 4ce5551284bb5b1b4194905a9fe619f852933405..404cb72536cdaf8f0320770392e02ac75c303cae 100644 +--- a/src/node_options.h ++++ b/src/node_options.h +@@ -248,11 +248,6 @@ HostPort NODE_EXTERN SplitHostPort(const std::string& arg, + std::vector* errors); + void GetOptions(const v8::FunctionCallbackInfo& args); + +-enum OptionEnvvarSettings { +- kAllowedInEnvironment, +- kDisallowedInEnvironment +-}; +- + enum OptionType { + kNoOp, + kV8Option, +diff --git a/src/node_worker.cc b/src/node_worker.cc +index c8b2e1699f26ac9bfeb373653d35271f9b6c841f..a4db86ad99c75e07960a95247a41ed78e5bf55ca 100644 +--- a/src/node_worker.cc ++++ b/src/node_worker.cc +@@ -16,7 +16,7 @@ + #include + #include + +-using node::options_parser::kDisallowedInEnvironment; ++using node::kDisallowedInEnvironment; + using v8::Array; + using v8::Boolean; + using v8::Context; + \ No newline at end of file diff --git a/script/node-disabled-tests.json b/script/node-disabled-tests.json index c86a4625bf..a90d2b3a9b 100644 --- a/script/node-disabled-tests.json +++ b/script/node-disabled-tests.json @@ -51,14 +51,8 @@ "parallel/test-buffer-constructor-node-modules-paths", "parallel/test-buffer-constructor-outside-node-modules", "parallel/test-child-process-fork-exec-path", - "parallel/test-child-process-windows-hide", - "parallel/test-cli-bad-options", - "parallel/test-cli-eval", - "parallel/test-cli-node-options", "parallel/test-cli-node-print-help", - "parallel/test-cli-syntax-eval", - "parallel/test-cli-syntax-piped-bad", - "parallel/test-cli-syntax-piped-good", + "parallel/test-cli-eval", "parallel/test-code-cache", "parallel/test-common-gc", "parallel/test-crypto", @@ -86,7 +80,6 @@ "parallel/test-debug-usage", "parallel/test-debugger-pid", "parallel/test-domain-abort-on-uncaught", - "parallel/test-domain-async-id-map-leak", "parallel/test-domain-with-abort-on-uncaught-exception", "parallel/test-dummy-stdio", "parallel/test-freeze-intrinsics", @@ -108,8 +101,7 @@ "parallel/test-inspector-tracing-domain", "parallel/test-inspector-vm-global-accessors-getter-sideeffect", "parallel/test-inspector-vm-global-accessors-sideeffects", - "parallel/test-inspector-workers-flat-list", - "parallel/test-internal-util-weakreference", + "parallel/test-inspector-heap-allocation-tracker", "parallel/test-module-loading-globalpaths", "parallel/test-module-version", "parallel/test-openssl-ca-options", @@ -121,12 +113,11 @@ "parallel/test-process-exception-capture", "parallel/test-process-exception-capture-should-abort-on-uncaught", "parallel/test-process-exception-capture-should-abort-on-uncaught-setflagsfromstring", - "parallel/test-process-exec-argv", - "parallel/test-process-exit-code", "parallel/test-process-external-stdio-close", "parallel/test-process-external-stdio-close-spawn", "parallel/test-process-versions", "parallel/test-readline-interface", + "parallel/test-inspector-tracing-domain", "parallel/test-repl", "parallel/test-repl-harmony", "parallel/test-repl-pretty-custom-stack", @@ -136,7 +127,6 @@ "parallel/test-repl-tab-complete", "parallel/test-repl-uncaught-exception", "parallel/test-repl-underscore", - "parallel/test-security-revert-unknown", "parallel/test-signal-handler", "parallel/test-source-map", "parallel/test-stdout-close-catch", @@ -242,9 +232,6 @@ "report/test-report-uncaught-exception", "report/test-report-writereport", "sequential/test-child-process-execsync", - "sequential/test-cli-syntax-bad", - "sequential/test-cli-syntax-good", - "sequential/test-cli-syntax-require", "sequential/test-cpu-prof-default", "sequential/test-cpu-prof-dir-absolute", "sequential/test-cpu-prof-dir-and-name", @@ -252,11 +239,9 @@ "sequential/test-cpu-prof-dir-worker", "sequential/test-cpu-prof-drained", "sequential/test-cpu-prof-exit", - "sequential/test-cpu-prof-invalid-options", "sequential/test-cpu-prof-kill", "sequential/test-cpu-prof-name", "sequential/test-cpu-prof-worker-argv", - "sequential/test-deprecation-flags", "sequential/test-fs-watch", "sequential/test-heap-prof", "sequential/test-heapdump", diff --git a/shell/app/node_main.cc b/shell/app/node_main.cc index b9bec9a629..e124f51171 100644 --- a/shell/app/node_main.cc +++ b/shell/app/node_main.cc @@ -6,10 +6,14 @@ #include #include +#include #include +#include #include "base/command_line.h" #include "base/feature_list.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "base/task/thread_pool/thread_pool_instance.h" #include "base/threading/thread_task_runner_handle.h" #include "electron/electron_version.h" @@ -29,6 +33,49 @@ #include "shell/common/crash_reporter/crash_reporter_win.h" #endif +namespace { + +// Initialize Node.js cli options to pass to Node.js +// See https://nodejs.org/api/cli.html#cli_options +void SetNodeCliFlags() { + // Options that are unilaterally disallowed + const std::unordered_set + disallowed = {"--openssl-config", "--use-bundled-ca", "--use-openssl-ca", + "--force-fips", "--enable-fips"}; + + const auto argv = base::CommandLine::ForCurrentProcess()->argv(); + std::vector 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 defined(OS_WIN) + const auto& option = base::UTF16ToUTF8(arg); +#else + const auto& option = arg; +#endif + const auto stripped = base::StringPiece(option).substr(0, option.find('=')); + if (disallowed.count(stripped) != 0) { + LOG(ERROR) << "The Node.js cli flag " << stripped + << " is not supported in Electron"; + } else { + args.push_back(option); + } + } + + std::vector errors; + + // Node.js itself will output parsing errors to + // console so we don't need to handle that ourselves + ProcessGlobalArgs(&args, nullptr, &errors, node::kDisallowedInEnvironment); +} + +} // namespace + namespace electron { #if !defined(OS_LINUX) @@ -64,6 +111,9 @@ int NodeMain(int argc, char* argv[]) { // Explicitly register electron's builtin modules. NodeBindings::RegisterBuiltinModules(); + // Parse and set Node.js cli flags. + SetNodeCliFlags(); + int exec_argc; const char** exec_argv; node::Init(&argc, const_cast(argv), &exec_argc, &exec_argv); diff --git a/shell/browser/node_debugger.cc b/shell/browser/node_debugger.cc index 3e72194115..9e30273118 100644 --- a/shell/browser/node_debugger.cc +++ b/shell/browser/node_debugger.cc @@ -41,9 +41,9 @@ void NodeDebugger::Start() { std::vector v8_args; std::vector errors; + // TODO(codebytere): remove this parsing and use ProcessGlobalArgs node::options_parser::Parse(&args, &exec_args, &v8_args, &options, - node::options_parser::kDisallowedInEnvironment, - &errors); + node::kDisallowedInEnvironment, &errors); if (!errors.empty()) { // TODO(jeremy): what's the appropriate behaviour here? diff --git a/shell/common/node_bindings.cc b/shell/common/node_bindings.cc index 261895353e..e722dec2eb 100644 --- a/shell/common/node_bindings.cc +++ b/shell/common/node_bindings.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -136,7 +137,51 @@ bool IsPackagedApp() { #endif } +// Initialize Node.js cli options to pass to Node.js +// See https://nodejs.org/api/cli.html#cli_options +void SetNodeCliFlags() { + // Only allow DebugOptions in non-ELECTRON_RUN_AS_NODE mode + const std::unordered_set allowed = { + "--inspect", "--inspect-brk", + "--inspect-port", "--debug", + "--debug-brk", "--debug-port", + "--inspect-brk-node", "--inspect-publish-uid", + }; + + const auto argv = base::CommandLine::ForCurrentProcess()->argv(); + std::vector 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 defined(OS_WIN) + const auto& option = base::UTF16ToUTF8(arg); +#else + const auto& option = arg; +#endif + const auto stripped = base::StringPiece(option).substr(0, option.find('=')); + if (allowed.count(stripped) != 0) + args.push_back(option); + } + + std::vector errors; + const int exit_code = ProcessGlobalArgs(&args, nullptr, &errors, + node::kDisallowedInEnvironment); + + if (exit_code != 0) { + if (!errors.empty()) + LOG(INFO) << base::JoinString(errors, " "); + else + LOG(INFO) << "Error parsing Node.js cli flags"; + } +} + // 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 unilaterally disallowed const std::set disallowed = { @@ -157,7 +202,7 @@ void SetNodeOptions(base::Environment* env) { for (const auto& part : parts) { // Strip off values passed to individual NODE_OPTIONs - std::string option = part.substr(0, part.find("=")); + std::string option = part.substr(0, part.find('=')); if (is_packaged_app && allowed_in_packaged.find(option) == allowed_in_packaged.end()) { @@ -270,6 +315,9 @@ void NodeBindings::Initialize() { // Explicitly register electron's builtin modules. RegisterBuiltinModules(); + // Parse and set Node.js cli flags. + SetNodeCliFlags(); + // pass non-null program name to argv so it doesn't crash // trying to index into a nullptr int argc = 1; diff --git a/spec-main/node-spec.ts b/spec-main/node-spec.ts index 6f6a8d85a3..078a20f06b 100644 --- a/spec-main/node-spec.ts +++ b/spec-main/node-spec.ts @@ -12,7 +12,7 @@ describe('node feature', () => { const fixtures = path.join(__dirname, '..', 'spec', 'fixtures') describe('child_process', () => { describe('child_process.fork', () => { - it('works in browser process', (done) => { + it('Works in browser process', (done) => { const child = childProcess.fork(path.join(fixtures, 'module', 'ping.js')) child.on('message', (msg) => { expect(msg).to.equal('message') @@ -25,11 +25,11 @@ describe('node feature', () => { describe('contexts', () => { describe('setTimeout called under Chromium event loop in browser process', () => { - it('can be scheduled in time', (done) => { + it('Can be scheduled in time', (done) => { setTimeout(done, 0) }) - it('can be promisified', (done) => { + it('Can be promisified', (done) => { util.promisify(setTimeout)(0).then(done) }) }) @@ -57,54 +57,104 @@ describe('node feature', () => { let child: childProcess.ChildProcessWithoutNullStreams let exitPromise: Promise - afterEach(async () => { - if (child && exitPromise) { + it('Fails for options disallowed by Node.js itself', (done) => { + after(async () => { const [code, signal] = await exitPromise expect(signal).to.equal(null) - expect(code).to.equal(0) - } else if (child) { - child.kill() - } - }) - it('fails for options disallowed by Node.js itself', (done) => { + // Exit code 9 indicates cli flag parsing failure + expect(code).to.equal(9) + child.kill() + }) + const env = Object.assign({}, process.env, { NODE_OPTIONS: '--v8-options' }) child = childProcess.spawn(process.execPath, { env }) + exitPromise = emittedOnce(child, 'exit') - function cleanup () { + let output = '' + let success = false + const cleanup = () => { child.stderr.removeListener('data', listener) child.stdout.removeListener('data', listener) } - let output = '' - function listener (data: Buffer) { + const listener = (data: Buffer) => { output += data if (/electron: --v8-options is not allowed in NODE_OPTIONS/m.test(output)) { + success = true cleanup() done() } } + child.stderr.on('data', listener) child.stdout.on('data', listener) + child.on('exit', () => { + if (!success) { + cleanup() + done(new Error(`Unexpected output: ${output.toString()}`)) + } + }) }) - it('disallows crypto-related options', (done) => { + it('Disallows crypto-related options', (done) => { + after(() => { + child.kill() + }) + const env = Object.assign({}, process.env, { NODE_OPTIONS: '--use-openssl-ca' }) child = childProcess.spawn(process.execPath, ['--enable-logging'], { env }) - function cleanup () { + let output = '' + const cleanup = () => { child.stderr.removeListener('data', listener) child.stdout.removeListener('data', listener) } - let output = '' - function listener (data: Buffer) { + const listener = (data: Buffer) => { output += data if (/The NODE_OPTION --use-openssl-ca is not supported in Electron/m.test(output)) { cleanup() done() } } + + child.stderr.on('data', listener) + child.stdout.on('data', listener) + }) + }) + + describe('Node.js cli flags', () => { + let child: childProcess.ChildProcessWithoutNullStreams + let exitPromise: Promise + + it('Prohibits crypto-related flags in ELECTRON_RUN_AS_NODE mode', (done) => { + after(async () => { + const [code, signal] = await exitPromise + expect(signal).to.equal(null) + expect(code).to.equal(9) + child.kill() + }) + + child = childProcess.spawn(process.execPath, ['--force-fips'], { + env: { ELECTRON_RUN_AS_NODE: 'true' } + }) + exitPromise = emittedOnce(child, 'exit') + + let output = '' + const cleanup = () => { + child.stderr.removeListener('data', listener) + child.stdout.removeListener('data', listener) + } + + const listener = (data: Buffer) => { + output += data + if (/.*The Node.js cli flag --force-fips is not supported in Electron/m.test(output)) { + cleanup() + done() + } + } + child.stderr.on('data', listener) child.stdout.on('data', listener) }) @@ -124,72 +174,63 @@ describe('node feature', () => { } }) - it('supports starting the v8 inspector with --inspect/--inspect-brk', (done) => { + it('Supports starting the v8 inspector with --inspect/--inspect-brk', (done) => { child = childProcess.spawn(process.execPath, ['--inspect-brk', path.join(fixtures, 'module', 'run-as-node.js')], { - env: { - ELECTRON_RUN_AS_NODE: 'true' - } + env: { ELECTRON_RUN_AS_NODE: 'true' } }) let output = '' - function cleanup () { - child.stderr.removeListener('data', errorDataListener) - child.stdout.removeListener('data', outDataHandler) + const cleanup = () => { + child.stderr.removeListener('data', listener) + child.stdout.removeListener('data', listener) } - function errorDataListener (data: Buffer) { + + const listener = (data: Buffer) => { output += data - if (/^Debugger listening on ws:/m.test(output)) { + if (/Debugger listening on ws:/m.test(output)) { cleanup() done() } } - function outDataHandler (data: Buffer) { - cleanup() - done(new Error(`Unexpected output: ${data.toString()}`)) - } - child.stderr.on('data', errorDataListener) - child.stdout.on('data', outDataHandler) + + child.stderr.on('data', listener) + child.stdout.on('data', listener) }) - it('supports starting the v8 inspector with --inspect and a provided port', (done) => { + it('Supports starting the v8 inspector with --inspect and a provided port', (done) => { child = childProcess.spawn(process.execPath, ['--inspect=17364', path.join(fixtures, 'module', 'run-as-node.js')], { - env: { - ELECTRON_RUN_AS_NODE: 'true' - } + env: { ELECTRON_RUN_AS_NODE: 'true' } }) exitPromise = emittedOnce(child, 'exit') let output = '' - function cleanup () { - child.stderr.removeListener('data', errorDataListener) - child.stdout.removeListener('data', outDataHandler) + const listener = (data: Buffer) => { output += data } + const cleanup = () => { + child.stderr.removeListener('data', listener) + child.stdout.removeListener('data', listener) } - function errorDataListener (data: Buffer) { - output += data + + child.stderr.on('data', listener) + child.stdout.on('data', listener) + child.on('exit', () => { + cleanup() if (/^Debugger listening on ws:/m.test(output)) { expect(output.trim()).to.contain(':17364', 'should be listening on port 17364') - cleanup() done() + } else { + done(new Error(`Unexpected output: ${output.toString()}`)) } - } - function outDataHandler (data: Buffer) { - cleanup() - done(new Error(`Unexpected output: ${data.toString()}`)) - } - child.stderr.on('data', errorDataListener) - child.stdout.on('data', outDataHandler) + }) }) - it('does not start the v8 inspector when --inspect is after a -- argument', (done) => { + it('Does not start the v8 inspector when --inspect is after a -- argument', (done) => { child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'noop.js'), '--', '--inspect']) exitPromise = emittedOnce(child, 'exit') let output = '' - function dataListener (data: Buffer) { - output += data - } - child.stderr.on('data', dataListener) - child.stdout.on('data', dataListener) + const listener = (data: Buffer) => { output += data } + child.stderr.on('data', listener) + child.stdout.on('data', listener) child.on('exit', () => { if (output.trim().startsWith('Debugger listening on ws://')) { done(new Error('Inspector was started when it should not have been')) @@ -200,22 +241,24 @@ describe('node feature', () => { }) // IPC Electron child process not supported on Windows - ifit(process.platform !== 'win32')('does does not crash when quitting with the inspector connected', function (done) { + ifit(process.platform !== 'win32')('Does does not crash when quitting with the inspector connected', function (done) { child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'delay-exit'), '--inspect=0'], { stdio: ['ipc'] }) as childProcess.ChildProcessWithoutNullStreams exitPromise = emittedOnce(child, 'exit') - let output = '' - function dataListener (data: Buffer) { - output += data + const cleanup = () => { + child.stderr.removeListener('data', listener) + child.stdout.removeListener('data', listener) + } + let output = '' + let success = false + function listener (data: Buffer) { + output += data if (output.trim().indexOf('Debugger listening on ws://') > -1 && output.indexOf('\n') > -1) { const socketMatch = output.trim().match(/(ws:\/\/.+:[0-9]+\/.+?)\n/gm) if (socketMatch && socketMatch[0]) { - child.stderr.removeListener('data', dataListener) - child.stdout.removeListener('data', dataListener) - const w = (webContents as any).create({}) as WebContents w.loadURL('about:blank') .then(() => w.executeJavaScript(`new Promise(resolve => { @@ -228,20 +271,24 @@ describe('node feature', () => { .then(() => { (w as any).destroy() child.send('plz-quit') + success = true + cleanup() done() }) } } } - child.stderr.on('data', dataListener) - child.stdout.on('data', dataListener) + + child.stderr.on('data', listener) + child.stdout.on('data', listener) + child.on('exit', () => { + if (!success) cleanup() + }) }) - it('supports js binding', (done) => { + it('Supports js binding', (done) => { child = childProcess.spawn(process.execPath, ['--inspect', path.join(fixtures, 'module', 'inspector-binding.js')], { - env: { - ELECTRON_RUN_AS_NODE: 'true' - }, + env: { ELECTRON_RUN_AS_NODE: 'true' }, stdio: ['ipc'] }) as childProcess.ChildProcessWithoutNullStreams exitPromise = emittedOnce(child, 'exit') @@ -256,7 +303,7 @@ describe('node feature', () => { }) }) - it('can find a module using a package.json main field', () => { + it('Can find a module using a package.json main field', () => { const result = childProcess.spawnSync(process.execPath, [path.resolve(fixtures, 'api', 'electron-main-module', 'app.asar')]) expect(result.status).to.equal(0) })