mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
44 Commits
v8.0.0-nig
...
v8.0.0-nig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39475f9404 | ||
|
|
8589ab27a4 | ||
|
|
f537366387 | ||
|
|
d7161742d2 | ||
|
|
6e88b6b445 | ||
|
|
614079654c | ||
|
|
51015c5b48 | ||
|
|
c621a36320 | ||
|
|
e37ad09330 | ||
|
|
bfe256891c | ||
|
|
0f5ff1f5bb | ||
|
|
e96a042223 | ||
|
|
5cbbd489d5 | ||
|
|
805a55099b | ||
|
|
654338693f | ||
|
|
a9e3dabc8a | ||
|
|
609403fba6 | ||
|
|
6b55584923 | ||
|
|
81e6f317c9 | ||
|
|
7d4e0ad7b0 | ||
|
|
35ebbb5f6e | ||
|
|
c819fbe852 | ||
|
|
c03288f458 | ||
|
|
90d62e5b98 | ||
|
|
b3947d6a83 | ||
|
|
eed72c35d7 | ||
|
|
79e936aea8 | ||
|
|
01fdb80f7c | ||
|
|
04debd5890 | ||
|
|
af138dab55 | ||
|
|
f212ed85dd | ||
|
|
99de0975c3 | ||
|
|
41d8247ffc | ||
|
|
217ed9aabc | ||
|
|
538c4763cf | ||
|
|
3bc5302d78 | ||
|
|
987300c97a | ||
|
|
27ce6a9cd3 | ||
|
|
832c926712 | ||
|
|
ae9424d93a | ||
|
|
1dcda7b809 | ||
|
|
bdc84d0bfb | ||
|
|
4b8e1588b4 | ||
|
|
4eee71ffbf |
10
.github/main.workflow
vendored
10
.github/main.workflow
vendored
@@ -1,10 +0,0 @@
|
||||
workflow "Clerk" {
|
||||
#TODO(codebytere): make this work properly on pull_request
|
||||
on = "repository_dispatch"
|
||||
resolves = "Check release notes"
|
||||
}
|
||||
|
||||
action "Check release notes" {
|
||||
uses = "electron/clerk@master"
|
||||
secrets = [ "GITHUB_TOKEN" ]
|
||||
}
|
||||
25
.github/stale.yml
vendored
25
.github/stale.yml
vendored
@@ -1,25 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 45
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- fixme/bug
|
||||
- fixme/crash
|
||||
- fixme/regression
|
||||
- fixme/security
|
||||
- blocked
|
||||
- blocking-stable
|
||||
- needs-review
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity and is not currently prioritized. It will be closed
|
||||
in a week if no further activity occurs :)
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
If you still think this issue is relevant, please ping a maintainer or
|
||||
leave a comment!
|
||||
@@ -1 +1 @@
|
||||
8.0.0-nightly.20190827
|
||||
8.0.0-nightly.20190903
|
||||
@@ -675,6 +675,8 @@ preferred over `name` by Electron.
|
||||
|
||||
Overrides the current application's name.
|
||||
|
||||
**Note:** This function overrides the name used internally by Electron; it does not affect the name that the OS uses.
|
||||
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `app.getLocale()`
|
||||
|
||||
@@ -379,6 +379,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
* `disableHtmlFullscreenWindowResize` Boolean (optional) - Whether to
|
||||
prevent the window from resizing when entering HTML Fullscreen. Default
|
||||
is `false`.
|
||||
* `accessibleTitle` String (optional) - An alternative title string provided only
|
||||
to accessibility tools such as screen readers. This string is not directly
|
||||
visible to users.
|
||||
|
||||
When setting minimum or maximum window size with `minWidth`/`maxWidth`/
|
||||
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from
|
||||
@@ -821,6 +824,12 @@ const menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
```
|
||||
|
||||
#### `win.accessibleTitle`
|
||||
|
||||
A `String` property that defines an alternative title provided only to
|
||||
accessibility tools such as screen readers. This string is not directly
|
||||
visible to users.
|
||||
|
||||
### Instance Methods
|
||||
|
||||
Objects created with `new BrowserWindow` have the following instance methods:
|
||||
|
||||
@@ -139,9 +139,9 @@ Writes any unwritten DOMStorage data to disk.
|
||||
#### `ses.setProxy(config)`
|
||||
|
||||
* `config` Object
|
||||
* `pacScript` String - The URL associated with the PAC file.
|
||||
* `proxyRules` String - Rules indicating which proxies to use.
|
||||
* `proxyBypassRules` String - Rules indicating which URLs should
|
||||
* `pacScript` String (optional) - The URL associated with the PAC file.
|
||||
* `proxyRules` String (optional) - Rules indicating which proxies to use.
|
||||
* `proxyBypassRules` String (optional) - Rules indicating which URLs should
|
||||
bypass the proxy settings.
|
||||
|
||||
Returns `Promise<void>` - Resolves when the proxy setting process is complete.
|
||||
@@ -267,7 +267,7 @@ the original network configuration.
|
||||
|
||||
#### `ses.setCertificateVerifyProc(proc)`
|
||||
|
||||
* `proc` Function
|
||||
* `proc` Function | null
|
||||
* `request` Object
|
||||
* `hostname` String
|
||||
* `certificate` [Certificate](structures/certificate.md)
|
||||
@@ -408,6 +408,17 @@ Returns `String` - The user agent for this session.
|
||||
|
||||
Returns `Promise<Buffer>` - resolves with blob data.
|
||||
|
||||
#### `ses.downloadURL(url)`
|
||||
|
||||
* `url` String
|
||||
|
||||
Initiates a download of the resource at `url`.
|
||||
The API will generate a [DownloadItem](download-item.md) that can be accessed
|
||||
with the [will-download](#event-will-download) event.
|
||||
|
||||
**Note:** This does not perform any security checks that relate to a page's origin,
|
||||
unlike [`webContents.downloadURL`](web-contents.md#contentsdownloadurlurl).
|
||||
|
||||
#### `ses.createInterruptedDownload(options)`
|
||||
|
||||
* `options` Object
|
||||
@@ -416,8 +427,8 @@ Returns `Promise<Buffer>` - resolves with blob data.
|
||||
* `mimeType` String (optional)
|
||||
* `offset` Integer - Start range for the download.
|
||||
* `length` Integer - Total length of the download.
|
||||
* `lastModified` String - Last-Modified header value.
|
||||
* `eTag` String - ETag header value.
|
||||
* `lastModified` String (optional) - Last-Modified header value.
|
||||
* `eTag` String (optional) - ETag header value.
|
||||
* `startTime` Double (optional) - Time when download was started in
|
||||
number of seconds since UNIX epoch.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# InputEvent Object
|
||||
|
||||
* `modifiers` String[] - An array of modifiers of the event, can
|
||||
be `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`,
|
||||
`leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`,
|
||||
`numLock`, `left`, `right`.
|
||||
* `modifiers` String[] (optional) - An array of modifiers of the event, can
|
||||
be `shift`, `control`, `ctrl`, `alt`, `meta`, `command`, `cmd`, `isKeypad`,
|
||||
`isAutoRepeat`, `leftButtonDown`, `middleButtonDown`, `rightButtonDown`,
|
||||
`capsLock`, `numLock`, `left`, `right`.
|
||||
|
||||
@@ -1593,8 +1593,8 @@ End subscribing for frame presentation events.
|
||||
|
||||
* `item` Object
|
||||
* `file` String[] | String - The path(s) to the file(s) being dragged.
|
||||
* `icon` [NativeImage](native-image.md) - The image must be non-empty on
|
||||
macOS.
|
||||
* `icon` [NativeImage](native-image.md) | String - The image must be
|
||||
non-empty on macOS.
|
||||
|
||||
Sets the `item` as dragging item for current drag-drop operation, `file` is the
|
||||
absolute path of the file to be dragged, and `icon` is the image showing under
|
||||
|
||||
@@ -99,16 +99,16 @@ Some examples of valid `urls`:
|
||||
* `timestamp` Double
|
||||
* `requestHeaders` Record<string, string>
|
||||
* `callback` Function
|
||||
* `response` Object
|
||||
* `beforeSendResponse` Object
|
||||
* `cancel` Boolean (optional)
|
||||
* `requestHeaders` Record<string, string> (optional) - When provided, request will be made
|
||||
* `requestHeaders` Record<string, string | string[]> (optional) - When provided, request will be made
|
||||
with these headers.
|
||||
|
||||
The `listener` will be called with `listener(details, callback)` before sending
|
||||
an HTTP request, once the request headers are available. This may occur after a
|
||||
TCP connection is made to the server, but before any http data is sent.
|
||||
|
||||
The `callback` has to be called with an `response` object.
|
||||
The `callback` has to be called with a `response` object.
|
||||
|
||||
#### `webRequest.onSendHeaders([filter, ]listener)`
|
||||
|
||||
@@ -148,9 +148,9 @@ response are visible by the time this listener is fired.
|
||||
* `statusCode` Integer
|
||||
* `responseHeaders` Record<string, string> (optional)
|
||||
* `callback` Function
|
||||
* `response` Object
|
||||
* `headersReceivedResponse` Object
|
||||
* `cancel` Boolean (optional)
|
||||
* `responseHeaders` Record<string, string> (optional) - When provided, the server is assumed
|
||||
* `responseHeaders` Record<string, string | string[]> (optional) - When provided, the server is assumed
|
||||
to have responded with these headers.
|
||||
* `statusLine` String (optional) - Should be provided when overriding
|
||||
`responseHeaders` to change header status otherwise original response
|
||||
@@ -159,7 +159,7 @@ response are visible by the time this listener is fired.
|
||||
The `listener` will be called with `listener(details, callback)` when HTTP
|
||||
response headers of a request have been received.
|
||||
|
||||
The `callback` has to be called with an `response` object.
|
||||
The `callback` has to be called with a `response` object.
|
||||
|
||||
#### `webRequest.onResponseStarted([filter, ]listener)`
|
||||
|
||||
@@ -201,6 +201,7 @@ and response headers are available.
|
||||
* `timestamp` Double
|
||||
* `redirectURL` String
|
||||
* `statusCode` Integer
|
||||
* `statusLine` String
|
||||
* `ip` String (optional) - The server IP address that the request was
|
||||
actually sent to.
|
||||
* `fromCache` Boolean
|
||||
|
||||
@@ -31,8 +31,7 @@ unifies the existing (and well maintained) build tools for Electron development
|
||||
into a cohesive package so that anyone can jump right in to Electron
|
||||
development.
|
||||
|
||||
Forge comes with [ready-to-use templates](https://electronforge.io/templates) for popular
|
||||
frameworks like React, Vue, or Angular. It uses the same core modules used by the
|
||||
Forge comes with [a ready-to-use template](https://electronforge.io/templates) using Webpack as a bundler. It includes an example typescript configuration and provides two configuration files to enable easy customization. It uses the same core modules used by the
|
||||
greater Electron community (like [`electron-packager`](https://github.com/electron/electron-packager)) –
|
||||
changes made by Electron maintainers (like Slack) benefit Forge's users, too.
|
||||
|
||||
|
||||
@@ -195,6 +195,8 @@ Finally the `index.html` is the web page you want to show:
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
|
||||
@@ -511,7 +511,7 @@ no circumstances should you enable features speculatively.
|
||||
// Bad
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
enableBlinkFeatures: ['ExecCommandInJavaScript']
|
||||
enableBlinkFeatures: 'ExecCommandInJavaScript'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -241,11 +241,16 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
|
||||
return args.map(metaToValue)
|
||||
}
|
||||
|
||||
const isRemoteModuleEnabledImpl = function (contents) {
|
||||
const webPreferences = contents.getLastWebPreferences() || {}
|
||||
return !!webPreferences.enableRemoteModule
|
||||
}
|
||||
|
||||
const isRemoteModuleEnabledCache = new WeakMap()
|
||||
|
||||
const isRemoteModuleEnabled = function (contents) {
|
||||
if (!isRemoteModuleEnabledCache.has(contents)) {
|
||||
isRemoteModuleEnabledCache.set(contents, contents._isRemoteModuleEnabled())
|
||||
isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents))
|
||||
}
|
||||
|
||||
return isRemoteModuleEnabledCache.get(contents)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "8.0.0-nightly.20190827",
|
||||
"version": "8.0.0-nightly.20190903",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
@@ -8,6 +8,7 @@
|
||||
"@electron/typescript-definitions": "^8.6.1",
|
||||
"@octokit/rest": "^16.3.2",
|
||||
"@primer/octicons": "^9.1.1",
|
||||
"@types/basic-auth": "^1.1.2",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chai-as-promised": "^7.1.0",
|
||||
"@types/dirty-chai": "^2.0.0",
|
||||
@@ -15,6 +16,7 @@
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node": "^12.0.10",
|
||||
"@types/send": "^0.14.5",
|
||||
"@types/split": "^1.0.0",
|
||||
"@types/webpack": "^4.4.32",
|
||||
"@types/webpack-env": "^1.13.9",
|
||||
@@ -77,6 +79,7 @@
|
||||
"create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --api=electron-api.json && node spec/ts-smoke/runner.js",
|
||||
"gn-typescript-definitions": "npm run create-typescript-definitions && shx cp electron.d.ts",
|
||||
"pre-flight": "pre-flight",
|
||||
"gn-check": "node ./script/gn-check.js",
|
||||
"preinstall": "node -e 'process.exit(0)'",
|
||||
"prepack": "check-for-leaks",
|
||||
"repl": "node ./script/start.js --interactive",
|
||||
@@ -113,6 +116,7 @@
|
||||
"remark -qf"
|
||||
],
|
||||
"*.{gn,gni}": [
|
||||
"npm run gn-check",
|
||||
"python script/run-gn-format.py",
|
||||
"git add"
|
||||
],
|
||||
|
||||
@@ -75,3 +75,5 @@ disable_color_correct_rendering.patch
|
||||
add_contentgpuclient_precreatemessageloop_callback.patch
|
||||
picture-in-picture.patch
|
||||
disable_compositor_recycling.patch
|
||||
allow_new_privileges_in_unsandboxed_child_processes.patch
|
||||
expose_setuseragent_on_networkcontext.patch
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jeremy Apthorp <nornagon@nornagon.net>
|
||||
Date: Mon, 26 Aug 2019 12:02:51 -0700
|
||||
Subject: allow new privileges in unsandboxed child processes
|
||||
|
||||
This allows unsandboxed renderers to launch setuid processes on Linux.
|
||||
|
||||
diff --git a/content/browser/child_process_launcher_helper_linux.cc b/content/browser/child_process_launcher_helper_linux.cc
|
||||
index 720b92a1a3a7ab5512f839005b272e4989d2ac65..b1759109627cd00053489dcdd397e942fa9d289f 100644
|
||||
--- a/content/browser/child_process_launcher_helper_linux.cc
|
||||
+++ b/content/browser/child_process_launcher_helper_linux.cc
|
||||
@@ -54,6 +54,18 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
|
||||
const int sandbox_fd = SandboxHostLinux::GetInstance()->GetChildSocket();
|
||||
options->fds_to_remap.push_back(
|
||||
std::make_pair(sandbox_fd, service_manager::GetSandboxFD()));
|
||||
+
|
||||
+ // (For Electron), if we're launching without zygote, that means we're
|
||||
+ // launching an unsandboxed process (since all sandboxed processes are
|
||||
+ // forked from the zygote). Relax the allow_new_privs option to permit
|
||||
+ // launching suid processes from unsandboxed renderers.
|
||||
+ service_manager::ZygoteHandle zygote_handle =
|
||||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoZygote)
|
||||
+ ? nullptr
|
||||
+ : delegate_->GetZygote();
|
||||
+ if (!zygote_handle) {
|
||||
+ options->allow_new_privs = true;
|
||||
+ }
|
||||
}
|
||||
|
||||
options->environment = delegate_->GetEnvironment();
|
||||
90
patches/chromium/expose_setuseragent_on_networkcontext.patch
Normal file
90
patches/chromium/expose_setuseragent_on_networkcontext.patch
Normal file
@@ -0,0 +1,90 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jeremy Apthorp <nornagon@nornagon.net>
|
||||
Date: Wed, 28 Aug 2019 12:21:25 -0700
|
||||
Subject: expose SetUserAgent on NetworkContext
|
||||
|
||||
Applying
|
||||
https://chromium-review.googlesource.com/c/chromium/src/+/1585083 before
|
||||
it's merged. There may end up being a better way of doing this, or the
|
||||
patch may be merged upstream, at which point this patch should be
|
||||
removed.
|
||||
|
||||
diff --git a/net/url_request/static_http_user_agent_settings.h b/net/url_request/static_http_user_agent_settings.h
|
||||
index 0ccfe130f00ec3b6c75cd8ee04d5a2777e1fd00c..653829457d58bf92057cc36aa8a289703d8b0577 100644
|
||||
--- a/net/url_request/static_http_user_agent_settings.h
|
||||
+++ b/net/url_request/static_http_user_agent_settings.h
|
||||
@@ -26,13 +26,17 @@ class NET_EXPORT StaticHttpUserAgentSettings : public HttpUserAgentSettings {
|
||||
accept_language_ = new_accept_language;
|
||||
}
|
||||
|
||||
+ void set_user_agent(const std::string& new_user_agent) {
|
||||
+ user_agent_ = new_user_agent;
|
||||
+ }
|
||||
+
|
||||
// HttpUserAgentSettings implementation
|
||||
std::string GetAcceptLanguage() const override;
|
||||
std::string GetUserAgent() const override;
|
||||
|
||||
private:
|
||||
std::string accept_language_;
|
||||
- const std::string user_agent_;
|
||||
+ std::string user_agent_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StaticHttpUserAgentSettings);
|
||||
};
|
||||
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
|
||||
index 20243f6c333478f14e5bff3789e780b996887512..34e4fd20f78ee0376053720742fc9af60dae37e3 100644
|
||||
--- a/services/network/network_context.cc
|
||||
+++ b/services/network/network_context.cc
|
||||
@@ -1095,6 +1095,13 @@ void NetworkContext::SetNetworkConditions(
|
||||
std::move(network_conditions));
|
||||
}
|
||||
|
||||
+void NetworkContext::SetUserAgent(const std::string& new_user_agent) {
|
||||
+ // This may only be called on NetworkContexts created with a constructor that
|
||||
+ // calls ApplyContextParamsToBuilder.
|
||||
+ DCHECK(user_agent_settings_);
|
||||
+ user_agent_settings_->set_user_agent(new_user_agent);
|
||||
+}
|
||||
+
|
||||
void NetworkContext::SetAcceptLanguage(const std::string& new_accept_language) {
|
||||
// This may only be called on NetworkContexts created with the constructor
|
||||
// that calls MakeURLRequestContext().
|
||||
diff --git a/services/network/network_context.h b/services/network/network_context.h
|
||||
index f117ba5edfa7da25efd25e52ac5fff8b37ba3de3..5a805b4ee688ee7a113c833535db861b0e3b2ef9 100644
|
||||
--- a/services/network/network_context.h
|
||||
+++ b/services/network/network_context.h
|
||||
@@ -213,6 +213,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
|
||||
void CloseIdleConnections(CloseIdleConnectionsCallback callback) override;
|
||||
void SetNetworkConditions(const base::UnguessableToken& throttling_profile_id,
|
||||
mojom::NetworkConditionsPtr conditions) override;
|
||||
+ void SetUserAgent(const std::string& new_user_agent) override;
|
||||
void SetAcceptLanguage(const std::string& new_accept_language) override;
|
||||
void SetEnableReferrers(bool enable_referrers) override;
|
||||
#if defined(OS_CHROMEOS)
|
||||
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
|
||||
index 034b5720ffa497429b2cdfcc06cd2d421f2db99c..aa2c88443e744396e381377ec20b03fb0e66d2db 100644
|
||||
--- a/services/network/public/mojom/network_context.mojom
|
||||
+++ b/services/network/public/mojom/network_context.mojom
|
||||
@@ -857,6 +857,9 @@ interface NetworkContext {
|
||||
SetNetworkConditions(mojo_base.mojom.UnguessableToken throttling_profile_id,
|
||||
NetworkConditions? conditions);
|
||||
|
||||
+ // Updates the user agent to be used for requests.
|
||||
+ SetUserAgent(string new_user_agent);
|
||||
+
|
||||
// Updates the Accept-Language header to be used for requests.
|
||||
SetAcceptLanguage(string new_accept_language);
|
||||
|
||||
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
|
||||
index aa21b8e434c01a866e3fae04b8de54a3cdfb725e..63880702102a396576c5825f8c5c7731cde091cc 100644
|
||||
--- a/services/network/test/test_network_context.h
|
||||
+++ b/services/network/test/test_network_context.h
|
||||
@@ -93,6 +93,7 @@ class TestNetworkContext : public mojom::NetworkContext {
|
||||
void CloseIdleConnections(CloseIdleConnectionsCallback callback) override {}
|
||||
void SetNetworkConditions(const base::UnguessableToken& throttling_profile_id,
|
||||
mojom::NetworkConditionsPtr conditions) override {}
|
||||
+ void SetUserAgent(const std::string& new_user_agent) override {}
|
||||
void SetAcceptLanguage(const std::string& new_accept_language) override {}
|
||||
void SetEnableReferrers(bool enable_referrers) override {}
|
||||
#if defined(OS_CHROMEOS)
|
||||
@@ -7,8 +7,6 @@ security create-keychain -p $KEYCHAIN_PASSWORD $KEY_CHAIN
|
||||
security default-keychain -s $KEY_CHAIN
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p $KEYCHAIN_PASSWORD $KEY_CHAIN
|
||||
# Set keychain locking timeout to 3600 seconds
|
||||
security set-keychain-settings -t 3600 -u $KEY_CHAIN
|
||||
|
||||
# Add certificates to keychain and allow codesign to access them
|
||||
security import "$(dirname $0)"/signing.cer -k $KEY_CHAIN -A /usr/bin/codesign
|
||||
@@ -16,7 +14,7 @@ security import "$(dirname $0)"/signing.pem -k $KEY_CHAIN -A /usr/bin/codesign
|
||||
security import "$(dirname $0)"/signing.p12 -k $KEY_CHAIN -P $SPEC_KEY_PASSWORD -A /usr/bin/codesign
|
||||
|
||||
echo "Add keychain to keychain-list"
|
||||
security list-keychains -s mac-build.keychain
|
||||
security list-keychains -s $KEY_CHAIN
|
||||
|
||||
echo "Setting key partition list"
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEY_CHAIN
|
||||
|
||||
34
script/gn-check.js
Normal file
34
script/gn-check.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const cp = require('child_process')
|
||||
const path = require('path')
|
||||
|
||||
const { getOutDir } = require('./lib/utils')
|
||||
|
||||
const SOURCE_ROOT = path.normalize(path.dirname(__dirname))
|
||||
const DEPOT_TOOLS = path.resolve(SOURCE_ROOT, '..', 'third_party', 'depot_tools')
|
||||
const OUT_DIR = getOutDir()
|
||||
|
||||
if (!OUT_DIR) {
|
||||
throw new Error(`No viable out dir: one of Debug, Testing, or Release must exist.`)
|
||||
}
|
||||
|
||||
const env = Object.assign({
|
||||
CHROMIUM_BUILDTOOLS_PATH: path.resolve(SOURCE_ROOT, '..', 'buildtools'),
|
||||
DEPOT_TOOLS_WIN_TOOLCHAIN: '0'
|
||||
}, process.env)
|
||||
// Users may not have depot_tools in PATH.
|
||||
env.PATH = `${env.PATH}${path.delimiter}${DEPOT_TOOLS}`
|
||||
|
||||
const gnCheckDirs = [
|
||||
'//electron:electron_lib',
|
||||
'//electron:electron_app',
|
||||
'//electron:manifests',
|
||||
'//electron/shell/common/api:mojo'
|
||||
]
|
||||
|
||||
for (const dir of gnCheckDirs) {
|
||||
const args = ['check', `../out/${OUT_DIR}`, dir]
|
||||
const result = cp.spawnSync('gn', args, { env, stdio: 'inherit' })
|
||||
if (result.status !== 0) process.exit(result.status)
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
@@ -6,8 +6,8 @@ const ELECTRON_DIR = path.resolve(__dirname, '..', '..')
|
||||
const SRC_DIR = path.resolve(ELECTRON_DIR, '..')
|
||||
|
||||
require('colors')
|
||||
const pass = '\u2713'.green
|
||||
const fail = '\u2717'.red
|
||||
const pass = '✓'.green
|
||||
const fail = '✗'.red
|
||||
|
||||
function getElectronExec () {
|
||||
const OUT_DIR = getOutDir()
|
||||
|
||||
@@ -19,8 +19,8 @@ const bumpType = args._[0]
|
||||
const targetRepo = bumpType === 'nightly' ? 'nightlies' : 'electron'
|
||||
|
||||
require('colors')
|
||||
const pass = '\u2713'.green
|
||||
const fail = '\u2717'.red
|
||||
const pass = '✓'.green
|
||||
const fail = '✗'.red
|
||||
|
||||
if (!bumpType && !args.notesOnly) {
|
||||
console.log(`Usage: prepare-release [stable | beta | nightly]` +
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
const pass = '\u2713'.green
|
||||
const fail = '\u2717'.red
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
string: ['tag', 'releaseID'],
|
||||
default: { releaseID: '' }
|
||||
})
|
||||
const path = require('path')
|
||||
const { execSync } = require('child_process')
|
||||
const { GitProcess } = require('dugite')
|
||||
const { getCurrentBranch, ELECTRON_DIR } = require('../lib/utils.js')
|
||||
@@ -16,7 +14,9 @@ const octokit = require('@octokit/rest')({
|
||||
auth: process.env.ELECTRON_GITHUB_TOKEN
|
||||
})
|
||||
|
||||
const path = require('path')
|
||||
require('colors')
|
||||
const pass = '✓'.green
|
||||
const fail = '✗'.red
|
||||
|
||||
function getLastBumpCommit (tag) {
|
||||
const data = execSync(`git log -n1 --grep "Bump ${tag}" --format='format:{"hash": "%H", "message": "%s"}'`).toString()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
boolean: [
|
||||
'validateRelease',
|
||||
@@ -17,13 +17,15 @@ const nugget = require('nugget')
|
||||
const got = require('got')
|
||||
const pkg = require('../../package.json')
|
||||
const pkgVersion = `v${pkg.version}`
|
||||
const pass = '\u2713'.green
|
||||
const path = require('path')
|
||||
const fail = '\u2717'.red
|
||||
const sumchecker = require('sumchecker')
|
||||
const temp = require('temp').track()
|
||||
const { URL } = require('url')
|
||||
|
||||
require('colors')
|
||||
const pass = '✓'.green
|
||||
const fail = '✗'.red
|
||||
|
||||
const { ELECTRON_DIR } = require('../lib/utils')
|
||||
|
||||
const octokit = require('@octokit/rest')({
|
||||
|
||||
@@ -7,6 +7,10 @@ const { hashElement } = require('folder-hash')
|
||||
const path = require('path')
|
||||
const unknownFlags = []
|
||||
|
||||
require('colors')
|
||||
const pass = '✓'.green
|
||||
const fail = '✗'.red
|
||||
|
||||
const args = require('minimist')(process.argv, {
|
||||
string: ['runners'],
|
||||
unknown: arg => unknownFlags.push(arg)
|
||||
@@ -28,14 +32,23 @@ const BASE = path.resolve(__dirname, '../..')
|
||||
const NPM_CMD = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
||||
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx'
|
||||
|
||||
const runners = new Map([
|
||||
['main', { description: 'Main process specs', run: runMainProcessElectronTests }],
|
||||
['remote', { description: 'Remote based specs', run: runRemoteBasedElectronTests }]
|
||||
])
|
||||
|
||||
const specHashPath = path.resolve(__dirname, '../spec/.hash')
|
||||
|
||||
let runnersToRun = null
|
||||
if (args.runners) {
|
||||
runnersToRun = args.runners.split(',')
|
||||
if (!runnersToRun.every(r => [...runners.keys()].includes(r))) {
|
||||
console.log(`${fail} ${runnersToRun} must be a subset of [${[...runners.keys()].join(' | ')}]`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Only running:', runnersToRun)
|
||||
} else {
|
||||
console.log('Will trigger all spec runners')
|
||||
console.log(`Triggering both ${[...runners.keys()].join(' and ')} runners`)
|
||||
}
|
||||
|
||||
async function main () {
|
||||
@@ -79,10 +92,6 @@ function saveSpecHash ([newSpecHash, newSpecInstallHash]) {
|
||||
|
||||
async function runElectronTests () {
|
||||
const errors = []
|
||||
const runners = new Map([
|
||||
['main', { description: 'Main process specs', run: runMainProcessElectronTests }],
|
||||
['remote', { description: 'Remote based specs', run: runRemoteBasedElectronTests }]
|
||||
])
|
||||
|
||||
const testResultsDir = process.env.ELECTRON_TEST_RESULTS_DIR
|
||||
for (const [runnerId, { description, run }] of runners) {
|
||||
@@ -106,7 +115,8 @@ async function runElectronTests () {
|
||||
console.error('\n\nRunner Failed:', err[0])
|
||||
console.error(err[1])
|
||||
}
|
||||
throw new Error('Electron test runners have failed')
|
||||
console.log(`${fail} Electron test runners have failed`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,21 +134,30 @@ async function runRemoteBasedElectronTests () {
|
||||
})
|
||||
if (status !== 0) {
|
||||
const textStatus = process.platform === 'win32' ? `0x${status.toString(16)}` : status.toString()
|
||||
throw new Error(`Electron tests failed with code ${textStatus}.`)
|
||||
console.log(`${fail} Electron tests failed with code ${textStatus}.`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`${pass} Electron remote process tests passed.`)
|
||||
}
|
||||
|
||||
async function runMainProcessElectronTests () {
|
||||
const exe = path.resolve(BASE, utils.getElectronExec())
|
||||
let exe = path.resolve(BASE, utils.getElectronExec())
|
||||
const runnerArgs = ['electron/spec-main', ...unknownArgs.slice(2)]
|
||||
if (process.platform === 'linux') {
|
||||
runnerArgs.unshift(path.resolve(__dirname, 'dbus_mock.py'), exe)
|
||||
exe = 'python'
|
||||
}
|
||||
|
||||
const { status } = childProcess.spawnSync(exe, ['electron/spec-main', ...unknownArgs.slice(2)], {
|
||||
const { status } = childProcess.spawnSync(exe, runnerArgs, {
|
||||
cwd: path.resolve(__dirname, '../..'),
|
||||
stdio: 'inherit'
|
||||
})
|
||||
if (status !== 0) {
|
||||
const textStatus = process.platform === 'win32' ? `0x${status.toString(16)}` : status.toString()
|
||||
throw new Error(`Electron tests failed with code ${textStatus}.`)
|
||||
console.log(`${fail} Electron tests failed with code ${textStatus}.`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`${pass} Electron main process tests passed.`)
|
||||
}
|
||||
|
||||
async function installSpecModules () {
|
||||
@@ -153,7 +172,8 @@ async function installSpecModules () {
|
||||
stdio: 'inherit'
|
||||
})
|
||||
if (status !== 0 && !process.env.IGNORE_YARN_INSTALL_ERROR) {
|
||||
throw new Error('Failed to yarn install in the spec folder')
|
||||
console.log(`${fail} Failed to yarn install in the spec folder`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from lib.util import execute, get_out_dir
|
||||
|
||||
LINUX_BINARIES_TO_STRIP = [
|
||||
'electron',
|
||||
'chrome_sandbox',
|
||||
'libffmpeg.so',
|
||||
'libGLESv2.so',
|
||||
'libEGL.so',
|
||||
|
||||
@@ -42,12 +42,13 @@ bool UvTaskRunner::PostNonNestableDelayedTask(const base::Location& from_here,
|
||||
|
||||
// static
|
||||
void UvTaskRunner::OnTimeout(uv_timer_t* timer) {
|
||||
UvTaskRunner* self = static_cast<UvTaskRunner*>(timer->data);
|
||||
if (!base::Contains(self->tasks_, timer))
|
||||
auto& tasks = static_cast<UvTaskRunner*>(timer->data)->tasks_;
|
||||
const auto iter = tasks.find(timer);
|
||||
if (iter == std::end(tasks))
|
||||
return;
|
||||
|
||||
std::move(self->tasks_[timer]).Run();
|
||||
self->tasks_.erase(timer);
|
||||
std::move(iter->second).Run();
|
||||
tasks.erase(iter);
|
||||
uv_timer_stop(timer);
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(timer), UvTaskRunner::OnClose);
|
||||
}
|
||||
|
||||
@@ -107,10 +107,9 @@ bool GlobalShortcut::Register(const ui::Accelerator& accelerator,
|
||||
}
|
||||
|
||||
void GlobalShortcut::Unregister(const ui::Accelerator& accelerator) {
|
||||
if (!base::Contains(accelerator_callback_map_, accelerator))
|
||||
if (accelerator_callback_map_.erase(accelerator) == 0)
|
||||
return;
|
||||
|
||||
accelerator_callback_map_.erase(accelerator);
|
||||
GlobalShortcutListener::GetInstance()->UnregisterAccelerator(accelerator,
|
||||
this);
|
||||
}
|
||||
|
||||
@@ -176,21 +176,15 @@ void ProtocolNS::RegisterURLLoaderFactories(
|
||||
ProtocolError ProtocolNS::RegisterProtocol(ProtocolType type,
|
||||
const std::string& scheme,
|
||||
const ProtocolHandler& handler) {
|
||||
ProtocolError error = ProtocolError::OK;
|
||||
if (!base::Contains(handlers_, scheme))
|
||||
handlers_[scheme] = std::make_pair(type, handler);
|
||||
else
|
||||
error = ProtocolError::REGISTERED;
|
||||
return error;
|
||||
const bool added = base::TryEmplace(handlers_, scheme, type, handler).second;
|
||||
return added ? ProtocolError::OK : ProtocolError::REGISTERED;
|
||||
}
|
||||
|
||||
void ProtocolNS::UnregisterProtocol(const std::string& scheme,
|
||||
mate::Arguments* args) {
|
||||
ProtocolError error = ProtocolError::OK;
|
||||
if (base::Contains(handlers_, scheme))
|
||||
handlers_.erase(scheme);
|
||||
else
|
||||
error = ProtocolError::NOT_REGISTERED;
|
||||
const bool removed = handlers_.erase(scheme) != 0;
|
||||
const auto error =
|
||||
removed ? ProtocolError::OK : ProtocolError::NOT_REGISTERED;
|
||||
HandleOptionalCallback(args, error);
|
||||
}
|
||||
|
||||
@@ -201,21 +195,16 @@ bool ProtocolNS::IsProtocolRegistered(const std::string& scheme) {
|
||||
ProtocolError ProtocolNS::InterceptProtocol(ProtocolType type,
|
||||
const std::string& scheme,
|
||||
const ProtocolHandler& handler) {
|
||||
ProtocolError error = ProtocolError::OK;
|
||||
if (!base::Contains(intercept_handlers_, scheme))
|
||||
intercept_handlers_[scheme] = std::make_pair(type, handler);
|
||||
else
|
||||
error = ProtocolError::INTERCEPTED;
|
||||
return error;
|
||||
const bool added =
|
||||
base::TryEmplace(intercept_handlers_, scheme, type, handler).second;
|
||||
return added ? ProtocolError::OK : ProtocolError::INTERCEPTED;
|
||||
}
|
||||
|
||||
void ProtocolNS::UninterceptProtocol(const std::string& scheme,
|
||||
mate::Arguments* args) {
|
||||
ProtocolError error = ProtocolError::OK;
|
||||
if (base::Contains(intercept_handlers_, scheme))
|
||||
intercept_handlers_.erase(scheme);
|
||||
else
|
||||
error = ProtocolError::NOT_INTERCEPTED;
|
||||
const bool removed = intercept_handlers_.erase(scheme) != 0;
|
||||
const auto error =
|
||||
removed ? ProtocolError::OK : ProtocolError::NOT_INTERCEPTED;
|
||||
HandleOptionalCallback(args, error);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "components/download/public/common/download_danger_type.h"
|
||||
#include "components/download/public/common/download_url_parameters.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "components/prefs/value_map_pref_store.h"
|
||||
#include "components/proxy_config/proxy_config_dictionary.h"
|
||||
@@ -489,8 +490,10 @@ void Session::AllowNTLMCredentialsForDomains(const std::string& domains) {
|
||||
|
||||
void Session::SetUserAgent(const std::string& user_agent,
|
||||
mate::Arguments* args) {
|
||||
CHECK(false)
|
||||
<< "TODO: This was disabled when the network service was turned on";
|
||||
browser_context_->SetUserAgent(user_agent);
|
||||
content::BrowserContext::GetDefaultStoragePartition(browser_context_.get())
|
||||
->GetNetworkContext()
|
||||
->SetUserAgent(user_agent);
|
||||
}
|
||||
|
||||
std::string Session::GetUserAgent() {
|
||||
@@ -510,6 +513,14 @@ v8::Local<v8::Promise> Session::GetBlobData(v8::Isolate* isolate,
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Session::DownloadURL(const GURL& url) {
|
||||
auto* download_manager =
|
||||
content::BrowserContext::GetDownloadManager(browser_context());
|
||||
auto download_params = std::make_unique<download::DownloadUrlParameters>(
|
||||
url, MISSING_TRAFFIC_ANNOTATION);
|
||||
download_manager->DownloadUrl(std::move(download_params));
|
||||
}
|
||||
|
||||
void Session::CreateInterruptedDownload(const mate::Dictionary& options) {
|
||||
int64_t offset = 0, length = 0;
|
||||
double start_time = 0.0;
|
||||
@@ -694,6 +705,7 @@ void Session::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("setUserAgent", &Session::SetUserAgent)
|
||||
.SetMethod("getUserAgent", &Session::GetUserAgent)
|
||||
.SetMethod("getBlobData", &Session::GetBlobData)
|
||||
.SetMethod("downloadURL", &Session::DownloadURL)
|
||||
.SetMethod("createInterruptedDownload",
|
||||
&Session::CreateInterruptedDownload)
|
||||
.SetMethod("setPreloads", &Session::SetPreloads)
|
||||
|
||||
@@ -79,6 +79,7 @@ class Session : public mate::TrackableObject<Session>,
|
||||
std::string GetUserAgent();
|
||||
v8::Local<v8::Promise> GetBlobData(v8::Isolate* isolate,
|
||||
const std::string& uuid);
|
||||
void DownloadURL(const GURL& url);
|
||||
void CreateInterruptedDownload(const mate::Dictionary& options);
|
||||
void SetPreloads(const std::vector<base::FilePath::StringType>& preloads);
|
||||
std::vector<base::FilePath::StringType> GetPreloads() const;
|
||||
|
||||
@@ -575,6 +575,14 @@ std::string TopLevelWindow::GetTitle() {
|
||||
return window_->GetTitle();
|
||||
}
|
||||
|
||||
void TopLevelWindow::SetAccessibleTitle(const std::string& title) {
|
||||
window_->SetAccessibleTitle(title);
|
||||
}
|
||||
|
||||
std::string TopLevelWindow::GetAccessibleTitle() {
|
||||
return window_->GetAccessibleTitle();
|
||||
}
|
||||
|
||||
void TopLevelWindow::FlashFrame(bool flash) {
|
||||
window_->FlashFrame(flash);
|
||||
}
|
||||
@@ -964,9 +972,6 @@ bool TopLevelWindow::HookWindowMessage(UINT message,
|
||||
}
|
||||
|
||||
void TopLevelWindow::UnhookWindowMessage(UINT message) {
|
||||
if (!base::Contains(messages_callback_map_, message))
|
||||
return;
|
||||
|
||||
messages_callback_map_.erase(message);
|
||||
}
|
||||
|
||||
@@ -1124,6 +1129,8 @@ void TopLevelWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("getPosition", &TopLevelWindow::GetPosition)
|
||||
.SetMethod("setTitle", &TopLevelWindow::SetTitle)
|
||||
.SetMethod("getTitle", &TopLevelWindow::GetTitle)
|
||||
.SetProperty("accessibleTitle", &TopLevelWindow::GetAccessibleTitle,
|
||||
&TopLevelWindow::SetAccessibleTitle)
|
||||
.SetMethod("flashFrame", &TopLevelWindow::FlashFrame)
|
||||
.SetMethod("setSkipTaskbar", &TopLevelWindow::SetSkipTaskbar)
|
||||
.SetMethod("setSimpleFullScreen", &TopLevelWindow::SetSimpleFullScreen)
|
||||
|
||||
@@ -145,6 +145,8 @@ class TopLevelWindow : public mate::TrackableObject<TopLevelWindow>,
|
||||
std::vector<int> GetPosition();
|
||||
void SetTitle(const std::string& title);
|
||||
std::string GetTitle();
|
||||
void SetAccessibleTitle(const std::string& title);
|
||||
std::string GetAccessibleTitle();
|
||||
void FlashFrame(bool flash);
|
||||
void SetSkipTaskbar(bool skip);
|
||||
void SetExcludedFromShownWindowsMenu(bool excluded);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "mojo/public/cpp/system/string_data_source.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "native_mate/object_template_builder.h"
|
||||
@@ -179,10 +178,7 @@ URLRequestNS::URLRequestNS(mate::Arguments* args) : weak_factory_(this) {
|
||||
session = Session::FromPartition(args->isolate(), "");
|
||||
}
|
||||
|
||||
auto* browser_context = session->browser_context();
|
||||
url_loader_factory_ =
|
||||
content::BrowserContext::GetDefaultStoragePartition(browser_context)
|
||||
->GetURLLoaderFactoryForBrowserProcess();
|
||||
url_loader_factory_ = session->browser_context()->GetURLLoaderFactory();
|
||||
|
||||
InitWith(args->isolate(), args->GetThis());
|
||||
}
|
||||
|
||||
@@ -100,6 +100,12 @@
|
||||
|
||||
#if !defined(OS_MACOSX)
|
||||
#include "ui/aura/window.h"
|
||||
#else
|
||||
#include "ui/base/cocoa/defaults_utils.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
#include "ui/views/linux_ui/linux_ui.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_WIN)
|
||||
@@ -458,6 +464,25 @@ void WebContents::InitWithSessionAndOptions(
|
||||
prefs->subpixel_rendering = params->subpixel_rendering;
|
||||
#endif
|
||||
|
||||
// Honor the system's cursor blink rate settings
|
||||
#if defined(OS_MACOSX)
|
||||
base::TimeDelta interval;
|
||||
if (ui::TextInsertionCaretBlinkPeriod(&interval))
|
||||
prefs->caret_blink_interval = interval;
|
||||
#elif defined(OS_LINUX)
|
||||
views::LinuxUI* linux_ui = views::LinuxUI::instance();
|
||||
if (linux_ui)
|
||||
prefs->caret_blink_interval = linux_ui->GetCursorBlinkInterval();
|
||||
#elif defined(OS_WIN)
|
||||
const auto system_msec = ::GetCaretBlinkTime();
|
||||
if (system_msec != 0) {
|
||||
prefs->caret_blink_interval =
|
||||
(system_msec == INFINITE)
|
||||
? base::TimeDelta()
|
||||
: base::TimeDelta::FromMilliseconds(system_msec);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Save the preferences in C++.
|
||||
new WebContentsPreferences(web_contents(), options);
|
||||
|
||||
@@ -2235,16 +2260,6 @@ v8::Local<v8::Value> WebContents::GetLastWebPreferences(
|
||||
return mate::ConvertToV8(isolate, *web_preferences->last_preference());
|
||||
}
|
||||
|
||||
bool WebContents::IsRemoteModuleEnabled() const {
|
||||
if (web_contents()->GetVisibleURL().SchemeIs("devtools")) {
|
||||
return false;
|
||||
}
|
||||
if (auto* web_preferences = WebContentsPreferences::From(web_contents())) {
|
||||
return web_preferences->IsRemoteModuleEnabled();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> WebContents::GetOwnerBrowserWindow() const {
|
||||
if (owner_window())
|
||||
return BrowserWindow::From(isolate(), owner_window());
|
||||
@@ -2453,7 +2468,6 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("_getPreloadPaths", &WebContents::GetPreloadPaths)
|
||||
.SetMethod("getWebPreferences", &WebContents::GetWebPreferences)
|
||||
.SetMethod("getLastWebPreferences", &WebContents::GetLastWebPreferences)
|
||||
.SetMethod("_isRemoteModuleEnabled", &WebContents::IsRemoteModuleEnabled)
|
||||
.SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow)
|
||||
.SetMethod("inspectServiceWorker", &WebContents::InspectServiceWorker)
|
||||
.SetMethod("inspectSharedWorker", &WebContents::InspectSharedWorker)
|
||||
|
||||
@@ -291,8 +291,6 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||
v8::Local<v8::Value> GetWebPreferences(v8::Isolate* isolate) const;
|
||||
v8::Local<v8::Value> GetLastWebPreferences(v8::Isolate* isolate) const;
|
||||
|
||||
bool IsRemoteModuleEnabled() const;
|
||||
|
||||
// Returns the owner window.
|
||||
v8::Local<v8::Value> GetOwnerBrowserWindow() const;
|
||||
|
||||
|
||||
@@ -396,10 +396,11 @@ template <typename... Args>
|
||||
void WebRequestNS::HandleSimpleEvent(SimpleEvent event,
|
||||
extensions::WebRequestInfo* request_info,
|
||||
Args... args) {
|
||||
if (!base::Contains(simple_listeners_, event))
|
||||
const auto iter = simple_listeners_.find(event);
|
||||
if (iter == std::end(simple_listeners_))
|
||||
return;
|
||||
|
||||
const auto& info = simple_listeners_[event];
|
||||
const auto& info = iter->second;
|
||||
if (!MatchesFilterCondition(request_info, info.url_patterns))
|
||||
return;
|
||||
|
||||
@@ -416,10 +417,11 @@ int WebRequestNS::HandleResponseEvent(ResponseEvent event,
|
||||
net::CompletionOnceCallback callback,
|
||||
Out out,
|
||||
Args... args) {
|
||||
if (!base::Contains(response_listeners_, event))
|
||||
const auto iter = response_listeners_.find(event);
|
||||
if (iter == std::end(response_listeners_))
|
||||
return net::OK;
|
||||
|
||||
const auto& info = response_listeners_[event];
|
||||
const auto& info = iter->second;
|
||||
if (!MatchesFilterCondition(request_info, info.url_patterns))
|
||||
return net::OK;
|
||||
|
||||
@@ -441,7 +443,8 @@ template <typename T>
|
||||
void WebRequestNS::OnListenerResult(uint64_t id,
|
||||
T out,
|
||||
v8::Local<v8::Value> response) {
|
||||
if (!base::Contains(callbacks_, id))
|
||||
const auto iter = callbacks_.find(id);
|
||||
if (iter == std::end(callbacks_))
|
||||
return;
|
||||
|
||||
int result = net::OK;
|
||||
@@ -461,7 +464,7 @@ void WebRequestNS::OnListenerResult(uint64_t id,
|
||||
// asynchronously, because it used to work on IO thread before NetworkService.
|
||||
base::SequencedTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, base::BindOnce(std::move(callbacks_[id]), result));
|
||||
callbacks_.erase(id);
|
||||
callbacks_.erase(iter);
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -85,6 +85,7 @@ class EventEmitter : public Wrappable<T> {
|
||||
base::Optional<electron::mojom::ElectronBrowser::MessageSyncCallback>
|
||||
callback,
|
||||
Args&&... args) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
v8::Locker locker(isolate());
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
v8::Local<v8::Object> wrapper = GetWrapper();
|
||||
|
||||
@@ -197,8 +197,9 @@ content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID(
|
||||
int process_id) {
|
||||
// If the process is a pending process, we should use the web contents
|
||||
// for the frame host passed into RegisterPendingProcess.
|
||||
if (base::Contains(pending_processes_, process_id))
|
||||
return pending_processes_[process_id];
|
||||
const auto iter = pending_processes_.find(process_id);
|
||||
if (iter != std::end(pending_processes_))
|
||||
return iter->second;
|
||||
|
||||
// Certain render process will be created with no associated render view,
|
||||
// for example: ServiceWorker.
|
||||
@@ -561,9 +562,6 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
|
||||
|
||||
content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
|
||||
if (web_contents) {
|
||||
if (web_contents->GetVisibleURL().SchemeIs("devtools")) {
|
||||
command_line->AppendSwitch(switches::kDisableRemoteModule);
|
||||
}
|
||||
auto* web_preferences = WebContentsPreferences::From(web_contents);
|
||||
if (web_preferences)
|
||||
web_preferences->AppendCommandLineSwitches(
|
||||
@@ -988,18 +986,11 @@ bool AtomBrowserClient::WillCreateURLLoaderFactory(
|
||||
mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver,
|
||||
network::mojom::TrustedURLLoaderHeaderClientPtrInfo* header_client,
|
||||
bool* bypass_redirect_checks) {
|
||||
content::WebContents* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(frame_host);
|
||||
// For service workers there might be no WebContents.
|
||||
if (!web_contents)
|
||||
return false;
|
||||
|
||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||
api::ProtocolNS* protocol = api::ProtocolNS::FromWrappedClass(
|
||||
isolate, web_contents->GetBrowserContext());
|
||||
api::ProtocolNS* protocol =
|
||||
api::ProtocolNS::FromWrappedClass(isolate, browser_context);
|
||||
DCHECK(protocol);
|
||||
auto web_request = api::WebRequestNS::FromOrCreate(
|
||||
isolate, web_contents->GetBrowserContext());
|
||||
auto web_request = api::WebRequestNS::FromOrCreate(isolate, browser_context);
|
||||
DCHECK(web_request.get());
|
||||
|
||||
auto proxied_receiver = std::move(*factory_receiver);
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "net/base/escape.h"
|
||||
#include "services/network/public/cpp/features.h"
|
||||
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
|
||||
#include "shell/browser/atom_blob_reader.h"
|
||||
#include "shell/browser/atom_browser_client.h"
|
||||
#include "shell/browser/atom_browser_main_parts.h"
|
||||
@@ -271,6 +272,43 @@ predictors::PreconnectManager* AtomBrowserContext::GetPreconnectManager() {
|
||||
return preconnect_manager_.get();
|
||||
}
|
||||
|
||||
scoped_refptr<network::SharedURLLoaderFactory>
|
||||
AtomBrowserContext::GetURLLoaderFactory() {
|
||||
if (url_loader_factory_)
|
||||
return url_loader_factory_;
|
||||
|
||||
network::mojom::URLLoaderFactoryPtr network_factory;
|
||||
mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_request =
|
||||
mojo::MakeRequest(&network_factory);
|
||||
|
||||
// Consult the embedder.
|
||||
network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client;
|
||||
static_cast<content::ContentBrowserClient*>(AtomBrowserClient::Get())
|
||||
->WillCreateURLLoaderFactory(
|
||||
this, nullptr, -1,
|
||||
content::ContentBrowserClient::URLLoaderFactoryType::kNavigation,
|
||||
url::Origin(), &factory_request, &header_client, nullptr);
|
||||
|
||||
network::mojom::URLLoaderFactoryParamsPtr params =
|
||||
network::mojom::URLLoaderFactoryParams::New();
|
||||
params->header_client = std::move(header_client);
|
||||
params->process_id = network::mojom::kBrowserProcessId;
|
||||
params->is_trusted = true;
|
||||
params->is_corb_enabled = false;
|
||||
// The tests of net module would fail if this setting is true, it seems that
|
||||
// the non-NetworkService implementation always has web security enabled.
|
||||
params->disable_web_security = false;
|
||||
|
||||
auto* storage_partition =
|
||||
content::BrowserContext::GetDefaultStoragePartition(this);
|
||||
storage_partition->GetNetworkContext()->CreateURLLoaderFactory(
|
||||
std::move(factory_request), std::move(params));
|
||||
url_loader_factory_ =
|
||||
base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
|
||||
std::move(network_factory));
|
||||
return url_loader_factory_;
|
||||
}
|
||||
|
||||
content::PushMessagingService* AtomBrowserContext::GetPushMessagingService() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "content/public/browser/resource_context.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "services/network/public/mojom/url_loader_factory.mojom.h"
|
||||
#include "shell/browser/media/media_device_id_salt.h"
|
||||
|
||||
class PrefRegistrySimple;
|
||||
class PrefService;
|
||||
class ValueMapPrefStore;
|
||||
|
||||
namespace network {
|
||||
class SharedURLLoaderFactory;
|
||||
}
|
||||
|
||||
namespace storage {
|
||||
class SpecialStoragePolicy;
|
||||
}
|
||||
@@ -87,8 +92,8 @@ class AtomBrowserContext
|
||||
int GetMaxCacheSize() const;
|
||||
AtomBlobReader* GetBlobReader();
|
||||
ResolveProxyHelper* GetResolveProxyHelper();
|
||||
|
||||
predictors::PreconnectManager* GetPreconnectManager();
|
||||
scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory();
|
||||
|
||||
// content::BrowserContext:
|
||||
base::FilePath GetPath() override;
|
||||
@@ -180,6 +185,9 @@ class AtomBrowserContext
|
||||
extensions::AtomExtensionSystem* extension_system_;
|
||||
#endif
|
||||
|
||||
// Shared URLLoaderFactory.
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
|
||||
|
||||
base::WeakPtrFactory<AtomBrowserContext> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomBrowserContext);
|
||||
|
||||
@@ -460,10 +460,6 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() {
|
||||
if (command_line->HasSwitch(switches::kRemoteDebuggingPort))
|
||||
DevToolsManagerDelegate::StartHttpHandler();
|
||||
|
||||
#if defined(USE_X11)
|
||||
libgtkui::GtkInitFromCommandLine(*base::CommandLine::ForCurrentProcess());
|
||||
#endif
|
||||
|
||||
#if !defined(OS_MACOSX)
|
||||
// The corresponding call in macOS is in AtomApplicationDelegate.
|
||||
Browser::Get()->WillFinishLaunching();
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/resource_request_info.h"
|
||||
#include "content/public/common/user_agent.h"
|
||||
#include "extensions/browser/api/extensions_api_client.h"
|
||||
#include "extensions/browser/component_extension_resource_manager.h"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/window_list.h"
|
||||
@@ -582,6 +584,22 @@ const views::Widget* NativeWindow::GetWidget() const {
|
||||
return widget();
|
||||
}
|
||||
|
||||
base::string16 NativeWindow::GetAccessibleWindowTitle() const {
|
||||
if (accessible_title_.empty()) {
|
||||
return views::WidgetDelegate::GetAccessibleWindowTitle();
|
||||
}
|
||||
|
||||
return accessible_title_;
|
||||
}
|
||||
|
||||
void NativeWindow::SetAccessibleTitle(const std::string& title) {
|
||||
accessible_title_ = base::UTF8ToUTF16(title);
|
||||
}
|
||||
|
||||
std::string NativeWindow::GetAccessibleTitle() {
|
||||
return base::UTF16ToUTF8(accessible_title_);
|
||||
}
|
||||
|
||||
// static
|
||||
void NativeWindowRelay::CreateForWebContents(
|
||||
content::WebContents* web_contents,
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "base/supports_user_data.h"
|
||||
#include "base/values.h"
|
||||
#include "content/public/browser/desktop_media_id.h"
|
||||
#include "content/public/browser/web_contents_user_data.h"
|
||||
#include "extensions/browser/app_window/size_constraints.h"
|
||||
@@ -134,6 +136,11 @@ class NativeWindow : public base::SupportsUserData,
|
||||
virtual void Invalidate() = 0;
|
||||
virtual void SetTitle(const std::string& title) = 0;
|
||||
virtual std::string GetTitle() = 0;
|
||||
|
||||
// Ability to augment the window title for the screen readers.
|
||||
void SetAccessibleTitle(const std::string& title);
|
||||
std::string GetAccessibleTitle();
|
||||
|
||||
virtual void FlashFrame(bool flash) = 0;
|
||||
virtual void SetSkipTaskbar(bool skip) = 0;
|
||||
virtual void SetExcludedFromShownWindowsMenu(bool excluded) = 0;
|
||||
@@ -301,6 +308,7 @@ class NativeWindow : public base::SupportsUserData,
|
||||
// views::WidgetDelegate:
|
||||
views::Widget* GetWidget() override;
|
||||
const views::Widget* GetWidget() const override;
|
||||
base::string16 GetAccessibleWindowTitle() const override;
|
||||
|
||||
void set_content_view(views::View* view) { content_view_ = view; }
|
||||
|
||||
@@ -355,6 +363,9 @@ class NativeWindow : public base::SupportsUserData,
|
||||
// Observers of this window.
|
||||
base::ObserverList<NativeWindowObserver> observers_;
|
||||
|
||||
// Accessible title.
|
||||
base::string16 accessible_title_;
|
||||
|
||||
base::WeakPtrFactory<NativeWindow> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeWindow);
|
||||
|
||||
@@ -345,24 +345,35 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
|
||||
return false;
|
||||
}
|
||||
case WM_GETMINMAXINFO: {
|
||||
WINDOWPLACEMENT wp;
|
||||
wp.length = sizeof(WINDOWPLACEMENT);
|
||||
// We need to handle GETMINMAXINFO ourselves because chromium tries to
|
||||
// get the scale factor of the window during it's version of this handler
|
||||
// based on the window position, which is invalid at this point. The
|
||||
// previous method of calling SetWindowPlacement fixed the window
|
||||
// position for the scale factor calculation but broke other things.
|
||||
MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(l_param);
|
||||
|
||||
// We do this to work around a Windows bug, where the minimized Window
|
||||
// would report that the closest display to it is not the one that it was
|
||||
// previously on (but the leftmost one instead). We restore the position
|
||||
// of the window during the restore operation, this way chromium can
|
||||
// use the proper display to calculate the scale factor to use.
|
||||
if (!last_normal_placement_bounds_.IsEmpty() &&
|
||||
GetWindowPlacement(GetAcceleratedWidget(), &wp)) {
|
||||
last_normal_placement_bounds_.set_size(gfx::Size(0, 0));
|
||||
wp.rcNormalPosition = last_normal_placement_bounds_.ToRECT();
|
||||
SetWindowPlacement(GetAcceleratedWidget(), &wp);
|
||||
display::Display display =
|
||||
display::Screen::GetScreen()->GetDisplayNearestPoint(
|
||||
last_normal_placement_bounds_.origin());
|
||||
|
||||
last_normal_placement_bounds_ = gfx::Rect();
|
||||
gfx::Size min_size = gfx::ScaleToCeiledSize(
|
||||
widget()->GetMinimumSize(), display.device_scale_factor());
|
||||
gfx::Size max_size = gfx::ScaleToCeiledSize(
|
||||
widget()->GetMaximumSize(), display.device_scale_factor());
|
||||
|
||||
info->ptMinTrackSize.x = min_size.width();
|
||||
info->ptMinTrackSize.y = min_size.height();
|
||||
if (max_size.width() || max_size.height()) {
|
||||
if (!max_size.width())
|
||||
max_size.set_width(GetSystemMetrics(SM_CXMAXTRACK));
|
||||
if (!max_size.height())
|
||||
max_size.set_height(GetSystemMetrics(SM_CYMAXTRACK));
|
||||
info->ptMaxTrackSize.x = max_size.width();
|
||||
info->ptMaxTrackSize.y = max_size.height();
|
||||
}
|
||||
|
||||
return false;
|
||||
*result = 1;
|
||||
return true;
|
||||
}
|
||||
case WM_NCCALCSIZE: {
|
||||
if (!has_frame() && w_param == TRUE) {
|
||||
@@ -378,8 +389,21 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
|
||||
// https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/
|
||||
DefWindowProcW(GetAcceleratedWidget(), WM_NCCALCSIZE, w_param, l_param);
|
||||
|
||||
// When fullscreen the window has no border
|
||||
int border = 0;
|
||||
if (!IsFullscreen()) {
|
||||
// When not fullscreen calculate the border size
|
||||
border = GetSystemMetrics(SM_CXFRAME) +
|
||||
GetSystemMetrics(SM_CXPADDEDBORDER);
|
||||
if (!thick_frame_) {
|
||||
border -= GetSystemMetrics(SM_CXBORDER);
|
||||
}
|
||||
}
|
||||
|
||||
if (last_window_state_ == ui::SHOW_STATE_MAXIMIZED) {
|
||||
params->rgrc[0].top = 0;
|
||||
// Position the top of the frame offset from where windows thinks by
|
||||
// exactly the border amount. When fullscreen this is 0.
|
||||
params->rgrc[0].top = PROPOSED.top + border;
|
||||
} else {
|
||||
params->rgrc[0] = PROPOSED;
|
||||
params->rgrc[1] = BEFORE;
|
||||
|
||||
@@ -396,12 +396,9 @@ void AtomURLLoaderFactory::StartLoadingHttp(
|
||||
}
|
||||
}
|
||||
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
|
||||
content::BrowserContext::GetDefaultStoragePartition(browser_context.get())
|
||||
->GetURLLoaderFactoryForBrowserProcess();
|
||||
new URLPipeLoader(
|
||||
url_loader_factory, std::move(request), std::move(loader),
|
||||
std::move(client),
|
||||
browser_context->GetURLLoaderFactory(), std::move(request),
|
||||
std::move(loader), std::move(client),
|
||||
static_cast<net::NetworkTrafficAnnotationTag>(traffic_annotation),
|
||||
std::move(upload_data));
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 8,0,0,20190827
|
||||
PRODUCTVERSION 8,0,0,20190827
|
||||
FILEVERSION 8,0,0,20190903
|
||||
PRODUCTVERSION 8,0,0,20190903
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -89,8 +89,9 @@ void GenerateAcceleratorTable(AcceleratorTable* table,
|
||||
|
||||
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
|
||||
const ui::Accelerator& accelerator) {
|
||||
if (base::Contains(*table, accelerator)) {
|
||||
const accelerator_util::MenuItem& item = (*table)[accelerator];
|
||||
const auto iter = table->find(accelerator);
|
||||
if (iter != std::end(*table)) {
|
||||
const accelerator_util::MenuItem& item = iter->second;
|
||||
if (item.model->IsEnabledAt(item.position)) {
|
||||
const auto event_flags =
|
||||
accelerator.MaskOutKeyEventFlags(accelerator.modifiers());
|
||||
|
||||
@@ -25,11 +25,9 @@ void AtomMenuModel::SetToolTip(int index, const base::string16& toolTip) {
|
||||
}
|
||||
|
||||
base::string16 AtomMenuModel::GetToolTipAt(int index) {
|
||||
int command_id = GetCommandIdAt(index);
|
||||
if (base::Contains(toolTips_, command_id))
|
||||
return toolTips_[command_id];
|
||||
else
|
||||
return base::string16();
|
||||
const int command_id = GetCommandIdAt(index);
|
||||
const auto iter = toolTips_.find(command_id);
|
||||
return iter == std::end(toolTips_) ? base::string16() : iter->second;
|
||||
}
|
||||
|
||||
void AtomMenuModel::SetRole(int index, const base::string16& role) {
|
||||
@@ -38,11 +36,9 @@ void AtomMenuModel::SetRole(int index, const base::string16& role) {
|
||||
}
|
||||
|
||||
base::string16 AtomMenuModel::GetRoleAt(int index) {
|
||||
int command_id = GetCommandIdAt(index);
|
||||
if (base::Contains(roles_, command_id))
|
||||
return roles_[command_id];
|
||||
else
|
||||
return base::string16();
|
||||
const int command_id = GetCommandIdAt(index);
|
||||
const auto iter = roles_.find(command_id);
|
||||
return iter == std::end(roles_) ? base::string16() : iter->second;
|
||||
}
|
||||
|
||||
bool AtomMenuModel::GetAcceleratorAtWithParams(
|
||||
|
||||
@@ -270,12 +270,6 @@ void MenuBar::OnMenuButtonClicked(views::Button* source,
|
||||
if (!window_->HasFocus())
|
||||
window_->RequestFocus();
|
||||
|
||||
// This ensures that if you focus the menubar by clicking on an item, you can
|
||||
// still use the arrow keys to move around
|
||||
if (GetPaneFocusTraversable() == nullptr) {
|
||||
SetPaneFocus(source);
|
||||
}
|
||||
|
||||
int id = source->tag();
|
||||
AtomMenuModel::ItemType type = menu_model_->GetTypeAt(id);
|
||||
if (type != AtomMenuModel::TYPE_SUBMENU) {
|
||||
|
||||
@@ -199,8 +199,9 @@ bool TaskbarHost::SetThumbnailToolTip(HWND window, const std::string& tooltip) {
|
||||
}
|
||||
|
||||
bool TaskbarHost::HandleThumbarButtonEvent(int button_id) {
|
||||
if (base::Contains(callback_map_, button_id)) {
|
||||
auto callback = callback_map_[button_id];
|
||||
const auto iter = callback_map_.find(button_id);
|
||||
if (iter != std::end(callback_map_)) {
|
||||
auto callback = iter->second;
|
||||
if (!callback.is_null())
|
||||
callback.Run();
|
||||
return true;
|
||||
|
||||
@@ -124,7 +124,6 @@ WebContentsPreferences::WebContentsPreferences(
|
||||
SetDefaultBoolIfUndefined(options::kWebviewTag, false);
|
||||
SetDefaultBoolIfUndefined(options::kSandbox, false);
|
||||
SetDefaultBoolIfUndefined(options::kNativeWindowOpen, false);
|
||||
SetDefaultBoolIfUndefined(options::kEnableRemoteModule, true);
|
||||
SetDefaultBoolIfUndefined(options::kContextIsolation, false);
|
||||
SetDefaultBoolIfUndefined(options::kJavaScript, true);
|
||||
SetDefaultBoolIfUndefined(options::kImages, true);
|
||||
@@ -171,6 +170,8 @@ WebContentsPreferences::~WebContentsPreferences() {
|
||||
}
|
||||
|
||||
void WebContentsPreferences::SetDefaults() {
|
||||
SetDefaultBoolIfUndefined(options::kEnableRemoteModule, true);
|
||||
|
||||
if (IsEnabled(options::kSandbox)) {
|
||||
SetBool(options::kNativeWindowOpen, true);
|
||||
}
|
||||
@@ -220,10 +221,6 @@ bool WebContentsPreferences::GetPreference(base::StringPiece name,
|
||||
return GetAsString(&preference_, name, value);
|
||||
}
|
||||
|
||||
bool WebContentsPreferences::IsRemoteModuleEnabled() const {
|
||||
return IsEnabled(options::kEnableRemoteModule, true);
|
||||
}
|
||||
|
||||
bool WebContentsPreferences::GetPreloadPath(
|
||||
base::FilePath::StringType* path) const {
|
||||
DCHECK(path);
|
||||
@@ -327,8 +324,8 @@ void WebContentsPreferences::AppendCommandLineSwitches(
|
||||
}
|
||||
|
||||
// Whether to enable the remote module
|
||||
if (!IsRemoteModuleEnabled())
|
||||
command_line->AppendSwitch(switches::kDisableRemoteModule);
|
||||
if (IsEnabled(options::kEnableRemoteModule))
|
||||
command_line->AppendSwitch(switches::kEnableRemoteModule);
|
||||
|
||||
// Run Electron APIs and preload script in isolated world
|
||||
if (IsEnabled(options::kContextIsolation))
|
||||
|
||||
@@ -58,9 +58,6 @@ class WebContentsPreferences
|
||||
// Return true if the particular preference value exists.
|
||||
bool GetPreference(base::StringPiece name, std::string* value) const;
|
||||
|
||||
// Whether to enable the remote module
|
||||
bool IsRemoteModuleEnabled() const;
|
||||
|
||||
// Returns the preload script path.
|
||||
bool GetPreloadPath(base::FilePath::StringType* path) const;
|
||||
|
||||
|
||||
@@ -28,11 +28,9 @@ void WebViewManager::AddGuest(int guest_instance_id,
|
||||
}
|
||||
|
||||
void WebViewManager::RemoveGuest(int guest_instance_id) {
|
||||
if (!base::Contains(web_contents_embedder_map_, guest_instance_id))
|
||||
if (web_contents_embedder_map_.erase(guest_instance_id) == 0)
|
||||
return;
|
||||
|
||||
web_contents_embedder_map_.erase(guest_instance_id);
|
||||
|
||||
// Remove the record of element in embedder too.
|
||||
for (const auto& element : element_instance_id_to_guest_map_)
|
||||
if (element.second == guest_instance_id) {
|
||||
@@ -42,24 +40,24 @@ void WebViewManager::RemoveGuest(int guest_instance_id) {
|
||||
}
|
||||
|
||||
content::WebContents* WebViewManager::GetEmbedder(int guest_instance_id) {
|
||||
if (base::Contains(web_contents_embedder_map_, guest_instance_id))
|
||||
return web_contents_embedder_map_[guest_instance_id].embedder;
|
||||
else
|
||||
return nullptr;
|
||||
const auto iter = web_contents_embedder_map_.find(guest_instance_id);
|
||||
return iter == std::end(web_contents_embedder_map_) ? nullptr
|
||||
: iter->second.embedder;
|
||||
}
|
||||
|
||||
content::WebContents* WebViewManager::GetGuestByInstanceID(
|
||||
int owner_process_id,
|
||||
int element_instance_id) {
|
||||
ElementInstanceKey key(owner_process_id, element_instance_id);
|
||||
if (!base::Contains(element_instance_id_to_guest_map_, key))
|
||||
const ElementInstanceKey key(owner_process_id, element_instance_id);
|
||||
const auto guest_iter = element_instance_id_to_guest_map_.find(key);
|
||||
if (guest_iter == std::end(element_instance_id_to_guest_map_))
|
||||
return nullptr;
|
||||
|
||||
int guest_instance_id = element_instance_id_to_guest_map_[key];
|
||||
if (base::Contains(web_contents_embedder_map_, guest_instance_id))
|
||||
return web_contents_embedder_map_[guest_instance_id].web_contents;
|
||||
else
|
||||
return nullptr;
|
||||
const int guest_instance_id = guest_iter->second;
|
||||
const auto iter = web_contents_embedder_map_.find(guest_instance_id);
|
||||
return iter == std::end(web_contents_embedder_map_)
|
||||
? nullptr
|
||||
: iter->second.web_contents;
|
||||
}
|
||||
|
||||
bool WebViewManager::ForEachGuest(content::WebContents* embedder_web_contents,
|
||||
|
||||
@@ -65,16 +65,27 @@ void RemoteObjectFreer::RunDestructor() {
|
||||
if (!render_frame)
|
||||
return;
|
||||
|
||||
// Reset our local ref count in case we are in a GC race condition
|
||||
// and will get more references in an inbound IPC message
|
||||
int ref_count = 0;
|
||||
const auto objects_it = ref_mapper_.find(context_id_);
|
||||
if (objects_it != std::end(ref_mapper_)) {
|
||||
auto& objects = objects_it->second;
|
||||
const auto ref_it = objects.find(object_id_);
|
||||
if (ref_it != std::end(objects)) {
|
||||
ref_count = ref_it->second;
|
||||
objects.erase(ref_it);
|
||||
}
|
||||
if (objects.empty())
|
||||
ref_mapper_.erase(objects_it);
|
||||
}
|
||||
|
||||
auto* channel = "ELECTRON_BROWSER_DEREFERENCE";
|
||||
|
||||
base::ListValue args;
|
||||
args.AppendString(context_id_);
|
||||
args.AppendInteger(object_id_);
|
||||
args.AppendInteger(ref_mapper_[context_id_][object_id_]);
|
||||
// Reset our local ref count in case we are in a GC race condition and will
|
||||
// get more references in an inbound IPC message
|
||||
ref_mapper_[context_id_].erase(object_id_);
|
||||
if (ref_mapper_[context_id_].empty())
|
||||
ref_mapper_.erase(context_id_);
|
||||
args.AppendInteger(ref_count);
|
||||
|
||||
mojom::ElectronBrowserAssociatedPtr electron_ptr;
|
||||
render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
|
||||
@@ -30,14 +30,22 @@ const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar");
|
||||
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
|
||||
if (!g_archive_map_tls.Pointer()->Get())
|
||||
g_archive_map_tls.Pointer()->Set(new ArchiveMap);
|
||||
ArchiveMap& archive_map = *g_archive_map_tls.Pointer()->Get();
|
||||
if (!base::Contains(archive_map, path)) {
|
||||
std::shared_ptr<Archive> archive(new Archive(path));
|
||||
if (!archive->Init())
|
||||
return nullptr;
|
||||
archive_map[path] = archive;
|
||||
ArchiveMap& map = *g_archive_map_tls.Pointer()->Get();
|
||||
|
||||
// if we have it, return it
|
||||
const auto lower = map.lower_bound(path);
|
||||
if (lower != std::end(map) && !map.key_comp()(path, lower->first))
|
||||
return lower->second;
|
||||
|
||||
// if we can create it, return it
|
||||
auto archive = std::make_shared<Archive>(path);
|
||||
if (archive->Init()) {
|
||||
base::TryEmplace(map, lower, path, archive);
|
||||
return archive;
|
||||
}
|
||||
return archive_map[path];
|
||||
|
||||
// didn't have it, couldn't create it
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ClearArchives() {
|
||||
|
||||
@@ -224,7 +224,7 @@ const char kBackgroundColor[] = "background-color";
|
||||
const char kPreloadScript[] = "preload";
|
||||
const char kPreloadScripts[] = "preload-scripts";
|
||||
const char kNodeIntegration[] = "node-integration";
|
||||
const char kDisableRemoteModule[] = "disable-remote-module";
|
||||
const char kEnableRemoteModule[] = "enable-remote-module";
|
||||
const char kContextIsolation[] = "context-isolation";
|
||||
const char kGuestInstanceID[] = "guest-instance-id";
|
||||
const char kOpenerID[] = "opener-id";
|
||||
|
||||
@@ -107,7 +107,7 @@ extern const char kBackgroundColor[];
|
||||
extern const char kPreloadScript[];
|
||||
extern const char kPreloadScripts[];
|
||||
extern const char kNodeIntegration[];
|
||||
extern const char kDisableRemoteModule[];
|
||||
extern const char kEnableRemoteModule[];
|
||||
extern const char kContextIsolation[];
|
||||
extern const char kGuestInstanceID[];
|
||||
extern const char kOpenerID[];
|
||||
|
||||
@@ -153,14 +153,12 @@ void AtomRendererClient::DidCreateScriptContext(
|
||||
void AtomRendererClient::WillReleaseScriptContext(
|
||||
v8::Handle<v8::Context> context,
|
||||
content::RenderFrame* render_frame) {
|
||||
if (injected_frames_.find(render_frame) == injected_frames_.end())
|
||||
if (injected_frames_.erase(render_frame) == 0)
|
||||
return;
|
||||
injected_frames_.erase(render_frame);
|
||||
|
||||
node::Environment* env = node::Environment::GetCurrent(context);
|
||||
if (environments_.find(env) == environments_.end())
|
||||
if (environments_.erase(env) == 0)
|
||||
return;
|
||||
environments_.erase(env);
|
||||
|
||||
mate::EmitEvent(env->isolate(), env->process_object(), "exit");
|
||||
|
||||
|
||||
@@ -294,9 +294,8 @@ void AtomSandboxedRendererClient::SetupExtensionWorldOverrides(
|
||||
void AtomSandboxedRendererClient::WillReleaseScriptContext(
|
||||
v8::Handle<v8::Context> context,
|
||||
content::RenderFrame* render_frame) {
|
||||
if (injected_frames_.find(render_frame) == injected_frames_.end())
|
||||
if (injected_frames_.erase(render_frame) == 0)
|
||||
return;
|
||||
injected_frames_.erase(render_frame);
|
||||
|
||||
auto* isolate = context->GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
@@ -119,7 +119,7 @@ void RendererClientBase::DidCreateScriptContext(
|
||||
|
||||
auto* command_line = base::CommandLine::ForCurrentProcess();
|
||||
bool enableRemoteModule =
|
||||
!command_line->HasSwitch(switches::kDisableRemoteModule);
|
||||
command_line->HasSwitch(switches::kEnableRemoteModule);
|
||||
global.SetHidden("enableRemoteModule", enableRemoteModule);
|
||||
}
|
||||
|
||||
|
||||
11
spec-main/ambient.d.ts
vendored
11
spec-main/ambient.d.ts
vendored
@@ -1,4 +1,5 @@
|
||||
declare var isCI: boolean;
|
||||
declare var standardScheme: string;
|
||||
|
||||
declare namespace Electron {
|
||||
interface Menu {
|
||||
@@ -15,6 +16,11 @@ declare namespace Electron {
|
||||
|
||||
interface WebContents {
|
||||
getOwnerBrowserWindow(): BrowserWindow;
|
||||
getWebPreferences(): any;
|
||||
}
|
||||
|
||||
interface Session {
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
// Experimental views API
|
||||
@@ -23,4 +29,9 @@ declare namespace Electron {
|
||||
setContentView(view: View): void
|
||||
}
|
||||
class View {}
|
||||
class WebContentsView {
|
||||
constructor(webContents: WebContents)
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'dbus-native';
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
'use strict'
|
||||
import { expect } from 'chai'
|
||||
import * as path from 'path'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const path = require('path')
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { ipcMain, BrowserWindow } = remote
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
import { ipcMain, BrowserWindow, WebPreferences } from 'electron'
|
||||
import { closeWindow } from './window-helpers'
|
||||
|
||||
describe('BrowserWindow with affinity module', () => {
|
||||
const fixtures = path.resolve(__dirname, 'fixtures')
|
||||
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures')
|
||||
const myAffinityName = 'myAffinity'
|
||||
const myAffinityNameUpper = 'MYAFFINITY'
|
||||
const anotherAffinityName = 'anotherAffinity'
|
||||
|
||||
async function createWindowWithWebPrefs (webPrefs) {
|
||||
async function createWindowWithWebPrefs (webPrefs: WebPreferences) {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
width: 400,
|
||||
@@ -24,16 +21,16 @@ describe('BrowserWindow with affinity module', () => {
|
||||
return w
|
||||
}
|
||||
|
||||
function testAffinityProcessIds (name, webPreferences = {}) {
|
||||
function testAffinityProcessIds (name: string, webPreferences: WebPreferences = {}) {
|
||||
describe(name, () => {
|
||||
let mAffinityWindow
|
||||
let mAffinityWindow: BrowserWindow
|
||||
before(async () => {
|
||||
mAffinityWindow = await createWindowWithWebPrefs({ affinity: myAffinityName, ...webPreferences })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await closeWindow(mAffinityWindow, { assertSingleWindow: false })
|
||||
mAffinityWindow = null
|
||||
await closeWindow(mAffinityWindow, { assertNotWindows: false })
|
||||
mAffinityWindow = null as unknown as BrowserWindow
|
||||
})
|
||||
|
||||
it('should have a different process id than a default window', async () => {
|
||||
@@ -42,7 +39,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
const wcID = w.webContents.getOSProcessId()
|
||||
|
||||
expect(affinityID).to.not.equal(wcID, 'Should have different OS process IDs')
|
||||
await closeWindow(w, { assertSingleWindow: false })
|
||||
await closeWindow(w, { assertNotWindows: false })
|
||||
})
|
||||
|
||||
it(`should have a different process id than a window with a different affinity '${anotherAffinityName}'`, async () => {
|
||||
@@ -51,7 +48,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
const wcID = w.webContents.getOSProcessId()
|
||||
|
||||
expect(affinityID).to.not.equal(wcID, 'Should have different OS process IDs')
|
||||
await closeWindow(w, { assertSingleWindow: false })
|
||||
await closeWindow(w, { assertNotWindows: false })
|
||||
})
|
||||
|
||||
it(`should have the same OS process id than a window with the same affinity '${myAffinityName}'`, async () => {
|
||||
@@ -60,7 +57,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
const wcID = w.webContents.getOSProcessId()
|
||||
|
||||
expect(affinityID).to.equal(wcID, 'Should have the same OS process ID')
|
||||
await closeWindow(w, { assertSingleWindow: false })
|
||||
await closeWindow(w, { assertNotWindows: false })
|
||||
})
|
||||
|
||||
it(`should have the same OS process id than a window with an equivalent affinity '${myAffinityNameUpper}' (case insensitive)`, async () => {
|
||||
@@ -69,7 +66,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
const wcID = w.webContents.getOSProcessId()
|
||||
|
||||
expect(affinityID).to.equal(wcID, 'Should have the same OS process ID')
|
||||
await closeWindow(w, { assertSingleWindow: false })
|
||||
await closeWindow(w, { assertNotWindows: false })
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -83,7 +80,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
const affinityWithNodeTrue = 'affinityWithNodeTrue'
|
||||
const affinityWithNodeFalse = 'affinityWithNodeFalse'
|
||||
|
||||
function testNodeIntegration (present) {
|
||||
function testNodeIntegration (present: boolean) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcMain.once('answer', (event, typeofProcess, typeofBuffer) => {
|
||||
if (present) {
|
||||
@@ -107,7 +104,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
nodeIntegration: false
|
||||
})
|
||||
])
|
||||
await closeWindow(w, { assertSingleWindow: false })
|
||||
await closeWindow(w, { assertNotWindows: false })
|
||||
})
|
||||
it('disables node integration when first window is false', async () => {
|
||||
const [, w1] = await Promise.all([
|
||||
@@ -127,8 +124,8 @@ describe('BrowserWindow with affinity module', () => {
|
||||
})
|
||||
])
|
||||
await Promise.all([
|
||||
closeWindow(w1, { assertSingleWindow: false }),
|
||||
closeWindow(w2, { assertSingleWindow: false })
|
||||
closeWindow(w1, { assertNotWindows: false }),
|
||||
closeWindow(w2, { assertNotWindows: false })
|
||||
])
|
||||
})
|
||||
|
||||
@@ -141,7 +138,7 @@ describe('BrowserWindow with affinity module', () => {
|
||||
nodeIntegration: true
|
||||
})
|
||||
])
|
||||
await closeWindow(w, { assertSingleWindow: false })
|
||||
await closeWindow(w, { assertNotWindows: false })
|
||||
})
|
||||
|
||||
it('enables node integration when first window is true', async () => {
|
||||
@@ -162,8 +159,8 @@ describe('BrowserWindow with affinity module', () => {
|
||||
})
|
||||
])
|
||||
await Promise.all([
|
||||
closeWindow(w1, { assertSingleWindow: false }),
|
||||
closeWindow(w2, { assertSingleWindow: false })
|
||||
closeWindow(w1, { assertNotWindows: false }),
|
||||
closeWindow(w2, { assertNotWindows: false })
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,53 +1,48 @@
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const chaiAsPromised = require('chai-as-promised')
|
||||
const { desktopCapturer, ipcRenderer, remote } = require('electron')
|
||||
const { screen } = remote
|
||||
import { expect } from 'chai'
|
||||
import { desktopCapturer, ipcRenderer, screen, BrowserWindow, SourcesOptions } from 'electron'
|
||||
import { emittedOnce } from './events-helpers'
|
||||
import { ifdescribe } from './spec-helpers';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
|
||||
const features = process.electronBinding('features')
|
||||
const { emittedOnce } = require('./events-helpers')
|
||||
|
||||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
chai.use(chaiAsPromised)
|
||||
|
||||
const isCI = remote.getGlobal('isCi')
|
||||
|
||||
describe('desktopCapturer', () => {
|
||||
before(function () {
|
||||
if (!features.isDesktopCapturerEnabled() || process.arch.indexOf('arm') === 0) {
|
||||
// It's been disabled during build time.
|
||||
this.skip()
|
||||
return
|
||||
}
|
||||
|
||||
if (isCI && process.platform === 'win32') {
|
||||
this.skip()
|
||||
}
|
||||
ifdescribe(features.isDesktopCapturerEnabled() && !process.arch.includes('arm') && process.platform !== 'win32')('desktopCapturer', () => {
|
||||
let w: BrowserWindow
|
||||
before(async () => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
await w.loadURL('about:blank')
|
||||
})
|
||||
after(closeAllWindows)
|
||||
|
||||
const getSources: typeof desktopCapturer.getSources = (options: SourcesOptions) => {
|
||||
return w.webContents.executeJavaScript(`
|
||||
require('electron').desktopCapturer.getSources(${JSON.stringify(options)})
|
||||
`)
|
||||
}
|
||||
|
||||
it('should return a non-empty array of sources', async () => {
|
||||
const sources = await desktopCapturer.getSources({ types: ['window', 'screen'] })
|
||||
const sources = await getSources({ types: ['window', 'screen'] })
|
||||
expect(sources).to.be.an('array').that.is.not.empty()
|
||||
})
|
||||
|
||||
it('throws an error for invalid options', async () => {
|
||||
const promise = desktopCapturer.getSources(['window', 'screen'])
|
||||
const promise = getSources(['window', 'screen'] as any)
|
||||
expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options')
|
||||
})
|
||||
|
||||
it('does not throw an error when called more than once (regression)', async () => {
|
||||
const sources1 = await desktopCapturer.getSources({ types: ['window', 'screen'] })
|
||||
const sources1 = await getSources({ types: ['window', 'screen'] })
|
||||
expect(sources1).to.be.an('array').that.is.not.empty()
|
||||
|
||||
const sources2 = await desktopCapturer.getSources({ types: ['window', 'screen'] })
|
||||
const sources2 = await getSources({ types: ['window', 'screen'] })
|
||||
expect(sources2).to.be.an('array').that.is.not.empty()
|
||||
})
|
||||
|
||||
it('responds to subsequent calls of different options', async () => {
|
||||
const promise1 = desktopCapturer.getSources({ types: ['window'] })
|
||||
const promise1 = getSources({ types: ['window'] })
|
||||
expect(promise1).to.not.eventually.be.rejected()
|
||||
|
||||
const promise2 = desktopCapturer.getSources({ types: ['screen'] })
|
||||
const promise2 = getSources({ types: ['screen'] })
|
||||
expect(promise2).to.not.eventually.be.rejected()
|
||||
})
|
||||
|
||||
@@ -55,10 +50,9 @@ describe('desktopCapturer', () => {
|
||||
// Linux doesn't return any window sources.
|
||||
if (process.platform !== 'win32' && process.platform !== 'darwin') return
|
||||
|
||||
const { BrowserWindow } = remote
|
||||
const w = new BrowserWindow({ width: 200, height: 200 })
|
||||
|
||||
const sources = await desktopCapturer.getSources({ types: ['window'] })
|
||||
const sources = await getSources({ types: ['window'] })
|
||||
w.destroy()
|
||||
expect(sources).to.be.an('array').that.is.not.empty()
|
||||
for (const { display_id: displayId } of sources) {
|
||||
@@ -70,7 +64,7 @@ describe('desktopCapturer', () => {
|
||||
if (process.platform !== 'win32' && process.platform !== 'darwin') return
|
||||
|
||||
const displays = screen.getAllDisplays()
|
||||
const sources = await desktopCapturer.getSources({ types: ['screen'] })
|
||||
const sources = await getSources({ types: ['screen'] })
|
||||
expect(sources).to.be.an('array').of.length(displays.length)
|
||||
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
@@ -79,32 +73,32 @@ describe('desktopCapturer', () => {
|
||||
|
||||
it('returns empty sources when blocked', async () => {
|
||||
ipcRenderer.send('handle-next-desktop-capturer-get-sources')
|
||||
const sources = await desktopCapturer.getSources({ types: ['screen'] })
|
||||
const sources = await getSources({ types: ['screen'] })
|
||||
expect(sources).to.be.empty()
|
||||
})
|
||||
})
|
||||
|
||||
it('disabling thumbnail should return empty images', async () => {
|
||||
const { BrowserWindow } = remote
|
||||
const w = new BrowserWindow({ show: false, width: 200, height: 200 })
|
||||
const wShown = emittedOnce(w, 'show')
|
||||
w.show()
|
||||
const w2 = new BrowserWindow({ show: false, width: 200, height: 200 })
|
||||
const wShown = emittedOnce(w2, 'show')
|
||||
w2.show()
|
||||
await wShown
|
||||
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['window', 'screen'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
})
|
||||
w.destroy()
|
||||
expect(sources).to.be.an('array').that.is.not.empty()
|
||||
for (const { thumbnail: thumbnailImage } of sources) {
|
||||
expect(thumbnailImage).to.be.a('NativeImage')
|
||||
expect(thumbnailImage.isEmpty()).to.be.true()
|
||||
}
|
||||
const isEmpties: boolean[] = await w.webContents.executeJavaScript(`
|
||||
require('electron').desktopCapturer.getSources({
|
||||
types: ['window', 'screen'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
}).then((sources) => {
|
||||
return sources.map(s => s.thumbnail.constructor.name === 'NativeImage' && s.thumbnail.isEmpty())
|
||||
})
|
||||
`)
|
||||
|
||||
w2.destroy()
|
||||
expect(isEmpties).to.be.an('array').that.is.not.empty()
|
||||
expect(isEmpties.every(e => e === true)).to.be.true()
|
||||
})
|
||||
|
||||
it('getMediaSourceId should match DesktopCapturerSource.id', async () => {
|
||||
const { BrowserWindow } = remote
|
||||
const w = new BrowserWindow({ show: false, width: 100, height: 100 })
|
||||
const wShown = emittedOnce(w, 'show')
|
||||
const wFocused = emittedOnce(w, 'focus')
|
||||
@@ -114,7 +108,7 @@ describe('desktopCapturer', () => {
|
||||
await wFocused
|
||||
|
||||
const mediaSourceId = w.getMediaSourceId()
|
||||
const sources = await desktopCapturer.getSources({
|
||||
const sources = await getSources({
|
||||
types: ['window'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
})
|
||||
@@ -132,18 +126,16 @@ describe('desktopCapturer', () => {
|
||||
const foundSource = sources.find((source) => {
|
||||
return source.id === mediaSourceId
|
||||
})
|
||||
expect(mediaSourceId).to.equal(foundSource.id)
|
||||
expect(mediaSourceId).to.equal(foundSource!.id)
|
||||
})
|
||||
|
||||
it('moveAbove should move the window at the requested place', async () => {
|
||||
// DesktopCapturer.getSources() is guaranteed to return in the correct
|
||||
// z-order from foreground to background.
|
||||
const MAX_WIN = 4
|
||||
const { BrowserWindow } = remote
|
||||
const mainWindow = remote.getCurrentWindow()
|
||||
const mainWindow = w
|
||||
const wList = [mainWindow]
|
||||
try {
|
||||
// Add MAX_WIN-1 more window so we have MAX_WIN in total.
|
||||
for (let i = 0; i < MAX_WIN - 1; i++) {
|
||||
const w = new BrowserWindow({ show: true, width: 100, height: 100 })
|
||||
wList.push(w)
|
||||
@@ -160,7 +152,7 @@ describe('desktopCapturer', () => {
|
||||
|
||||
// DesktopCapturer.getSources() returns sources sorted from foreground to
|
||||
// background, i.e. top to bottom.
|
||||
let sources = await desktopCapturer.getSources({
|
||||
let sources = await getSources({
|
||||
types: ['window'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
})
|
||||
@@ -206,7 +198,7 @@ describe('desktopCapturer', () => {
|
||||
}
|
||||
})
|
||||
|
||||
sources = await desktopCapturer.getSources({
|
||||
sources = await getSources({
|
||||
types: ['window'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
})
|
||||
@@ -628,7 +628,7 @@ describe('net module', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('webRequest', () => {
|
||||
describe('webRequest', () => {
|
||||
afterEach(() => {
|
||||
session.defaultSession.webRequest.onBeforeRequest(null)
|
||||
})
|
||||
|
||||
@@ -6,20 +6,19 @@
|
||||
//
|
||||
// See https://pypi.python.org/pypi/python-dbusmock to read about dbusmock.
|
||||
|
||||
const { expect } = require('chai')
|
||||
const dbus = require('dbus-native')
|
||||
const Promise = require('bluebird')
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { app } = remote
|
||||
import { expect } from 'chai'
|
||||
import * as dbus from 'dbus-native'
|
||||
import { app } from 'electron'
|
||||
import { ifdescribe } from './spec-helpers'
|
||||
import { promisify } from 'util';
|
||||
|
||||
const skip = process.platform !== 'linux' ||
|
||||
process.arch === 'ia32' ||
|
||||
process.arch.indexOf('arm') === 0 ||
|
||||
!process.env.DBUS_SESSION_BUS_ADDRESS;
|
||||
!process.env.DBUS_SESSION_BUS_ADDRESS
|
||||
|
||||
(skip ? describe.skip : describe)('Notification module (dbus)', () => {
|
||||
let mock, Notification, getCalls, reset
|
||||
ifdescribe(!skip)('Notification module (dbus)', () => {
|
||||
let mock: any, Notification, getCalls: any, reset: any
|
||||
const realAppName = app.name
|
||||
const realAppVersion = app.getVersion()
|
||||
const appName = 'api-notification-dbus-spec'
|
||||
@@ -35,10 +34,10 @@ const skip = process.platform !== 'linux' ||
|
||||
const bus = dbus.sessionBus()
|
||||
console.log(`session bus: ${process.env.DBUS_SESSION_BUS_ADDRESS}`)
|
||||
const service = bus.getService(serviceName)
|
||||
const getInterface = Promise.promisify(service.getInterface, { context: service })
|
||||
const getInterface = promisify(service.getInterface.bind(service))
|
||||
mock = await getInterface(path, iface)
|
||||
getCalls = Promise.promisify(mock.GetCalls, { context: mock })
|
||||
reset = Promise.promisify(mock.Reset, { context: mock })
|
||||
getCalls = promisify(mock.GetCalls.bind(mock))
|
||||
reset = promisify(mock.Reset.bind(mock))
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
@@ -50,8 +49,8 @@ const skip = process.platform !== 'linux' ||
|
||||
})
|
||||
|
||||
describe(`Notification module using ${serviceName}`, () => {
|
||||
function onMethodCalled (done) {
|
||||
function cb (name) {
|
||||
function onMethodCalled (done: () => void) {
|
||||
function cb (name: string) {
|
||||
console.log(`onMethodCalled: ${name}`)
|
||||
if (name === 'Notify') {
|
||||
mock.removeListener('MethodCalled', cb)
|
||||
@@ -62,8 +61,8 @@ const skip = process.platform !== 'linux' ||
|
||||
return cb
|
||||
}
|
||||
|
||||
function unmarshalDBusNotifyHints (dbusHints) {
|
||||
const o = {}
|
||||
function unmarshalDBusNotifyHints (dbusHints: any) {
|
||||
const o: Record<string, any> = {}
|
||||
for (const hint of dbusHints) {
|
||||
const key = hint[0]
|
||||
const value = hint[1][1][0]
|
||||
@@ -72,7 +71,7 @@ const skip = process.platform !== 'linux' ||
|
||||
return o
|
||||
}
|
||||
|
||||
function unmarshalDBusNotifyArgs (dbusArgs) {
|
||||
function unmarshalDBusNotifyArgs (dbusArgs: any) {
|
||||
return {
|
||||
app_name: dbusArgs[0][1][0],
|
||||
replaces_id: dbusArgs[1][1][0],
|
||||
@@ -87,7 +86,7 @@ const skip = process.platform !== 'linux' ||
|
||||
before(done => {
|
||||
mock.on('MethodCalled', onMethodCalled(done))
|
||||
// lazy load Notification after we listen to MethodCalled mock signal
|
||||
Notification = require('electron').remote.Notification
|
||||
Notification = require('electron').Notification
|
||||
const n = new Notification({
|
||||
title: 'title',
|
||||
subtitle: 'subtitle',
|
||||
@@ -6,37 +6,30 @@
|
||||
//
|
||||
// See https://pypi.python.org/pypi/python-dbusmock for more information about
|
||||
// python-dbusmock.
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const dbus = require('dbus-native')
|
||||
const Promise = require('bluebird')
|
||||
|
||||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const skip = process.platform !== 'linux' || !process.env.DBUS_SYSTEM_BUS_ADDRESS
|
||||
import { expect } from 'chai'
|
||||
import * as dbus from 'dbus-native'
|
||||
import { ifdescribe } from './spec-helpers'
|
||||
import { promisify } from 'util'
|
||||
|
||||
describe('powerMonitor', () => {
|
||||
let logindMock, dbusMockPowerMonitor, getCalls, emitSignal, reset
|
||||
let logindMock: any, dbusMockPowerMonitor: any, getCalls: any, emitSignal: any, reset: any
|
||||
|
||||
if (!skip) {
|
||||
ifdescribe(process.platform === 'linux' && process.env.DBUS_SYSTEM_BUS_ADDRESS != null)('when powerMonitor module is loaded with dbus mock', () => {
|
||||
before(async () => {
|
||||
const systemBus = dbus.systemBus()
|
||||
const loginService = systemBus.getService('org.freedesktop.login1')
|
||||
const getInterface = Promise.promisify(loginService.getInterface, { context: loginService })
|
||||
const getInterface = promisify(loginService.getInterface.bind(loginService))
|
||||
logindMock = await getInterface('/org/freedesktop/login1', 'org.freedesktop.DBus.Mock')
|
||||
getCalls = Promise.promisify(logindMock.GetCalls, { context: logindMock })
|
||||
emitSignal = Promise.promisify(logindMock.EmitSignal, { context: logindMock })
|
||||
reset = Promise.promisify(logindMock.Reset, { context: logindMock })
|
||||
getCalls = promisify(logindMock.GetCalls.bind(logindMock))
|
||||
emitSignal = promisify(logindMock.EmitSignal.bind(logindMock))
|
||||
reset = promisify(logindMock.Reset.bind(logindMock))
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await reset()
|
||||
})
|
||||
}
|
||||
|
||||
(skip ? describe.skip : describe)('when powerMonitor module is loaded with dbus mock', () => {
|
||||
function onceMethodCalled (done) {
|
||||
function onceMethodCalled (done: () => void) {
|
||||
function cb () {
|
||||
logindMock.removeListener('MethodCalled', cb)
|
||||
}
|
||||
@@ -47,7 +40,7 @@ describe('powerMonitor', () => {
|
||||
before(done => {
|
||||
logindMock.on('MethodCalled', onceMethodCalled(done))
|
||||
// lazy load powerMonitor after we listen to MethodCalled mock signal
|
||||
dbusMockPowerMonitor = require('electron').remote.powerMonitor
|
||||
dbusMockPowerMonitor = require('electron').powerMonitor
|
||||
})
|
||||
|
||||
it('should call Inhibit to delay suspend', async () => {
|
||||
@@ -123,11 +116,10 @@ describe('powerMonitor', () => {
|
||||
})
|
||||
|
||||
describe('when powerMonitor module is loaded', () => {
|
||||
let powerMonitor
|
||||
let powerMonitor: typeof Electron.powerMonitor
|
||||
before(() => {
|
||||
powerMonitor = require('electron').remote.powerMonitor
|
||||
powerMonitor = require('electron').powerMonitor
|
||||
})
|
||||
|
||||
describe('powerMonitor.getSystemIdleState', () => {
|
||||
it('gets current system idle state', () => {
|
||||
// this function is not mocked out, so we can test the result's
|
||||
@@ -148,7 +140,7 @@ describe('powerMonitor', () => {
|
||||
}).to.throw(/conversion failure/)
|
||||
|
||||
expect(() => {
|
||||
powerMonitor.getSystemIdleState('a')
|
||||
powerMonitor.getSystemIdleState('a' as any)
|
||||
}).to.throw(/conversion failure/)
|
||||
})
|
||||
})
|
||||
@@ -1,41 +1,23 @@
|
||||
const chai = require('chai')
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const send = require('send')
|
||||
const auth = require('basic-auth')
|
||||
const ChildProcess = require('child_process')
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
const { emittedOnce } = require('./events-helpers')
|
||||
|
||||
const { session, BrowserWindow, net, ipcMain } = require('electron')
|
||||
const { expect } = chai
|
||||
import { expect } from 'chai'
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as ChildProcess from 'child_process'
|
||||
import { session, BrowserWindow, net, ipcMain, Session } from 'electron'
|
||||
import * as send from 'send'
|
||||
import * as auth from 'basic-auth'
|
||||
import { closeAllWindows } from './window-helpers'
|
||||
import { emittedOnce } from './events-helpers'
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
/* The whole session API doesn't use standard callbacks */
|
||||
/* eslint-disable standard/no-callback-literal */
|
||||
|
||||
describe('session module', () => {
|
||||
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures')
|
||||
let w = null
|
||||
const url = 'http://127.0.0.1'
|
||||
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
width: 400,
|
||||
height: 400,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
return closeWindow(w).then(() => { w = null })
|
||||
})
|
||||
|
||||
describe('session.defaultSession', () => {
|
||||
it('returns the default session', () => {
|
||||
expect(session.defaultSession).to.equal(session.fromPartition(''))
|
||||
@@ -59,21 +41,22 @@ describe('session module', () => {
|
||||
expect(ses2.getUserAgent()).to.not.equal(userAgent)
|
||||
})
|
||||
|
||||
it('created session is ref-counted', () => {
|
||||
it.skip('created session is ref-counted', () => {
|
||||
const partition = 'test2'
|
||||
const userAgent = 'test-agent'
|
||||
const ses1 = session.fromPartition(partition)
|
||||
ses1.userAgent = userAgent
|
||||
expect(ses1.userAgent).to.equal(userAgent)
|
||||
ses1.setUserAgent(userAgent)
|
||||
expect(ses1.getUserAgent()).to.equal(userAgent)
|
||||
ses1.destroy()
|
||||
const ses2 = session.fromPartition(partition)
|
||||
expect(ses2.userAgent).to.not.equal(userAgent)
|
||||
expect(ses2.getUserAgent()).to.not.equal(userAgent)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.cookies', () => {
|
||||
const name = '0'
|
||||
const value = '0'
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('should get cookies', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
@@ -82,7 +65,8 @@ describe('session module', () => {
|
||||
server.close()
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = server.address()
|
||||
const { port } = server.address() as AddressInfo
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await w.loadURL(`${url}:${port}`)
|
||||
const list = await w.webContents.session.cookies.get({ url })
|
||||
const cookie = list.find(cookie => cookie.name === name)
|
||||
@@ -144,7 +128,6 @@ describe('session module', () => {
|
||||
|
||||
it.skip('should set cookie for standard scheme', async () => {
|
||||
const { cookies } = session.defaultSession
|
||||
const standardScheme = global.standardScheme
|
||||
const domain = 'fake-host'
|
||||
const url = `${standardScheme}://${domain}`
|
||||
const name = 'custom'
|
||||
@@ -160,12 +143,9 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
it('emits a changed event when a cookie is added or removed', async () => {
|
||||
const changes = []
|
||||
|
||||
const { cookies } = session.fromPartition('cookies-changed')
|
||||
const name = 'foo'
|
||||
const value = 'bar'
|
||||
const listener = (event, cookie, cause, removed) => { changes.push({ cookie, cause, removed }) }
|
||||
|
||||
const a = emittedOnce(cookies, 'changed')
|
||||
await cookies.set({ url, name, value, expirationDate: (+new Date()) / 1000 + 120 })
|
||||
@@ -200,7 +180,7 @@ describe('session module', () => {
|
||||
it('should survive an app restart for persistent partition', async () => {
|
||||
const appPath = path.join(fixtures, 'api', 'cookie-app')
|
||||
|
||||
const runAppWithPhase = (phase) => {
|
||||
const runAppWithPhase = (phase: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let output = ''
|
||||
|
||||
@@ -223,7 +203,9 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('ses.clearStorageData(options)', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('clears localstorage data', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
await w.loadFile(path.join(fixtures, 'api', 'localstorage.html'))
|
||||
const options = {
|
||||
origin: 'file://',
|
||||
@@ -239,7 +221,9 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('will-download event', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('can cancel default download behavior', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
const mockFile = Buffer.alloc(1024)
|
||||
const contentDisposition = 'inline; filename="mockFile.txt"'
|
||||
const downloadServer = http.createServer((req, res) => {
|
||||
@@ -253,10 +237,10 @@ describe('session module', () => {
|
||||
})
|
||||
await new Promise(resolve => downloadServer.listen(0, '127.0.0.1', resolve))
|
||||
|
||||
const port = downloadServer.address().port
|
||||
const port = (downloadServer.address() as AddressInfo).port
|
||||
const url = `http://127.0.0.1:${port}/`
|
||||
|
||||
const downloadPrevented = new Promise(resolve => {
|
||||
const downloadPrevented: Promise<Electron.DownloadItem> = new Promise(resolve => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
e.preventDefault()
|
||||
resolve(item)
|
||||
@@ -274,29 +258,22 @@ describe('session module', () => {
|
||||
describe('ses.protocol', () => {
|
||||
const partitionName = 'temp'
|
||||
const protocolName = 'sp'
|
||||
let customSession = null
|
||||
let customSession: Session
|
||||
const protocol = session.defaultSession.protocol
|
||||
const handler = (ignoredError, callback) => {
|
||||
const handler = (ignoredError: any, callback: Function) => {
|
||||
callback({ data: `<script>require('electron').ipcRenderer.send('hello')</script>`, mimeType: 'text/html' })
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
if (w != null) w.destroy()
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
partition: partitionName,
|
||||
nodeIntegration: true,
|
||||
}
|
||||
})
|
||||
customSession = session.fromPartition(partitionName)
|
||||
await customSession.protocol.registerStringProtocol(protocolName, handler)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await customSession.protocol.unregisterProtocol(protocolName)
|
||||
customSession = null
|
||||
customSession = null as any
|
||||
})
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('does not affect defaultSession', async () => {
|
||||
const result1 = await protocol.isProtocolHandled(protocolName)
|
||||
@@ -307,14 +284,23 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
it('handles requests from partition', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
partition: partitionName,
|
||||
nodeIntegration: true,
|
||||
}
|
||||
})
|
||||
customSession = session.fromPartition(partitionName)
|
||||
await customSession.protocol.registerStringProtocol(protocolName, handler)
|
||||
w.loadURL(`${protocolName}://fake-host`)
|
||||
await emittedOnce(ipcMain, 'hello')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.setProxy(options)', () => {
|
||||
let server = null
|
||||
let customSession = null
|
||||
let server: http.Server
|
||||
let customSession: Electron.Session
|
||||
|
||||
beforeEach(async () => {
|
||||
customSession = session.fromPartition('proxyconfig')
|
||||
@@ -365,7 +351,7 @@ describe('session module', () => {
|
||||
res.end(pac)
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const config = { pacScript: `http://127.0.0.1:${server.address().port}` }
|
||||
const config = { pacScript: `http://127.0.0.1:${(server.address() as AddressInfo).port}` }
|
||||
await customSession.setProxy(config)
|
||||
const proxy = await customSession.resolveProxy('https://google.com')
|
||||
expect(proxy).to.equal('PROXY myproxy:8132')
|
||||
@@ -389,6 +375,7 @@ describe('session module', () => {
|
||||
after(async () => {
|
||||
await protocol.unregisterProtocol(scheme)
|
||||
})
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('returns blob data for uuid', (done) => {
|
||||
const postData = JSON.stringify({
|
||||
@@ -409,20 +396,21 @@ describe('session module', () => {
|
||||
} else if (request.method === 'POST') {
|
||||
const uuid = request.uploadData[1].blobUUID
|
||||
expect(uuid).to.be.a('string')
|
||||
session.defaultSession.getBlobData(uuid).then(result => {
|
||||
session.defaultSession.getBlobData(uuid!).then(result => {
|
||||
expect(result.toString()).to.equal(postData)
|
||||
done()
|
||||
})
|
||||
}
|
||||
}, (error) => {
|
||||
if (error) return done(error)
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.loadURL(url)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.setCertificateVerifyProc(callback)', (done) => {
|
||||
let server = null
|
||||
describe('ses.setCertificateVerifyProc(callback)', () => {
|
||||
let server: http.Server
|
||||
|
||||
beforeEach((done) => {
|
||||
const certPath = path.join(fixtures, 'certificates')
|
||||
@@ -448,6 +436,7 @@ describe('session module', () => {
|
||||
session.defaultSession.setCertificateVerifyProc(null)
|
||||
server.close(done)
|
||||
})
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('accepts the request when the callback is called with 0', async () => {
|
||||
session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult, errorCode }, callback) => {
|
||||
@@ -456,7 +445,8 @@ describe('session module', () => {
|
||||
callback(0)
|
||||
})
|
||||
|
||||
await w.loadURL(`https://127.0.0.1:${server.address().port}`)
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await w.loadURL(`https://127.0.0.1:${(server.address() as AddressInfo).port}`)
|
||||
expect(w.webContents.getTitle()).to.equal('hello')
|
||||
})
|
||||
|
||||
@@ -476,7 +466,8 @@ describe('session module', () => {
|
||||
callback(-2)
|
||||
})
|
||||
|
||||
const url = `https://127.0.0.1:${server.address().port}`
|
||||
const url = `https://127.0.0.1:${(server.address() as AddressInfo).port}`
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await expect(w.loadURL(url)).to.eventually.be.rejectedWith(/ERR_FAILED/)
|
||||
expect(w.webContents.getTitle()).to.equal(url)
|
||||
})
|
||||
@@ -488,7 +479,8 @@ describe('session module', () => {
|
||||
callback(-2)
|
||||
})
|
||||
|
||||
const url = `https://127.0.0.1:${server.address().port}`
|
||||
const url = `https://127.0.0.1:${(server.address() as AddressInfo).port}`
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await expect(w.loadURL(url), 'first load').to.eventually.be.rejectedWith(/ERR_FAILED/)
|
||||
await emittedOnce(w.webContents, 'did-stop-loading')
|
||||
await expect(w.loadURL(url + '/test'), 'second load').to.eventually.be.rejectedWith(/ERR_FAILED/)
|
||||
@@ -497,8 +489,8 @@ describe('session module', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('ses.clearAuthCache(options)', () => {
|
||||
it('can clear http auth info from cache', (done) => {
|
||||
describe('ses.clearAuthCache(options)', () => {
|
||||
it('can clear http auth info from cache', async () => {
|
||||
const ses = session.fromPartition('auth-cache')
|
||||
const server = http.createServer((req, res) => {
|
||||
const credentials = auth(req)
|
||||
@@ -510,44 +502,31 @@ describe('session module', () => {
|
||||
res.end('authenticated')
|
||||
}
|
||||
})
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const port = server.address().port
|
||||
function issueLoginRequest (attempt = 1) {
|
||||
if (attempt > 2) {
|
||||
server.close()
|
||||
return done()
|
||||
}
|
||||
const request = net.request({
|
||||
url: `http://127.0.0.1:${port}`,
|
||||
session: ses
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
const port = (server.address() as AddressInfo).port
|
||||
const fetch = (url: string) => new Promise((resolve, reject) => {
|
||||
const request = net.request({ url, session: ses })
|
||||
request.on('response', (response) => {
|
||||
let data = ''
|
||||
response.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
request.on('login', (info, callback) => {
|
||||
attempt += 1
|
||||
expect(info.scheme).to.equal('basic')
|
||||
expect(info.realm).to.equal('Restricted')
|
||||
callback('test', 'test')
|
||||
response.on('end', () => {
|
||||
resolve(data)
|
||||
})
|
||||
request.on('response', (response) => {
|
||||
let data = ''
|
||||
response.pause()
|
||||
response.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
response.on('end', () => {
|
||||
expect(data).to.equal('authenticated')
|
||||
ses.clearAuthCache({ type: 'password' }).then(() => {
|
||||
issueLoginRequest(attempt)
|
||||
})
|
||||
})
|
||||
response.on('error', (error) => { done(error) })
|
||||
response.resume()
|
||||
})
|
||||
// Internal api to bypass cache for testing.
|
||||
request.urlRequest._setLoadFlags(1 << 1)
|
||||
request.end()
|
||||
}
|
||||
issueLoginRequest()
|
||||
response.on('error', (error: any) => { reject(new Error(error)) })
|
||||
});
|
||||
request.end()
|
||||
})
|
||||
// the first time should throw due to unauthenticated
|
||||
await expect(fetch(`http://127.0.0.1:${port}`)).to.eventually.be.rejected()
|
||||
// passing the password should let us in
|
||||
expect(await fetch(`http://test:test@127.0.0.1:${port}`)).to.equal('authenticated')
|
||||
// subsequently, the credentials are cached
|
||||
expect(await fetch(`http://127.0.0.1:${port}`)).to.equal('authenticated')
|
||||
await ses.clearAuthCache({ type: 'password' })
|
||||
// once the cache is cleared, we should get an error again
|
||||
await expect(fetch(`http://127.0.0.1:${port}`)).to.eventually.be.rejected()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -556,11 +535,10 @@ describe('session module', () => {
|
||||
const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
|
||||
const protocolName = 'custom-dl'
|
||||
const contentDisposition = 'inline; filename="mock.pdf"'
|
||||
let address = null
|
||||
let downloadServer = null
|
||||
let address: AddressInfo
|
||||
let downloadServer: http.Server
|
||||
before(async () => {
|
||||
downloadServer = http.createServer((req, res) => {
|
||||
address = downloadServer.address()
|
||||
res.writeHead(200, {
|
||||
'Content-Length': mockPDF.length,
|
||||
'Content-Type': 'application/pdf',
|
||||
@@ -569,15 +547,17 @@ describe('session module', () => {
|
||||
res.end(mockPDF)
|
||||
})
|
||||
await new Promise(resolve => downloadServer.listen(0, '127.0.0.1', resolve))
|
||||
address = downloadServer.address() as AddressInfo
|
||||
})
|
||||
after(async () => {
|
||||
await new Promise(resolve => downloadServer.close(resolve))
|
||||
})
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
const isPathEqual = (path1, path2) => {
|
||||
const isPathEqual = (path1: string, path2: string) => {
|
||||
return path.relative(path1, path2) === ''
|
||||
}
|
||||
const assertDownload = (state, item, isCustom = false) => {
|
||||
const assertDownload = (state: string, item: Electron.DownloadItem, isCustom = false) => {
|
||||
expect(state).to.equal('completed')
|
||||
expect(item.getFilename()).to.equal('mock.pdf')
|
||||
expect(path.isAbsolute(item.savePath)).to.equal(true)
|
||||
@@ -595,8 +575,21 @@ describe('session module', () => {
|
||||
fs.unlinkSync(downloadFilePath)
|
||||
}
|
||||
|
||||
it('can download using session.downloadURL', (done) => {
|
||||
const port = address.port
|
||||
session.defaultSession.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
item.on('done', function (e, state) {
|
||||
assertDownload(state, item)
|
||||
done()
|
||||
})
|
||||
})
|
||||
session.defaultSession.downloadURL(`${url}:${port}`)
|
||||
})
|
||||
|
||||
it('can download using WebContents.downloadURL', (done) => {
|
||||
const port = downloadServer.address().port
|
||||
const port = address.port
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
item.on('done', function (e, state) {
|
||||
@@ -609,12 +602,13 @@ describe('session module', () => {
|
||||
|
||||
it('can download from custom protocols using WebContents.downloadURL', (done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
const port = downloadServer.address().port
|
||||
const handler = (ignoredError, callback) => {
|
||||
const port = address.port
|
||||
const handler = (ignoredError: any, callback: Function) => {
|
||||
callback({ url: `${url}:${port}` })
|
||||
}
|
||||
protocol.registerHttpProtocol(protocolName, handler, (error) => {
|
||||
if (error) return done(error)
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
item.on('done', function (e, state) {
|
||||
@@ -627,17 +621,18 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
it('can download using WebView.downloadURL', async () => {
|
||||
const port = downloadServer.address().port
|
||||
const port = address.port
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { webviewTag: true } })
|
||||
await w.loadURL('about:blank')
|
||||
function webviewDownload({fixtures, url, port}) {
|
||||
const webview = new WebView()
|
||||
function webviewDownload({fixtures, url, port}: {fixtures: string, url: string, port: string}) {
|
||||
const webview = new (window as any).WebView()
|
||||
webview.addEventListener('did-finish-load', () => {
|
||||
webview.downloadURL(`${url}:${port}/`)
|
||||
})
|
||||
webview.src = `file://${fixtures}/api/blank.html`
|
||||
document.body.appendChild(webview)
|
||||
}
|
||||
const done = new Promise(resolve => {
|
||||
const done: Promise<[string, Electron.DownloadItem]> = new Promise(resolve => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
item.on('done', function (e, state) {
|
||||
@@ -651,7 +646,8 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
it('can cancel download', (done) => {
|
||||
const port = downloadServer.address().port
|
||||
const port = address.port
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
item.on('done', function (e, state) {
|
||||
@@ -675,7 +671,8 @@ describe('session module', () => {
|
||||
return done()
|
||||
}
|
||||
|
||||
const port = downloadServer.address().port
|
||||
const port = address.port
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
item.on('done', function (e, state) {
|
||||
@@ -689,7 +686,7 @@ describe('session module', () => {
|
||||
|
||||
it('can set options for the save dialog', (done) => {
|
||||
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
|
||||
const port = downloadServer.address().port
|
||||
const port = address.port
|
||||
const options = {
|
||||
window: null,
|
||||
title: 'title',
|
||||
@@ -706,6 +703,7 @@ describe('session module', () => {
|
||||
securityScopedBookmarks: true
|
||||
}
|
||||
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(filePath)
|
||||
item.setSaveDialogOptions(options)
|
||||
@@ -720,6 +718,7 @@ describe('session module', () => {
|
||||
|
||||
describe('when a save path is specified and the URL is unavailable', () => {
|
||||
it('does not display a save dialog and reports the done state as interrupted', (done) => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.savePath = downloadFilePath
|
||||
if (item.getState() === 'interrupted') {
|
||||
@@ -736,6 +735,7 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('ses.createInterruptedDownload(options)', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('can create an interrupted download item', (done) => {
|
||||
const downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
|
||||
const options = {
|
||||
@@ -745,6 +745,7 @@ describe('session module', () => {
|
||||
offset: 0,
|
||||
length: 5242880
|
||||
}
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
expect(item.getState()).to.equal('interrupted')
|
||||
item.cancel()
|
||||
@@ -762,13 +763,14 @@ describe('session module', () => {
|
||||
const downloadFilePath = path.join(fixtures, 'logo.png')
|
||||
const rangeServer = http.createServer((req, res) => {
|
||||
const options = { root: fixtures }
|
||||
send(req, req.url, options)
|
||||
.on('error', (error) => { done(error) }).pipe(res)
|
||||
send(req, req.url!, options)
|
||||
.on('error', (error: any) => { throw error }).pipe(res)
|
||||
})
|
||||
try {
|
||||
await new Promise(resolve => rangeServer.listen(0, '127.0.0.1', resolve))
|
||||
const port = rangeServer.address().port
|
||||
const downloadCancelled = new Promise((resolve) => {
|
||||
const port = (rangeServer.address() as AddressInfo).port
|
||||
const w = new BrowserWindow({ show: false })
|
||||
const downloadCancelled: Promise<Electron.DownloadItem> = new Promise((resolve) => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
item.setSavePath(downloadFilePath)
|
||||
item.on('done', function (e, state) {
|
||||
@@ -791,7 +793,7 @@ describe('session module', () => {
|
||||
lastModified: item.getLastModifiedTime(),
|
||||
eTag: item.getETag(),
|
||||
}
|
||||
const downloadResumed = new Promise((resolve) => {
|
||||
const downloadResumed: Promise<Electron.DownloadItem> = new Promise((resolve) => {
|
||||
w.webContents.session.once('will-download', function (e, item) {
|
||||
expect(item.getState()).to.equal('interrupted')
|
||||
item.setSavePath(downloadFilePath)
|
||||
@@ -818,9 +820,9 @@ describe('session module', () => {
|
||||
})
|
||||
|
||||
describe('ses.setPermissionRequestHandler(handler)', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('cancels any pending requests when cleared', async () => {
|
||||
if (w != null) w.destroy()
|
||||
w = new BrowserWindow({
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
partition: `very-temp-permision-handler`,
|
||||
@@ -840,7 +842,7 @@ describe('session module', () => {
|
||||
const result = emittedOnce(require('electron').ipcMain, 'message')
|
||||
|
||||
function remote() {
|
||||
navigator.requestMIDIAccess({sysex: true}).then(() => {}, (err) => {
|
||||
(navigator as any).requestMIDIAccess({sysex: true}).then(() => {}, (err: any) => {
|
||||
require('electron').ipcRenderer.send('message', err.name);
|
||||
});
|
||||
}
|
||||
@@ -851,4 +853,31 @@ describe('session module', () => {
|
||||
expect(name).to.deep.equal('SecurityError')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ses.setUserAgent()', () => {
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('can be retrieved with getUserAgent()', () => {
|
||||
const userAgent = 'test-agent'
|
||||
const ses = session.fromPartition(''+Math.random())
|
||||
ses.setUserAgent(userAgent)
|
||||
expect(ses.getUserAgent()).to.equal(userAgent)
|
||||
})
|
||||
|
||||
it('sets the User-Agent header for web requests made from renderers', async () => {
|
||||
const userAgent = 'test-agent'
|
||||
const ses = session.fromPartition(''+Math.random())
|
||||
ses.setUserAgent(userAgent)
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } })
|
||||
let headers: http.IncomingHttpHeaders | null = null
|
||||
const server = http.createServer((req, res) => {
|
||||
headers = req.headers
|
||||
res.end()
|
||||
server.close()
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`)
|
||||
expect(headers!['user-agent']).to.equal(userAgent)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,18 +1,17 @@
|
||||
const { expect } = require('chai')
|
||||
const { remote } = require('electron')
|
||||
const path = require('path')
|
||||
const http = require('http')
|
||||
|
||||
const { emittedNTimes, emittedOnce } = require('./events-helpers')
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
|
||||
const { app, BrowserWindow, ipcMain } = remote
|
||||
import { expect } from 'chai'
|
||||
import * as path from 'path'
|
||||
import * as http from 'http'
|
||||
import { emittedNTimes, emittedOnce } from './events-helpers'
|
||||
import { closeWindow } from './window-helpers'
|
||||
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||
import { AddressInfo } from 'net'
|
||||
import { ifdescribe } from './spec-helpers';
|
||||
|
||||
describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
const generateTests = (description, webPreferences) => {
|
||||
const generateTests = (description: string, webPreferences: any) => {
|
||||
describe(description, () => {
|
||||
const fixtureSuffix = webPreferences.webviewTag ? '-webview' : ''
|
||||
let w
|
||||
let w: BrowserWindow
|
||||
|
||||
beforeEach(async () => {
|
||||
await closeWindow(w)
|
||||
@@ -24,10 +23,9 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
return closeWindow(w).then(() => {
|
||||
w = null
|
||||
})
|
||||
afterEach(async () => {
|
||||
await closeWindow(w)
|
||||
w = null as unknown as BrowserWindow
|
||||
})
|
||||
|
||||
it('should load preload scripts in top level iframes', async () => {
|
||||
@@ -103,8 +101,8 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const generateConfigs = (webPreferences, ...permutations) => {
|
||||
const configs = [{ webPreferences, names: [] }]
|
||||
const generateConfigs = (webPreferences: any, ...permutations: {name: string, webPreferences: any}[]) => {
|
||||
const configs = [{ webPreferences, names: [] as string[] }]
|
||||
for (let i = 0; i < permutations.length; i++) {
|
||||
const length = configs.length
|
||||
for (let j = 0; j < length; j++) {
|
||||
@@ -117,7 +115,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
}
|
||||
}
|
||||
|
||||
return configs.map(config => {
|
||||
return configs.map((config: any) => {
|
||||
if (config.names.length > 0) {
|
||||
config.title = `with ${config.names.join(', ')} on`
|
||||
} else {
|
||||
@@ -125,7 +123,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
}
|
||||
delete config.names
|
||||
|
||||
return config
|
||||
return config as {title: string, webPreferences: any}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -151,7 +149,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
})
|
||||
|
||||
describe('internal <iframe> inside of <webview>', () => {
|
||||
let w
|
||||
let w: BrowserWindow
|
||||
|
||||
beforeEach(async () => {
|
||||
await closeWindow(w)
|
||||
@@ -167,10 +165,9 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
return closeWindow(w).then(() => {
|
||||
w = null
|
||||
})
|
||||
afterEach(async () => {
|
||||
await closeWindow(w)
|
||||
w = null as unknown as BrowserWindow
|
||||
})
|
||||
|
||||
it('should not load preload scripts', async () => {
|
||||
@@ -184,40 +181,36 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('cross-site frame sandboxing', () => {
|
||||
let server = null
|
||||
|
||||
beforeEach(function () {
|
||||
if (process.platform === 'linux') {
|
||||
this.skip()
|
||||
}
|
||||
})
|
||||
// app.getAppMetrics() does not return sandbox information on Linux.
|
||||
ifdescribe(process.platform !== 'linux')('cross-site frame sandboxing', () => {
|
||||
let server: http.Server
|
||||
let crossSiteUrl: string
|
||||
let serverUrl: string
|
||||
|
||||
before(function (done) {
|
||||
server = http.createServer((req, res) => {
|
||||
res.end(`<iframe name="frame" src="${server.cross_site_url}" />`)
|
||||
res.end(`<iframe name="frame" src="${crossSiteUrl}" />`)
|
||||
})
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
server.url = `http://127.0.0.1:${server.address().port}/`
|
||||
server.cross_site_url = `http://localhost:${server.address().port}/`
|
||||
serverUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`
|
||||
crossSiteUrl = `http://localhost:${(server.address() as AddressInfo).port}/`
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
server.close()
|
||||
server = null
|
||||
server = null as unknown as http.Server
|
||||
})
|
||||
|
||||
let w
|
||||
let w: BrowserWindow
|
||||
|
||||
afterEach(() => {
|
||||
return closeWindow(w).then(() => {
|
||||
w = null
|
||||
})
|
||||
afterEach(async () => {
|
||||
await closeWindow(w)
|
||||
w = null as unknown as BrowserWindow
|
||||
})
|
||||
|
||||
const generateSpecs = (description, webPreferences) => {
|
||||
const generateSpecs = (description: string, webPreferences: any) => {
|
||||
describe(description, () => {
|
||||
it('iframe process is sandboxed if possible', async () => {
|
||||
w = new BrowserWindow({
|
||||
@@ -225,13 +218,13 @@ describe('cross-site frame sandboxing', () => {
|
||||
webPreferences
|
||||
})
|
||||
|
||||
await w.loadURL(server.url)
|
||||
await w.loadURL(serverUrl)
|
||||
|
||||
const pidMain = w.webContents.getOSProcessId()
|
||||
const pidFrame = w.webContents._getOSProcessIdForFrame('frame', server.cross_site_url)
|
||||
const pidFrame = (w.webContents as any)._getOSProcessIdForFrame('frame', crossSiteUrl)
|
||||
|
||||
const metrics = app.getAppMetrics()
|
||||
const isProcessSandboxed = function (pid) {
|
||||
const isProcessSandboxed = function (pid: number) {
|
||||
const entry = metrics.filter(metric => metric.pid === pid)[0]
|
||||
return entry && entry.sandboxed
|
||||
}
|
||||
@@ -3,9 +3,10 @@ import { AddressInfo } from 'net'
|
||||
import * as chaiAsPromised from 'chai-as-promised'
|
||||
import * as path from 'path'
|
||||
import * as http from 'http'
|
||||
import { BrowserWindow, ipcMain, webContents } from 'electron'
|
||||
import { BrowserWindow, ipcMain, webContents, session } from 'electron'
|
||||
import { emittedOnce } from './events-helpers';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
import { ifdescribe } from './spec-helpers';
|
||||
|
||||
const { expect } = chai
|
||||
|
||||
@@ -100,6 +101,7 @@ describe('webContents module', () => {
|
||||
})
|
||||
|
||||
describe('webContents.print()', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('throws when invalid settings are passed', () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
expect(() => {
|
||||
@@ -221,4 +223,727 @@ describe('webContents module', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadURL() promise API', () => {
|
||||
let w: BrowserWindow
|
||||
beforeEach(async () => {
|
||||
w = new BrowserWindow({show: false})
|
||||
})
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('resolves when done loading', async () => {
|
||||
await expect(w.loadURL('about:blank')).to.eventually.be.fulfilled()
|
||||
})
|
||||
|
||||
it('resolves when done loading a file URL', async () => {
|
||||
await expect(w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))).to.eventually.be.fulfilled()
|
||||
})
|
||||
|
||||
it('rejects when failing to load a file URL', async () => {
|
||||
await expect(w.loadURL('file:non-existent')).to.eventually.be.rejected()
|
||||
.and.have.property('code', 'ERR_FILE_NOT_FOUND')
|
||||
})
|
||||
|
||||
it('rejects when loading fails due to DNS not resolved', async () => {
|
||||
await expect(w.loadURL('https://err.name.not.resolved')).to.eventually.be.rejected()
|
||||
.and.have.property('code', 'ERR_NAME_NOT_RESOLVED')
|
||||
})
|
||||
|
||||
it('rejects when navigation is cancelled due to a bad scheme', async () => {
|
||||
await expect(w.loadURL('bad-scheme://foo')).to.eventually.be.rejected()
|
||||
.and.have.property('code', 'ERR_FAILED')
|
||||
})
|
||||
|
||||
it('sets appropriate error information on rejection', async () => {
|
||||
let err
|
||||
try {
|
||||
await w.loadURL('file:non-existent')
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).not.to.be.null()
|
||||
expect(err.code).to.eql('ERR_FILE_NOT_FOUND')
|
||||
expect(err.errno).to.eql(-6)
|
||||
expect(err.url).to.eql(process.platform === 'win32' ? 'file://non-existent/' : 'file:///non-existent')
|
||||
})
|
||||
|
||||
it('rejects if the load is aborted', async () => {
|
||||
const s = http.createServer((req, res) => { /* never complete the request */ })
|
||||
await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = s.address() as AddressInfo
|
||||
const p = expect(w.loadURL(`http://127.0.0.1:${port}`)).to.eventually.be.rejectedWith(Error, /ERR_ABORTED/)
|
||||
// load a different file before the first load completes, causing the
|
||||
// first load to be aborted.
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))
|
||||
await p
|
||||
s.close()
|
||||
})
|
||||
|
||||
it("doesn't reject when a subframe fails to load", async () => {
|
||||
let resp = null as unknown as http.ServerResponse
|
||||
const s = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.write('<iframe src="http://err.name.not.resolved"></iframe>')
|
||||
resp = res
|
||||
// don't end the response yet
|
||||
})
|
||||
await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = s.address() as AddressInfo
|
||||
const p = new Promise(resolve => {
|
||||
w.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
|
||||
if (!isMainFrame) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
const main = w.loadURL(`http://127.0.0.1:${port}`)
|
||||
await p
|
||||
resp.end()
|
||||
await main
|
||||
s.close()
|
||||
})
|
||||
|
||||
it("doesn't resolve when a subframe loads", async () => {
|
||||
let resp = null as unknown as http.ServerResponse
|
||||
const s = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.write('<iframe src="data:text/html,hi"></iframe>')
|
||||
resp = res
|
||||
// don't end the response yet
|
||||
})
|
||||
await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = s.address() as AddressInfo
|
||||
const p = new Promise(resolve => {
|
||||
w.webContents.on('did-frame-finish-load', (event, isMainFrame, frameProcessId, frameRoutingId) => {
|
||||
if (!isMainFrame) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
const main = w.loadURL(`http://127.0.0.1:${port}`)
|
||||
await p
|
||||
resp.destroy() // cause the main request to fail
|
||||
await expect(main).to.eventually.be.rejected()
|
||||
.and.have.property('errno', -355) // ERR_INCOMPLETE_CHUNKED_ENCODING
|
||||
s.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFocusedWebContents() API', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('returns the focused web contents', async () => {
|
||||
const w = new BrowserWindow({show: true})
|
||||
await w.loadURL('about:blank')
|
||||
expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id)
|
||||
|
||||
const devToolsOpened = emittedOnce(w.webContents, 'devtools-opened')
|
||||
w.webContents.openDevTools()
|
||||
await devToolsOpened
|
||||
expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.devToolsWebContents.id)
|
||||
const devToolsClosed = emittedOnce(w.webContents, 'devtools-closed')
|
||||
w.webContents.closeDevTools()
|
||||
await devToolsClosed
|
||||
expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id)
|
||||
})
|
||||
|
||||
it('does not crash when called on a detached dev tools window', async () => {
|
||||
const w = new BrowserWindow({show: true})
|
||||
|
||||
w.webContents.openDevTools({ mode: 'detach' })
|
||||
w.webContents.inspectElement(100, 100)
|
||||
|
||||
// For some reason we have to wait for two focused events...?
|
||||
await emittedOnce(w.webContents, 'devtools-focused')
|
||||
await emittedOnce(w.webContents, 'devtools-focused')
|
||||
|
||||
expect(() => { webContents.getFocusedWebContents() }).to.not.throw()
|
||||
|
||||
// Work around https://github.com/electron/electron/issues/19985
|
||||
await new Promise(r => setTimeout(r, 0))
|
||||
|
||||
const devToolsClosed = emittedOnce(w.webContents, 'devtools-closed')
|
||||
w.webContents.closeDevTools()
|
||||
await devToolsClosed
|
||||
expect(() => { webContents.getFocusedWebContents() }).to.not.throw()
|
||||
})
|
||||
})
|
||||
|
||||
describe('setDevToolsWebContents() API', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('sets arbitrary webContents as devtools', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
const devtools = new BrowserWindow({ show: false })
|
||||
const promise = emittedOnce(devtools.webContents, 'dom-ready')
|
||||
w.webContents.setDevToolsWebContents(devtools.webContents)
|
||||
w.webContents.openDevTools()
|
||||
await promise
|
||||
expect(devtools.webContents.getURL().startsWith('devtools://devtools')).to.be.true()
|
||||
const result = await devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name')
|
||||
expect(result).to.equal('InspectorFrontendHostImpl')
|
||||
devtools.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isFocused() API', () => {
|
||||
it('returns false when the window is hidden', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await w.loadURL('about:blank')
|
||||
expect(w.isVisible()).to.be.false()
|
||||
expect(w.webContents.isFocused()).to.be.false()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isCurrentlyAudible() API', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('returns whether audio is playing', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await w.loadURL('about:blank')
|
||||
await w.webContents.executeJavaScript(`
|
||||
window.context = new AudioContext
|
||||
// Start in suspended state, because of the
|
||||
// new web audio api policy.
|
||||
context.suspend()
|
||||
window.oscillator = context.createOscillator()
|
||||
oscillator.connect(context.destination)
|
||||
oscillator.start()
|
||||
`)
|
||||
let p = emittedOnce(w.webContents, '-audio-state-changed')
|
||||
w.webContents.executeJavaScript('context.resume()')
|
||||
await p
|
||||
expect(w.webContents.isCurrentlyAudible()).to.be.true()
|
||||
p = emittedOnce(w.webContents, '-audio-state-changed')
|
||||
w.webContents.executeJavaScript('oscillator.stop()')
|
||||
await p
|
||||
expect(w.webContents.isCurrentlyAudible()).to.be.false()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWebPreferences() API', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('should not crash when called for devTools webContents', (done) => {
|
||||
const w = new BrowserWindow({show: false})
|
||||
w.webContents.openDevTools()
|
||||
w.webContents.once('devtools-opened', () => {
|
||||
expect(w.webContents.devToolsWebContents.getWebPreferences()).to.be.null()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('openDevTools() API', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('can show window with activation', async () => {
|
||||
const w = new BrowserWindow({show: false})
|
||||
const focused = emittedOnce(w, 'focus')
|
||||
w.show()
|
||||
await focused
|
||||
expect(w.isFocused()).to.be.true()
|
||||
w.webContents.openDevTools({ mode: 'detach', activate: true })
|
||||
await emittedOnce(w.webContents, 'devtools-focused')
|
||||
await emittedOnce(w.webContents, 'devtools-focused')
|
||||
await emittedOnce(w.webContents, 'devtools-opened')
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(w.isFocused()).to.be.false()
|
||||
})
|
||||
|
||||
it('can show window without activation', async () => {
|
||||
const w = new BrowserWindow({show: false})
|
||||
const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened')
|
||||
w.webContents.openDevTools({ mode: 'detach', activate: false })
|
||||
await devtoolsOpened
|
||||
expect(w.webContents.isDevToolsOpened()).to.be.true()
|
||||
})
|
||||
})
|
||||
|
||||
describe('before-input-event event', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('can prevent document keyboard events', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'key-events.html'))
|
||||
const keyDown = new Promise(resolve => {
|
||||
ipcMain.once('keydown', (event, key) => resolve(key))
|
||||
})
|
||||
w.webContents.once('before-input-event', (event, input) => {
|
||||
if ('a' === input.key) event.preventDefault()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'a' })
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'b' })
|
||||
expect(await keyDown).to.equal('b')
|
||||
})
|
||||
|
||||
it('has the correct properties', async () => {
|
||||
const w = new BrowserWindow({show: false})
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))
|
||||
const testBeforeInput = async (opts: any) => {
|
||||
const modifiers = []
|
||||
if (opts.shift) modifiers.push('shift')
|
||||
if (opts.control) modifiers.push('control')
|
||||
if (opts.alt) modifiers.push('alt')
|
||||
if (opts.meta) modifiers.push('meta')
|
||||
if (opts.isAutoRepeat) modifiers.push('isAutoRepeat')
|
||||
|
||||
const p = emittedOnce(w.webContents, 'before-input-event')
|
||||
w.webContents.sendInputEvent({
|
||||
type: opts.type,
|
||||
keyCode: opts.keyCode,
|
||||
modifiers: modifiers as any
|
||||
})
|
||||
const [, input] = await p
|
||||
|
||||
expect(input.type).to.equal(opts.type)
|
||||
expect(input.key).to.equal(opts.key)
|
||||
expect(input.code).to.equal(opts.code)
|
||||
expect(input.isAutoRepeat).to.equal(opts.isAutoRepeat)
|
||||
expect(input.shift).to.equal(opts.shift)
|
||||
expect(input.control).to.equal(opts.control)
|
||||
expect(input.alt).to.equal(opts.alt)
|
||||
expect(input.meta).to.equal(opts.meta)
|
||||
}
|
||||
await testBeforeInput({
|
||||
type: 'keyDown',
|
||||
key: 'A',
|
||||
code: 'KeyA',
|
||||
keyCode: 'a',
|
||||
shift: true,
|
||||
control: true,
|
||||
alt: true,
|
||||
meta: true,
|
||||
isAutoRepeat: true
|
||||
})
|
||||
await testBeforeInput({
|
||||
type: 'keyUp',
|
||||
key: '.',
|
||||
code: 'Period',
|
||||
keyCode: '.',
|
||||
shift: false,
|
||||
control: true,
|
||||
alt: true,
|
||||
meta: false,
|
||||
isAutoRepeat: false
|
||||
})
|
||||
await testBeforeInput({
|
||||
type: 'keyUp',
|
||||
key: '!',
|
||||
code: 'Digit1',
|
||||
keyCode: '1',
|
||||
shift: true,
|
||||
control: false,
|
||||
alt: false,
|
||||
meta: true,
|
||||
isAutoRepeat: false
|
||||
})
|
||||
await testBeforeInput({
|
||||
type: 'keyUp',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
keyCode: 'Tab',
|
||||
shift: false,
|
||||
control: true,
|
||||
alt: false,
|
||||
meta: false,
|
||||
isAutoRepeat: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// On Mac, zooming isn't done with the mouse wheel.
|
||||
ifdescribe(process.platform !== 'darwin')('zoom-changed', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('is emitted with the correct zooming info', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'))
|
||||
|
||||
const testZoomChanged = async ({ zoomingIn }: { zoomingIn: boolean }) => {
|
||||
w.webContents.sendInputEvent({
|
||||
type: 'mouseWheel',
|
||||
x: 300,
|
||||
y: 300,
|
||||
deltaX: 0,
|
||||
deltaY: zoomingIn ? 1 : -1,
|
||||
wheelTicksX: 0,
|
||||
wheelTicksY: zoomingIn ? 1 : -1,
|
||||
modifiers: ['control', 'meta']
|
||||
})
|
||||
|
||||
const [, zoomDirection] = await emittedOnce(w.webContents, 'zoom-changed')
|
||||
expect(zoomDirection).to.equal(zoomingIn ? 'in' : 'out')
|
||||
// Apparently we get two zoom-changed events??
|
||||
await emittedOnce(w.webContents, 'zoom-changed')
|
||||
}
|
||||
|
||||
await testZoomChanged({ zoomingIn: true })
|
||||
await testZoomChanged({ zoomingIn: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendInputEvent(event)', () => {
|
||||
let w: BrowserWindow
|
||||
beforeEach(async () => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'key-events.html'))
|
||||
})
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
it('can send keydown events', (done) => {
|
||||
ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('a')
|
||||
expect(code).to.equal('KeyA')
|
||||
expect(keyCode).to.equal(65)
|
||||
expect(shiftKey).to.be.false()
|
||||
expect(ctrlKey).to.be.false()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
|
||||
})
|
||||
|
||||
it('can send keydown events with modifiers', (done) => {
|
||||
ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('Z')
|
||||
expect(code).to.equal('KeyZ')
|
||||
expect(keyCode).to.equal(90)
|
||||
expect(shiftKey).to.be.true()
|
||||
expect(ctrlKey).to.be.true()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
|
||||
})
|
||||
|
||||
it('can send keydown events with special keys', (done) => {
|
||||
ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('Tab')
|
||||
expect(code).to.equal('Tab')
|
||||
expect(keyCode).to.equal(9)
|
||||
expect(shiftKey).to.be.false()
|
||||
expect(ctrlKey).to.be.false()
|
||||
expect(altKey).to.be.true()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab', modifiers: ['alt'] })
|
||||
})
|
||||
|
||||
it('can send char events', (done) => {
|
||||
ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('a')
|
||||
expect(code).to.equal('KeyA')
|
||||
expect(keyCode).to.equal(65)
|
||||
expect(shiftKey).to.be.false()
|
||||
expect(ctrlKey).to.be.false()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
|
||||
w.webContents.sendInputEvent({ type: 'char', keyCode: 'A' })
|
||||
})
|
||||
|
||||
it('can send char events with modifiers', (done) => {
|
||||
ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('Z')
|
||||
expect(code).to.equal('KeyZ')
|
||||
expect(keyCode).to.equal(90)
|
||||
expect(shiftKey).to.be.true()
|
||||
expect(ctrlKey).to.be.true()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z' })
|
||||
w.webContents.sendInputEvent({ type: 'char', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('insertCSS', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('supports inserting CSS', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.loadURL('about:blank')
|
||||
await w.webContents.insertCSS('body { background-repeat: round; }')
|
||||
const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
|
||||
expect(result).to.equal('round')
|
||||
})
|
||||
|
||||
it('supports removing inserted CSS', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.loadURL('about:blank')
|
||||
const key = await w.webContents.insertCSS('body { background-repeat: round; }')
|
||||
await w.webContents.removeInsertedCSS(key)
|
||||
const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
|
||||
expect(result).to.equal('repeat')
|
||||
})
|
||||
})
|
||||
|
||||
describe('inspectElement()', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('supports inspecting an element in the devtools', (done) => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
w.loadURL('about:blank')
|
||||
w.webContents.once('devtools-opened', () => { done() })
|
||||
w.webContents.inspectElement(10, 10)
|
||||
})
|
||||
})
|
||||
|
||||
describe('startDrag({file, icon})', () => {
|
||||
it('throws errors for a missing file or a missing/empty icon', () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
expect(() => {
|
||||
w.webContents.startDrag({ icon: path.join(fixturesPath, 'assets', 'logo.png') } as any)
|
||||
}).to.throw(`Must specify either 'file' or 'files' option`)
|
||||
|
||||
expect(() => {
|
||||
w.webContents.startDrag({ file: __filename } as any)
|
||||
}).to.throw(`Must specify 'icon' option`)
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
expect(() => {
|
||||
w.webContents.startDrag({ file: __filename, icon: __filename })
|
||||
}).to.throw(`Must specify non-empty 'icon' option`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('focus()', () => {
|
||||
describe('when the web contents is hidden', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('does not blur the focused window', (done) => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
ipcMain.once('answer', (event, parentFocused, childFocused) => {
|
||||
expect(parentFocused).to.be.true()
|
||||
expect(childFocused).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.show()
|
||||
w.loadFile(path.join(fixturesPath, 'pages', 'focus-web-contents.html'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOSProcessId()', () => {
|
||||
afterEach(closeAllWindows)
|
||||
it('returns a valid procress id', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
expect(w.webContents.getOSProcessId()).to.equal(0)
|
||||
|
||||
await w.loadURL('about:blank')
|
||||
expect(w.webContents.getOSProcessId()).to.be.above(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('zoom api', () => {
|
||||
const scheme = (global as any).standardScheme
|
||||
const hostZoomMap: Record<string, number> = {
|
||||
host1: 0.3,
|
||||
host2: 0.7,
|
||||
host3: 0.2
|
||||
}
|
||||
|
||||
before((done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
protocol.registerStringProtocol(scheme, (request, callback) => {
|
||||
const response = `<script>
|
||||
const {ipcRenderer, remote} = require('electron')
|
||||
ipcRenderer.send('set-zoom', window.location.hostname)
|
||||
ipcRenderer.on(window.location.hostname + '-zoom-set', () => {
|
||||
const { zoomLevel } = remote.getCurrentWebContents()
|
||||
ipcRenderer.send(window.location.hostname + '-zoom-level', zoomLevel)
|
||||
})
|
||||
</script>`
|
||||
callback({ data: response, mimeType: 'text/html' })
|
||||
}, (error) => done(error))
|
||||
})
|
||||
|
||||
after((done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
protocol.unregisterProtocol(scheme, (error) => done(error))
|
||||
})
|
||||
|
||||
afterEach(closeAllWindows)
|
||||
|
||||
// TODO(codebytere): remove in Electron v8.0.0
|
||||
it('can set the correct zoom level (functions)', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
try {
|
||||
await w.loadURL('about:blank')
|
||||
const zoomLevel = w.webContents.getZoomLevel()
|
||||
expect(zoomLevel).to.eql(0.0)
|
||||
w.webContents.setZoomLevel(0.5)
|
||||
const newZoomLevel = w.webContents.getZoomLevel()
|
||||
expect(newZoomLevel).to.eql(0.5)
|
||||
} finally {
|
||||
w.webContents.setZoomLevel(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('can set the correct zoom level', async () => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
try {
|
||||
await w.loadURL('about:blank')
|
||||
const zoomLevel = w.webContents.zoomLevel
|
||||
expect(zoomLevel).to.eql(0.0)
|
||||
w.webContents.zoomLevel = 0.5
|
||||
const newZoomLevel = w.webContents.zoomLevel
|
||||
expect(newZoomLevel).to.eql(0.5)
|
||||
} finally {
|
||||
w.webContents.zoomLevel = 0
|
||||
}
|
||||
})
|
||||
|
||||
it('can persist zoom level across navigation', (done) => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
let finalNavigation = false
|
||||
ipcMain.on('set-zoom', (e, host) => {
|
||||
const zoomLevel = hostZoomMap[host]
|
||||
if (!finalNavigation) w.webContents.zoomLevel = zoomLevel
|
||||
e.sender.send(`${host}-zoom-set`)
|
||||
})
|
||||
ipcMain.on('host1-zoom-level', (e, zoomLevel) => {
|
||||
const expectedZoomLevel = hostZoomMap.host1
|
||||
expect(zoomLevel).to.equal(expectedZoomLevel)
|
||||
if (finalNavigation) {
|
||||
done()
|
||||
} else {
|
||||
w.loadURL(`${scheme}://host2`)
|
||||
}
|
||||
})
|
||||
ipcMain.once('host2-zoom-level', (e, zoomLevel) => {
|
||||
const expectedZoomLevel = hostZoomMap.host2
|
||||
expect(zoomLevel).to.equal(expectedZoomLevel)
|
||||
finalNavigation = true
|
||||
w.webContents.goBack()
|
||||
})
|
||||
w.loadURL(`${scheme}://host1`)
|
||||
})
|
||||
|
||||
it('can propagate zoom level across same session', (done) => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
const w2 = new BrowserWindow({ show: false })
|
||||
w2.webContents.on('did-finish-load', () => {
|
||||
const zoomLevel1 = w.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(hostZoomMap.host3)
|
||||
|
||||
const zoomLevel2 = w2.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(zoomLevel2)
|
||||
w2.setClosable(true)
|
||||
w2.close()
|
||||
done()
|
||||
})
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
w.webContents.zoomLevel = hostZoomMap.host3
|
||||
w2.loadURL(`${scheme}://host3`)
|
||||
})
|
||||
w.loadURL(`${scheme}://host3`)
|
||||
})
|
||||
|
||||
it('cannot propagate zoom level across different session', (done) => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
const w2 = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
partition: 'temp'
|
||||
}
|
||||
})
|
||||
const protocol = w2.webContents.session.protocol
|
||||
protocol.registerStringProtocol(scheme, (request, callback) => {
|
||||
callback('hello')
|
||||
}, (error) => {
|
||||
if (error) return done(error)
|
||||
w2.webContents.on('did-finish-load', () => {
|
||||
const zoomLevel1 = w.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(hostZoomMap.host3)
|
||||
|
||||
const zoomLevel2 = w2.webContents.zoomLevel
|
||||
expect(zoomLevel2).to.equal(0)
|
||||
expect(zoomLevel1).to.not.equal(zoomLevel2)
|
||||
|
||||
protocol.unregisterProtocol(scheme, (error) => {
|
||||
if (error) return done(error)
|
||||
w2.setClosable(true)
|
||||
w2.close()
|
||||
done()
|
||||
})
|
||||
})
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
w.webContents.zoomLevel = hostZoomMap.host3
|
||||
w2.loadURL(`${scheme}://host3`)
|
||||
})
|
||||
w.loadURL(`${scheme}://host3`)
|
||||
})
|
||||
})
|
||||
|
||||
it('can persist when it contains iframe', (done) => {
|
||||
const w = new BrowserWindow({ show: false })
|
||||
const server = http.createServer((req, res) => {
|
||||
setTimeout(() => {
|
||||
res.end()
|
||||
}, 200)
|
||||
})
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const url = 'http://127.0.0.1:' + (server.address() as AddressInfo).port
|
||||
const content = `<iframe src=${url}></iframe>`
|
||||
w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
|
||||
if (!isMainFrame) {
|
||||
const zoomLevel = w.webContents.zoomLevel
|
||||
expect(zoomLevel).to.equal(2.0)
|
||||
|
||||
w.webContents.zoomLevel = 0
|
||||
server.close()
|
||||
done()
|
||||
}
|
||||
})
|
||||
w.webContents.on('dom-ready', () => {
|
||||
w.webContents.zoomLevel = 2.0
|
||||
})
|
||||
w.loadURL(`data:text/html,${content}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot propagate when used with webframe', (done) => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
let finalZoomLevel = 0
|
||||
const w2 = new BrowserWindow({
|
||||
show: false
|
||||
})
|
||||
w2.webContents.on('did-finish-load', () => {
|
||||
const zoomLevel1 = w.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(finalZoomLevel)
|
||||
|
||||
const zoomLevel2 = w2.webContents.zoomLevel
|
||||
expect(zoomLevel2).to.equal(0)
|
||||
expect(zoomLevel1).to.not.equal(zoomLevel2)
|
||||
|
||||
w2.setClosable(true)
|
||||
w2.close()
|
||||
done()
|
||||
})
|
||||
ipcMain.once('temporary-zoom-set', (e, zoomLevel) => {
|
||||
w2.loadFile(path.join(fixturesPath, 'pages', 'c.html'))
|
||||
finalZoomLevel = zoomLevel
|
||||
})
|
||||
w.loadFile(path.join(fixturesPath, 'pages', 'webframe-zoom.html'))
|
||||
})
|
||||
|
||||
it('cannot persist zoom level after navigation with webFrame', (done) => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
let initialNavigation = true
|
||||
const source = `
|
||||
const {ipcRenderer, webFrame} = require('electron')
|
||||
webFrame.setZoomLevel(0.6)
|
||||
ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel())
|
||||
`
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
if (initialNavigation) {
|
||||
w.webContents.executeJavaScript(source)
|
||||
} else {
|
||||
const zoomLevel = w.webContents.zoomLevel
|
||||
expect(zoomLevel).to.equal(0)
|
||||
done()
|
||||
}
|
||||
})
|
||||
ipcMain.once('zoom-level-set', (e, zoomLevel) => {
|
||||
expect(zoomLevel).to.equal(0.6)
|
||||
w.loadFile(path.join(fixturesPath, 'pages', 'd.html'))
|
||||
initialNavigation = false
|
||||
})
|
||||
w.loadFile(path.join(fixturesPath, 'pages', 'c.html'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
'use strict'
|
||||
import { expect } from 'chai'
|
||||
import * as ChildProcess from 'child_process'
|
||||
import * as path from 'path'
|
||||
import { emittedOnce } from './events-helpers'
|
||||
import { closeWindow } from './window-helpers'
|
||||
|
||||
const chai = require('chai')
|
||||
const ChildProcess = require('child_process')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const path = require('path')
|
||||
const { emittedOnce } = require('./events-helpers')
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { webContents, TopLevelWindow, WebContentsView } = remote
|
||||
|
||||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
import { webContents, TopLevelWindow, WebContentsView } from 'electron'
|
||||
|
||||
describe('WebContentsView', () => {
|
||||
let w = null
|
||||
afterEach(() => closeWindow(w).then(() => { w = null }))
|
||||
let w: TopLevelWindow
|
||||
afterEach(() => closeWindow(w as any).then(() => { w = null as unknown as TopLevelWindow }))
|
||||
|
||||
it('can be used as content view', () => {
|
||||
const web = webContents.create({})
|
||||
const web = (webContents as any).create({})
|
||||
w = new TopLevelWindow({ show: false })
|
||||
w.setContentView(new WebContentsView(web))
|
||||
})
|
||||
|
||||
it('prevents adding same WebContents', () => {
|
||||
const web = webContents.create({})
|
||||
const web = (webContents as any).create({})
|
||||
w = new TopLevelWindow({ show: false })
|
||||
w.setContentView(new WebContentsView(web))
|
||||
expect(() => {
|
||||
@@ -35,7 +28,7 @@ describe('WebContentsView', () => {
|
||||
describe('new WebContentsView()', () => {
|
||||
it('does not crash on exit', async () => {
|
||||
const appPath = path.join(__dirname, 'fixtures', 'api', 'leak-exit-webcontentsview.js')
|
||||
const electronPath = remote.getGlobal('process').execPath
|
||||
const electronPath = process.execPath
|
||||
const appProcess = ChildProcess.spawn(electronPath, [appPath])
|
||||
const [code] = await emittedOnce(appProcess, 'close')
|
||||
expect(code).to.equal(0)
|
||||
@@ -1,16 +1,11 @@
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
import { expect } from 'chai'
|
||||
import * as http from 'http'
|
||||
import * as qs from 'querystring'
|
||||
import * as path from 'path'
|
||||
import { session, WebContents, webContents } from 'electron'
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
const http = require('http')
|
||||
const qs = require('querystring')
|
||||
const remote = require('electron').remote
|
||||
const session = remote.session
|
||||
|
||||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
|
||||
/* The whole webRequest API doesn't use standard callbacks */
|
||||
/* eslint-disable standard/no-callback-literal */
|
||||
const fixturesPath = path.resolve(__dirname, '..', 'spec', 'fixtures')
|
||||
|
||||
describe('webRequest module', () => {
|
||||
const ses = session.defaultSession
|
||||
@@ -28,12 +23,12 @@ describe('webRequest module', () => {
|
||||
res.end(content)
|
||||
}
|
||||
})
|
||||
let defaultURL = null
|
||||
let defaultURL: string
|
||||
|
||||
before((done) => {
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const port = server.address().port
|
||||
defaultURL = 'http://127.0.0.1:' + port + '/'
|
||||
const port = (server.address() as AddressInfo).port
|
||||
defaultURL = `http://127.0.0.1:${port}/`
|
||||
done()
|
||||
})
|
||||
})
|
||||
@@ -42,48 +37,43 @@ describe('webRequest module', () => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
let contents: WebContents = null as unknown as WebContents
|
||||
// NB. sandbox: true is used because it makes navigations much (~8x) faster.
|
||||
before(async () => {
|
||||
contents = (webContents as any).create({sandbox: true})
|
||||
await contents.loadFile(path.join(fixturesPath, 'pages', 'jquery.html'))
|
||||
})
|
||||
after(() => (contents as any).destroy())
|
||||
|
||||
async function ajax (url: string, options = {}) {
|
||||
return contents.executeJavaScript(`ajax("${url}", ${JSON.stringify(options)})`)
|
||||
}
|
||||
|
||||
describe('webRequest.onBeforeRequest', () => {
|
||||
afterEach(() => {
|
||||
ses.webRequest.onBeforeRequest(null)
|
||||
})
|
||||
|
||||
it('can cancel the request', (done) => {
|
||||
it('can cancel the request', async () => {
|
||||
ses.webRequest.onBeforeRequest((details, callback) => {
|
||||
callback({
|
||||
cancel: true
|
||||
})
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: () => {
|
||||
done('unexpected success')
|
||||
},
|
||||
error: () => {
|
||||
done()
|
||||
}
|
||||
})
|
||||
await expect(ajax(defaultURL)).to.eventually.be.rejectedWith('404')
|
||||
})
|
||||
|
||||
it('can filter URLs', (done) => {
|
||||
it('can filter URLs', async () => {
|
||||
const filter = { urls: [defaultURL + 'filter/*'] }
|
||||
ses.webRequest.onBeforeRequest(filter, (details, callback) => {
|
||||
callback({ cancel: true })
|
||||
})
|
||||
$.ajax({
|
||||
url: `${defaultURL}nofilter/test`,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/nofilter/test')
|
||||
$.ajax({
|
||||
url: `${defaultURL}filter/test`,
|
||||
success: () => done('unexpected success'),
|
||||
error: () => done()
|
||||
})
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(`${defaultURL}nofilter/test`)
|
||||
expect(data).to.equal('/nofilter/test')
|
||||
await expect(ajax(`${defaultURL}filter/test`)).to.eventually.be.rejectedWith('404')
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onBeforeRequest((details, callback) => {
|
||||
expect(details.id).to.be.a('number')
|
||||
expect(details.timestamp).to.be.a('number')
|
||||
@@ -94,17 +84,11 @@ describe('webRequest module', () => {
|
||||
expect(details.uploadData).to.be.undefined()
|
||||
callback({})
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
|
||||
it('receives post data in details object', (done) => {
|
||||
it('receives post data in details object', async () => {
|
||||
const postData = {
|
||||
name: 'post test',
|
||||
type: 'string'
|
||||
@@ -117,16 +101,13 @@ describe('webRequest module', () => {
|
||||
expect(data).to.deep.equal(postData)
|
||||
callback({ cancel: true })
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
await expect(ajax(defaultURL, {
|
||||
type: 'POST',
|
||||
data: postData,
|
||||
success: () => {},
|
||||
error: () => done()
|
||||
})
|
||||
})).to.eventually.be.rejectedWith('404')
|
||||
})
|
||||
|
||||
it('can redirect the request', (done) => {
|
||||
it('can redirect the request', async () => {
|
||||
ses.webRequest.onBeforeRequest((details, callback) => {
|
||||
if (details.url === defaultURL) {
|
||||
callback({ redirectURL: `${defaultURL}redirect` })
|
||||
@@ -134,14 +115,8 @@ describe('webRequest module', () => {
|
||||
callback({})
|
||||
}
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/redirect')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/redirect')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -150,40 +125,27 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onBeforeSendHeaders(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
expect(details.requestHeaders).to.be.an('object')
|
||||
expect(details.requestHeaders['Foo.Bar']).to.equal('baz')
|
||||
callback({})
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
headers: { 'Foo.Bar': 'baz' },
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL, { headers: { 'Foo.Bar': 'baz' } })
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
|
||||
it('can change the request headers', (done) => {
|
||||
it('can change the request headers', async () => {
|
||||
ses.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
const requestHeaders = details.requestHeaders
|
||||
requestHeaders.Accept = '*/*;test/header'
|
||||
callback({ requestHeaders: requestHeaders })
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/header/received')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/header/received')
|
||||
})
|
||||
|
||||
it('resets the whole headers', (done) => {
|
||||
it('resets the whole headers', async () => {
|
||||
const requestHeaders = {
|
||||
Test: 'header'
|
||||
}
|
||||
@@ -192,12 +154,8 @@ describe('webRequest module', () => {
|
||||
})
|
||||
ses.webRequest.onSendHeaders((details) => {
|
||||
expect(details.requestHeaders).to.deep.equal(requestHeaders)
|
||||
done()
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
await ajax(defaultURL)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -206,18 +164,12 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onSendHeaders(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onSendHeaders((details) => {
|
||||
expect(details.requestHeaders).to.be.an('object')
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -226,71 +178,46 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onHeadersReceived(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onHeadersReceived((details, callback) => {
|
||||
expect(details.statusLine).to.equal('HTTP/1.1 200 OK')
|
||||
expect(details.statusCode).to.equal(200)
|
||||
expect(details.responseHeaders['Custom']).to.deep.equal(['Header'])
|
||||
expect(details.responseHeaders!['Custom']).to.deep.equal(['Header'])
|
||||
callback({})
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
|
||||
it('can change the response header', (done) => {
|
||||
it('can change the response header', async () => {
|
||||
ses.webRequest.onHeadersReceived((details, callback) => {
|
||||
const responseHeaders = details.responseHeaders
|
||||
responseHeaders['Custom'] = ['Changed']
|
||||
const responseHeaders = details.responseHeaders!
|
||||
responseHeaders['Custom'] = ['Changed'] as any
|
||||
callback({ responseHeaders: responseHeaders })
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data, status, xhr) => {
|
||||
expect(xhr.getResponseHeader('Custom')).to.equal('Changed')
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { headers } = await ajax(defaultURL)
|
||||
expect(headers).to.match(/^custom: Changed$/m)
|
||||
})
|
||||
|
||||
it('does not change header by default', (done) => {
|
||||
it('does not change header by default', async () => {
|
||||
ses.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({})
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data, status, xhr) => {
|
||||
expect(xhr.getResponseHeader('Custom')).to.equal('Header')
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data, headers } = await ajax(defaultURL)
|
||||
expect(headers).to.match(/^custom: Header$/m)
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
|
||||
it('follows server redirect', (done) => {
|
||||
it('follows server redirect', async () => {
|
||||
ses.webRequest.onHeadersReceived((details, callback) => {
|
||||
const responseHeaders = details.responseHeaders
|
||||
callback({ responseHeaders: responseHeaders })
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL + 'serverRedirect',
|
||||
success: (data, status, xhr) => {
|
||||
expect(xhr.getResponseHeader('Custom')).to.equal('Header')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { headers } = await ajax(defaultURL + 'serverRedirect')
|
||||
expect(headers).to.match(/^custom: Header$/m)
|
||||
})
|
||||
|
||||
it('can change the header status', (done) => {
|
||||
it('can change the header status', async () => {
|
||||
ses.webRequest.onHeadersReceived((details, callback) => {
|
||||
const responseHeaders = details.responseHeaders
|
||||
callback({
|
||||
@@ -298,14 +225,19 @@ describe('webRequest module', () => {
|
||||
statusLine: 'HTTP/1.1 404 Not Found'
|
||||
})
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data, status, xhr) => {},
|
||||
error: (xhr, errorType) => {
|
||||
expect(xhr.getResponseHeader('Custom')).to.equal('Header')
|
||||
done()
|
||||
const { headers } = await contents.executeJavaScript(`new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
...${JSON.stringify({url: defaultURL})},
|
||||
success: (data, status, request) => {
|
||||
reject(new Error('expected failure'))
|
||||
},
|
||||
error: (xhr) => {
|
||||
resolve({ headers: xhr.getAllResponseHeaders() })
|
||||
}
|
||||
}
|
||||
})
|
||||
$.ajax(options)
|
||||
})`)
|
||||
expect(headers).to.match(/^custom: Header$/m)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -314,22 +246,16 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onResponseStarted(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onResponseStarted((details) => {
|
||||
expect(details.fromCache).to.be.a('boolean')
|
||||
expect(details.statusLine).to.equal('HTTP/1.1 200 OK')
|
||||
expect(details.statusCode).to.equal(200)
|
||||
expect(details.responseHeaders['Custom']).to.deep.equal(['Header'])
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data, status, xhr) => {
|
||||
expect(xhr.getResponseHeader('Custom')).to.equal('Header')
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
expect(details.responseHeaders!['Custom']).to.deep.equal(['Header'])
|
||||
})
|
||||
const { data, headers } = await ajax(defaultURL)
|
||||
expect(headers).to.match(/^custom: Header$/m)
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -339,7 +265,7 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onBeforeRequest(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
const redirectURL = defaultURL + 'redirect'
|
||||
ses.webRequest.onBeforeRequest((details, callback) => {
|
||||
if (details.url === defaultURL) {
|
||||
@@ -354,14 +280,8 @@ describe('webRequest module', () => {
|
||||
expect(details.statusCode).to.equal(307)
|
||||
expect(details.redirectURL).to.equal(redirectURL)
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/redirect')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/redirect')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -370,20 +290,14 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onCompleted(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onCompleted((details) => {
|
||||
expect(details.fromCache).to.be.a('boolean')
|
||||
expect(details.statusLine).to.equal('HTTP/1.1 200 OK')
|
||||
expect(details.statusCode).to.equal(200)
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: (data) => {
|
||||
expect(data).to.equal('/')
|
||||
done()
|
||||
},
|
||||
error: (xhr, errorType) => done(errorType)
|
||||
})
|
||||
const { data } = await ajax(defaultURL)
|
||||
expect(data).to.equal('/')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -393,18 +307,14 @@ describe('webRequest module', () => {
|
||||
ses.webRequest.onBeforeRequest(null)
|
||||
})
|
||||
|
||||
it('receives details object', (done) => {
|
||||
it('receives details object', async () => {
|
||||
ses.webRequest.onBeforeRequest((details, callback) => {
|
||||
callback({ cancel: true })
|
||||
})
|
||||
ses.webRequest.onErrorOccurred((details) => {
|
||||
expect(details.error).to.equal('net::ERR_BLOCKED_BY_CLIENT')
|
||||
done()
|
||||
})
|
||||
$.ajax({
|
||||
url: defaultURL,
|
||||
success: () => done('unexpected success')
|
||||
})
|
||||
await expect(ajax(defaultURL)).to.eventually.be.rejectedWith('404')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,17 +1,16 @@
|
||||
const { expect } = require('chai')
|
||||
const { remote } = require('electron')
|
||||
const path = require('path')
|
||||
import { expect } from 'chai'
|
||||
import * as path from 'path'
|
||||
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
const { emittedNTimes } = require('./events-helpers')
|
||||
import { closeWindow } from './window-helpers'
|
||||
import { emittedNTimes } from './events-helpers'
|
||||
|
||||
const { BrowserWindow, ipcMain } = remote
|
||||
import { BrowserWindow, ipcMain, WebContents } from 'electron'
|
||||
|
||||
describe('chrome extension content scripts', () => {
|
||||
const fixtures = path.resolve(__dirname, 'fixtures')
|
||||
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures')
|
||||
const extensionPath = path.resolve(fixtures, 'extensions')
|
||||
|
||||
const addExtension = (name) => BrowserWindow.addExtension(path.resolve(extensionPath, name))
|
||||
const addExtension = (name: string) => BrowserWindow.addExtension(path.resolve(extensionPath, name))
|
||||
const removeAllExtensions = () => {
|
||||
Object.keys(BrowserWindow.getExtensions()).map(extName => {
|
||||
BrowserWindow.removeExtension(extName)
|
||||
@@ -19,7 +18,7 @@ describe('chrome extension content scripts', () => {
|
||||
}
|
||||
|
||||
let responseIdCounter = 0
|
||||
const executeJavaScriptInFrame = (webContents, frameRoutingId, code) => {
|
||||
const executeJavaScriptInFrame = (webContents: WebContents, frameRoutingId: number, code: string) => {
|
||||
return new Promise(resolve => {
|
||||
const responseId = responseIdCounter++
|
||||
ipcMain.once(`executeJavaScriptInFrame_${responseId}`, (event, result) => {
|
||||
@@ -29,9 +28,9 @@ describe('chrome extension content scripts', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const generateTests = (sandboxEnabled, contextIsolationEnabled) => {
|
||||
const generateTests = (sandboxEnabled: boolean, contextIsolationEnabled: boolean) => {
|
||||
describe(`with sandbox ${sandboxEnabled ? 'enabled' : 'disabled'} and context isolation ${contextIsolationEnabled ? 'enabled' : 'disabled'}`, () => {
|
||||
let w
|
||||
let w: BrowserWindow
|
||||
|
||||
describe('supports "run_at" option', () => {
|
||||
beforeEach(async () => {
|
||||
@@ -49,7 +48,7 @@ describe('chrome extension content scripts', () => {
|
||||
|
||||
afterEach(() => {
|
||||
removeAllExtensions()
|
||||
return closeWindow(w).then(() => { w = null })
|
||||
return closeWindow(w).then(() => { w = null as unknown as BrowserWindow })
|
||||
})
|
||||
|
||||
it('should run content script at document_start', () => {
|
||||
@@ -107,7 +106,7 @@ describe('chrome extension content scripts', () => {
|
||||
|
||||
afterEach(() =>
|
||||
closeWindow(w).then(() => {
|
||||
w = null
|
||||
w = null as unknown as BrowserWindow
|
||||
})
|
||||
)
|
||||
|
||||
@@ -118,7 +117,7 @@ describe('chrome extension content scripts', () => {
|
||||
await Promise.all(
|
||||
frameEvents.map(async frameEvent => {
|
||||
const [, isMainFrame, , frameRoutingId] = frameEvent
|
||||
const result = await executeJavaScriptInFrame(
|
||||
const result: any = await executeJavaScriptInFrame(
|
||||
w.webContents,
|
||||
frameRoutingId,
|
||||
`(() => {
|
||||
@@ -25,8 +25,10 @@ app.on('window-all-closed', () => null)
|
||||
app.commandLine.appendSwitch('ignore-certificate-errors')
|
||||
|
||||
global.standardScheme = 'app'
|
||||
global.zoomScheme = 'zoom'
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: global.standardScheme, privileges: { standard: true, secure: true } },
|
||||
{ scheme: global.zoomScheme, privileges: { standard: true, secure: true } },
|
||||
{ scheme: 'cors-blob', privileges: { corsEnabled: true, supportFetchAPI: true } },
|
||||
{ scheme: 'cors', privileges: { corsEnabled: true, supportFetchAPI: true } },
|
||||
{ scheme: 'no-cors', privileges: { supportFetchAPI: true } },
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('feature-string parsing', () => {
|
||||
it('is indifferent to whitespace around keys and values', () => {
|
||||
const parseFeaturesString = require('../lib/common/parse-features-string')
|
||||
const checkParse = (string, parsed) => {
|
||||
const features = {}
|
||||
parseFeaturesString(string, (k, v) => { features[k] = v })
|
||||
const checkParse = (string: string, parsed: Record<string, string | boolean>) => {
|
||||
const features: Record<string, string | boolean> = {}
|
||||
parseFeaturesString(string, (k: string, v: any) => { features[k] = v })
|
||||
expect(features).to.deep.equal(parsed)
|
||||
}
|
||||
checkParse('a=yes,c=d', { a: true, c: 'd' })
|
||||
235
spec-main/security-warnings-spec.ts
Normal file
235
spec-main/security-warnings-spec.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { expect } from 'chai'
|
||||
import * as http from 'http'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as url from 'url'
|
||||
|
||||
import { BrowserWindow, WebPreferences } from 'electron'
|
||||
|
||||
import { closeWindow } from './window-helpers'
|
||||
import { AddressInfo } from 'net';
|
||||
import { emittedOnce } from './events-helpers';
|
||||
|
||||
describe('security warnings', () => {
|
||||
let server: http.Server
|
||||
let w: BrowserWindow
|
||||
let useCsp = true
|
||||
let serverUrl: string
|
||||
|
||||
before((done) => {
|
||||
// Create HTTP Server
|
||||
server = http.createServer((request, response) => {
|
||||
const uri = url.parse(request.url!).pathname!
|
||||
let filename = path.join(__dirname, '..', 'spec', 'fixtures', 'pages', uri)
|
||||
|
||||
fs.stat(filename, (error, stats) => {
|
||||
if (error) {
|
||||
response.writeHead(404, { 'Content-Type': 'text/plain' })
|
||||
response.end()
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
filename += '/index.html'
|
||||
}
|
||||
|
||||
fs.readFile(filename, 'binary', (err, file) => {
|
||||
if (err) {
|
||||
response.writeHead(404, { 'Content-Type': 'text/plain' })
|
||||
response.end()
|
||||
return
|
||||
}
|
||||
|
||||
const cspHeaders = { 'Content-Security-Policy': `script-src 'self' 'unsafe-inline'` }
|
||||
response.writeHead(200, useCsp ? cspHeaders : undefined)
|
||||
response.write(file, 'binary')
|
||||
response.end()
|
||||
})
|
||||
})
|
||||
}).listen(0, '127.0.0.1', () => {
|
||||
serverUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
// Close server
|
||||
server.close()
|
||||
server = null as unknown as any
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
useCsp = true
|
||||
await closeWindow(w)
|
||||
w = null as unknown as any
|
||||
})
|
||||
|
||||
it('should warn about Node.js integration with remote content', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('Node.js Integration with Remote Content')
|
||||
})
|
||||
|
||||
it('should not warn about Node.js integration with remote content from localhost', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.not.include('Node.js Integration with Remote Content')
|
||||
|
||||
if (message === 'loaded') {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security-onload-message.html`)
|
||||
})
|
||||
|
||||
const generateSpecs = (description: string, webPreferences: WebPreferences) => {
|
||||
describe(description, () => {
|
||||
it('should warn about disabled webSecurity', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('Disabled webSecurity')
|
||||
})
|
||||
|
||||
it('should warn about insecure Content-Security-Policy', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
|
||||
useCsp = false
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('Insecure Content-Security-Policy')
|
||||
})
|
||||
|
||||
it('should warn about allowRunningInsecureContent', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
allowRunningInsecureContent: true,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('allowRunningInsecureContent')
|
||||
})
|
||||
|
||||
it('should warn about experimentalFeatures', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
experimentalFeatures: true,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('experimentalFeatures')
|
||||
})
|
||||
|
||||
it('should warn about enableBlinkFeatures', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
enableBlinkFeatures: 'my-cool-feature',
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('enableBlinkFeatures')
|
||||
})
|
||||
|
||||
it('should warn about allowpopups', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/webview-allowpopups.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('allowpopups')
|
||||
})
|
||||
|
||||
it('should warn about insecure resources', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: { ...webPreferences }
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/insecure-resources.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('Insecure Resources')
|
||||
})
|
||||
|
||||
it('should not warn about loading insecure-resources.html from localhost', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/insecure-resources.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.not.include('insecure-resources.html')
|
||||
})
|
||||
|
||||
it('should warn about enabled remote module with remote content', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`)
|
||||
const [,, message] = await emittedOnce(w.webContents, 'console-message')
|
||||
expect(message).to.include('enableRemoteModule')
|
||||
})
|
||||
|
||||
it('should not warn about enabled remote module with remote content from localhost', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.not.include('enableRemoteModule')
|
||||
|
||||
if (message === 'loaded') {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`${serverUrl}/base-page-security-onload-message.html`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
generateSpecs('without sandbox', {})
|
||||
generateSpecs('with sandbox', { sandbox: true })
|
||||
})
|
||||
@@ -4,10 +4,12 @@ const dirtyChai = require('dirty-chai')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const http = require('http')
|
||||
const { shell, remote } = require('electron')
|
||||
const { BrowserWindow } = remote
|
||||
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
const { emittedOnce } = require('./events-helpers')
|
||||
|
||||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
@@ -47,32 +49,34 @@ describe('shell module', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('opens an external link', done => {
|
||||
const url = 'http://www.example.com'
|
||||
it('opens an external link', async () => {
|
||||
let url = 'http://127.0.0.1'
|
||||
let requestReceived
|
||||
if (process.platform === 'linux') {
|
||||
process.env.BROWSER = '/bin/true'
|
||||
process.env.DE = 'generic'
|
||||
process.env.DISPLAY = ''
|
||||
requestReceived = Promise.resolve()
|
||||
} else if (process.platform === 'darwin') {
|
||||
// On the Mac CI machines, Safari tries to ask for a password to the
|
||||
// code signing keychain we set up to test code signing (see
|
||||
// https://github.com/electron/electron/pull/19969#issuecomment-526278890),
|
||||
// so use a blur event as a crude proxy.
|
||||
w = new BrowserWindow({ show: true })
|
||||
requestReceived = emittedOnce(w, 'blur')
|
||||
} else {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end()
|
||||
})
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
|
||||
requestReceived = new Promise(resolve => server.on('connection', () => resolve()))
|
||||
url = `http://127.0.0.1:${server.address().port}`
|
||||
}
|
||||
|
||||
// Ensure an external window is activated via a new window's blur event
|
||||
w = new BrowserWindow()
|
||||
let promiseResolved = false
|
||||
let blurEventEmitted = false
|
||||
|
||||
w.on('blur', () => {
|
||||
blurEventEmitted = true
|
||||
if (promiseResolved) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
shell.openExternal(url).then(() => {
|
||||
promiseResolved = true
|
||||
if (blurEventEmitted || process.platform === 'linux') {
|
||||
done()
|
||||
}
|
||||
})
|
||||
await Promise.all([
|
||||
shell.openExternal(url),
|
||||
requestReceived
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -40,347 +40,6 @@ describe('webContents module', () => {
|
||||
|
||||
afterEach(() => closeWindow(w).then(() => { w = null }))
|
||||
|
||||
describe('loadURL() promise API', () => {
|
||||
it('resolves when done loading', async () => {
|
||||
await expect(w.loadURL('about:blank')).to.eventually.be.fulfilled
|
||||
})
|
||||
|
||||
it('resolves when done loading a file URL', async () => {
|
||||
await expect(w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))).to.eventually.be.fulfilled
|
||||
})
|
||||
|
||||
it('rejects when failing to load a file URL', async () => {
|
||||
await expect(w.loadURL('file:non-existent')).to.eventually.be.rejected
|
||||
.and.have.property('code', 'ERR_FILE_NOT_FOUND')
|
||||
})
|
||||
|
||||
it('rejects when loading fails due to DNS not resolved', async () => {
|
||||
await expect(w.loadURL('https://err.name.not.resolved')).to.eventually.be.rejected
|
||||
.and.have.property('code', 'ERR_NAME_NOT_RESOLVED')
|
||||
})
|
||||
|
||||
it('rejects when navigation is cancelled due to a bad scheme', async () => {
|
||||
await expect(w.loadURL('bad-scheme://foo')).to.eventually.be.rejected
|
||||
.and.have.property('code', 'ERR_FAILED')
|
||||
})
|
||||
|
||||
it('sets appropriate error information on rejection', async () => {
|
||||
let err
|
||||
try {
|
||||
await w.loadURL('file:non-existent')
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).not.to.be.null()
|
||||
expect(err.code).to.eql('ERR_FILE_NOT_FOUND')
|
||||
expect(err.errno).to.eql(-6)
|
||||
expect(err.url).to.eql(process.platform === 'win32' ? 'file://non-existent/' : 'file:///non-existent')
|
||||
})
|
||||
|
||||
it('rejects if the load is aborted', async () => {
|
||||
const s = http.createServer((req, res) => { /* never complete the request */ })
|
||||
await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = s.address()
|
||||
const p = expect(w.loadURL(`http://127.0.0.1:${port}`)).to.eventually.be.rejectedWith(Error, /ERR_ABORTED/)
|
||||
// load a different file before the first load completes, causing the
|
||||
// first load to be aborted.
|
||||
await w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
|
||||
await p
|
||||
s.close()
|
||||
})
|
||||
|
||||
it("doesn't reject when a subframe fails to load", async () => {
|
||||
let resp = null
|
||||
const s = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.write('<iframe src="http://err.name.not.resolved"></iframe>')
|
||||
resp = res
|
||||
// don't end the response yet
|
||||
})
|
||||
await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = s.address()
|
||||
const p = new Promise(resolve => {
|
||||
w.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
|
||||
if (!isMainFrame) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
const main = w.loadURL(`http://127.0.0.1:${port}`)
|
||||
await p
|
||||
resp.end()
|
||||
await main
|
||||
s.close()
|
||||
})
|
||||
|
||||
it("doesn't resolve when a subframe loads", async () => {
|
||||
let resp = null
|
||||
const s = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.write('<iframe src="data:text/html,hi"></iframe>')
|
||||
resp = res
|
||||
// don't end the response yet
|
||||
})
|
||||
await new Promise(resolve => s.listen(0, '127.0.0.1', resolve))
|
||||
const { port } = s.address()
|
||||
const p = new Promise(resolve => {
|
||||
w.webContents.on('did-frame-finish-load', (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
|
||||
if (!isMainFrame) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
const main = w.loadURL(`http://127.0.0.1:${port}`)
|
||||
await p
|
||||
resp.destroy() // cause the main request to fail
|
||||
await expect(main).to.eventually.be.rejected
|
||||
.and.have.property('errno', -355) // ERR_INCOMPLETE_CHUNKED_ENCODING
|
||||
s.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFocusedWebContents() API', () => {
|
||||
it('returns the focused web contents', (done) => {
|
||||
if (isCi) return done()
|
||||
|
||||
const specWebContents = remote.getCurrentWebContents()
|
||||
expect(specWebContents.id).to.equal(webContents.getFocusedWebContents().id)
|
||||
|
||||
specWebContents.once('devtools-opened', () => {
|
||||
expect(specWebContents.devToolsWebContents.id).to.equal(webContents.getFocusedWebContents().id)
|
||||
specWebContents.closeDevTools()
|
||||
})
|
||||
|
||||
specWebContents.once('devtools-closed', () => {
|
||||
expect(specWebContents.id).to.equal(webContents.getFocusedWebContents().id)
|
||||
done()
|
||||
})
|
||||
|
||||
specWebContents.openDevTools()
|
||||
})
|
||||
|
||||
it('does not crash when called on a detached dev tools window', (done) => {
|
||||
const specWebContents = w.webContents
|
||||
|
||||
specWebContents.once('devtools-opened', () => {
|
||||
expect(() => {
|
||||
webContents.getFocusedWebContents()
|
||||
}).to.not.throw()
|
||||
specWebContents.closeDevTools()
|
||||
})
|
||||
|
||||
specWebContents.once('devtools-closed', () => {
|
||||
expect(() => {
|
||||
webContents.getFocusedWebContents()
|
||||
}).to.not.throw()
|
||||
done()
|
||||
})
|
||||
|
||||
specWebContents.openDevTools({ mode: 'detach' })
|
||||
w.inspectElement(100, 100)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setDevToolsWebContents() API', () => {
|
||||
it('sets arbitrary webContents as devtools', async () => {
|
||||
const devtools = new BrowserWindow({ show: false })
|
||||
const promise = emittedOnce(devtools.webContents, 'dom-ready')
|
||||
w.webContents.setDevToolsWebContents(devtools.webContents)
|
||||
w.webContents.openDevTools()
|
||||
await promise
|
||||
expect(devtools.getURL().startsWith('devtools://devtools')).to.be.true()
|
||||
const result = await devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name')
|
||||
expect(result).to.equal('InspectorFrontendHostImpl')
|
||||
devtools.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isFocused() API', () => {
|
||||
it('returns false when the window is hidden', () => {
|
||||
BrowserWindow.getAllWindows().forEach((window) => {
|
||||
expect(!window.isVisible() && window.webContents.isFocused()).to.be.false()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isCurrentlyAudible() API', () => {
|
||||
it('returns whether audio is playing', async () => {
|
||||
const webContents = remote.getCurrentWebContents()
|
||||
const context = new window.AudioContext()
|
||||
// Start in suspended state, because of the
|
||||
// new web audio api policy.
|
||||
context.suspend()
|
||||
const oscillator = context.createOscillator()
|
||||
oscillator.connect(context.destination)
|
||||
oscillator.start()
|
||||
let p = emittedOnce(webContents, '-audio-state-changed')
|
||||
await context.resume()
|
||||
await p
|
||||
expect(webContents.isCurrentlyAudible()).to.be.true()
|
||||
p = emittedOnce(webContents, '-audio-state-changed')
|
||||
oscillator.stop()
|
||||
await p
|
||||
expect(webContents.isCurrentlyAudible()).to.be.false()
|
||||
oscillator.disconnect()
|
||||
context.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWebPreferences() API', () => {
|
||||
it('should not crash when called for devTools webContents', (done) => {
|
||||
w.webContents.openDevTools()
|
||||
w.webContents.once('devtools-opened', () => {
|
||||
expect(w.devToolsWebContents.getWebPreferences()).to.be.null()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('openDevTools() API', () => {
|
||||
it('can show window with activation', async () => {
|
||||
const focused = emittedOnce(w, 'focus')
|
||||
w.show()
|
||||
await focused
|
||||
expect(w.isFocused()).to.be.true()
|
||||
const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened')
|
||||
w.webContents.openDevTools({ mode: 'detach', activate: true })
|
||||
await devtoolsOpened
|
||||
expect(w.isFocused()).to.be.false()
|
||||
})
|
||||
|
||||
it('can show window without activation', async () => {
|
||||
const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened')
|
||||
w.webContents.openDevTools({ mode: 'detach', activate: false })
|
||||
await devtoolsOpened
|
||||
expect(w.isDevToolsOpened()).to.be.true()
|
||||
})
|
||||
})
|
||||
|
||||
describe('before-input-event event', () => {
|
||||
it('can prevent document keyboard events', async () => {
|
||||
await w.loadFile(path.join(fixtures, 'pages', 'key-events.html'))
|
||||
const keyDown = new Promise(resolve => {
|
||||
ipcMain.once('keydown', (event, key) => resolve(key))
|
||||
})
|
||||
ipcRenderer.sendSync('prevent-next-input-event', 'a', w.webContents.id)
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'a' })
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'b' })
|
||||
expect(await keyDown).to.equal('b')
|
||||
})
|
||||
|
||||
it('has the correct properties', async () => {
|
||||
await w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
|
||||
const testBeforeInput = async (opts) => {
|
||||
const modifiers = []
|
||||
if (opts.shift) modifiers.push('shift')
|
||||
if (opts.control) modifiers.push('control')
|
||||
if (opts.alt) modifiers.push('alt')
|
||||
if (opts.meta) modifiers.push('meta')
|
||||
if (opts.isAutoRepeat) modifiers.push('isAutoRepeat')
|
||||
|
||||
const p = emittedOnce(w.webContents, 'before-input-event')
|
||||
w.webContents.sendInputEvent({
|
||||
type: opts.type,
|
||||
keyCode: opts.keyCode,
|
||||
modifiers: modifiers
|
||||
})
|
||||
const [, input] = await p
|
||||
|
||||
expect(input.type).to.equal(opts.type)
|
||||
expect(input.key).to.equal(opts.key)
|
||||
expect(input.code).to.equal(opts.code)
|
||||
expect(input.isAutoRepeat).to.equal(opts.isAutoRepeat)
|
||||
expect(input.shift).to.equal(opts.shift)
|
||||
expect(input.control).to.equal(opts.control)
|
||||
expect(input.alt).to.equal(opts.alt)
|
||||
expect(input.meta).to.equal(opts.meta)
|
||||
}
|
||||
await testBeforeInput({
|
||||
type: 'keyDown',
|
||||
key: 'A',
|
||||
code: 'KeyA',
|
||||
keyCode: 'a',
|
||||
shift: true,
|
||||
control: true,
|
||||
alt: true,
|
||||
meta: true,
|
||||
isAutoRepeat: true
|
||||
})
|
||||
await testBeforeInput({
|
||||
type: 'keyUp',
|
||||
key: '.',
|
||||
code: 'Period',
|
||||
keyCode: '.',
|
||||
shift: false,
|
||||
control: true,
|
||||
alt: true,
|
||||
meta: false,
|
||||
isAutoRepeat: false
|
||||
})
|
||||
await testBeforeInput({
|
||||
type: 'keyUp',
|
||||
key: '!',
|
||||
code: 'Digit1',
|
||||
keyCode: '1',
|
||||
shift: true,
|
||||
control: false,
|
||||
alt: false,
|
||||
meta: true,
|
||||
isAutoRepeat: false
|
||||
})
|
||||
await testBeforeInput({
|
||||
type: 'keyUp',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
keyCode: 'Tab',
|
||||
shift: false,
|
||||
control: true,
|
||||
alt: false,
|
||||
meta: false,
|
||||
isAutoRepeat: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('zoom-changed', () => {
|
||||
beforeEach(function () {
|
||||
// On Mac, zooming isn't done with the mouse wheel.
|
||||
if (process.platform === 'darwin') {
|
||||
return closeWindow(w).then(() => {
|
||||
w = null
|
||||
this.skip()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('is emitted with the correct zooming info', async () => {
|
||||
w.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
|
||||
await emittedOnce(w.webContents, 'did-finish-load')
|
||||
|
||||
const testZoomChanged = async ({ zoomingIn }) => {
|
||||
const promise = emittedOnce(w.webContents, 'zoom-changed')
|
||||
|
||||
w.webContents.sendInputEvent({
|
||||
type: 'mousewheel',
|
||||
x: 300,
|
||||
y: 300,
|
||||
deltaX: 0,
|
||||
deltaY: zoomingIn ? 1 : -1,
|
||||
wheelTicksX: 0,
|
||||
wheelTicksY: zoomingIn ? 1 : -1,
|
||||
phase: 'began',
|
||||
modifiers: ['control', 'meta']
|
||||
})
|
||||
|
||||
const [, zoomDirection] = await promise
|
||||
expect(zoomDirection).to.equal(zoomingIn ? 'in' : 'out')
|
||||
}
|
||||
|
||||
await testZoomChanged({ zoomingIn: true })
|
||||
await testZoomChanged({ zoomingIn: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('devtools window', () => {
|
||||
let testFn = it
|
||||
if (process.platform === 'darwin' && isCi) {
|
||||
@@ -439,353 +98,6 @@ describe('webContents module', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendInputEvent(event)', () => {
|
||||
beforeEach(async () => {
|
||||
await w.loadFile(path.join(fixtures, 'pages', 'key-events.html'))
|
||||
})
|
||||
|
||||
it('can send keydown events', (done) => {
|
||||
ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('a')
|
||||
expect(code).to.equal('KeyA')
|
||||
expect(keyCode).to.equal(65)
|
||||
expect(shiftKey).to.be.false()
|
||||
expect(ctrlKey).to.be.false()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
|
||||
})
|
||||
|
||||
it('can send keydown events with modifiers', (done) => {
|
||||
ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('Z')
|
||||
expect(code).to.equal('KeyZ')
|
||||
expect(keyCode).to.equal(90)
|
||||
expect(shiftKey).to.be.true()
|
||||
expect(ctrlKey).to.be.true()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
|
||||
})
|
||||
|
||||
it('can send keydown events with special keys', (done) => {
|
||||
ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('Tab')
|
||||
expect(code).to.equal('Tab')
|
||||
expect(keyCode).to.equal(9)
|
||||
expect(shiftKey).to.be.false()
|
||||
expect(ctrlKey).to.be.false()
|
||||
expect(altKey).to.be.true()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab', modifiers: ['alt'] })
|
||||
})
|
||||
|
||||
it('can send char events', (done) => {
|
||||
ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('a')
|
||||
expect(code).to.equal('KeyA')
|
||||
expect(keyCode).to.equal(65)
|
||||
expect(shiftKey).to.be.false()
|
||||
expect(ctrlKey).to.be.false()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' })
|
||||
w.webContents.sendInputEvent({ type: 'char', keyCode: 'A' })
|
||||
})
|
||||
|
||||
it('can send char events with modifiers', (done) => {
|
||||
ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
|
||||
expect(key).to.equal('Z')
|
||||
expect(code).to.equal('KeyZ')
|
||||
expect(keyCode).to.equal(90)
|
||||
expect(shiftKey).to.be.true()
|
||||
expect(ctrlKey).to.be.true()
|
||||
expect(altKey).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z' })
|
||||
w.webContents.sendInputEvent({ type: 'char', keyCode: 'Z', modifiers: ['shift', 'ctrl'] })
|
||||
})
|
||||
})
|
||||
|
||||
it('supports inserting CSS', async () => {
|
||||
w.loadURL('about:blank')
|
||||
await w.webContents.insertCSS('body { background-repeat: round; }')
|
||||
const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
|
||||
expect(result).to.equal('round')
|
||||
})
|
||||
|
||||
it('supports removing inserted CSS', async () => {
|
||||
w.loadURL('about:blank')
|
||||
const key = await w.webContents.insertCSS('body { background-repeat: round; }')
|
||||
await w.webContents.removeInsertedCSS(key)
|
||||
const result = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")')
|
||||
expect(result).to.equal('repeat')
|
||||
})
|
||||
|
||||
it('supports inspecting an element in the devtools', (done) => {
|
||||
w.loadURL('about:blank')
|
||||
w.webContents.once('devtools-opened', () => { done() })
|
||||
w.webContents.inspectElement(10, 10)
|
||||
})
|
||||
|
||||
describe('startDrag({file, icon})', () => {
|
||||
it('throws errors for a missing file or a missing/empty icon', () => {
|
||||
expect(() => {
|
||||
w.webContents.startDrag({ icon: path.join(fixtures, 'assets', 'logo.png') })
|
||||
}).to.throw(`Must specify either 'file' or 'files' option`)
|
||||
|
||||
expect(() => {
|
||||
w.webContents.startDrag({ file: __filename })
|
||||
}).to.throw(`Must specify 'icon' option`)
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
expect(() => {
|
||||
w.webContents.startDrag({ file: __filename, icon: __filename })
|
||||
}).to.throw(`Must specify non-empty 'icon' option`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('focus()', () => {
|
||||
describe('when the web contents is hidden', () => {
|
||||
it('does not blur the focused window', (done) => {
|
||||
ipcMain.once('answer', (event, parentFocused, childFocused) => {
|
||||
expect(parentFocused).to.be.true()
|
||||
expect(childFocused).to.be.false()
|
||||
done()
|
||||
})
|
||||
w.show()
|
||||
w.loadFile(path.join(fixtures, 'pages', 'focus-web-contents.html'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOSProcessId()', () => {
|
||||
it('returns a valid procress id', async () => {
|
||||
expect(w.webContents.getOSProcessId()).to.equal(0)
|
||||
|
||||
await w.loadURL('about:blank')
|
||||
expect(w.webContents.getOSProcessId()).to.be.above(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('zoom api', () => {
|
||||
const zoomScheme = remote.getGlobal('zoomScheme')
|
||||
const hostZoomMap = {
|
||||
host1: 0.3,
|
||||
host2: 0.7,
|
||||
host3: 0.2
|
||||
}
|
||||
|
||||
before((done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
protocol.registerStringProtocol(zoomScheme, (request, callback) => {
|
||||
const response = `<script>
|
||||
const {ipcRenderer, remote} = require('electron')
|
||||
ipcRenderer.send('set-zoom', window.location.hostname)
|
||||
ipcRenderer.on(window.location.hostname + '-zoom-set', () => {
|
||||
const { zoomLevel } = remote.getCurrentWebContents()
|
||||
ipcRenderer.send(window.location.hostname + '-zoom-level', zoomLevel)
|
||||
})
|
||||
</script>`
|
||||
callback({ data: response, mimeType: 'text/html' })
|
||||
}, (error) => done(error))
|
||||
})
|
||||
|
||||
after((done) => {
|
||||
const protocol = session.defaultSession.protocol
|
||||
protocol.unregisterProtocol(zoomScheme, (error) => done(error))
|
||||
})
|
||||
|
||||
// TODO(codebytere): remove in Electron v8.0.0
|
||||
it('can set the correct zoom level (functions)', async () => {
|
||||
try {
|
||||
await w.loadURL('about:blank')
|
||||
const zoomLevel = w.webContents.getZoomLevel()
|
||||
expect(zoomLevel).to.eql(0.0)
|
||||
w.webContents.setZoomLevel(0.5)
|
||||
const newZoomLevel = w.webContents.getZoomLevel()
|
||||
expect(newZoomLevel).to.eql(0.5)
|
||||
} finally {
|
||||
w.webContents.setZoomLevel(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('can set the correct zoom level', async () => {
|
||||
try {
|
||||
await w.loadURL('about:blank')
|
||||
const zoomLevel = w.webContents.zoomLevel
|
||||
expect(zoomLevel).to.eql(0.0)
|
||||
w.webContents.zoomLevel = 0.5
|
||||
const newZoomLevel = w.webContents.zoomLevel
|
||||
expect(newZoomLevel).to.eql(0.5)
|
||||
} finally {
|
||||
w.webContents.zoomLevel = 0
|
||||
}
|
||||
})
|
||||
|
||||
it('can persist zoom level across navigation', (done) => {
|
||||
let finalNavigation = false
|
||||
ipcMain.on('set-zoom', (e, host) => {
|
||||
const zoomLevel = hostZoomMap[host]
|
||||
if (!finalNavigation) w.webContents.zoomLevel = zoomLevel
|
||||
console.log()
|
||||
e.sender.send(`${host}-zoom-set`)
|
||||
})
|
||||
ipcMain.on('host1-zoom-level', (e, zoomLevel) => {
|
||||
const expectedZoomLevel = hostZoomMap.host1
|
||||
expect(zoomLevel).to.equal(expectedZoomLevel)
|
||||
if (finalNavigation) {
|
||||
done()
|
||||
} else {
|
||||
w.loadURL(`${zoomScheme}://host2`)
|
||||
}
|
||||
})
|
||||
ipcMain.once('host2-zoom-level', (e, zoomLevel) => {
|
||||
const expectedZoomLevel = hostZoomMap.host2
|
||||
expect(zoomLevel).to.equal(expectedZoomLevel)
|
||||
finalNavigation = true
|
||||
w.webContents.goBack()
|
||||
})
|
||||
w.loadURL(`${zoomScheme}://host1`)
|
||||
})
|
||||
|
||||
it('can propagate zoom level across same session', (done) => {
|
||||
const w2 = new BrowserWindow({
|
||||
show: false
|
||||
})
|
||||
w2.webContents.on('did-finish-load', () => {
|
||||
const zoomLevel1 = w.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(hostZoomMap.host3)
|
||||
|
||||
const zoomLevel2 = w2.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(zoomLevel2)
|
||||
w2.setClosable(true)
|
||||
w2.close()
|
||||
done()
|
||||
})
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
w.webContents.zoomLevel = hostZoomMap.host3
|
||||
w2.loadURL(`${zoomScheme}://host3`)
|
||||
})
|
||||
w.loadURL(`${zoomScheme}://host3`)
|
||||
})
|
||||
|
||||
it('cannot propagate zoom level across different session', (done) => {
|
||||
const w2 = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
partition: 'temp'
|
||||
}
|
||||
})
|
||||
const protocol = w2.webContents.session.protocol
|
||||
protocol.registerStringProtocol(zoomScheme, (request, callback) => {
|
||||
callback('hello')
|
||||
}, (error) => {
|
||||
if (error) return done(error)
|
||||
w2.webContents.on('did-finish-load', () => {
|
||||
const zoomLevel1 = w.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(hostZoomMap.host3)
|
||||
|
||||
const zoomLevel2 = w2.webContents.zoomLevel
|
||||
expect(zoomLevel2).to.equal(0)
|
||||
expect(zoomLevel1).to.not.equal(zoomLevel2)
|
||||
|
||||
protocol.unregisterProtocol(zoomScheme, (error) => {
|
||||
if (error) return done(error)
|
||||
w2.setClosable(true)
|
||||
w2.close()
|
||||
done()
|
||||
})
|
||||
})
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
w.webContents.zoomLevel = hostZoomMap.host3
|
||||
w2.loadURL(`${zoomScheme}://host3`)
|
||||
})
|
||||
w.loadURL(`${zoomScheme}://host3`)
|
||||
})
|
||||
})
|
||||
|
||||
it('can persist when it contains iframe', (done) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
setTimeout(() => {
|
||||
res.end()
|
||||
}, 200)
|
||||
})
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const url = 'http://127.0.0.1:' + server.address().port
|
||||
const content = `<iframe src=${url}></iframe>`
|
||||
w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
|
||||
if (!isMainFrame) {
|
||||
const zoomLevel = w.webContents.zoomLevel
|
||||
expect(zoomLevel).to.equal(2.0)
|
||||
|
||||
w.webContents.zoomLevel = 0
|
||||
server.close()
|
||||
done()
|
||||
}
|
||||
})
|
||||
w.webContents.on('dom-ready', () => {
|
||||
w.webContents.zoomLevel = 2.0
|
||||
})
|
||||
w.loadURL(`data:text/html,${content}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot propagate when used with webframe', (done) => {
|
||||
let finalZoomLevel = 0
|
||||
const w2 = new BrowserWindow({
|
||||
show: false
|
||||
})
|
||||
w2.webContents.on('did-finish-load', () => {
|
||||
const zoomLevel1 = w.webContents.zoomLevel
|
||||
expect(zoomLevel1).to.equal(finalZoomLevel)
|
||||
|
||||
const zoomLevel2 = w2.webContents.zoomLevel
|
||||
expect(zoomLevel2).to.equal(0)
|
||||
expect(zoomLevel1).to.not.equal(zoomLevel2)
|
||||
|
||||
w2.setClosable(true)
|
||||
w2.close()
|
||||
done()
|
||||
})
|
||||
ipcMain.once('temporary-zoom-set', (e, zoomLevel) => {
|
||||
w2.loadFile(path.join(fixtures, 'pages', 'c.html'))
|
||||
finalZoomLevel = zoomLevel
|
||||
})
|
||||
w.loadFile(path.join(fixtures, 'pages', 'webframe-zoom.html'))
|
||||
})
|
||||
|
||||
it('cannot persist zoom level after navigation with webFrame', (done) => {
|
||||
let initialNavigation = true
|
||||
const source = `
|
||||
const {ipcRenderer, webFrame} = require('electron')
|
||||
webFrame.setZoomLevel(0.6)
|
||||
ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel())
|
||||
`
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
if (initialNavigation) {
|
||||
w.webContents.executeJavaScript(source)
|
||||
} else {
|
||||
const zoomLevel = w.webContents.zoomLevel
|
||||
expect(zoomLevel).to.equal(0)
|
||||
done()
|
||||
}
|
||||
})
|
||||
ipcMain.once('zoom-level-set', (e, zoomLevel) => {
|
||||
expect(zoomLevel).to.equal(0.6)
|
||||
w.loadFile(path.join(fixtures, 'pages', 'd.html'))
|
||||
initialNavigation = false
|
||||
})
|
||||
w.loadFile(path.join(fixtures, 'pages', 'c.html'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('webrtc ip policy api', () => {
|
||||
it('can set and get webrtc ip policies', () => {
|
||||
const policies = [
|
||||
|
||||
2
spec/fixtures/pages/insecure-resources.html
vendored
2
spec/fixtures/pages/insecure-resources.html
vendored
@@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="http://127.0.0.1:8881/save_page/test.css">
|
||||
<link rel="stylesheet" href="/save_page/test.css">
|
||||
<script type="text/javascript">
|
||||
window.ELECTRON_ENABLE_SECURITY_WARNINGS = true
|
||||
</script>
|
||||
|
||||
1
spec/fixtures/pages/jquery.html
vendored
1
spec/fixtures/pages/jquery.html
vendored
@@ -7,7 +7,6 @@
|
||||
window.ajax = (url, options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.url = url
|
||||
options.cache = false
|
||||
options.success = (data, status, request) => {
|
||||
resolve({data, status: request.status, headers: request.getAllResponseHeaders()})
|
||||
}
|
||||
|
||||
@@ -155,6 +155,19 @@ describe('node feature', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('child_process.exec', () => {
|
||||
(process.platform === 'linux' ? it : it.skip)('allows executing a setuid binary from non-sandboxed renderer', () => {
|
||||
// Chrome uses prctl(2) to set the NO_NEW_PRIVILEGES flag on Linux (see
|
||||
// https://github.com/torvalds/linux/blob/40fde647cc/Documentation/userspace-api/no_new_privs.rst).
|
||||
// We disable this for unsandboxed processes, which the remote tests
|
||||
// are running in. If this test fails with an error like 'effective uid
|
||||
// is not 0', then it's likely that our patch to prevent the flag from
|
||||
// being set has become ineffective.
|
||||
const stdout = ChildProcess.execSync('sudo --help')
|
||||
expect(stdout).to.not.be.empty()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('contexts', () => {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"basic-auth": "^2.0.1",
|
||||
"bluebird": "^3.5.3",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"coffeescript": "^2.4.1",
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
|
||||
const http = require('http')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { BrowserWindow } = remote
|
||||
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
|
||||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
|
||||
describe('security warnings', () => {
|
||||
let server
|
||||
let w = null
|
||||
let useCsp = true
|
||||
|
||||
before((done) => {
|
||||
// Create HTTP Server
|
||||
server = http.createServer((request, response) => {
|
||||
const uri = url.parse(request.url).pathname
|
||||
let filename = path.join(__dirname, './fixtures/pages', uri)
|
||||
|
||||
fs.stat(filename, (error, stats) => {
|
||||
if (error) {
|
||||
response.writeHead(404, { 'Content-Type': 'text/plain' })
|
||||
response.end()
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
filename += '/index.html'
|
||||
}
|
||||
|
||||
fs.readFile(filename, 'binary', (err, file) => {
|
||||
if (err) {
|
||||
response.writeHead(404, { 'Content-Type': 'text/plain' })
|
||||
response.end()
|
||||
return
|
||||
}
|
||||
|
||||
const cspHeaders = { 'Content-Security-Policy': `script-src 'self' 'unsafe-inline'` }
|
||||
response.writeHead(200, useCsp ? cspHeaders : undefined)
|
||||
response.write(file, 'binary')
|
||||
response.end()
|
||||
})
|
||||
})
|
||||
}).listen(8881, () => done())
|
||||
})
|
||||
|
||||
after(() => {
|
||||
// Close server
|
||||
server.close()
|
||||
server = null
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
useCsp = true
|
||||
return closeWindow(w).then(() => { w = null })
|
||||
})
|
||||
|
||||
it('should warn about Node.js integration with remote content', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('Node.js Integration with Remote Content')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should not warn about Node.js integration with remote content from localhost', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.not.include('Node.js Integration with Remote Content')
|
||||
|
||||
if (message === 'loaded') {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`http://localhost:8881/base-page-security-onload-message.html`)
|
||||
})
|
||||
|
||||
const generateSpecs = (description, webPreferences) => {
|
||||
describe(description, () => {
|
||||
it('should warn about disabled webSecurity', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('Disabled webSecurity')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should warn about insecure Content-Security-Policy', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('Insecure Content-Security-Policy')
|
||||
done()
|
||||
})
|
||||
|
||||
useCsp = false
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should warn about allowRunningInsecureContent', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
allowRunningInsecureContent: true,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('allowRunningInsecureContent')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should warn about experimentalFeatures', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
experimentalFeatures: true,
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('experimentalFeatures')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should warn about enableBlinkFeatures', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
enableBlinkFeatures: ['my-cool-feature'],
|
||||
...webPreferences
|
||||
}
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('enableBlinkFeatures')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should warn about allowpopups', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('allowpopups')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/webview-allowpopups.html`)
|
||||
})
|
||||
|
||||
it('should warn about insecure resources', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('Insecure Resources')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/insecure-resources.html`)
|
||||
w.webContents.openDevTools()
|
||||
})
|
||||
|
||||
it('should not warn about loading insecure-resources.html from localhost', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.not.include('insecure-resources.html')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://localhost:8881/insecure-resources.html`)
|
||||
w.webContents.openDevTools()
|
||||
})
|
||||
|
||||
it('should warn about enabled remote module with remote content', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.include('enableRemoteModule')
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL(`http://127.0.0.1:8881/base-page-security.html`)
|
||||
})
|
||||
|
||||
it('should not warn about enabled remote module with remote content from localhost', (done) => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences
|
||||
})
|
||||
w.webContents.once('console-message', (e, level, message) => {
|
||||
expect(message).to.not.include('enableRemoteModule')
|
||||
|
||||
if (message === 'loaded') {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
w.loadURL(`http://localhost:8881/base-page-security-onload-message.html`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
generateSpecs('without sandbox', {})
|
||||
generateSpecs('with sandbox', { sandbox: true })
|
||||
})
|
||||
@@ -918,10 +918,17 @@ app.on('ready', () => {
|
||||
{ label: 'Item3', type: 'radio', checked: true },
|
||||
{ label: 'Item4', type: 'radio' }
|
||||
])
|
||||
|
||||
appIcon.setTitle('title')
|
||||
appIcon.setToolTip('This is my application.')
|
||||
appIcon.setContextMenu(contextMenu)
|
||||
|
||||
appIcon.setImage('/path/to/new/icon')
|
||||
appIcon.setPressedImage('/path/to/new/icon')
|
||||
|
||||
appIcon.popUpContextMenu(contextMenu, { x: 100, y: 100 })
|
||||
appIcon.setContextMenu(contextMenu)
|
||||
|
||||
appIcon.setIgnoreDoubleClickEvents(true)
|
||||
|
||||
appIcon.on('click', (event, bounds) => {
|
||||
console.log('click', event, bounds)
|
||||
@@ -933,7 +940,12 @@ app.on('ready', () => {
|
||||
|
||||
appIcon.displayBalloon({
|
||||
title: 'Hello World!',
|
||||
content: 'This the the balloon content.'
|
||||
content: 'This the the balloon content.',
|
||||
iconType: 'error',
|
||||
icon: 'path/to/icon',
|
||||
respectQuietTime: true,
|
||||
largeIcon: true,
|
||||
noSound: true
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -126,11 +126,6 @@ bl@^1.0.0:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
bluebird@^3.5.3:
|
||||
version "3.5.4"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
|
||||
integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
|
||||
15
yarn.lock
15
yarn.lock
@@ -141,6 +141,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
|
||||
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
|
||||
|
||||
"@types/basic-auth@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/basic-auth/-/basic-auth-1.1.2.tgz#810fbded879f14327fc1d3413bdb92cfe9e24f73"
|
||||
integrity sha512-NzkkcC+gkkILWaBi3+/z/3do6Ybk6TWeTqV5zCVXmG2KaBoT5YqlJvfqP44HCyDA+Cu58pp7uKAxy/G58se/TA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c"
|
||||
@@ -254,6 +261,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
|
||||
|
||||
"@types/send@^0.14.5":
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.14.5.tgz#653f7d25b93c3f7f51a8994addaf8a229de022a7"
|
||||
integrity sha512-0mwoiK3DXXBu0GIfo+jBv4Wo5s1AcsxdpdwNUtflKm99VEMvmBPJ+/NBNRZy2R5JEYfWL/u4nAHuTUTA3wFecQ==
|
||||
dependencies:
|
||||
"@types/mime" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48"
|
||||
|
||||
Reference in New Issue
Block a user