mirror of
https://github.com/electron/electron.git
synced 2026-05-02 03:00:22 -04:00
Compare commits
6 Commits
sattard/fi
...
nikwen/way
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8d373abd4 | ||
|
|
e429708dcd | ||
|
|
4f38f357f1 | ||
|
|
aaf328930d | ||
|
|
d0612e2c92 | ||
|
|
8f0f08e818 |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -39,8 +39,8 @@ body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating System Version
|
||||
description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
|
||||
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
|
||||
description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a and include whether you use Wayland or X11.
|
||||
placeholder: "e.g. Windows 11 25H2, macOS Tahoe 26.4.1, or Ubuntu 26.04 (Wayland)"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -2,7 +2,7 @@ is_electron_build = true
|
||||
root_extra_deps = [ "//electron" ]
|
||||
|
||||
# Registry of NMVs --> https://github.com/nodejs/node/blob/main/doc/abi_version_registry.json
|
||||
node_module_version = 146
|
||||
node_module_version = 148
|
||||
|
||||
v8_promise_internal_field_count = 1
|
||||
v8_embedder_string = "-electron.0"
|
||||
|
||||
@@ -17,6 +17,16 @@ Process: [Main](../glossary.md#main-process)<br />
|
||||
* `env` Object (optional) - Environment key-value pairs. Default is `process.env`.
|
||||
* `execArgv` string[] (optional) - List of string arguments passed to the executable.
|
||||
* `cwd` string (optional) - Current working directory of the child process.
|
||||
* `session` [Session](session.md) (optional) - Sets the session used by the process for network
|
||||
requests. By default, network requests from the utility process will use the system network
|
||||
context which does not have HTTP cache support. Setting a session enables HTTP caching and
|
||||
other session-specific network features. See [session](session.md) for more information.
|
||||
* `partition` string (optional) - Sets the session used by the process according to the
|
||||
session's partition string. If `partition` starts with `persist:`, the process will use a
|
||||
persistent session available to all pages in the app with the same `partition`. If there is
|
||||
no `persist:` prefix, the process will use an in-memory session. By assigning the same
|
||||
`partition`, multiple processes can share the same session. If the `session` option is set,
|
||||
this option is ignored.
|
||||
* `stdio` (string[] | string) (optional) - Allows configuring the mode for `stdout` and `stderr`
|
||||
of the child process. Default is `inherit`.
|
||||
String value can be one of `pipe`, `ignore`, `inherit`, for more details on these values you can refer to
|
||||
@@ -44,7 +54,9 @@ Process: [Main](../glossary.md#main-process)<br />
|
||||
that run third-party or otherwise untrusted code. Default is `false`.
|
||||
* `respondToAuthRequestsFromMainProcess` boolean (optional) - With this flag, all HTTP 401 and 407 network
|
||||
requests created via the [net module](net.md) will allow responding to them via the
|
||||
[`app#login`](app.md#event-login) event in the main process instead of the default
|
||||
[`login`](#event-login) event on the `UtilityProcess` instance when a `session` is provided, or via
|
||||
the [`app#login`](app.md#event-login) event in the main process when using the default system network
|
||||
context. Without this flag, auth challenges are handled by the default
|
||||
[`login`](client-request.md#event-login) event on the [`ClientRequest`](client-request.md) object. Default is
|
||||
`false`.
|
||||
|
||||
@@ -176,6 +188,45 @@ Returns:
|
||||
|
||||
Emitted when the child process sends a message using [`process.parentPort.postMessage()`](process.md#processparentport).
|
||||
|
||||
#### Event: 'login'
|
||||
|
||||
Returns:
|
||||
|
||||
* `authenticationResponseDetails` Object
|
||||
* `url` URL
|
||||
* `pid` number
|
||||
* `authInfo` Object
|
||||
* `isProxy` boolean
|
||||
* `scheme` string
|
||||
* `host` string
|
||||
* `port` Integer
|
||||
* `realm` string
|
||||
* `callback` Function
|
||||
* `username` string (optional)
|
||||
* `password` string (optional)
|
||||
|
||||
Emitted when the utility process encounters an HTTP 401 or 407 authentication challenge, if the
|
||||
process was created with both `respondToAuthRequestsFromMainProcess: true` and a `session` option.
|
||||
The `callback` should be called with credentials to respond to the challenge. Calling `callback`
|
||||
without arguments will cancel the request.
|
||||
|
||||
This behaves the same as the [`login` event on `app`](app.md#event-login) but is scoped to the
|
||||
individual utility process instance.
|
||||
|
||||
```js
|
||||
const { session, utilityProcess } = require('electron')
|
||||
|
||||
const ses = session.defaultSession
|
||||
const child = utilityProcess.fork('./worker.js', [], {
|
||||
session: ses,
|
||||
respondToAuthRequestsFromMainProcess: true
|
||||
})
|
||||
|
||||
child.on('login', (authenticationResponseDetails, authInfo, callback) => {
|
||||
callback('username', 'password')
|
||||
})
|
||||
```
|
||||
|
||||
[`child_process.fork`]: https://nodejs.org/dist/latest-v16.x/docs/api/child_process.html#child_processforkmodulepath-args-options
|
||||
[Services API]: https://chromium.googlesource.com/chromium/src/+/main/docs/mojo_and_services.md
|
||||
[stdio]: https://nodejs.org/dist/latest/docs/api/child_process.html#optionsstdio
|
||||
|
||||
@@ -134,6 +134,12 @@ When a cookie is deleted, the change cause remains `explicit`.
|
||||
When the cookie being set is identical to an existing one (same name, domain, path, and value, with no actual changes), the change cause is `inserted-no-change-overwrite`.
|
||||
When the value of the cookie being set remains unchanged but some of its attributes are updated, such as the expiration attribute, the change cause will be `inserted-no-value-change-overwrite`.
|
||||
|
||||
### Deprecated: `showHiddenFiles` in Dialogs on Linux
|
||||
|
||||
This property will still be honored on macOS and Windows, but support on Linux
|
||||
will be removed in Electron 42. GTK intends for this to be a user choice rather
|
||||
than an app choice and has removed the API to do this programmatically.
|
||||
|
||||
## Planned Breaking API Changes (40.0)
|
||||
|
||||
### Deprecated: `clipboard` API access from renderer processes
|
||||
@@ -147,12 +153,6 @@ your preload script and expose it using the [contextBridge](https://www.electron
|
||||
Debug symbols for MacOS (dSYM) now use xz compression in order to handle larger file sizes. `dsym.zip` files are now
|
||||
`dsym.tar.xz` files. End users using debug symbols may need to update their zip utilities.
|
||||
|
||||
### Deprecated: `showHiddenFiles` in Dialogs on Linux
|
||||
|
||||
This property will still be honored on macOS and Windows, but support on Linux
|
||||
will be removed in Electron 42. GTK intends for this to be a user choice rather
|
||||
than an app choice and has removed the API to do this programmatically.
|
||||
|
||||
## Planned Breaking API Changes (39.0)
|
||||
|
||||
### Deprecated: `--host-rules` command line switch
|
||||
|
||||
@@ -18,13 +18,16 @@
|
||||
#include "content/browser/network_service_instance_impl.h" // nogncheck
|
||||
#include "content/public/browser/child_process_host.h"
|
||||
#include "content/public/browser/service_process_host.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "content/public/common/result_codes.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "gin/persistent.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
#include "services/network/public/cpp/originating_process_id.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/api/message_port.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/electron_child_process_host_flags.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/net/system_network_context_manager.h"
|
||||
@@ -92,8 +95,9 @@ UtilityProcessWrapper::UtilityProcessWrapper(
|
||||
base::FilePath current_working_directory,
|
||||
bool use_plugin_helper,
|
||||
bool create_network_observer,
|
||||
bool disclaim_responsibility)
|
||||
: create_network_observer_(create_network_observer) {
|
||||
bool disclaim_responsibility,
|
||||
Session* session)
|
||||
: create_network_observer_(create_network_observer), session_(session) {
|
||||
auto& allocation_handle =
|
||||
JavascriptEnvironment::GetIsolate()->GetCppHeap()->GetAllocationHandle();
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
@@ -451,6 +455,7 @@ UtilityProcessWrapper::CreateURLLoaderFactoryParams() {
|
||||
node::mojom::URLLoaderFactoryParamsPtr params =
|
||||
node::mojom::URLLoaderFactoryParams::New();
|
||||
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
|
||||
network::mojom::NetworkContext* network_context;
|
||||
network::mojom::URLLoaderFactoryParamsPtr loader_params =
|
||||
network::mojom::URLLoaderFactoryParams::New();
|
||||
loader_params->process_id = network::OriginatingProcessId::browser();
|
||||
@@ -462,11 +467,27 @@ UtilityProcessWrapper::CreateURLLoaderFactoryParams() {
|
||||
url_loader_network_observer_->Bind();
|
||||
}
|
||||
|
||||
network::mojom::NetworkContext* network_context =
|
||||
g_browser_process->system_network_context_manager()->GetContext();
|
||||
network_context->CreateURLLoaderFactory(
|
||||
url_loader_factory.InitWithNewPipeAndPassReceiver(),
|
||||
std::move(loader_params));
|
||||
if (session_) {
|
||||
auto* browser_context = session_->browser_context();
|
||||
network_context =
|
||||
browser_context->GetDefaultStoragePartition()->GetNetworkContext();
|
||||
// Build a factory through CreateURLLoaderFactoryBuilder so requests go
|
||||
// through ProxyingURLLoaderFactory (enabling webRequest interception).
|
||||
auto [factory_builder, header_client] =
|
||||
browser_context->CreateURLLoaderFactoryBuilder();
|
||||
loader_params->header_client = std::move(header_client);
|
||||
url_loader_factory =
|
||||
std::move(factory_builder)
|
||||
.Finish<mojo::PendingRemote<network::mojom::URLLoaderFactory>>(
|
||||
network_context, std::move(loader_params));
|
||||
} else {
|
||||
network_context =
|
||||
g_browser_process->system_network_context_manager()->GetContext();
|
||||
network_context->CreateURLLoaderFactory(
|
||||
url_loader_factory.InitWithNewPipeAndPassReceiver(),
|
||||
std::move(loader_params));
|
||||
}
|
||||
|
||||
params->url_loader_factory = std::move(url_loader_factory);
|
||||
mojo::PendingRemote<network::mojom::HostResolver> host_resolver;
|
||||
network_context->CreateHostResolver(
|
||||
@@ -503,6 +524,7 @@ UtilityProcessWrapper* UtilityProcessWrapper::Create(
|
||||
bool use_plugin_helper = false;
|
||||
bool create_network_observer = false;
|
||||
bool disclaim_responsibility = false;
|
||||
api::Session* session = nullptr;
|
||||
std::map<IOHandle, IOType> stdio;
|
||||
base::FilePath current_working_directory;
|
||||
base::EnvironmentMap env_map;
|
||||
@@ -548,12 +570,19 @@ UtilityProcessWrapper* UtilityProcessWrapper::Create(
|
||||
opts.Get("allowLoadingUnsignedLibraries", &use_plugin_helper);
|
||||
opts.Get("disclaim", &disclaim_responsibility);
|
||||
#endif
|
||||
|
||||
std::string partition;
|
||||
if (opts.Get("session", &session) && session) {
|
||||
} else if (opts.Get("partition", &partition)) {
|
||||
session = Session::FromPartition(args->isolate(), partition);
|
||||
}
|
||||
}
|
||||
v8::Isolate* isolate = args->isolate();
|
||||
return cppgc::MakeGarbageCollected<UtilityProcessWrapper>(
|
||||
isolate->GetCppHeap()->GetAllocationHandle(), std::move(params),
|
||||
display_name, std::move(stdio), env_map, current_working_directory,
|
||||
use_plugin_helper, create_network_observer, disclaim_responsibility);
|
||||
use_plugin_helper, create_network_observer, disclaim_responsibility,
|
||||
session);
|
||||
}
|
||||
|
||||
gin::ObjectTemplateBuilder UtilityProcessWrapper::GetObjectTemplateBuilder(
|
||||
@@ -567,6 +596,7 @@ gin::ObjectTemplateBuilder UtilityProcessWrapper::GetObjectTemplateBuilder(
|
||||
|
||||
void UtilityProcessWrapper::Trace(cppgc::Visitor* visitor) const {
|
||||
gin::Wrappable<UtilityProcessWrapper>::Trace(visitor);
|
||||
visitor->Trace(session_);
|
||||
visitor->Trace(weak_factory_);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ class Connector;
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
class Session;
|
||||
|
||||
class UtilityProcessWrapper final
|
||||
: public gin::Wrappable<UtilityProcessWrapper>,
|
||||
public gin_helper::EventEmitterMixin<UtilityProcessWrapper>,
|
||||
@@ -55,7 +57,8 @@ class UtilityProcessWrapper final
|
||||
base::FilePath current_working_directory,
|
||||
bool use_plugin_helper,
|
||||
bool create_network_observer,
|
||||
bool disclaim_responsibility);
|
||||
bool disclaim_responsibility,
|
||||
Session* session);
|
||||
~UtilityProcessWrapper() override;
|
||||
|
||||
static UtilityProcessWrapper* Create(gin::Arguments* args);
|
||||
@@ -63,6 +66,8 @@ class UtilityProcessWrapper final
|
||||
|
||||
void Shutdown(uint32_t exit_code);
|
||||
|
||||
bool has_session() const { return session_.Get(); }
|
||||
|
||||
// gin::Wrappable
|
||||
static const gin::WrapperInfo kWrapperInfo;
|
||||
static const char* GetClassName() { return "UtilityProcess"; }
|
||||
@@ -126,6 +131,7 @@ class UtilityProcessWrapper final
|
||||
GC_PLUGIN_IGNORE(
|
||||
"Context tracking of remote is not needed in the browser process.")
|
||||
mojo::Remote<node::mojom::NodeService> node_service_remote_;
|
||||
cppgc::Member<Session> session_;
|
||||
std::optional<electron::URLLoaderNetworkObserver>
|
||||
url_loader_network_observer_;
|
||||
base::CallbackListSubscription network_service_gone_subscription_;
|
||||
|
||||
@@ -84,13 +84,10 @@ struct LoginItemSettings {
|
||||
std::wstring name;
|
||||
|
||||
// used in browser::getLoginItemSettings
|
||||
bool executable_will_launch_at_login = false;
|
||||
std::vector<LaunchItem> launch_items;
|
||||
#endif
|
||||
|
||||
// used in browser::getLoginItemSettings; only meaningful on Windows but
|
||||
// always emitted so consumers don't observe `undefined`.
|
||||
bool executable_will_launch_at_login = false;
|
||||
|
||||
LoginItemSettings();
|
||||
~LoginItemSettings();
|
||||
LoginItemSettings(const LoginItemSettings&);
|
||||
|
||||
@@ -98,6 +98,10 @@ class ElectronBrowserContext : public content::BrowserContext {
|
||||
scoped_refptr<network::SharedURLLoaderFactory> InterceptURLLoaderFactory(
|
||||
scoped_refptr<network::SharedURLLoaderFactory> factory);
|
||||
|
||||
std::pair<network::URLLoaderFactoryBuilder,
|
||||
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>>
|
||||
CreateURLLoaderFactoryBuilder();
|
||||
|
||||
std::string GetMediaDeviceIDSalt();
|
||||
|
||||
// content::BrowserContext:
|
||||
@@ -187,10 +191,6 @@ class ElectronBrowserContext : public content::BrowserContext {
|
||||
content::MediaResponseCallback callback,
|
||||
gin::Arguments* args);
|
||||
|
||||
std::pair<network::URLLoaderFactoryBuilder,
|
||||
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>>
|
||||
CreateURLLoaderFactoryBuilder();
|
||||
|
||||
// Initialize pref registry.
|
||||
void InitPrefs();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "gin/arguments.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "shell/browser/api/electron_api_app.h"
|
||||
#include "shell/browser/api/electron_api_utility_process.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
@@ -100,6 +101,17 @@ void LoginHandler::EmitEvent(
|
||||
api_web_contents->Emit("login", std::move(details), auth_info,
|
||||
base::BindOnce(&LoginHandler::CallbackFromJS,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
} else if (auto* utility_process =
|
||||
api::UtilityProcessWrapper::FromProcessId(process_id);
|
||||
utility_process && utility_process->has_session()) {
|
||||
// Route auth to the utility process wrapper when the request originated
|
||||
// from a utility process with a session and
|
||||
// respondToAuthRequestsFromMainProcess. Without a session, auth falls
|
||||
// through to app.on('login') for backward compatibility.
|
||||
default_prevented =
|
||||
utility_process->Emit("login", std::move(details), auth_info,
|
||||
base::BindOnce(&LoginHandler::CallbackFromJS,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
} else {
|
||||
default_prevented =
|
||||
api::App::Get()->Emit("login", nullptr, std::move(details), auth_info,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <shellapi.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/win/atl.h" // Must be before UIAutomationCore.h
|
||||
#include "base/win/registry.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
@@ -677,8 +678,21 @@ void NativeWindowViews::SetForwardMouseMessages(bool forward) {
|
||||
RemoveWindowSubclass(legacy_window_, SubclassProc, 1);
|
||||
|
||||
if (forwarding_windows_->empty()) {
|
||||
UnhookWindowsHookEx(mouse_hook_);
|
||||
mouse_hook_ = nullptr;
|
||||
// If UnhookWindowsHookEx fails, the hook is still installed in the
|
||||
// system. Leave |mouse_hook_| pointing at the existing hook so that a
|
||||
// subsequent SetForwardMouseMessages(true) reuses it instead of
|
||||
// installing a duplicate hook.
|
||||
if (UnhookWindowsHookEx(mouse_hook_)) {
|
||||
mouse_hook_ = nullptr;
|
||||
} else {
|
||||
const DWORD error = ::GetLastError();
|
||||
if (error == ERROR_INVALID_HOOK_HANDLE) {
|
||||
mouse_hook_ = nullptr;
|
||||
} else {
|
||||
LOG(WARNING) << "Failed to unhook low-level mouse hook: "
|
||||
<< logging::SystemErrorCodeToString(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +70,11 @@ v8::Local<v8::Value> Converter<electron::LoginItemSettings>::ToV8(
|
||||
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
dict.Set("launchItems", val.launch_items);
|
||||
dict.Set("executableWillLaunchAtLogin", val.executable_will_launch_at_login);
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
if (base::mac::MacOSMajorVersion() >= 13)
|
||||
dict.Set("status", val.status);
|
||||
#endif
|
||||
// Always emit `executableWillLaunchAtLogin` so callers don't see undefined
|
||||
// on non-Windows platforms; the value is only meaningful on Windows.
|
||||
dict.Set("executableWillLaunchAtLogin", val.executable_will_launch_at_login);
|
||||
dict.Set("openAtLogin", val.open_at_login);
|
||||
dict.Set("openAsHidden", val.open_as_hidden);
|
||||
dict.Set("restoreState", val.restore_state);
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { systemPreferences } from 'electron';
|
||||
import { BrowserWindow, MessageChannelMain, utilityProcess, app } from 'electron/main';
|
||||
import { BrowserWindow, MessageChannelMain, utilityProcess, app, session } from 'electron/main';
|
||||
|
||||
import { expect } from 'chai';
|
||||
|
||||
import * as childProcess from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as http from 'node:http';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { setImmediate } from 'node:timers/promises';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
import { respondOnce, randomString, kOneKiloByte } from './lib/net-helpers';
|
||||
import { ifit, startRemoteControlApp } from './lib/spec-helpers';
|
||||
import { ifit, listen, startRemoteControlApp } from './lib/spec-helpers';
|
||||
import { closeWindow } from './lib/window-helpers';
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'utility-process');
|
||||
@@ -993,4 +994,596 @@ describe('utilityProcess module', () => {
|
||||
await exit;
|
||||
});
|
||||
});
|
||||
|
||||
describe('session', () => {
|
||||
it('can use a session object for network requests', async () => {
|
||||
const customSession = session.fromPartition(`utility-session-test-${Math.random()}`);
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.end('session-response');
|
||||
});
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch', url: serverUrl });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.body).to.equal('session-response');
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
});
|
||||
|
||||
it('can use a partition string for network requests', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.end('partition-response');
|
||||
});
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
partition: `utility-partition-test-${Math.random()}`
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch', url: serverUrl });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.body).to.equal('partition-response');
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
});
|
||||
|
||||
it('uses HTTP cache when session is provided', async () => {
|
||||
let requestCount = 0;
|
||||
const server = http.createServer((request, response) => {
|
||||
requestCount++;
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'max-age=3600',
|
||||
'Content-Type': 'text/plain',
|
||||
'X-Request-Count': String(requestCount)
|
||||
});
|
||||
response.end(`response-${requestCount}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
const customSession = session.fromPartition(`utility-cache-test-${Math.random()}`);
|
||||
const cacheFlags: boolean[] = [];
|
||||
customSession.webRequest.onResponseStarted((details) => {
|
||||
cacheFlags.push(details.fromCache);
|
||||
});
|
||||
try {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-cached', url: `${url}/cached` });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.first.body).to.equal('response-1');
|
||||
expect(data.second.body).to.equal('response-1');
|
||||
expect(requestCount).to.equal(1);
|
||||
// Verify cache flags: first request from network, second from cache
|
||||
expect(cacheFlags).to.deep.equal([false, true]);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
customSession.webRequest.onResponseStarted(null);
|
||||
await customSession.clearCache();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not use HTTP cache when using the system network context (no session)', async () => {
|
||||
let requestCount = 0;
|
||||
const server = http.createServer((request, response) => {
|
||||
requestCount++;
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'max-age=3600',
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
response.end(`response-${requestCount}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
try {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'));
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-cached', url: `${url}/no-cache` });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.first.body).to.equal('response-1');
|
||||
expect(data.second.body).to.equal('response-2');
|
||||
expect(requestCount).to.equal(2);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('respects cache: "no-store" fetch option to bypass cache', async () => {
|
||||
let requestCount = 0;
|
||||
const server = http.createServer((request, response) => {
|
||||
requestCount++;
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'max-age=3600',
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
response.end(`response-${requestCount}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
const customSession = session.fromPartition(`utility-cache-nostore-${Math.random()}`);
|
||||
const cacheFlags: boolean[] = [];
|
||||
customSession.webRequest.onResponseStarted((details) => {
|
||||
cacheFlags.push(details.fromCache);
|
||||
});
|
||||
try {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-cached', url: `${url}/nostore`, cacheMode: 'no-store' });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.first.body).to.equal('response-1');
|
||||
expect(data.second.body).to.equal('response-2');
|
||||
expect(requestCount).to.equal(2);
|
||||
// Neither response should be from cache
|
||||
expect(cacheFlags).to.deep.equal([false, false]);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
customSession.webRequest.onResponseStarted(null);
|
||||
await customSession.clearCache();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('respects cache: "no-cache" fetch option to revalidate', async () => {
|
||||
let requestCount = 0;
|
||||
const server = http.createServer((request, response) => {
|
||||
requestCount++;
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'max-age=3600',
|
||||
'Content-Type': 'text/plain',
|
||||
ETag: '"test-etag"'
|
||||
});
|
||||
response.end(`response-${requestCount}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
const customSession = session.fromPartition(`utility-cache-nocache-${Math.random()}`);
|
||||
const cacheFlags: boolean[] = [];
|
||||
customSession.webRequest.onResponseStarted((details) => {
|
||||
cacheFlags.push(details.fromCache);
|
||||
});
|
||||
try {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-cached', url: `${url}/nocache`, cacheMode: 'no-cache' });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.first.body).to.equal('response-1');
|
||||
expect(requestCount).to.equal(2);
|
||||
// First from network, second revalidated (not from cache)
|
||||
expect(cacheFlags).to.deep.equal([false, false]);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
customSession.webRequest.onResponseStarted(null);
|
||||
await customSession.clearCache();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('respects cache: "force-cache" fetch option to use stale cache', async () => {
|
||||
let requestCount = 0;
|
||||
const server = http.createServer((request, response) => {
|
||||
requestCount++;
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'max-age=3600',
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
response.end(`response-${requestCount}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
const customSession = session.fromPartition(`utility-cache-forcecache-${Math.random()}`);
|
||||
const cacheFlags: boolean[] = [];
|
||||
customSession.webRequest.onResponseStarted((details) => {
|
||||
cacheFlags.push(details.fromCache);
|
||||
});
|
||||
try {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-cached', url: `${url}/forcecache`, cacheMode: 'force-cache' });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.first.body).to.equal('response-1');
|
||||
expect(data.second.body).to.equal('response-1');
|
||||
expect(requestCount).to.equal(1);
|
||||
// First from network, second from cache
|
||||
expect(cacheFlags).to.deep.equal([false, true]);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
customSession.webRequest.onResponseStarted(null);
|
||||
await customSession.clearCache();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('respects cache: "reload" fetch option to bypass cache entirely', async () => {
|
||||
let requestCount = 0;
|
||||
const server = http.createServer((request, response) => {
|
||||
requestCount++;
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'max-age=3600',
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
response.end(`response-${requestCount}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
const customSession = session.fromPartition(`utility-cache-reload-${Math.random()}`);
|
||||
const cacheFlags: boolean[] = [];
|
||||
customSession.webRequest.onResponseStarted((details) => {
|
||||
cacheFlags.push(details.fromCache);
|
||||
});
|
||||
try {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-cached', url: `${url}/reload`, cacheMode: 'reload' });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.first.body).to.equal('response-1');
|
||||
expect(data.second.body).to.equal('response-2');
|
||||
expect(requestCount).to.equal(2);
|
||||
// Neither response should be from cache
|
||||
expect(cacheFlags).to.deep.equal([false, false]);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
customSession.webRequest.onResponseStarted(null);
|
||||
await customSession.clearCache();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('isolates cookies between different sessions', async () => {
|
||||
const sess1 = session.fromPartition(`utility-cookie-test-1-${Math.random()}`);
|
||||
const sess2 = session.fromPartition(`utility-cookie-test-2-${Math.random()}`);
|
||||
|
||||
const server = http.createServer((request, response) => {
|
||||
const cookie = request.headers.cookie || 'none';
|
||||
response.writeHead(200, {
|
||||
'Content-Type': 'text/plain',
|
||||
'Set-Cookie': 'testcookie=value; Path=/'
|
||||
});
|
||||
response.end(cookie);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
try {
|
||||
await sess1.cookies.set({ url, name: 'testcookie', value: 'sess1value' });
|
||||
const child1 = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: sess1
|
||||
});
|
||||
await once(child1, 'spawn');
|
||||
await once(child1, 'message');
|
||||
child1.postMessage({ type: 'fetch', url: `${url}/cookies`, options: { credentials: 'include' } });
|
||||
const [data1] = await once(child1, 'message');
|
||||
expect(data1.ok).to.be.true();
|
||||
expect(data1.body).to.include('testcookie=sess1value');
|
||||
const exit1 = once(child1, 'exit');
|
||||
child1.kill();
|
||||
await exit1;
|
||||
|
||||
const child2 = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: sess2
|
||||
});
|
||||
await once(child2, 'spawn');
|
||||
await once(child2, 'message');
|
||||
child2.postMessage({ type: 'fetch', url: `${url}/cookies`, options: { credentials: 'include' } });
|
||||
const [data2] = await once(child2, 'message');
|
||||
expect(data2.ok).to.be.true();
|
||||
expect(data2.body).to.equal('none');
|
||||
const exit2 = once(child2, 'exit');
|
||||
child2.kill();
|
||||
await exit2;
|
||||
} finally {
|
||||
await sess1.clearStorageData();
|
||||
await sess2.clearStorageData();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('shares cookies when utility processes use the same session', async () => {
|
||||
const sharedSession = session.fromPartition(`utility-shared-session-${Math.random()}`);
|
||||
const server = http.createServer((request, response) => {
|
||||
const cookie = request.headers.cookie || 'none';
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.end(cookie);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
try {
|
||||
await sharedSession.cookies.set({ url, name: 'shared', value: 'cookie123' });
|
||||
const child1 = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: sharedSession
|
||||
});
|
||||
await once(child1, 'spawn');
|
||||
await once(child1, 'message');
|
||||
child1.postMessage({ type: 'fetch', url: `${url}/shared`, options: { credentials: 'include' } });
|
||||
const [data1] = await once(child1, 'message');
|
||||
expect(data1.ok).to.be.true();
|
||||
expect(data1.body).to.include('shared=cookie123');
|
||||
const exit1 = once(child1, 'exit');
|
||||
child1.kill();
|
||||
await exit1;
|
||||
|
||||
const child2 = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: sharedSession
|
||||
});
|
||||
await once(child2, 'spawn');
|
||||
await once(child2, 'message');
|
||||
child2.postMessage({ type: 'fetch', url: `${url}/shared`, options: { credentials: 'include' } });
|
||||
const [data2] = await once(child2, 'message');
|
||||
expect(data2.ok).to.be.true();
|
||||
expect(data2.body).to.include('shared=cookie123');
|
||||
const exit2 = once(child2, 'exit');
|
||||
child2.kill();
|
||||
await exit2;
|
||||
} finally {
|
||||
await sharedSession.clearStorageData();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('session option takes precedence over partition', async () => {
|
||||
const customSession = session.fromPartition(`utility-precedence-session-${Math.random()}`);
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.end('precedence-ok');
|
||||
});
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession,
|
||||
partition: 'this-should-be-ignored'
|
||||
} as any);
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch', url: serverUrl });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.body).to.equal('precedence-ok');
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
});
|
||||
|
||||
it('session webRequest handlers intercept utility process requests', async () => {
|
||||
const customSession = session.fromPartition(`utility-webrequest-test-${Math.random()}`);
|
||||
const server = http.createServer((request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.end(`header: ${request.headers['x-custom-header'] || 'missing'}`);
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
try {
|
||||
customSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
details.requestHeaders['X-Custom-Header'] = 'intercepted';
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
});
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch', url: `${url}/webrequest` });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.body).to.equal('header: intercepted');
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
customSession.webRequest.onBeforeSendHeaders(null);
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('fires ClientRequest login event when respondToAuthRequestsFromMainProcess is false', async () => {
|
||||
const customSession = session.fromPartition(`utility-login-client-${Math.random()}`);
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
if (!request.headers.authorization) {
|
||||
return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
|
||||
}
|
||||
response.writeHead(200).end('authenticated');
|
||||
});
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message'); // ready
|
||||
child.postMessage({ type: 'net-request-login', url: serverUrl });
|
||||
const [loginMsg] = await once(child, 'message');
|
||||
expect(loginMsg.event).to.equal('login');
|
||||
expect(loginMsg.authInfo.realm).to.equal('Foo');
|
||||
expect(loginMsg.authInfo.scheme).to.equal('basic');
|
||||
const [responseMsg] = await once(child, 'message');
|
||||
expect(responseMsg.event).to.equal('response');
|
||||
expect(responseMsg.statusCode).to.equal(200);
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
});
|
||||
|
||||
it('fires app login event when respondToAuthRequestsFromMainProcess is true without session', async () => {
|
||||
const { remotely } = await startRemoteControlApp();
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
if (!request.headers.authorization) {
|
||||
return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
|
||||
}
|
||||
response.writeHead(200).end('authenticated');
|
||||
});
|
||||
const [loginAuthInfo, statusCode] = await remotely(
|
||||
async (serverUrl: string, fixture: string) => {
|
||||
const { app, utilityProcess } = require('electron');
|
||||
const { once } = require('node:events');
|
||||
const child = utilityProcess.fork(fixture, [], {
|
||||
stdio: 'ignore',
|
||||
respondToAuthRequestsFromMainProcess: true
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-auth', url: serverUrl });
|
||||
const [ev, , , authInfo, cb] = await once(app, 'login');
|
||||
ev.preventDefault();
|
||||
cb('user', 'pass');
|
||||
const [result] = await once(child, 'message');
|
||||
return [authInfo, result.status];
|
||||
},
|
||||
serverUrl,
|
||||
path.join(fixturesPath, 'net-session.js')
|
||||
);
|
||||
expect(statusCode).to.equal(200);
|
||||
expect(loginAuthInfo!.realm).to.equal('Foo');
|
||||
expect(loginAuthInfo!.scheme).to.equal('basic');
|
||||
});
|
||||
|
||||
it('fires utility process login event when respondToAuthRequestsFromMainProcess is true with session', async () => {
|
||||
const { remotely } = await startRemoteControlApp();
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
if (!request.headers.authorization) {
|
||||
return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
|
||||
}
|
||||
response.writeHead(200).end('authenticated');
|
||||
});
|
||||
const [loginAuthInfo, statusCode, appLoginFired] = await remotely(
|
||||
async (serverUrl: string, fixture: string) => {
|
||||
const { app, session, utilityProcess } = require('electron');
|
||||
const { once } = require('node:events');
|
||||
const customSession = session.fromPartition(`utility-login-session-${Math.random()}`);
|
||||
let appLoginFired = false;
|
||||
app.on('login', () => {
|
||||
appLoginFired = true;
|
||||
});
|
||||
const child = utilityProcess.fork(fixture, [], {
|
||||
stdio: 'ignore',
|
||||
respondToAuthRequestsFromMainProcess: true,
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch-auth', url: serverUrl });
|
||||
const [ev, , authInfo, cb] = await once(child, 'login');
|
||||
ev.preventDefault();
|
||||
cb('user', 'pass');
|
||||
const [result] = await once(child, 'message');
|
||||
return [authInfo, result.status, appLoginFired];
|
||||
},
|
||||
serverUrl,
|
||||
path.join(fixturesPath, 'net-session.js')
|
||||
);
|
||||
expect(statusCode).to.equal(200);
|
||||
expect(loginAuthInfo!.realm).to.equal('Foo');
|
||||
expect(loginAuthInfo!.scheme).to.equal('basic');
|
||||
expect(appLoginFired).to.be.false();
|
||||
});
|
||||
|
||||
it('resolves hosts using the session network context, not the default', async () => {
|
||||
const proxyServer = http.createServer((request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.end(`proxied:${request.headers.host}`);
|
||||
});
|
||||
const { port: proxyPort } = await listen(proxyServer);
|
||||
|
||||
const customSession = session.fromPartition(`utility-resolver-test-${Math.random()}`);
|
||||
try {
|
||||
await customSession.setProxy({
|
||||
proxyRules: `http=127.0.0.1:${proxyPort}`,
|
||||
proxyBypassRules: '<-loopback>'
|
||||
});
|
||||
|
||||
await session.defaultSession.setProxy({});
|
||||
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'net-session.js'), [], {
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch', url: 'http://non-existent-host.test:12345/path' });
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data.ok).to.be.true();
|
||||
expect(data.body).to.equal('proxied:non-existent-host.test:12345');
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
} finally {
|
||||
await session.defaultSession.setProxy({});
|
||||
proxyServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not use system network proxy set via app.setProxy when session is provided', async () => {
|
||||
const { remotely } = await startRemoteControlApp();
|
||||
const systemProxyServer = http.createServer((request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.end('system-proxy');
|
||||
});
|
||||
const { port: systemProxyPort } = await listen(systemProxyServer);
|
||||
|
||||
const targetServer = http.createServer((request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.end('direct-response');
|
||||
});
|
||||
const { url: targetUrl } = await listen(targetServer);
|
||||
|
||||
try {
|
||||
const body = await remotely(
|
||||
async (targetUrl: string, systemProxyPort: number, fixture: string) => {
|
||||
const { app, session, utilityProcess } = require('electron');
|
||||
const { once } = require('node:events');
|
||||
|
||||
await app.setProxy({
|
||||
proxyRules: `http=127.0.0.1:${systemProxyPort}`,
|
||||
proxyBypassRules: '<-loopback>'
|
||||
});
|
||||
|
||||
const customSession = session.fromPartition(`utility-app-proxy-test-${Math.random()}`);
|
||||
await customSession.setProxy({});
|
||||
|
||||
const child = utilityProcess.fork(fixture, [], {
|
||||
stdio: 'ignore',
|
||||
session: customSession
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
await once(child, 'message');
|
||||
child.postMessage({ type: 'fetch', url: targetUrl });
|
||||
const [data] = await once(child, 'message');
|
||||
const exit = once(child, 'exit');
|
||||
child.kill();
|
||||
await exit;
|
||||
return data.body;
|
||||
},
|
||||
targetUrl,
|
||||
systemProxyPort,
|
||||
path.join(fixturesPath, 'net-session.js')
|
||||
);
|
||||
expect(body).to.equal('direct-response');
|
||||
} finally {
|
||||
targetServer.close();
|
||||
systemProxyServer.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
84
spec/fixtures/api/utility-process/net-session.js
vendored
Normal file
84
spec/fixtures/api/utility-process/net-session.js
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
const { net } = require('electron');
|
||||
|
||||
process.parentPort.on('message', async (e) => {
|
||||
const { type, url, options } = e.data;
|
||||
|
||||
if (type === 'fetch') {
|
||||
try {
|
||||
const response = await net.fetch(url, options || {});
|
||||
const body = await response.text();
|
||||
const headers = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
process.parentPort.postMessage({
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
body,
|
||||
headers
|
||||
});
|
||||
} catch (error) {
|
||||
process.parentPort.postMessage({
|
||||
ok: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
} else if (type === 'fetch-cached') {
|
||||
try {
|
||||
const response1 = await net.fetch(url);
|
||||
const body1 = await response1.text();
|
||||
|
||||
const fetchOpts = {};
|
||||
if (e.data.cacheMode) {
|
||||
fetchOpts.cache = e.data.cacheMode;
|
||||
}
|
||||
const response2 = await net.fetch(url, fetchOpts);
|
||||
const body2 = await response2.text();
|
||||
|
||||
process.parentPort.postMessage({
|
||||
ok: true,
|
||||
first: { status: response1.status, body: body1 },
|
||||
second: { status: response2.status, body: body2 }
|
||||
});
|
||||
} catch (error) {
|
||||
process.parentPort.postMessage({
|
||||
ok: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
} else if (type === 'net-request-login') {
|
||||
const request = net.request({ method: 'GET', url });
|
||||
request.on('login', (authInfo, callback) => {
|
||||
process.parentPort.postMessage({ event: 'login', authInfo });
|
||||
callback('user', 'pass');
|
||||
});
|
||||
request.on('response', (response) => {
|
||||
const data = [];
|
||||
response.on('data', (chunk) => data.push(chunk));
|
||||
response.on('end', () => {
|
||||
process.parentPort.postMessage({
|
||||
event: 'response',
|
||||
statusCode: response.statusCode
|
||||
});
|
||||
});
|
||||
});
|
||||
request.end();
|
||||
} else if (type === 'fetch-auth') {
|
||||
try {
|
||||
const response = await net.fetch(url);
|
||||
const body = await response.text();
|
||||
process.parentPort.postMessage({
|
||||
event: 'response',
|
||||
status: response.status,
|
||||
body
|
||||
});
|
||||
} catch (error) {
|
||||
process.parentPort.postMessage({
|
||||
event: 'error',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.parentPort.postMessage({ type: 'ready' });
|
||||
Reference in New Issue
Block a user