Compare commits

...

70 Commits

Author SHA1 Message Date
Electron Bot
ec61041d41 Bump v7.1.8 2020-01-08 11:40:06 -08:00
trop[bot]
32b51bce75 refactor: throw error for getLastCrashReport if crashReporter not started (#21683) 2020-01-07 09:15:21 -05:00
trop[bot]
e0576ef11a fix: highlight defaulted button correctly (#21652) 2020-01-03 11:03:03 -05:00
trop[bot]
a889ec7957 fix: SimpleURLLoaderWrapper redirects (#21566) (#21645) 2020-01-01 09:32:26 +09:00
Electron Bot
bef0dd868b Bump v7.1.7 2019-12-19 06:49:00 -08:00
Electron Bot
3d74cacefe Revert "Bump v7.1.7"
This reverts commit 0b1248c8e6.
2019-12-19 06:47:37 -08:00
Electron Bot
0b1248c8e6 Bump v7.1.7 2019-12-19 05:56:00 -08:00
Jeremy Apthorp
0569c929a1 fix: set enable_negotiate_port to false in allowNTLMCredentialsForDomains (#21572) 2019-12-19 05:54:19 -08:00
trop[bot]
2dc42971ce docs: update webContents.printToPDF() example for promisified API (#21550)
References: https://github.com/electron/electron/pull/16795 https://github.com/electron/electron/pull/17907
2019-12-18 15:20:52 +09:00
Electron Bot
577d0483e9 Bump v7.1.6 2019-12-17 14:43:26 -08:00
John Kleinschmidt
a3fde67056 Revert "Bump v7.1.6"
This reverts commit fc800837bd.
2019-12-17 13:52:26 -08:00
Shelley Vohr
79aebdffb7 fix: MediaKey globalShortcuts not working on macOS (#21548) 2019-12-17 13:39:18 -08:00
Electron Bot
fc800837bd Bump v7.1.6 2019-12-16 17:26:41 -08:00
Shelley Vohr
58469efd1c Revert "Bump v7.1.6"
This reverts commit f17dc63c16.
2019-12-16 17:23:47 -08:00
Shelley Vohr
80c36602fd Revert "Bump v7.1.7"
This reverts commit f910704838.
2019-12-16 17:23:12 -08:00
Electron Bot
f910704838 Bump v7.1.7 2019-12-16 17:15:21 -08:00
Electron Bot
f17dc63c16 Bump v7.1.6 2019-12-16 17:15:04 -08:00
John Kleinschmidt
04b0d61323 Revert "Bump v7.1.6"
This reverts commit 544b4c5209.
2019-12-16 17:07:15 -08:00
Electron Bot
544b4c5209 Bump v7.1.6 2019-12-16 13:54:16 -08:00
Milan Burda
b35f37ea5a refactor: export internalWindowOpen from guest-window-manager (#21542) 2019-12-16 13:52:04 -08:00
Milan Burda
7f0bbd27b1 fix: enforce parent-child relationship in custom postMessage() handler (#21528) 2019-12-16 13:51:20 -08:00
trop[bot]
3b557bcf2e docs: update installation docs to reflect latest @electron/get changes (#21539) 2019-12-16 09:45:30 -08:00
Robo
ef2f0580d9 fix: select box not rendered properly with OOPIF (#21526)
Backports https://chromium-review.googlesource.com/c/chromium/src/+/1879597
2019-12-15 22:46:16 -05:00
trop[bot]
425d2fa6f1 fix: quit after Chromium is fully started (#21505) 2019-12-14 14:08:27 -05:00
Electron Bot
1409056baf Bump v7.1.5 2019-12-13 11:48:14 -08:00
Samuel Attard
72cb493023 Revert "Bump v7.1.5"
This reverts commit 383e75796a.
2019-12-13 13:43:09 -05:00
trop[bot]
1e7f26e5fd fix: avoid contextBridge crash when RenderFrame address is reused (#21514)
* fix: avoid contextBridge crash when RenderFrame address is reused

Co-Authored-By: Jeremy Apthorp <nornagon@nornagon.net>

* make routing_id_ const
2019-12-13 13:39:50 -05:00
Electron Bot
383e75796a Bump v7.1.5 2019-12-13 08:07:11 -08:00
loc
1edfffae25 fix: workaround for hang when preventDefault-ing nativeWindowOpen (7-1-x) (#21497)
* fix: enable workaround for nativeWindowOpen hang

* add test

* test: ensure window doesn't leak into other test
2019-12-13 11:06:11 -05:00
trop[bot]
96e7b443fc fix: sourcemaps not loading with network service (#21494)
Backports https://chromium-review.googlesource.com/c/chromium/src/+/1525270
Backports https://chromium-review.googlesource.com/c/chromium/src/+/1852212
2019-12-12 17:29:49 -08:00
Robo
8f728af13a fix: avoid Electron.dsym files in the main app bundle (#21447) (#21487)
* ci: CHECK_DIST_MANIFEST in release builds

* fix: skip Electron.dSYM on macOS app zip
2019-12-12 16:27:17 +09:00
trop[bot]
281b0741f7 fix: name and expirationDate should be optional when setting cookie (#21454) (#21481)
* fix: correctly set cookie date

* fix: name is not required for setting cookie

* test: clear cookie after each cookie test

* test: should test session property

* chore: style fixes
2019-12-12 09:18:51 +09:00
Cheng Zhao
38c43ab48f fix: hiding window menu should work on startup (#21436) (#21449)
* fix: menu visibility should not be overwritten on startup

* fix: removing menu for window without global menubar

* test: setMenu tests are not for mac
2019-12-11 15:41:29 +09:00
trop[bot]
d1d3d1bd1f fix: restore accessibility window title on macOS (#21466)
Electron's `AtomNSWindow` implements `accessibilityAttributeValue` to
provide various accessibility info to the OS, including window titles.

Chromium 75 changed to Apple's newer accessibility API for window titles
in the super class that `AtomNSWindow` inherits from. macOS still
supports both the old and new style APIs, but it will prefer the new
style if it is implemented.  This means the Electron window title is
being ignored because the newer API at the Chromium level has taken
precedence.

By implementing the newer accessibility API in `AtomNSWindow`, this
restores correct accessibility window titles in macOS Electron apps.

This is a regression has been present since Electron 6.0.0 (the first
release including the Chromium change above).
2019-12-10 22:07:55 -05:00
trop[bot]
89a7bdb566 fix: window menu should handle keys correctly (#21453) 2019-12-10 10:05:28 -08:00
Electron Bot
09d59f2b83 Bump v7.1.4 2019-12-09 13:34:26 -08:00
Samuel Attard
ebec22045d Revert "Bump v7.1.4"
This reverts commit a2c4000b84.
2019-12-09 13:32:40 -08:00
Electron Bot
a2c4000b84 Bump v7.1.4 2019-12-09 11:30:37 -08:00
trop[bot]
9010838f21 fix: fix ClientRequest.getUploadProgress (#21425) 2019-12-09 11:18:29 -08:00
trop[bot]
6fe40ca797 docs: fix return type of getPrinters (#21422) 2019-12-08 10:38:04 -08:00
Shelley Vohr
101b971f36 fix: pass noLink and checkboxChecked correctly on Windows (#21406) 2019-12-06 18:20:05 -08:00
trop[bot]
6156254886 fix: ensure persistence store still exists when GC runs (#21417)
Fix a bad access crash that happens when a render frame is deleted (window closed) and garbage collection runs afterward.
2019-12-06 12:35:47 -08:00
Electron Bot
d0e4bd3fc9 chore: bump chromium in DEPS to 78.0.3904.130 (#21413) 2019-12-06 11:29:19 -08:00
trop[bot]
3e5213b4e9 fix: Fix compositor recycling when creating new BrowserView (#21400)
In #20829, we fixed compositor recycling when switching between
BrowserViews, but it turns out that there is one additional case that we
need to handle. When we create a completely new BrowserView instance, it
starts of as visible (even when it hasn't been added to the window),
which means that it will need its own compositor instead of using the
recycled compositor.

To fix this, lets make BrowserViews hidden by default until they're
added to the window. See also #19988. This is a potentially breaking
change given that the initial value of `document.visibilityState` will
now be `hidden`, but given the experimental status of BrowserViews, I
think this is a fine change to make. The old behavior can be restored
with `webPreferences: { show: true }`.

Notes: Fix compositor recycling when creating new BrowserView
2019-12-05 15:55:29 -06:00
Electron Bot
7ba8855ac1 chore: bump chromium in DEPS to 78.0.3904.129 (#21394) 2019-12-05 10:40:18 -08:00
Electron Bot
c3e0ae9646 chore: bump chromium in DEPS to 78.0.3904.128 (#21381) 2019-12-04 09:52:00 -08:00
trop[bot]
4b456bf3cb chore: remove unused shell/common/crash_reporter/win/crash_service.cc (#21375) 2019-12-04 16:40:30 +09:00
Electron Bot
2a4bdfff98 chore: bump chromium in DEPS to 78.0.3904.127 (#21367) 2019-12-03 08:16:43 -08:00
trop[bot]
c4baee0dab fix: backgroundThrottling rwh assignment (#21357)
* fix: backgroundThrottling rwh assignment

* fix: disable DOM timer throttling

* chore: fix typo
2019-12-02 15:42:32 -08:00
Electron Bot
15688c6d6e Bump v7.1.3 2019-12-02 12:31:58 -08:00
trop[bot]
aa4d7f1799 fix: ensure no node globals passively leak when nodeIntegration is disabled (#21354) 2019-12-02 12:27:40 -08:00
Jeremy Apthorp
0476eb67ab refactor: rewrite the net module to simplify state tracking (#21304)
* refactor: rewrite the net module to simplify state tracking (#21244)

* un-ginify some things

* lint

* qualify util::Promise

* network::mojom::URLResponseHead -> network::ResourceResponseHead

* fix build

* Update api-net-spec.ts

* Update api-net-spec.ts

* Init -> InitWith
2019-12-02 12:26:47 -08:00
Electron Bot
69aebc05e5 chore: bump chromium to 78.0.3904.126 (7-1-x) (#21346)
* chore: bump chromium in DEPS to 78.0.3904.125

* chore: bump chromium in DEPS to 78.0.3904.126
2019-12-02 10:09:38 -08:00
Electron Bot
98d06f6e5c chore: bump chromium in DEPS to 78.0.3904.124 (#21339) 2019-11-30 16:21:41 -08:00
Electron Bot
7d321a90aa chore: bump chromium to 78.0.3904.123 (7-1-x) (#21307)
* chore: bump chromium in DEPS to 78.0.3904.121

* chore: bump chromium in DEPS to 78.0.3904.122

* chore: bump chromium in DEPS to 78.0.3904.123
2019-11-29 11:38:40 -08:00
trop[bot]
cfddc0a125 build: disable strip_absolute_paths_from_debug_symbols on debug.gn (#21317) 2019-11-29 17:21:44 +09:00
Jeremy Apthorp
ade70463c1 fix: restore --ignore-connections-limit functionality (#21286) (#21299) 2019-11-27 16:26:17 +09:00
Jeremy Apthorp
4cb983ed40 ci: generate debug symbols on Linux (#18676) (#21279) 2019-11-26 17:33:31 -08:00
trop[bot]
79b3fcb2ab fix: allow reading body from non-2xx responses in net.request (#21055) (#21295)
* fix: allow reading body from non-2xx responses in net.request (#21055)

* fix(urlrequest): allow non-2xx repsponse results

- closes #21046

* test(net): add test cases to verify non-2xx body

* test(session): update spec to match clientrequest behavior

* test(net): update test cases to match clientrequest behavior

* spec: clean up async net spec

* Update api-session-spec.js

* chore: fixup test as per original PR to master
2019-11-26 17:32:56 -08:00
Electron Bot
4cfa7be79d chore: bump chromium to 78.0.3904.120 (7-1-x) (#21252)
* chore: bump chromium in DEPS to 78.0.3904.116

* chore: bump chromium in DEPS to 78.0.3904.117

* chore: bump chromium in DEPS to 78.0.3904.118

* chore: bump chromium in DEPS to 78.0.3904.119

* chore: bump chromium in DEPS to 78.0.3904.120
2019-11-26 13:12:25 -08:00
Samuel Attard
67bcc2a972 fix: reloadIgnoringCache() should ignore the cache (#21263) (#21284) 2019-11-25 13:09:41 -08:00
Jeremy Apthorp
449bdcfda2 fix: record cpu_profiler data for main process (#21187) (#21277)
* fix: record cpu_profiler data for main process

* kick ci
2019-11-25 12:56:31 -08:00
trop[bot]
e70adc8f83 fix: add missing early return (#21281) 2019-11-25 12:50:41 -08:00
trop[bot]
a6ae1ca755 spec: skip flaky <webview>.capturePage() test on Windows (#21211) 2019-11-22 15:31:19 -05:00
trop[bot]
b6fb8d3a63 docs: remove string literal type from window events (#21246) 2019-11-21 14:50:38 -08:00
Electron Bot
5462a2c197 chore: bump chromium in DEPS to 78.0.3904.115 (#21239) 2019-11-21 12:08:06 -05:00
Jeremy Apthorp
de428e9a7a fix: implement 'login' event for net.ClientRequest (#21135)
* fix: implement 'login' event for net.ClientRequest (#21096)

* fix patch

* lint

* include mojo header in atom_browser_context.h

* fix compile

* kick ci

* URLRequest -> URLRequestNS

* fix ts

* no ts in js
2019-11-21 12:06:23 -05:00
Cheng Zhao
fbe36e2365 fix: menu should not be garbage-collected when popuping (7-1-x) (#21225)
* fix: retain menu when popuping

* test: menu should not be garbage-collected when popuping
2019-11-21 07:14:24 +09:00
Electron Bot
5ac12c9e44 chore: bump chromium in DEPS to 78.0.3904.114 (#21228) 2019-11-20 10:04:55 -08:00
Robo
8d11391cb4 fix: focus with OOPIF embedded inside <webview> (#21223) 2019-11-20 09:11:27 -08:00
103 changed files with 3179 additions and 2320 deletions

View File

@@ -41,6 +41,7 @@ env-release-build: &env-release-build
GN_CONFIG: //electron/build/args/release.gn
STRIP_BINARIES: true
GENERATE_SYMBOLS: true
CHECK_DIST_MANIFEST: '1'
env-headless-testing: &env-headless-testing
DISPLAY: ':99.0'
@@ -291,9 +292,18 @@ step-maybe-electron-dist-strip: &step-maybe-electron-dist-strip
run:
name: Strip electron binaries
command: |
if [ "$STRIP_BINARIES" == "true" ] && [ "`uname`" != "Darwin" ]; then
if [ "$STRIP_BINARIES" == "true" ] && [ "`uname`" == "Linux" ]; then
if [ x"$TARGET_ARCH" == x ]; then
target_cpu=x64
elif [ "$TARGET_ARCH" == "ia32" ]; then
target_cpu=x86
else
target_cpu="$TARGET_ARCH"
fi
cd src
electron/script/strip-binaries.py --target-cpu="$TARGET_ARCH"
electron/script/copy-debug-symbols.py --target-cpu="$target_cpu" --out-dir=out/Default/debug --compress
electron/script/strip-binaries.py --target-cpu="$target_cpu"
electron/script/add-debug-link.py --target-cpu="$target_cpu" --debug-dir=out/Default/debug
fi
step-electron-dist-build: &step-electron-dist-build

2
DEPS
View File

@@ -11,7 +11,7 @@ gclient_gn_args = [
vars = {
'chromium_version':
'78.0.3904.113',
'78.0.3904.130',
'node_version':
'v12.8.1',
'nan_version':

View File

@@ -1 +1 @@
7.1.2
7.1.8

View File

@@ -6,6 +6,8 @@ is_official_build = false
dcheck_always_on = true
symbol_level = 1
strip_absolute_paths_from_debug_symbols = false
# This may be guarded behind is_chrome_branded alongside
# proprietary_codecs https://webrtc-review.googlesource.com/c/src/+/36321,
# explicitly override here to build OpenH264 encoder/FFmpeg decoder.

View File

@@ -16,6 +16,10 @@ PATHS_TO_SKIP = [
'./libVkICD_mock_', #Skipping because these are outputs that we don't need
'./VkICD_mock_', #Skipping because these are outputs that we don't need
# Skipping because its an output of create_bundle from //build/config/mac/rules.gni
# that we don't need
'Electron.dSYM',
# //chrome/browser:resources depends on this via
# //chrome/browser/resources/ssl/ssl_error_assistant, but we don't need to
# ship it.
@@ -51,14 +55,13 @@ def main(argv):
with open(runtime_deps) as f:
for dep in f.readlines():
dep = dep.strip()
dist_files.add(dep)
if not skip_path(dep, dist_zip, target_cpu):
dist_files.add(dep)
if sys.platform == 'darwin':
execute(['zip', '-r', '-y', dist_zip] + list(dist_files))
else:
with zipfile.ZipFile(dist_zip, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as z:
for dep in dist_files:
if skip_path(dep, dist_zip, target_cpu):
continue
if os.path.isdir(dep):
for root, dirs, files in os.walk(dep):
for file in files:

View File

@@ -19,6 +19,7 @@ buildflag_header("buildflags") {
"ENABLE_COLOR_CHOOSER=$enable_color_chooser",
"ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions",
"ENABLE_PICTURE_IN_PICTURE=$enable_picture_in_picture",
"ENABLE_MEDIA_KEY_OVERRIDES=$enable_media_key_overrides",
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
]
}

View File

@@ -20,6 +20,8 @@ declare_args() {
enable_picture_in_picture = true
enable_media_key_overrides = true
# Provide a fake location provider for mocking
# the geolocation responses. Disable it if you
# need to test with chromium's location provider.

View File

@@ -323,8 +323,8 @@ Returns:
* `port` Integer
* `realm` String
* `callback` Function
* `username` String
* `password` String
* `username` String (optional)
* `password` String (optional)
Emitted when `webContents` wants to do basic auth.
@@ -341,6 +341,10 @@ app.on('login', (event, webContents, details, authInfo, callback) => {
})
```
If `callback` is called without a username or password, the authentication
request will be cancelled and the authentication error will be returned to the
page.
### Event: 'gpu-info-update'
Emitted whenever there is a GPU info update.

View File

@@ -513,7 +513,7 @@ Emitted when the window is restored from a minimized state.
Returns:
* `event` Event
* `newBounds` [`Rectangle`](structures/rectangle.md) - Size the window is being resized to.
* `newBounds` [Rectangle](structures/rectangle.md) - Size the window is being resized to.
Emitted before the window is resized. Calling `event.preventDefault()` will prevent the window from being resized.
@@ -528,7 +528,7 @@ Emitted after the window has been resized.
Returns:
* `event` Event
* `newBounds` [`Rectangle`](structures/rectangle.md) - Location the window is being moved to.
* `newBounds` [Rectangle](structures/rectangle.md) - Location the window is being moved to.
Emitted before the window is moved. Calling `event.preventDefault()` will prevent the window from being moved.

View File

@@ -32,8 +32,8 @@ the hostname and the port number 'hostname:port'.
* `redirect` String (optional) - The redirect mode for this request. Should be
one of `follow`, `error` or `manual`. Defaults to `follow`. When mode is `error`,
any redirection will be aborted. When mode is `manual` the redirection will be
deferred until [`request.followRedirect`](#requestfollowredirect) is invoked. Listen for the [`redirect`](#event-redirect) event in
this mode to get more details about the redirect request.
cancelled unless [`request.followRedirect`](#requestfollowredirect) is invoked
synchronously during the [`redirect`](#event-redirect) event.
`options` properties such as `protocol`, `host`, `hostname`, `port` and `path`
strictly follow the Node.js model as described in the
@@ -70,8 +70,8 @@ Returns:
* `port` Integer
* `realm` String
* `callback` Function
* `username` String
* `password` String
* `username` String (optional)
* `password` String (optional)
Emitted when an authenticating proxy is asking for user credentials.
@@ -136,8 +136,11 @@ Returns:
* `redirectUrl` String
* `responseHeaders` Record<String, String[]>
Emitted when there is redirection and the mode is `manual`. Calling
[`request.followRedirect`](#requestfollowredirect) will continue with the redirection.
Emitted when the server returns a redirect response (e.g. 301 Moved
Permanently). Calling [`request.followRedirect`](#requestfollowredirect) will
continue with the redirection. If this event is handled,
[`request.followRedirect`](#requestfollowredirect) must be called
**synchronously**, otherwise the request will be cancelled.
### Instance Properties
@@ -214,7 +217,8 @@ response object,it will emit the `aborted` event.
#### `request.followRedirect()`
Continues any deferred redirection request when the redirection mode is `manual`.
Continues any pending redirection. Can only be called during a `'redirect'`
event.
#### `request.getUploadProgress()`

View File

@@ -463,8 +463,8 @@ Returns:
* `port` Integer
* `realm` String
* `callback` Function
* `username` String
* `password` String
* `username` String (optional)
* `password` String (optional)
Emitted when `webContents` wants to do basic auth.
@@ -1236,7 +1236,7 @@ Captures a snapshot of the page within `rect`. Omitting `rect` will capture the
Get the system printer list.
Returns [`PrinterInfo[]`](structures/printer-info.md).
Returns [`PrinterInfo[]`](structures/printer-info.md)
#### `contents.print([options], [callback])`
@@ -1323,12 +1323,13 @@ win.loadURL('http://github.com')
win.webContents.on('did-finish-load', () => {
// Use default printing options
win.webContents.printToPDF({}, (error, data) => {
if (error) throw error
win.webContents.printToPDF({}).then(data => {
fs.writeFile('/tmp/print.pdf', data, (error) => {
if (error) throw error
console.log('Write PDF successfully.')
})
}).catch(error => {
console.log(error)
})
})
```

View File

@@ -56,7 +56,7 @@ can do so by either providing a mirror or an existing cache directory.
#### Mirror
You can use environment variables to override the base URL, the path at which to
look for Electron binaries, and the binary filename. The url used by `electron-download`
look for Electron binaries, and the binary filename. The url used by `@electron/get`
is composed as follows:
```plaintext
@@ -66,11 +66,11 @@ url = ELECTRON_MIRROR + ELECTRON_CUSTOM_DIR + '/' + ELECTRON_CUSTOM_FILENAME
For instance, to use the China mirror:
```plaintext
ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
ELECTRON_MIRROR="https://cdn.npm.taobao.org/dist/electron/"
```
#### Cache
Alternatively, you can override the local cache. `electron-download` will cache
Alternatively, you can override the local cache. `@electron/get` will cache
downloaded binaries in a local directory to not stress your network. You can use
that cache folder to provide custom builds of Electron or to avoid making contact
with the network at all.
@@ -89,16 +89,26 @@ The cache contains the version's official zip file as well as a checksum, stored
a text file. A typical cache might look like this:
```sh
├── electron-v1.7.9-darwin-x64.zip
── electron-v1.8.1-darwin-x64.zip
├── electron-v1.8.2-beta.1-darwin-x64.zip
├── electron-v1.8.2-beta.2-darwin-x64.zip
├── electron-v1.8.2-beta.3-darwin-x64.zip
├── SHASUMS256.txt-1.7.9
├── SHASUMS256.txt-1.8.1
── SHASUMS256.txt-1.8.2-beta.1
├── SHASUMS256.txt-1.8.2-beta.2
├── SHASUMS256.txt-1.8.2-beta.3
├── httpsgithub.comelectronelectronreleasesdownloadv1.7.9electron-v1.7.9-darwin-x64.zip
│ └── electron-v1.7.9-darwin-x64.zip
├── httpsgithub.comelectronelectronreleasesdownloadv1.7.9SHASUMS256.txt
│ └── SHASUMS256.txt
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.1electron-v1.8.1-darwin-x64.zip
│ └── electron-v1.8.1-darwin-x64.zip
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.1SHASUMS256.txt
│ └── SHASUMS256.txt
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.2-beta.1electron-v1.8.2-beta.1-darwin-x64.zip
│ └── electron-v1.8.2-beta.1-darwin-x64.zip
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.2-beta.1SHASUMS256.txt
│ └── SHASUMS256.txt
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.2-beta.2electron-v1.8.2-beta.2-darwin-x64.zip
│ └── electron-v1.8.2-beta.2-darwin-x64.zip
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.2-beta.2SHASUMS256.txt
│ └── SHASUMS256.txt
├── httpsgithub.comelectronelectronreleasesdownloadv1.8.2-beta.3electron-v1.8.2-beta.3-darwin-x64.zip
│ └── electron-v1.8.2-beta.3-darwin-x64.zip
└── httpsgithub.comelectronelectronreleasesdownloadv1.8.2-beta.3SHASUMS256.txt
└── SHASUMS256.txt
```
## Skip binary download

View File

@@ -93,8 +93,8 @@ filenames = {
"shell/browser/api/atom_api_top_level_window.h",
"shell/browser/api/atom_api_tray.cc",
"shell/browser/api/atom_api_tray.h",
"shell/browser/api/atom_api_url_request_ns.cc",
"shell/browser/api/atom_api_url_request_ns.h",
"shell/browser/api/atom_api_url_loader.cc",
"shell/browser/api/atom_api_url_loader.h",
"shell/browser/api/atom_api_view.cc",
"shell/browser/api/atom_api_view.h",
"shell/browser/api/atom_api_web_contents.cc",

View File

@@ -141,6 +141,7 @@ const messageBox = (sync, window, options) => {
defaultId = -1,
detail = '',
icon = null,
noLink = false,
message = '',
title = '',
type = 'none'
@@ -151,11 +152,15 @@ const messageBox = (sync, window, options) => {
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array')
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey)
if (typeof title !== 'string') throw new TypeError('Title must be a string')
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean')
if (typeof message !== 'string') throw new TypeError('Message must be a string')
if (typeof detail !== 'string') throw new TypeError('Detail must be a string')
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string')
checkboxChecked = !!checkboxChecked
if (checkboxChecked && !checkboxLabel) {
throw new Error('checkboxChecked requires that checkboxLabel also be passed')
}
// Choose a default button to get selected when dialog is cancelled.
if (cancelId == null) {
@@ -170,15 +175,13 @@ const messageBox = (sync, window, options) => {
}
}
const flags = options.noLink ? messageBoxOptions.noLink : 0
const settings = {
window,
messageBoxType,
buttons,
defaultId,
cancelId,
flags,
noLink,
title,
message,
detail,

View File

@@ -2,17 +2,13 @@
const url = require('url')
const { EventEmitter } = require('events')
const { Readable } = require('stream')
const { Readable, Writable } = require('stream')
const { app } = require('electron')
const { Session } = process.electronBinding('session')
const { net, Net } = process.electronBinding('net')
const { URLRequest } = net
const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net')
const { URLLoader } = net
// Net is an EventEmitter.
Object.setPrototypeOf(Net.prototype, EventEmitter.prototype)
EventEmitter.call(net)
Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype)
Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype)
const kSupportedProtocols = new Set(['http:', 'https:'])
@@ -40,32 +36,24 @@ const discardableDuplicateHeaders = new Set([
])
class IncomingMessage extends Readable {
constructor (urlRequest) {
constructor (responseHead) {
super()
this.urlRequest = urlRequest
this.shouldPush = false
this.data = []
this.urlRequest.on('data', (event, chunk) => {
this._storeInternalData(chunk)
this._pushInternalData()
})
this.urlRequest.on('end', () => {
this._storeInternalData(null)
this._pushInternalData()
})
this._shouldPush = false
this._data = []
this._responseHead = responseHead
}
get statusCode () {
return this.urlRequest.statusCode
return this._responseHead.statusCode
}
get statusMessage () {
return this.urlRequest.statusMessage
return this._responseHead.statusMessage
}
get headers () {
const filteredHeaders = {}
const rawHeaders = this.urlRequest.rawResponseHeaders
const rawHeaders = this._responseHead.headers
Object.keys(rawHeaders).forEach(header => {
if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) {
// do nothing with discardable duplicate headers
@@ -88,11 +76,11 @@ class IncomingMessage extends Readable {
}
get httpVersionMajor () {
return this.urlRequest.httpVersionMajor
return this._responseHead.httpVersion.major
}
get httpVersionMinor () {
return this.urlRequest.httpVersionMinor
return this._responseHead.httpVersion.minor
}
get rawTrailers () {
@@ -104,181 +92,197 @@ class IncomingMessage extends Readable {
}
_storeInternalData (chunk) {
this.data.push(chunk)
this._data.push(chunk)
this._pushInternalData()
}
_pushInternalData () {
while (this.shouldPush && this.data.length > 0) {
const chunk = this.data.shift()
this.shouldPush = this.push(chunk)
while (this._shouldPush && this._data.length > 0) {
const chunk = this._data.shift()
this._shouldPush = this.push(chunk)
}
}
_read () {
this.shouldPush = true
this._shouldPush = true
this._pushInternalData()
}
}
URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) {
if (isAsync) {
process.nextTick(() => {
this.clientRequest.emit(...rest)
})
} else {
this.clientRequest.emit(...rest)
}
}
URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) {
if (isAsync) {
process.nextTick(() => {
this._response.emit(...rest)
})
} else {
this._response.emit(...rest)
}
}
class ClientRequest extends EventEmitter {
constructor (options, callback) {
/** Writable stream that buffers up everything written to it. */
class SlurpStream extends Writable {
constructor () {
super()
this._data = Buffer.alloc(0)
}
_write (chunk, encoding, callback) {
this._data = Buffer.concat([this._data, chunk])
callback()
}
data () { return this._data }
}
class ChunkedBodyStream extends Writable {
constructor (clientRequest) {
super()
this._clientRequest = clientRequest
}
_write (chunk, encoding, callback) {
if (this._downstream) {
this._downstream.write(chunk).then(callback, callback)
} else {
// the contract of _write is that we won't be called again until we call
// the callback, so we're good to just save a single chunk.
this._pendingChunk = chunk
this._pendingCallback = callback
// The first write to a chunked body stream begins the request.
this._clientRequest._startRequest()
}
}
_final (callback) {
this._downstream.done()
callback()
}
startReading (pipe) {
if (this._downstream) {
throw new Error('two startReading calls???')
}
this._downstream = pipe
if (this._pendingChunk) {
const doneWriting = (maybeError) => {
const cb = this._pendingCallback
delete this._pendingCallback
delete this._pendingChunk
cb(maybeError)
}
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting)
}
}
}
function parseOptions (options) {
if (typeof options === 'string') {
options = url.parse(options)
} else {
options = { ...options }
}
const method = (options.method || 'GET').toUpperCase()
let urlStr = options.url
if (!urlStr) {
const urlObj = {}
const protocol = options.protocol || 'http:'
if (!kSupportedProtocols.has(protocol)) {
throw new Error('Protocol "' + protocol + '" not supported')
}
urlObj.protocol = protocol
if (options.host) {
urlObj.host = options.host
} else {
if (options.hostname) {
urlObj.hostname = options.hostname
} else {
urlObj.hostname = 'localhost'
}
if (options.port) {
urlObj.port = options.port
}
}
if (options.path && / /.test(options.path)) {
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
// with an additional rule for ignoring percentage-escaped characters
// but that's a) hard to capture in a regular expression that performs
// well, and b) possibly too restrictive for real-world usage. That's
// why it only scans for spaces because those are guaranteed to create
// an invalid request.
throw new TypeError('Request path contains unescaped characters')
}
const pathObj = url.parse(options.path || '/')
urlObj.pathname = pathObj.pathname
urlObj.search = pathObj.search
urlObj.hash = pathObj.hash
urlStr = url.format(urlObj)
}
const redirectPolicy = options.redirect || 'follow'
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual')
}
if (options.headers != null && typeof options.headers !== 'object') {
throw new TypeError('headers must be an object')
}
const urlLoaderOptions = {
method: method,
url: urlStr,
redirectPolicy,
extraHeaders: options.headers || {}
}
for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) {
if (!_isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`)
}
if (!_isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`)
}
}
if (options.session) {
if (options.session instanceof Session) {
urlLoaderOptions.session = options.session
} else {
throw new TypeError('`session` should be an instance of the Session class')
}
} else if (options.partition) {
if (typeof options.partition === 'string') {
urlLoaderOptions.partition = options.partition
} else {
throw new TypeError('`partition` should be a string')
}
}
return urlLoaderOptions
}
class ClientRequest extends Writable {
constructor (options, callback) {
super({ autoDestroy: true })
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready')
}
if (typeof options === 'string') {
options = url.parse(options)
} else {
options = Object.assign({}, options)
}
const method = (options.method || 'GET').toUpperCase()
let urlStr = options.url
if (!urlStr) {
const urlObj = {}
const protocol = options.protocol || 'http:'
if (!kSupportedProtocols.has(protocol)) {
throw new Error('Protocol "' + protocol + '" not supported')
}
urlObj.protocol = protocol
if (options.host) {
urlObj.host = options.host
} else {
if (options.hostname) {
urlObj.hostname = options.hostname
} else {
urlObj.hostname = 'localhost'
}
if (options.port) {
urlObj.port = options.port
}
}
if (options.path && / /.test(options.path)) {
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
// with an additional rule for ignoring percentage-escaped characters
// but that's a) hard to capture in a regular expression that performs
// well, and b) possibly too restrictive for real-world usage. That's
// why it only scans for spaces because those are guaranteed to create
// an invalid request.
throw new TypeError('Request path contains unescaped characters')
}
const pathObj = url.parse(options.path || '/')
urlObj.pathname = pathObj.pathname
urlObj.search = pathObj.search
urlObj.hash = pathObj.hash
urlStr = url.format(urlObj)
}
const redirectPolicy = options.redirect || 'follow'
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual')
}
const urlRequestOptions = {
method: method,
url: urlStr,
redirect: redirectPolicy
}
if (options.session) {
if (options.session instanceof Session) {
urlRequestOptions.session = options.session
} else {
throw new TypeError('`session` should be an instance of the Session class')
}
} else if (options.partition) {
if (typeof options.partition === 'string') {
urlRequestOptions.partition = options.partition
} else {
throw new TypeError('`partition` should be a string')
}
}
const urlRequest = new URLRequest(urlRequestOptions)
// Set back and forward links.
this.urlRequest = urlRequest
urlRequest.clientRequest = this
// This is a copy of the extra headers structure held by the native
// net::URLRequest. The main reason is to keep the getHeader API synchronous
// after the request starts.
this.extraHeaders = {}
if (options.headers) {
for (const key in options.headers) {
this.setHeader(key, options.headers[key])
}
}
// Set when the request uses chunked encoding. Can be switched
// to true only once and never set back to false.
this.chunkedEncodingEnabled = false
urlRequest.on('response', () => {
const response = new IncomingMessage(urlRequest)
urlRequest._response = response
this.emit('response', response)
})
urlRequest.on('login', (event, authInfo, callback) => {
this.emit('login', authInfo, (username, password) => {
// If null or undefined username/password, force to empty string.
if (username === null || username === undefined) {
username = ''
}
if (typeof username !== 'string') {
throw new Error('username must be a string')
}
if (password === null || password === undefined) {
password = ''
}
if (typeof password !== 'string') {
throw new Error('password must be a string')
}
callback(username, password)
})
})
if (callback) {
this.once('response', callback)
}
}
get chunkedEncoding () {
return this.chunkedEncodingEnabled
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options)
this._urlLoaderOptions = urlLoaderOptions
this._redirectPolicy = redirectPolicy
this._started = false
}
set chunkedEncoding (value) {
if (!this.urlRequest.notStarted) {
throw new Error('Can\'t set the transfer encoding, headers have been sent')
if (this._started) {
throw new Error('chunkedEncoding can only be set before the request is started')
}
if (typeof this._chunkedEncoding !== 'undefined') {
throw new Error('chunkedEncoding can only be set once')
}
this._chunkedEncoding = !!value
if (this._chunkedEncoding) {
this._body = new ChunkedBodyStream(this)
this._urlLoaderOptions.body = (pipe) => {
this._body.startReading(pipe)
}
}
this.chunkedEncodingEnabled = value
}
setHeader (name, value) {
@@ -288,13 +292,18 @@ class ClientRequest extends EventEmitter {
if (value == null) {
throw new Error('`value` required in setHeader("' + name + '", value)')
}
if (!this.urlRequest.notStarted) {
if (this._started || this._firstWrite) {
throw new Error('Can\'t set headers after they are sent')
}
if (!_isValidHeaderName(name)) {
throw new Error(`Invalid header name: '${name}'`)
}
if (!_isValidHeaderValue(value.toString())) {
throw new Error(`Invalid value for header '${name}': '${value}'`)
}
const key = name.toLowerCase()
this.extraHeaders[key] = value
this.urlRequest.setExtraHeader(name, value.toString())
this._urlLoaderOptions.extraHeaders[key] = value
}
getHeader (name) {
@@ -302,12 +311,8 @@ class ClientRequest extends EventEmitter {
throw new Error('`name` is required for getHeader(name)')
}
if (!this.extraHeaders) {
return
}
const key = name.toLowerCase()
return this.extraHeaders[key]
return this._urlLoaderOptions.extraHeaders[key]
}
removeHeader (name) {
@@ -315,93 +320,144 @@ class ClientRequest extends EventEmitter {
throw new Error('`name` is required for removeHeader(name)')
}
if (!this.urlRequest.notStarted) {
if (this._started || this._firstWrite) {
throw new Error('Can\'t remove headers after they are sent')
}
const key = name.toLowerCase()
delete this.extraHeaders[key]
this.urlRequest.removeExtraHeader(name)
delete this._urlLoaderOptions.extraHeaders[key]
}
_write (chunk, encoding, callback, isLast) {
const chunkIsString = typeof chunk === 'string'
const chunkIsBuffer = chunk instanceof Buffer
if (!chunkIsString && !chunkIsBuffer) {
throw new TypeError('First argument must be a string or Buffer')
_write (chunk, encoding, callback) {
this._firstWrite = true
if (!this._body) {
this._body = new SlurpStream()
this._body.on('finish', () => {
this._urlLoaderOptions.body = this._body.data()
this._startRequest()
})
}
if (chunkIsString) {
// We convert all strings into binary buffers.
chunk = Buffer.from(chunk, encoding)
}
// Since writing to the network is asynchronous, we conservatively
// assume that request headers are written after delivering the first
// buffer to the network IO thread.
if (this.urlRequest.notStarted) {
this.urlRequest.setChunkedUpload(this.chunkedEncoding)
}
// Headers are assumed to be sent on first call to _writeBuffer,
// i.e. after the first call to write or end.
const result = this.urlRequest.write(chunk, isLast)
// The write callback is fired asynchronously to mimic Node.js.
if (callback) {
process.nextTick(callback)
}
return result
// TODO: is this the right way to forward to another stream?
this._body.write(chunk, encoding, callback)
}
write (data, encoding, callback) {
if (this.urlRequest.finished) {
const error = new Error('Write after end')
process.nextTick(writeAfterEndNT, this, error, callback)
return true
_final (callback) {
if (this._body) {
// TODO: is this the right way to forward to another stream?
this._body.end(callback)
} else {
// end() called without a body, go ahead and start the request
this._startRequest()
callback()
}
return this._write(data, encoding, callback, false)
}
end (data, encoding, callback) {
if (this.urlRequest.finished) {
return false
_startRequest () {
this._started = true
const stringifyValues = (obj) => {
const ret = {}
for (const k in obj) {
ret[k] = obj[k].toString()
}
return ret
}
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) }
this._urlLoader = new URLLoader(opts)
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
const response = this._response = new IncomingMessage(responseHead)
this.emit('response', response)
})
this._urlLoader.on('data', (event, data) => {
this._response._storeInternalData(Buffer.from(data))
})
this._urlLoader.on('complete', () => {
if (this._response) { this._response._storeInternalData(null) }
})
this._urlLoader.on('error', (event, netErrorString) => {
const error = new Error(netErrorString)
if (this._response) this._response.destroy(error)
this._die(error)
})
if (typeof data === 'function') {
callback = data
encoding = null
data = null
} else if (typeof encoding === 'function') {
callback = encoding
encoding = null
}
this._urlLoader.on('login', (event, authInfo, callback) => {
const handled = this.emit('login', authInfo, callback)
if (!handled) {
// If there were no listeners, cancel the authentication request.
callback()
}
})
data = data || ''
this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
const { statusCode, newMethod, newUrl } = redirectInfo
if (this._redirectPolicy === 'error') {
this._die(new Error(`Attempted to redirect, but redirect policy was 'error'`))
} else if (this._redirectPolicy === 'manual') {
let _followRedirect = false
this._followRedirectCb = () => { _followRedirect = true }
try {
this.emit('redirect', statusCode, newMethod, newUrl, headers)
} finally {
this._followRedirectCb = null
if (!_followRedirect && !this._aborted) {
this._die(new Error('Redirect was cancelled'))
}
}
} else if (this._redirectPolicy === 'follow') {
// Calling followRedirect() when the redirect policy is 'follow' is
// allowed but does nothing. (Perhaps it should throw an error
// though...? Since the redirect will happen regardless.)
try {
this._followRedirectCb = () => {}
this.emit('redirect', statusCode, newMethod, newUrl, headers)
} finally {
this._followRedirectCb = null
}
} else {
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`))
}
})
return this._write(data, encoding, callback, true)
this._urlLoader.on('upload-progress', (event, position, total) => {
this._uploadProgress = { active: true, started: true, current: position, total }
this.emit('upload-progress', position, total) // Undocumented, for now
})
this._urlLoader.on('download-progress', (event, current) => {
if (this._response) {
this._response.emit('download-progress', current) // Undocumented, for now
}
})
}
followRedirect () {
this.urlRequest.followRedirect()
if (this._followRedirectCb) {
this._followRedirectCb()
} else {
throw new Error('followRedirect() called, but was not waiting for a redirect')
}
}
abort () {
this.urlRequest.cancel()
if (!this._aborted) {
process.nextTick(() => { this.emit('abort') })
}
this._aborted = true
this._die()
}
_die (err) {
this.destroy(err)
if (this._urlLoader) {
this._urlLoader.cancel()
if (this._response) this._response.destroy(err)
}
}
getUploadProgress () {
return this.urlRequest.getUploadProgress()
return this._uploadProgress ? { ...this._uploadProgress } : { active: false }
}
}
function writeAfterEndNT (self, error, callback) {
self.emit('error', error)
if (callback) callback(error)
}
Net.prototype.request = function (options, callback) {
return new ClientRequest(options, callback)
}

View File

@@ -7,6 +7,7 @@ const path = require('path')
const url = require('url')
const { app, ipcMain, session, deprecate } = electron
const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager')
const NavigationController = require('@electron/internal/browser/navigation-controller')
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
@@ -266,6 +267,7 @@ WebContents.prototype.getPrinters = function () {
return this._getPrinters()
} else {
console.error('Error: Printing feature is disabled.')
return []
}
}
@@ -386,9 +388,7 @@ WebContents.prototype._init = function () {
width: 800,
height: 600
}
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
event, url, referrer, frameName, disposition,
options, additionalFeatures, postData)
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData)
})
// Create a new browser window for the native implementation of
@@ -410,8 +410,7 @@ WebContents.prototype._init = function () {
webContents
}
const referrer = { url: '', policy: 'default' }
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
event, url, referrer, frameName, disposition, options)
internalWindowOpen(event, url, referrer, frameName, disposition, options)
})
}

View File

@@ -1,6 +1,7 @@
'use strict'
const { BrowserWindow, webContents } = require('electron')
const electron = require('electron')
const { BrowserWindow } = electron
const { isSameOrigin } = process.electronBinding('v8_util')
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
@@ -244,14 +245,11 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, fra
}
const referrer = { url: '', policy: 'default' }
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
url, referrer, frameName, disposition, options, additionalFeatures)
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures)
})
// Routed window.open messages with fully parsed options
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer,
frameName, disposition, options,
additionalFeatures, postData) {
function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options)
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer)
const { newGuest } = event
@@ -269,11 +267,12 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', functio
} else {
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData)
}
})
}
const handleMessage = function (channel, handler) {
ipcMainUtils.handle(channel, (event, guestId, ...args) => {
const guestContents = webContents.fromId(guestId)
// Access webContents via electron to prevent circular require.
const guestContents = electron.webContents.fromId(guestId)
if (!guestContents) {
throw new Error(`Invalid guestId: ${guestId}`)
}
@@ -282,6 +281,13 @@ const handleMessage = function (channel, handler) {
})
}
const securityCheck = function (contents, guestContents, check) {
if (!check(contents, guestContents)) {
console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`)
throw new Error(`Access denied to guestId: ${guestContents.id}`)
}
}
const windowMethods = new Set([
'destroy',
'focus',
@@ -289,10 +295,7 @@ const windowMethods = new Set([
])
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => {
if (!canAccessWindow(event.sender, guestContents)) {
console.error(`Blocked ${event.sender.getURL()} from accessing guestId: ${guestContents.id}`)
throw new Error(`Access denied to guestId: ${guestContents.id}`)
}
securityCheck(event.sender, guestContents, canAccessWindow)
if (!windowMethods.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`)
@@ -310,6 +313,8 @@ handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestC
// The W3C does not seem to have word on how postMessage should work when the
// origins do not match, so we do not do |canAccessWindow| check here since
// postMessage across origins is useful and not harmful.
securityCheck(event.sender, guestContents, isRelatedWindow)
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
const sourceId = event.sender.id
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
@@ -324,10 +329,7 @@ const webContentsMethods = new Set([
])
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => {
if (!canAccessWindow(event.sender, guestContents)) {
console.error(`Blocked ${event.sender.getURL()} from accessing guestId: ${guestContents.id}`)
throw new Error(`Access denied to guestId: ${guestContents.id}`)
}
securityCheck(event.sender, guestContents, canAccessWindow)
if (!webContentsMethods.has(method)) {
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`)
@@ -336,3 +338,5 @@ handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guest
return guestContents[method](...args)
})
exports.internalWindowOpen = internalWindowOpen

View File

@@ -187,13 +187,14 @@ app.on('window-all-closed', () => {
}
})
Promise.all([
import('@electron/internal/browser/default-menu'),
app.whenReady()
]).then(([{ setDefaultApplicationMenu }]) => {
// Create default menu
setDefaultApplicationMenu()
})
const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu')
// Create default menu.
//
// Note that the task must be added before loading any app, so we can make sure
// the call is maded before any user window is created, otherwise the default
// menu may show even when user explicitly hides the menu.
app.once('ready', setDefaultApplicationMenu)
if (packagePath) {
// Finally load app's main.js and transfer control to C++.

View File

@@ -152,7 +152,8 @@ const NavigationController = (function () {
NavigationController.prototype.reloadIgnoringCache = function () {
this.pendingIndex = this.currentIndex
return this.webContents._loadURL(this.getURL(), {
extraHeaders: 'pragma: no-cache\n'
extraHeaders: 'pragma: no-cache\n',
reloadIgnoringCache: true
})
}

View File

@@ -54,7 +54,12 @@ class CrashReporter {
}
getUploadedReports () {
return binding.getUploadedReports(this.getCrashesDirectory())
const crashDir = this.getCrashesDirectory()
if (!crashDir) {
throw new Error('crashReporter has not been started')
}
return binding.getUploadedReports(crashDir)
}
getCrashesDirectory () {

View File

@@ -188,6 +188,8 @@ if (nodeIntegration) {
delete global.setImmediate
delete global.clearImmediate
delete global.global
delete global.root
delete global.GLOBAL
})
}
}

View File

@@ -5,6 +5,8 @@
#ifndef NATIVE_MATE_NATIVE_MATE_FUNCTION_TEMPLATE_H_
#define NATIVE_MATE_NATIVE_MATE_FUNCTION_TEMPLATE_H_
#include <utility>
#include "base/callback.h"
#include "base/logging.h"
#include "native_mate/arguments.h"
@@ -197,7 +199,8 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
void DispatchToCallback(base::Callback<ReturnType(ArgTypes...)> callback) {
v8::MicrotasksScope script_scope(args_->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
args_->Return(callback.Run(ArgumentHolder<indices, ArgTypes>::value...));
args_->Return(
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...));
}
// In C++, you can declare the function foo(void), but you can't pass a void
@@ -206,7 +209,7 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
void DispatchToCallback(base::Callback<void(ArgTypes...)> callback) {
v8::MicrotasksScope script_scope(args_->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
callback.Run(ArgumentHolder<indices, ArgTypes>::value...);
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...);
}
private:

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "7.1.2",
"version": "7.1.8",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -81,4 +81,9 @@ net_avoid_vector_const_elements.patch
feat_add_set_theme_source_to_allow_apps_to.patch
build_fix_when_building_with_enable_plugins_false.patch
x11_and_ozone_move_closing_logic_to_dwthplatform.patch
add_trustedauthclient_to_urlloaderfactory.patch
make_autocorrect_off_and_spellcheck_false_disable_touch_bar_typing.patch
fix_focusowningwebcontents_to_handle_renderwidgethosts_for_oopifs.patch
feat_allow_disbaling_blink_scheduler_throttling_per_renderview.patch
accessible_pane_view.patch
only_apply_transform_when_outermost_outer_webcontents.patch

View File

@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cheng Zhao <zcbenz@gmail.com>
Date: Thu, 4 Oct 2018 14:57:02 -0700
Subject: fix: add back virtual methods in AccessiblePaneView
Mark SetPaneFocus and RemovePaneFocus as virtual in AccessiblePaneView, as we
need to override them in MenuBar.
Pending upstream patch: https://crrev.com/c/1959189
diff --git a/ui/views/accessible_pane_view.h b/ui/views/accessible_pane_view.h
index 813fd13860a8..990c905e8f19 100644
--- a/ui/views/accessible_pane_view.h
+++ b/ui/views/accessible_pane_view.h
@@ -35,7 +35,7 @@ class VIEWS_EXPORT AccessiblePaneView : public View,
// If |initial_focus| is not NULL, that control will get
// the initial focus, if it's enabled and focusable. Returns true if
// the pane was able to receive focus.
- bool SetPaneFocus(View* initial_focus);
+ virtual bool SetPaneFocus(View* initial_focus);
bool pane_has_focus() const { return pane_has_focus_; }
@@ -83,7 +83,7 @@ class VIEWS_EXPORT AccessiblePaneView : public View,
bool ContainsForFocusSearch(View* root, const View* v);
// Remove pane focus.
- void RemovePaneFocus();
+ virtual void RemovePaneFocus();
View* GetFirstFocusableChild();
View* GetLastFocusableChild();

View File

@@ -0,0 +1,158 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jeremy Apthorp <nornagon@nornagon.net>
Date: Tue, 12 Nov 2019 11:50:16 -0800
Subject: add TrustedAuthClient to URLLoaderFactory
This allows intercepting authentication requests for the 'net' module.
Without this, the 'login' event for electron.net.ClientRequest can't be
implemented, because the existing path checks for the presence of a
WebContents, and cancels the authentication if there's no WebContents
available, which there isn't in the case of the 'net' module.
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 0671ed5ff69a53e6793c7e33db092150fc783601..b577333983ac1268d3a568699d7cde7a375e0877 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -168,6 +168,25 @@ interface TrustedURLLoaderHeaderClient {
pending_receiver<TrustedHeaderClient> header_client);
};
+interface TrustedAuthClient {
+ OnAuthRequired(
+ mojo_base.mojom.UnguessableToken? window_id,
+ uint32 process_id,
+ uint32 routing_id,
+ uint32 request_id,
+ url.mojom.Url url,
+ bool first_auth_attempt,
+ AuthChallengeInfo auth_info,
+ URLResponseHead? head,
+ pending_remote<AuthChallengeResponder> auth_challenge_responder);
+};
+interface TrustedURLLoaderAuthClient {
+ // When a new URLLoader is created, this will be called to pass a
+ // corresponding |auth_client|.
+ OnLoaderCreated(int32 request_id,
+ pending_receiver<TrustedAuthClient> auth_client);
+};
+
interface CertVerifierClient {
Verify(
int32 default_error,
@@ -535,6 +554,8 @@ struct URLLoaderFactoryParams {
// impact because of the extra process hops, so use should be minimized.
pending_remote<TrustedURLLoaderHeaderClient>? header_client;
+ pending_remote<TrustedURLLoaderAuthClient>? auth_client;
+
// If non-empty array is given, |factory_bound_allow_patterns| is used for
// CORS checks in addition to the per-context allow patterns that is managed
// via NetworkContext interface. This still respects the per-context block
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 341e1c78acf41b42b4ff210af575bbe870d543cc..5c2b111cf98ec6577e94da9bf5991d1396a367a8 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -336,6 +336,7 @@ URLLoader::URLLoader(
base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder,
base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator,
mojom::TrustedURLLoaderHeaderClient* url_loader_header_client,
+ mojom::TrustedURLLoaderAuthClient* url_loader_auth_client,
mojom::OriginPolicyManager* origin_policy_manager)
: url_request_context_(url_request_context),
network_service_client_(network_service_client),
@@ -386,6 +387,11 @@ URLLoader::URLLoader(
header_client_.set_disconnect_handler(
base::BindOnce(&URLLoader::OnConnectionError, base::Unretained(this)));
}
+ if (url_loader_auth_client) {
+ url_loader_auth_client->OnLoaderCreated(request_id_, auth_client_.BindNewPipeAndPassReceiver());
+ auth_client_.set_disconnect_handler(
+ base::BindOnce(&URLLoader::OnConnectionError, base::Unretained(this)));
+ }
if (want_raw_headers_) {
options_ |= mojom::kURLLoadOptionSendSSLInfoWithResponse |
mojom::kURLLoadOptionSendSSLInfoForCertificateError;
@@ -819,7 +825,7 @@ void URLLoader::OnReceivedRedirect(net::URLRequest* url_request,
void URLLoader::OnAuthRequired(net::URLRequest* url_request,
const net::AuthChallengeInfo& auth_info) {
- if (!network_context_client_) {
+ if (!network_context_client_ && !auth_client_) {
OnAuthCredentials(base::nullopt);
return;
}
@@ -835,10 +841,18 @@ void URLLoader::OnAuthRequired(net::URLRequest* url_request,
if (url_request->response_headers())
head.headers = url_request->response_headers();
head.auth_challenge_info = auth_info;
- network_context_client_->OnAuthRequired(
- fetch_window_id_, factory_params_->process_id, render_frame_id_,
- request_id_, url_request_->url(), first_auth_attempt_, auth_info, head,
- auth_challenge_responder_receiver_.BindNewPipeAndPassRemote());
+
+ if (auth_client_) {
+ auth_client_->OnAuthRequired(
+ fetch_window_id_, factory_params_->process_id, render_frame_id_,
+ request_id_, url_request_->url(), first_auth_attempt_, auth_info, head,
+ auth_challenge_responder_receiver_.BindNewPipeAndPassRemote());
+ } else {
+ network_context_client_->OnAuthRequired(
+ fetch_window_id_, factory_params_->process_id, render_frame_id_,
+ request_id_, url_request_->url(), first_auth_attempt_, auth_info, head,
+ auth_challenge_responder_receiver_.BindNewPipeAndPassRemote());
+ }
auth_challenge_responder_receiver_.set_disconnect_handler(
base::BindOnce(&URLLoader::DeleteSelf, base::Unretained(this)));
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index 302e469ad63bdb7fe5b4dea775079962df89efd3..92706003d7a0d8905cf27ad52972044354f03841 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -84,6 +84,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader
base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder,
base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator,
mojom::TrustedURLLoaderHeaderClient* url_loader_header_client,
+ mojom::TrustedURLLoaderAuthClient* url_loader_auth_client,
mojom::OriginPolicyManager* origin_policy_manager);
~URLLoader() override;
@@ -361,6 +362,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader
base::Optional<base::UnguessableToken> fetch_window_id_;
mojo::Remote<mojom::TrustedHeaderClient> header_client_;
+ mojo::Remote<mojom::TrustedAuthClient> auth_client_;
std::unique_ptr<FileOpenerForUpload> file_opener_for_upload_;
diff --git a/services/network/url_loader_factory.cc b/services/network/url_loader_factory.cc
index e01d555e62568a506ed8093e2e4c3ff61ffe4ac2..bccc9c79b4653371567f4ee479dcbd8aca5c7438 100644
--- a/services/network/url_loader_factory.cc
+++ b/services/network/url_loader_factory.cc
@@ -37,6 +37,7 @@ URLLoaderFactory::URLLoaderFactory(
params_(std::move(params)),
resource_scheduler_client_(std::move(resource_scheduler_client)),
header_client_(std::move(params_->header_client)),
+ auth_client_(std::move(params_->auth_client)),
cors_url_loader_factory_(cors_url_loader_factory) {
DCHECK(context);
DCHECK_NE(mojom::kInvalidProcessId, params_->process_id);
@@ -147,6 +148,7 @@ void URLLoaderFactory::CreateLoaderAndStart(
std::move(keepalive_statistics_recorder),
std::move(network_usage_accumulator),
header_client_.is_bound() ? header_client_.get() : nullptr,
+ auth_client_.is_bound() ? auth_client_.get() : nullptr,
context_->origin_policy_manager());
cors_url_loader_factory_->OnLoaderCreated(std::move(loader));
}
diff --git a/services/network/url_loader_factory.h b/services/network/url_loader_factory.h
index 30bdfafd8a951f35946c172e1bed0a8d7c367362..ec755f67f96422184d74d7b489f2887db12fb18c 100644
--- a/services/network/url_loader_factory.h
+++ b/services/network/url_loader_factory.h
@@ -70,6 +70,7 @@ class URLLoaderFactory : public mojom::URLLoaderFactory {
mojom::URLLoaderFactoryParamsPtr params_;
scoped_refptr<ResourceSchedulerClient> resource_scheduler_client_;
mojo::Remote<mojom::TrustedURLLoaderHeaderClient> header_client_;
+ mojo::Remote<mojom::TrustedURLLoaderAuthClient> auth_client_;
// |cors_url_loader_factory_| owns this.
cors::CorsURLLoaderFactory* cors_url_loader_factory_;

View File

@@ -3,48 +3,18 @@ From: Jeremy Apthorp <jeremya@chromium.org>
Date: Wed, 10 Oct 2018 15:07:34 -0700
Subject: command-ismediakey.patch
define Command::IsMediaKey on mac; copied from //chrome/common/extensions/command.cc,
which also defines a bunch of other stuff that depends on extensions.
since we only need IsMediaKey, and we don't want the extensions stuff
(and aren't compiling command.cc), it's safe to duplicate the
definition. A candidate for upstreaming would be to move the IsMediaKey
function into //ui.
Override MediaKeysListener::IsMediaKeycode to also listen for Volume Up, Volume Down,
and Mute. We also need to patch out Chromium's usage of RemoteCommandCenterDelegate, as
it uses MPRemoteCommandCenter. MPRemoteCommandCenter makes it such that GlobalShortcuts
in Electron will not work as intended, because by design an app does not receive remote
control events until it begins playing audio. This means that a media shortcut would not kick
into effect until you, for example, began playing a YouTube video which sort of defeats the
purpose of GlobalShortcuts.
Also apply electron/electron@0f67b1866a9f00b852370e721affa4efda623f3a
and electron/electron@d2368d2d3b3de9eec4cc32b6aaf035cc89921bf1 as
patches.
At the moment there is no upstream possibility for this; but perhaps Chromium may
consider some kind of switch, enabled by default, which would conditionally choose to avoid usage of
RemoteCommandCenterDelegate on macOS.
diff --git a/chrome/browser/extensions/global_shortcut_listener_mac.mm b/chrome/browser/extensions/global_shortcut_listener_mac.mm
index befe726af9c10b1563a7fc0bb77cc55f65943d5c..46c6fe08bab8471007f78d3ef227e5195bfdf0e1 100644
--- a/chrome/browser/extensions/global_shortcut_listener_mac.mm
+++ b/chrome/browser/extensions/global_shortcut_listener_mac.mm
@@ -21,6 +21,26 @@
namespace extensions {
+// NOTE: this is defined in command.cc, but command.cc is full of
+// chrome-extensions-specific logic that we don't want to depend on.
+// Since we don't build command.cc in Electron, it's safe to re-define this
+// function here. Ideally, though, `IsMediaKey` would be the responsibility of
+// `ui::Accelerator`, rather than `extensions::Command`.
+
+// static
+bool Command::IsMediaKey(const ui::Accelerator& accelerator) {
+ if (accelerator.modifiers() != 0)
+ return false;
+
+ return (accelerator.key_code() == ui::VKEY_MEDIA_NEXT_TRACK ||
+ accelerator.key_code() == ui::VKEY_MEDIA_PREV_TRACK ||
+ accelerator.key_code() == ui::VKEY_MEDIA_PLAY_PAUSE ||
+ accelerator.key_code() == ui::VKEY_MEDIA_STOP ||
+ accelerator.key_code() == ui::VKEY_VOLUME_UP ||
+ accelerator.key_code() == ui::VKEY_VOLUME_DOWN ||
+ accelerator.key_code() == ui::VKEY_VOLUME_MUTE);
+}
+
// static
GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
diff --git a/chrome/browser/extensions/global_shortcut_listener_win.cc b/chrome/browser/extensions/global_shortcut_listener_win.cc
index c5125495b4d178ffb18be4d2d9670f7556412cbd..cddb321abb938c667a4a2089f87eab999510e9b1 100644
--- a/chrome/browser/extensions/global_shortcut_listener_win.cc
@@ -87,10 +57,34 @@ index 392cf3d58c64c088596e8d321a2ce37b0ec60b6e..43e30f47240dc10a3a9b950255d4e487
ui::Accelerator accelerator(
ui::KeyboardCodeFromXKeyEvent(x_event), modifiers);
diff --git a/ui/base/accelerators/media_keys_listener.cc b/ui/base/accelerators/media_keys_listener.cc
index 1145e1f3d79482b5bb468c3128431ac674310e5f..e319e7a3e34e05b0e96d4a2dbb456355f327137a 100644
--- a/ui/base/accelerators/media_keys_listener.cc
+++ b/ui/base/accelerators/media_keys_listener.cc
@@ -13,7 +13,9 @@ MediaKeysListener::~MediaKeysListener() = default;
// static
bool MediaKeysListener::IsMediaKeycode(KeyboardCode key_code) {
return key_code == VKEY_MEDIA_PLAY_PAUSE || key_code == VKEY_MEDIA_STOP ||
- key_code == VKEY_MEDIA_PREV_TRACK || key_code == VKEY_MEDIA_NEXT_TRACK;
+ key_code == VKEY_MEDIA_PREV_TRACK || key_code == VKEY_MEDIA_NEXT_TRACK ||
+ key_code == VKEY_VOLUME_UP || key_code == VKEY_VOLUME_DOWN ||
+ key_code == VKEY_VOLUME_MUTE;
}
} // namespace ui
diff --git a/ui/base/accelerators/media_keys_listener_mac.mm b/ui/base/accelerators/media_keys_listener_mac.mm
index f4e3126a4efd66f05c4f13e40ba23db10b8cca96..bb4c1a891dd13855227b39a0e582fd4dbc342ec9 100644
--- a/ui/base/accelerators/media_keys_listener_mac.mm
+++ b/ui/base/accelerators/media_keys_listener_mac.mm
@@ -11,6 +11,7 @@
#include <IOKit/hidsystem/ev_keymap.h>
#include "base/containers/flat_set.h"
+#include "electron/buildflags/buildflags.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/remote_command_media_keys_listener_mac.h"
#include "ui/base/now_playing/remote_command_center_delegate.h"
@@ -33,6 +33,12 @@ KeyboardCode MediaKeyCodeToKeyboardCode(int key_code) {
case NX_KEYTYPE_NEXT:
case NX_KEYTYPE_FAST:
@@ -116,3 +110,20 @@ index f4e3126a4efd66f05c4f13e40ba23db10b8cca96..bb4c1a891dd13855227b39a0e582fd4d
return event;
}
@@ -233,6 +234,7 @@ static CGEventRef EventTapCallback(CGEventTapProxy proxy,
// For Mac OS 10.12.2 or later, we want to use MPRemoteCommandCenter for
// getting media keys globally if there is a RemoteCommandCenterDelegate
// available.
+#if !BUILDFLAG(ENABLE_MEDIA_KEY_OVERRIDES)
if (@available(macOS 10.12.2, *)) {
if (scope == Scope::kGlobal &&
now_playing::RemoteCommandCenterDelegate::GetInstance()) {
@@ -242,7 +244,7 @@ static CGEventRef EventTapCallback(CGEventTapProxy proxy,
return std::move(listener);
}
}
-
+#endif
return std::make_unique<MediaKeysListenerImpl>(delegate, scope);
}

View File

@@ -0,0 +1,155 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Fri, 29 Nov 2019 16:08:14 -0800
Subject: feat: allow disabling blink scheduler throttling per RenderView
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 344eba7291bdf3cbfd0c71f3167b821abd0b8f73..9462fea5323ee58ab8052ffe2c872d6b0556197b 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -416,6 +416,10 @@ void RenderViewHostImpl::SetBackgroundOpaque(bool opaque) {
Send(new ViewMsg_SetBackgroundOpaque(GetRoutingID(), opaque));
}
+void RenderViewHostImpl::SetSchedulerThrottling(bool allowed) {
+ Send(new ViewMsg_SetSchedulerThrottling(GetRoutingID(), allowed));
+}
+
bool RenderViewHostImpl::IsMainFrameActive() {
return is_active();
}
diff --git a/content/browser/renderer_host/render_view_host_impl.h b/content/browser/renderer_host/render_view_host_impl.h
index 1a98f40d1c76d94f1fb7bf1d7bd060b4f30f2c62..d68901213af1f57f77a934f602943e00a60ca4ff 100644
--- a/content/browser/renderer_host/render_view_host_impl.h
+++ b/content/browser/renderer_host/render_view_host_impl.h
@@ -101,6 +101,7 @@ class CONTENT_EXPORT RenderViewHostImpl
void SetWebUIProperty(const std::string& name,
const std::string& value) override;
void SyncRendererPrefs() override;
+ void SetSchedulerThrottling(bool allowed) override;
WebPreferences GetWebkitPreferences() override;
void UpdateWebkitPreferences(const WebPreferences& prefs) override;
void OnWebkitPreferencesChanged() override;
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 632b7b8159a7e308a382a6a59babf8f427c51e71..6070c41c268818558f34a37dec6a46678e76c955 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -117,6 +117,9 @@ IPC_MESSAGE_ROUTED1(ViewMsg_SetBackgroundOpaque, bool /* opaque */)
// Sends updated preferences to the renderer.
IPC_MESSAGE_ROUTED1(ViewMsg_SetRendererPrefs, blink::mojom::RendererPreferences)
+// Whether to enable the Renderer scheduler background throttling.
+IPC_MESSAGE_ROUTED1(ViewMsg_SetSchedulerThrottling, bool /* allowed */)
+
// This passes a set of webkit preferences down to the renderer.
IPC_MESSAGE_ROUTED1(ViewMsg_UpdateWebPreferences,
content::WebPreferences)
diff --git a/content/public/browser/render_view_host.h b/content/public/browser/render_view_host.h
index 832d14711c569eb69819752187bde9dbbeb5a70c..0e23ee0a47bb8bb39ed9bfa8ff3373a3b180c918 100644
--- a/content/public/browser/render_view_host.h
+++ b/content/public/browser/render_view_host.h
@@ -107,6 +107,9 @@ class CONTENT_EXPORT RenderViewHost : public IPC::Sender {
// RenderViewHostDelegate.
virtual void SyncRendererPrefs() = 0;
+ // Disable/Enable scheduler throttling.
+ virtual void SetSchedulerThrottling(bool allowed) = 0;
+
// TODO(mustaq): Replace "Webkit" from the following three method names.
//
// Returns the current WebKit preferences. Note: WebPreferences is cached, so
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index dfe6c07d9e8ef952bec7a350e80729870a990a39..eb89a8cd51045a741fcc18ad575db9320cb62673 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1237,6 +1237,8 @@ bool RenderViewImpl::OnMessageReceived(const IPC::Message& message) {
IPC_BEGIN_MESSAGE_MAP(RenderViewImpl, message)
IPC_MESSAGE_HANDLER(ViewMsg_SetPageScale, OnSetPageScale)
IPC_MESSAGE_HANDLER(ViewMsg_SetInitialFocus, OnSetInitialFocus)
+ IPC_MESSAGE_HANDLER(ViewMsg_SetSchedulerThrottling,
+ OnSetSchedulerThrottling)
IPC_MESSAGE_HANDLER(ViewMsg_UpdateTargetURL_ACK, OnUpdateTargetURLAck)
IPC_MESSAGE_HANDLER(ViewMsg_UpdateWebPreferences, OnUpdateWebPreferences)
IPC_MESSAGE_HANDLER(ViewMsg_ClosePage, OnClosePage)
@@ -1883,6 +1885,12 @@ void RenderViewImpl::OnSetPageScale(float page_scale_factor) {
webview()->SetPageScaleFactor(page_scale_factor);
}
+void RenderViewImpl::OnSetSchedulerThrottling(bool allowed) {
+ if (!webview())
+ return;
+ webview()->SetSchedulerThrottling(allowed);
+}
+
void RenderViewImpl::UpdateZoomLevel(double zoom_level) {
webview()->CancelPagePopup();
SetZoomLevel(zoom_level);
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index f3a3167f7f435b2ee009282ab2a9e1d5dba7fbb4..b41f368e02a5dbaa493c2c3c54907cc5c6982308 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -450,6 +450,7 @@ class CONTENT_EXPORT RenderViewImpl : public blink::WebViewClient,
void OnSetRendererPrefs(
const blink::mojom::RendererPreferences& renderer_prefs);
void OnSetWebUIProperty(const std::string& name, const std::string& value);
+ void OnSetSchedulerThrottling(bool allowed);
void OnSuppressDialogsUntilSwapOut();
void OnUpdateTargetURLAck();
void OnUpdateWebPreferences(const WebPreferences& prefs);
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index db7d7c87686a5481d7af8cbdd5a94875b6f86b03..e0cfad9e82e27213b31c95c5fcab73eee9ad860c 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -406,6 +406,7 @@ class WebView {
// Scheduling -----------------------------------------------------------
virtual PageScheduler* Scheduler() const = 0;
+ virtual void SetSchedulerThrottling(bool allowed) = 0;
// Visibility -----------------------------------------------------------
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index b286951e399b4adcdaea5cae9a1cac5812498469..43b60de6c56f822ceea9f2dca02835e9d392ea1a 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -3499,10 +3499,17 @@ PageScheduler* WebViewImpl::Scheduler() const {
return GetPage()->GetPageScheduler();
}
+void WebViewImpl::SetSchedulerThrottling(bool allowed) {
+ DCHECK(GetPage());
+ scheduler_throttling_allowed_ = allowed;
+ GetPage()->GetPageScheduler()->SetPageVisible(allowed ? !IsHidden() : true);
+}
+
void WebViewImpl::SetIsHidden(bool hidden, bool is_initial_state) {
DCHECK(GetPage());
GetPage()->SetIsHidden(hidden, is_initial_state);
- GetPage()->GetPageScheduler()->SetPageVisible(!hidden);
+ GetPage()->GetPageScheduler()->SetPageVisible(
+ scheduler_throttling_allowed_ ? !hidden : true);
}
bool WebViewImpl::IsHidden() {
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 61d54c30b50cb11d0eaae8e3a90944479d8b70fe..374d00b47c9037e4b37aafbef1d9b1156f4ed375 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -314,6 +314,7 @@ class CORE_EXPORT WebViewImpl final : public WebView,
PaintLayerCompositor* Compositor() const;
PageScheduler* Scheduler() const override;
+ void SetSchedulerThrottling(bool allowed) override;
void SetIsHidden(bool hidden, bool is_initial_state) override;
bool IsHidden() override;
@@ -690,6 +691,8 @@ class CORE_EXPORT WebViewImpl final : public WebView,
// WebViewImpl::Close while handling an input event.
bool debug_inside_input_handling_ = false;
+ bool scheduler_throttling_allowed_ = true;
+
FloatSize elastic_overscroll_;
Persistent<EventListener> popup_mouse_wheel_event_listener_;

View File

@@ -0,0 +1,109 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Alex Moshchuk <alexmos@chromium.org>
Date: Tue, 19 Nov 2019 22:41:28 +0000
Subject: Fix FocusOwningWebContents to handle RenderWidgetHosts for OOPIFs.
Previously, FocusOwningWebContents() would not focus anything when
called for an OOPIF's RenderWidgetHost. This is because
GetFocusedRenderWidgetHost() would always return that RWH back,
causing FocusOwningWebContents() to skip the call to
SetAsFocusedWebContentsIfNecessary() because the passed-in RWH matched
the focused RWH.
This is usually not a problem in Chrome, because inner WebContents
can't have OOPIFs and so an inner WebContents would only need to be
focused when this is called from a main frame's RenderWidgetHost, and
the outermost WebContents would probably already be focused via other
means. However, apparently inner WebContents could have OOPIFs in
embedders like Electron, and then this becomes problematic. This CL
fixes FocusOwningWebContents() to always pass in the main frame's
RenderWidgetHost to GetFocusedRenderWidgetHost(), since the latter was
never designed to take an OOPIF's RenderWidgetHost (it expects to take
an event arriving at a main frame's RenderWidgetHostView and then
target it to a subframe's RenderWidgetHost, if needed).
The setup in the added test is similar to ProcessSwapOnInnerContents,
which was also apparently added for an Electron-specific use case
(cross-process navigations inside a <webview>) which isn't currently
possible in regular Chrome.
Change-Id: If9559caf53274d415a360a976ebddfcc323d37dd
Bug: 1026056
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1922650
Reviewed-by: James MacLean <wjmaclean@chromium.org>
Commit-Queue: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#716803}
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index dff84def81f564bc32820d319fc782f3e9fae0af..b08cd1de7be13632f527049434dca6771193d41c 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -14032,6 +14032,52 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ProcessSwapOnInnerContents) {
EXPECT_NE(a_view, b_view);
}
+// This test ensures that WebContentsImpl::FocusOwningWebContents() focuses an
+// inner WebContents when it is given an OOPIF's RenderWidgetHost inside that
+// inner WebContents. This setup isn't currently supported in Chrome
+// (requiring issue 614463), but it can happen in embedders. See
+// https://crbug.com/1026056.
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, FocusInnerContentsFromOOPIF) {
+ GURL main_url(embedded_test_server()->GetURL(
+ "a.com", "/cross_site_iframe_factory.html?a(a)"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ // Set up and attach an artificial inner WebContents.
+ FrameTreeNode* child_frame =
+ web_contents()->GetFrameTree()->root()->child_at(0);
+ WebContentsImpl* inner_contents =
+ static_cast<WebContentsImpl*>(CreateAndAttachInnerContents(
+ ToRenderFrameHost(child_frame).render_frame_host()));
+ FrameTreeNode* inner_contents_root = inner_contents->GetFrameTree()->root();
+
+ // Navigate inner WebContents to b.com, and then navigate a subframe on that
+ // page to c.com.
+ GURL b_url(embedded_test_server()->GetURL(
+ "b.com", "/cross_site_iframe_factory.html?b(b)"));
+ NavigateFrameToURL(inner_contents_root, b_url);
+ GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
+ FrameTreeNode* inner_child = inner_contents_root->child_at(0);
+ NavigateFrameToURL(inner_child, c_url);
+
+ // Because |inner_contents| was set up without kGuestScheme, it can actually
+ // have OOPIFs. Ensure that the subframe is in an OOPIF.
+ EXPECT_NE(inner_contents_root->current_frame_host()->GetSiteInstance(),
+ inner_child->current_frame_host()->GetSiteInstance());
+ EXPECT_TRUE(inner_child->current_frame_host()->IsCrossProcessSubframe());
+
+ // Make sure the outer WebContents is focused to start with.
+ web_contents()->Focus();
+ web_contents()->SetAsFocusedWebContentsIfNecessary();
+ EXPECT_EQ(web_contents(), web_contents()->GetFocusedWebContents());
+
+ // Focus the inner WebContents as if an event were received and dispatched
+ // directly on the |inner_child|'s RenderWidgetHost, and ensure that this
+ // took effect.
+ inner_contents->FocusOwningWebContents(
+ inner_child->current_frame_host()->GetRenderWidgetHost());
+ EXPECT_EQ(inner_contents, web_contents()->GetFocusedWebContents());
+}
+
// Check that a web frame can't navigate a remote subframe to a file: URL. The
// frame should stay at the old URL, and the navigation attempt should produce
// a console error message. See https://crbug.com/894399.
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index b894dbb3d435ed3302bdd8e667ac0e628404a932..3863d5e9dfe970f0b893a1b060c85a5572cf26cd 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -6396,8 +6396,10 @@ void WebContentsImpl::FocusOwningWebContents(
if (!GuestMode::IsCrossProcessFrameGuest(this) && browser_plugin_guest_)
return;
+ RenderWidgetHostImpl* main_frame_widget_host =
+ GetMainFrame()->GetRenderWidgetHost();
RenderWidgetHostImpl* focused_widget =
- GetFocusedRenderWidgetHost(render_widget_host);
+ GetFocusedRenderWidgetHost(main_frame_widget_host);
if (focused_widget != render_widget_host &&
(!focused_widget ||

View File

@@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "W. James MacLean" <wjmaclean@chromium.org>
Date: Mon, 28 Oct 2019 16:24:21 +0000
Subject: Only apply transform when outermost != outer WebContents.
In applying the fix for Issue 1002598, we changed behaviour by
transforming coordinates for the case where outer and outer-most
WebContents are the same, and this seems to cause Issue 1015298.
This CL makes it so we only transform when there are different
outer and outer-most WebContents.
Bug: 1015298
Change-Id: I390bf37ca72cdfb5a2596721046e2c9937496df1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1879597
Commit-Queue: James MacLean <wjmaclean@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709924}
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 3863d5e9dfe970f0b893a1b060c85a5572cf26cd..f1c8f675d53c97f87f81705e07a9ade2de957f26 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3083,12 +3083,22 @@ void WebContentsImpl::ShowCreatedWidget(int process_id,
return;
// GetOutermostWebContents() returns |this| if there are no outer WebContents.
+ auto* outer_web_contents = GetOuterWebContents();
+ auto* outermost_web_contents = GetOutermostWebContents();
RenderWidgetHostView* view =
- GetOutermostWebContents()->GetRenderWidgetHostView();
+ outermost_web_contents->GetRenderWidgetHostView();
+ // It's not entirely obvious why we need the transform only in the case where
+ // the outer webcontents is not the same as the outermost webcontents. It may
+ // be due to the fact that oopifs that are children of the mainframe get
+ // correct values for their screenrects, but deeper cross-process frames do
+ // not. Hopefully this can be resolved with https://crbug.com/928825.
+ // Handling these cases separately is needed for http://crbug.com/1015298.
+ bool needs_transform = this != outermost_web_contents &&
+ outermost_web_contents != outer_web_contents;
gfx::Rect transformed_rect(initial_rect);
RenderWidgetHostView* this_view = GetRenderWidgetHostView();
- if (this_view != view) {
+ if (needs_transform) {
// We need to transform the coordinates of initial_rect.
gfx::Point origin =
this_view->TransformPointToRootCoordSpace(initial_rect.origin());

66
script/add-debug-link.py Executable file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python
from __future__ import print_function
import argparse
import os
import sys
from lib.config import LINUX_BINARIES, PLATFORM
from lib.util import execute, get_objcopy_path, get_out_dir
def add_debug_link_into_binaries(directory, target_cpu, debug_dir):
for binary in LINUX_BINARIES:
binary_path = os.path.join(directory, binary)
if os.path.isfile(binary_path):
add_debug_link_into_binary(binary_path, target_cpu, debug_dir)
def add_debug_link_into_binary(binary_path, target_cpu, debug_dir):
try:
objcopy = get_objcopy_path(target_cpu)
except:
if PLATFORM == 'linux' and (target_cpu == 'x86' or target_cpu == 'arm' or
target_cpu == 'arm64'):
# Skip because no objcopy binary on the given target.
return
raise
debug_name = get_debug_name(binary_path)
# Make sure the path to the binary is not relative because of cwd param.
real_binary_path = os.path.realpath(binary_path)
cmd = [objcopy, '--add-gnu-debuglink=' + debug_name, real_binary_path]
execute(cmd, cwd=debug_dir)
def get_debug_name(binary_path):
return os.path.basename(binary_path) + '.debug'
def main():
args = parse_args()
if args.file:
add_debug_link_into_binary(args.file, args.target_cpu, args.debug_dir)
else:
add_debug_link_into_binaries(args.directory, args.target_cpu,
args.debug_dir)
def parse_args():
parser = argparse.ArgumentParser(description='Add debug link to binaries')
parser.add_argument('-d', '--directory',
help='Path to the dir that contains files to add links',
default=get_out_dir(),
required=False)
parser.add_argument('-f', '--file',
help='Path to a specific file to add debug link',
required=False)
parser.add_argument('-s', '--debug-dir',
help='Path to the dir that contain the debugs',
default=None,
required=True)
parser.add_argument('-v', '--verbose',
action='store_true',
help='Prints the output of the subprocesses')
parser.add_argument('--target-cpu',
default='',
required=False,
help='Target cpu of binaries to add debug link')
return parser.parse_args()
if __name__ == '__main__':
sys.exit(main())

75
script/copy-debug-symbols.py Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python
from __future__ import print_function
import argparse
import os
import sys
from lib.config import LINUX_BINARIES, PLATFORM
from lib.util import execute, get_objcopy_path, get_out_dir, safe_mkdir
# It has to be done before stripping the binaries.
def copy_debug_from_binaries(directory, out_dir, target_cpu, compress):
for binary in LINUX_BINARIES:
binary_path = os.path.join(directory, binary)
if os.path.isfile(binary_path):
copy_debug_from_binary(binary_path, out_dir, target_cpu, compress)
def copy_debug_from_binary(binary_path, out_dir, target_cpu, compress):
try:
objcopy = get_objcopy_path(target_cpu)
except:
if PLATFORM == 'linux' and (target_cpu == 'x86' or target_cpu == 'arm' or
target_cpu == 'arm64'):
# Skip because no objcopy binary on the given target.
return
raise
debug_name = get_debug_name(binary_path)
cmd = [objcopy, '--only-keep-debug']
if compress:
cmd.extend(['--compress-debug-sections'])
cmd.extend([binary_path, os.path.join(out_dir, debug_name)])
execute(cmd)
return debug_name
def get_debug_name(binary_path):
return os.path.basename(binary_path) + '.debug'
def main():
args = parse_args()
safe_mkdir(args.out_dir)
if args.file:
copy_debug_from_binary(args.file, args.out_dir, args.target_cpu,
args.compress)
else:
copy_debug_from_binaries(args.directory, args.out_dir, args.target_cpu,
args.compress)
def parse_args():
parser = argparse.ArgumentParser(description='Copy debug from binaries')
parser.add_argument('-d', '--directory',
help='Path to the dir that contains files to copy',
default=get_out_dir(),
required=False)
parser.add_argument('-f', '--file',
help='Path to a specific file to copy debug symbols',
required=False)
parser.add_argument('-o', '--out-dir',
help='Path to the dir that will contain the debugs',
default=None,
required=True)
parser.add_argument('-v', '--verbose',
action='store_true',
help='Prints the output of the subprocesses')
parser.add_argument('--target-cpu',
default='',
required=False,
help='Target cpu of binaries to copy debug symbols')
parser.add_argument('--compress',
action='store_true',
required=False,
help='Compress the debug symbols')
return parser.parse_args()
if __name__ == '__main__':
sys.exit(main())

View File

@@ -26,6 +26,17 @@ PLATFORM = {
'win32': 'win32',
}[sys.platform]
LINUX_BINARIES = [
'electron',
'chrome-sandbox',
'libffmpeg.so',
'libGLESv2.so',
'libEGL.so',
'swiftshader/libGLESv2.so',
'swiftshader/libEGL.so',
'swiftshader/libvk_swiftshader.so'
]
verbose_mode = False

View File

@@ -123,7 +123,8 @@ def make_zip(zip_file_path, files, dirs):
files += dirs
execute(['zip', '-r', '-y', zip_file_path] + files)
else:
zip_file = zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED)
zip_file = zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED,
allowZip64=True)
for filename in files:
zip_file.write(filename, filename)
for dirname in dirs:
@@ -266,3 +267,14 @@ def get_buildtools_executable(name):
if sys.platform == 'win32':
path += '.exe'
return path
def get_objcopy_path(target_cpu):
if PLATFORM != 'linux':
raise Exception(
"get_objcopy_path: unexpected platform '{0}'".format(PLATFORM))
if target_cpu != 'x64':
raise Exception(
"get_objcopy_path: unexpected target cpu '{0}'".format(target_cpu))
return os.path.join(SRC_DIR, 'third_party', 'binutils', 'Linux_x64',
'Release', 'bin', 'objcopy')

View File

@@ -112,6 +112,7 @@ function assetsForVersion (version, validatingRelease) {
`electron-${version}-linux-armv7l.zip`,
`electron-${version}-linux-ia32-symbols.zip`,
`electron-${version}-linux-ia32.zip`,
`electron-${version}-linux-x64-debug.zip`,
`electron-${version}-linux-x64-symbols.zip`,
`electron-${version}-linux-x64.zip`,
`electron-${version}-mas-x64-dsym.zip`,

View File

@@ -35,6 +35,7 @@ DIST_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION)
SYMBOLS_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'symbols')
DSYM_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'dsym')
PDB_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb')
DEBUG_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'debug')
def main():
@@ -83,6 +84,10 @@ def main():
pdb_zip = os.path.join(OUT_DIR, PDB_NAME)
shutil.copy2(os.path.join(OUT_DIR, 'pdb.zip'), pdb_zip)
upload_electron(release, pdb_zip, args)
elif PLATFORM == 'linux':
debug_zip = os.path.join(OUT_DIR, DEBUG_NAME)
shutil.copy2(os.path.join(OUT_DIR, 'debug.zip'), debug_zip)
upload_electron(release, debug_zip, args)
# Upload free version of ffmpeg.
ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION)

View File

@@ -4,21 +4,11 @@ import argparse
import os
import sys
from lib.config import LINUX_BINARIES
from lib.util import execute, get_out_dir
LINUX_BINARIES_TO_STRIP = [
'electron',
'chrome-sandbox',
'libffmpeg.so',
'libGLESv2.so',
'libEGL.so',
'swiftshader/libGLESv2.so',
'swiftshader/libEGL.so',
'swiftshader/libvk_swiftshader.so'
]
def strip_binaries(directory, target_cpu):
for binary in LINUX_BINARIES_TO_STRIP:
for binary in LINUX_BINARIES:
binary_path = os.path.join(directory, binary)
if os.path.isfile(binary_path):
strip_binary(binary_path, target_cpu)
@@ -36,7 +26,6 @@ def strip_binary(binary_path, target_cpu):
def main():
args = parse_args()
print(args)
if args.file:
strip_binary(args.file, args.target_cpu)
else:

View File

@@ -43,6 +43,13 @@ def main():
pdb_zip_file = os.path.join(args.build_dir, pdb_name)
print('Making pdb zip: ' + pdb_zip_file)
make_zip(pdb_zip_file, pdbs + licenses, [])
elif PLATFORM == 'linux':
debug_name = 'debug.zip'
with scoped_cwd(args.build_dir):
dirs = ['debug']
debug_zip_file = os.path.join(args.build_dir, debug_name)
print('Making debug zip: ' + debug_zip_file)
make_zip(debug_zip_file, licenses, dirs)
def parse_args():
parser = argparse.ArgumentParser(description='Zip symbols')

View File

@@ -24,6 +24,7 @@
#include "ipc/ipc_buildflags.h"
#include "services/service_manager/embedder/switches.h"
#include "services/service_manager/sandbox/switches.h"
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
#include "shell/app/atom_content_client.h"
#include "shell/browser/atom_browser_client.h"
#include "shell/browser/atom_gpu_client.h"
@@ -182,6 +183,9 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
if (env->HasVar("ELECTRON_DISABLE_SANDBOX"))
command_line->AppendSwitch(service_manager::switches::kNoSandbox);
tracing_sampler_profiler_ =
tracing::TracingSamplerProfiler::CreateOnMainThread();
chrome::RegisterPathProvider();
#if defined(OS_MACOSX)

View File

@@ -11,6 +11,10 @@
#include "content/public/app/content_main_delegate.h"
#include "content/public/common/content_client.h"
namespace tracing {
class TracingSamplerProfiler;
}
namespace electron {
void LoadResourceBundle(const std::string& locale);
@@ -51,6 +55,7 @@ class AtomMainDelegate : public content::ContentMainDelegate {
std::unique_ptr<content::ContentGpuClient> gpu_client_;
std::unique_ptr<content::ContentRendererClient> renderer_client_;
std::unique_ptr<content::ContentUtilityClient> utility_client_;
std::unique_ptr<tracing::TracingSamplerProfiler> tracing_sampler_profiler_;
DISALLOW_COPY_AND_ASSIGN(AtomMainDelegate);
};

View File

@@ -141,6 +141,15 @@ void FilterCookieWithStatuses(const base::Value& filter,
net::cookie_util::StripStatuses(list));
}
// Parse dictionary property to CanonicalCookie time correctly.
base::Time ParseTimeProperty(const base::Optional<double>& value) {
if (!value) // empty time means ignoring the parameter
return base::Time();
if (*value == 0) // FromDoubleT would convert 0 to empty Time
return base::Time::UnixEpoch();
return base::Time::FromDoubleT(*value);
}
std::string InclusionStatusToString(
net::CanonicalCookie::CookieInclusionStatus status) {
if (status.HasExclusionReason(
@@ -243,21 +252,6 @@ v8::Local<v8::Promise> Cookies::Set(const base::DictionaryValue& details) {
const std::string* path = details.FindStringKey("path");
bool secure = details.FindBoolKey("secure").value_or(false);
bool http_only = details.FindBoolKey("httpOnly").value_or(false);
base::Optional<double> creation_date = details.FindDoubleKey("creationDate");
base::Optional<double> expiration_date =
details.FindDoubleKey("expirationDate");
base::Optional<double> last_access_date =
details.FindDoubleKey("lastAccessDate");
base::Time creation_time = creation_date
? base::Time::FromDoubleT(*creation_date)
: base::Time::UnixEpoch();
base::Time expiration_time = expiration_date
? base::Time::FromDoubleT(*expiration_date)
: base::Time::UnixEpoch();
base::Time last_access_time = last_access_date
? base::Time::FromDoubleT(*last_access_date)
: base::Time::UnixEpoch();
GURL url(url_string ? *url_string : "");
if (!url.is_valid()) {
@@ -268,18 +262,14 @@ v8::Local<v8::Promise> Cookies::Set(const base::DictionaryValue& details) {
return handle;
}
if (!name || name->empty()) {
promise.RejectWithErrorMessage(
InclusionStatusToString(net::CanonicalCookie::CookieInclusionStatus(
net::CanonicalCookie::CookieInclusionStatus::
EXCLUDE_FAILURE_TO_STORE)));
return handle;
}
auto canonical_cookie = net::CanonicalCookie::CreateSanitizedCookie(
url, *name, value ? *value : "", domain ? *domain : "", path ? *path : "",
creation_time, expiration_time, last_access_time, secure, http_only,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT);
url, name ? *name : "", value ? *value : "", domain ? *domain : "",
path ? *path : "",
ParseTimeProperty(details.FindDoubleKey("creationDate")),
ParseTimeProperty(details.FindDoubleKey("expirationDate")),
ParseTimeProperty(details.FindDoubleKey("lastAccessDate")), secure,
http_only, net::CookieSameSite::NO_RESTRICTION,
net::COOKIE_PRIORITY_DEFAULT);
if (!canonical_cookie || !canonical_cookie->IsCanonical()) {
promise.RejectWithErrorMessage(
InclusionStatusToString(net::CanonicalCookie::CookieInclusionStatus(

View File

@@ -4,13 +4,15 @@
#include "shell/browser/api/atom_api_menu.h"
#include <utility>
#include "native_mate/constructor.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "shell/browser/native_window.h"
#include "shell/common/native_mate_converters/accelerator_converter.h"
#include "shell/common/native_mate_converters/callback.h"
#include "shell/common/native_mate_converters/image_converter.h"
#include "shell/common/native_mate_converters/once_callback.h"
#include "shell/common/native_mate_converters/string16_converter.h"
#include "shell/common/node_includes.h"
@@ -207,6 +209,16 @@ void Menu::OnMenuWillShow() {
Emit("menu-will-show");
}
base::OnceClosure Menu::BindSelfToClosure(base::OnceClosure callback) {
// return ((callback, ref) => { callback() }).bind(null, callback, this)
v8::Global<v8::Value> ref(isolate(), GetWrapper());
return base::BindOnce(
[](base::OnceClosure callback, v8::Global<v8::Value> ref) {
std::move(callback).Run();
},
std::move(callback), std::move(ref));
}
// static
void Menu::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {

View File

@@ -12,7 +12,6 @@
#include "shell/browser/api/atom_api_top_level_window.h"
#include "shell/browser/api/trackable_object.h"
#include "shell/browser/ui/atom_menu_model.h"
#include "shell/common/api/locker.h"
namespace electron {
@@ -61,7 +60,7 @@ class Menu : public mate::TrackableObject<Menu>,
int x,
int y,
int positioning_item,
const base::Closure& callback) = 0;
base::OnceClosure callback) = 0;
virtual void ClosePopupAt(int32_t window_id) = 0;
std::unique_ptr<AtomMenuModel> model_;
@@ -71,6 +70,11 @@ class Menu : public mate::TrackableObject<Menu>,
void OnMenuWillClose() override;
void OnMenuWillShow() override;
protected:
// Returns a new callback which keeps references of the JS wrapper until the
// passed |callback| is called.
base::OnceClosure BindSelfToClosure(base::OnceClosure callback);
private:
void InsertItemAt(int index, int command_id, const base::string16& label);
void InsertSeparatorAt(int index);

View File

@@ -27,19 +27,19 @@ class MenuMac : public Menu {
int x,
int y,
int positioning_item,
const base::Closure& callback) override;
base::OnceClosure callback) override;
void PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
int32_t window_id,
int x,
int y,
int positioning_item,
base::Closure callback);
base::OnceClosure callback);
void ClosePopupAt(int32_t window_id) override;
private:
friend class Menu;
void OnClosed(int32_t window_id, base::Closure callback);
void OnClosed(int32_t window_id, base::OnceClosure callback);
scoped_nsobject<AtomMenuController> menu_controller_;

View File

@@ -39,15 +39,19 @@ void MenuMac::PopupAt(TopLevelWindow* window,
int x,
int y,
int positioning_item,
const base::Closure& callback) {
base::OnceClosure callback) {
NativeWindow* native_window = window->window();
if (!native_window)
return;
// Make sure the Menu object would not be garbage-collected until the callback
// has run.
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));
auto popup =
base::BindOnce(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(),
native_window->GetWeakPtr(), window->weak_map_id(), x, y,
positioning_item, callback);
positioning_item, std::move(callback_with_ref));
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, std::move(popup));
}
@@ -56,16 +60,14 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
int x,
int y,
int positioning_item,
base::Closure callback) {
mate::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
base::OnceClosure callback) {
if (!native_window)
return;
NSWindow* nswindow = native_window->GetNativeWindow().GetNativeNSWindow();
auto close_callback = base::BindRepeating(
&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
base::OnceClosure close_callback =
base::BindOnce(&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id,
std::move(callback));
popup_controllers_[window_id] = base::scoped_nsobject<AtomMenuController>(
[[AtomMenuController alloc] initWithModel:model()
useDefaultAccelerator:NO]);
@@ -103,7 +105,7 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
if (rightmostMenuPoint > screenRight)
position.x = position.x - [menu size].width;
[popup_controllers_[window_id] setCloseCallback:close_callback];
[popup_controllers_[window_id] setCloseCallback:std::move(close_callback)];
// Make sure events can be pumped while the menu is up.
base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
@@ -134,9 +136,9 @@ void MenuMac::ClosePopupAt(int32_t window_id) {
}
}
void MenuMac::OnClosed(int32_t window_id, base::Closure callback) {
void MenuMac::OnClosed(int32_t window_id, base::OnceClosure callback) {
popup_controllers_.erase(window_id);
callback.Run();
std::move(callback).Run();
}
// static

View File

@@ -5,6 +5,7 @@
#include "shell/browser/api/atom_api_menu_views.h"
#include <memory>
#include <utility>
#include "shell/browser/native_window_views.h"
#include "shell/browser/unresponsive_suppressor.h"
@@ -25,10 +26,7 @@ void MenuViews::PopupAt(TopLevelWindow* window,
int x,
int y,
int positioning_item,
const base::Closure& callback) {
mate::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
base::OnceClosure callback) {
auto* native_window = static_cast<NativeWindowViews*>(window->window());
if (!native_window)
return;
@@ -47,12 +45,21 @@ void MenuViews::PopupAt(TopLevelWindow* window,
// Don't emit unresponsive event when showing menu.
electron::UnresponsiveSuppressor suppressor;
// Make sure the Menu object would not be garbage-collected until the callback
// has run.
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));
// Show the menu.
//
// Note that while views::MenuRunner accepts RepeatingCallback as close
// callback, it is fine passing OnceCallback to it because we reset the
// menu runner immediately when the menu is closed.
int32_t window_id = window->weak_map_id();
auto close_callback = base::BindRepeating(
&MenuViews::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
auto close_callback = base::AdaptCallbackForRepeating(
base::BindOnce(&MenuViews::OnClosed, weak_factory_.GetWeakPtr(),
window_id, std::move(callback_with_ref)));
menu_runners_[window_id] =
std::make_unique<MenuRunner>(model(), flags, close_callback);
std::make_unique<MenuRunner>(model(), flags, std::move(close_callback));
menu_runners_[window_id]->RunMenuAt(
native_window->widget(), NULL, gfx::Rect(location, gfx::Size()),
views::MenuAnchorPosition::kTopLeft, ui::MENU_SOURCE_MOUSE);
@@ -72,9 +79,9 @@ void MenuViews::ClosePopupAt(int32_t window_id) {
}
}
void MenuViews::OnClosed(int32_t window_id, base::Closure callback) {
void MenuViews::OnClosed(int32_t window_id, base::OnceClosure callback) {
menu_runners_.erase(window_id);
callback.Run();
std::move(callback).Run();
}
// static

View File

@@ -27,11 +27,11 @@ class MenuViews : public Menu {
int x,
int y,
int positioning_item,
const base::Closure& callback) override;
base::OnceClosure callback) override;
void ClosePopupAt(int32_t window_id) override;
private:
void OnClosed(int32_t window_id, base::Closure callback);
void OnClosed(int32_t window_id, base::OnceClosure callback);
// window ID -> open context menu
std::map<int32_t, std::unique_ptr<views::MenuRunner>> menu_runners_;

View File

@@ -4,10 +4,12 @@
#include "shell/browser/api/atom_api_net.h"
#include <string>
#include "native_mate/dictionary.h"
#include "native_mate/handle.h"
#include "services/network/public/cpp/features.h"
#include "shell/browser/api/atom_api_url_request_ns.h"
#include "shell/browser/api/atom_api_url_loader.h"
#include "shell/common/node_includes.h"
@@ -31,12 +33,12 @@ void Net::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(mate::StringToV8(isolate, "Net"));
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetProperty("URLRequest", &Net::URLRequest);
.SetProperty("URLLoader", &Net::URLLoader);
}
v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
v8::Local<v8::Value> Net::URLLoader(v8::Isolate* isolate) {
v8::Local<v8::FunctionTemplate> constructor;
constructor = URLRequestNS::GetConstructor(isolate);
constructor = SimpleURLLoaderWrapper::GetConstructor(isolate);
return constructor->GetFunction(isolate->GetCurrentContext())
.ToLocalChecked();
}
@@ -47,8 +49,16 @@ v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
namespace {
bool IsValidHeaderName(std::string header_name) {
return net::HttpUtil::IsValidHeaderName(header_name);
}
bool IsValidHeaderValue(std::string header_value) {
return net::HttpUtil::IsValidHeaderValue(header_value);
}
using electron::api::Net;
using electron::api::URLRequestNS;
using electron::api::SimpleURLLoaderWrapper;
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
@@ -56,12 +66,15 @@ void Initialize(v8::Local<v8::Object> exports,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
URLRequestNS::SetConstructor(isolate, base::BindRepeating(URLRequestNS::New));
SimpleURLLoaderWrapper::SetConstructor(
isolate, base::BindRepeating(SimpleURLLoaderWrapper::New));
mate::Dictionary dict(isolate, exports);
dict.Set("net", Net::Create(isolate));
dict.Set("Net",
Net::GetConstructor(isolate)->GetFunction(context).ToLocalChecked());
dict.SetMethod("_isValidHeaderName", &IsValidHeaderName);
dict.SetMethod("_isValidHeaderValue", &IsValidHeaderValue);
}
} // namespace

View File

@@ -18,7 +18,7 @@ class Net : public mate::EventEmitter<Net> {
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
v8::Local<v8::Value> URLRequest(v8::Isolate* isolate);
v8::Local<v8::Value> URLLoader(v8::Isolate* isolate);
protected:
explicit Net(v8::Isolate* isolate);

View File

@@ -11,6 +11,7 @@
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/guid.h"
#include "base/strings/string_number_conversions.h"
@@ -484,6 +485,9 @@ void Session::AllowNTLMCredentialsForDomains(const std::string& domains) {
network::mojom::HttpAuthDynamicParamsPtr auth_dynamic_params =
network::mojom::HttpAuthDynamicParams::New();
auth_dynamic_params->server_allowlist = domains;
auth_dynamic_params->enable_negotiate_port =
base::CommandLine::ForCurrentProcess()->HasSwitch(
electron::switches::kEnableAuthNegotiatePort);
content::GetNetworkService()->ConfigureHttpAuthPrefs(
std::move(auth_dynamic_params));
}

View File

@@ -0,0 +1,454 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/atom_api_url_loader.h"
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/id_map.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "native_mate/dictionary.h"
#include "native_mate/handle.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "shell/browser/api/atom_api_session.h"
#include "shell/browser/atom_browser_context.h"
#include "shell/common/gin_converters/callback_converter_gin_adapter.h"
#include "shell/common/gin_converters/gurl_converter_gin_adapter.h"
#include "shell/common/gin_converters/net_converter_gin_adapter.h"
#include "shell/common/gin_converters/value_converter_gin_adapter.h"
#include "shell/common/native_mate_converters/map_converter.h"
#include "shell/common/node_includes.h"
#include "shell/common/promise_util.h"
class BufferDataSource : public mojo::DataPipeProducer::DataSource {
public:
explicit BufferDataSource(base::span<char> buffer) {
buffer_.resize(buffer.size());
memcpy(buffer_.data(), buffer.data(), buffer_.size());
}
~BufferDataSource() override = default;
private:
// mojo::DataPipeProducer::DataSource:
uint64_t GetLength() const override { return buffer_.size(); }
ReadResult Read(uint64_t offset, base::span<char> buffer) override {
ReadResult result;
if (offset <= buffer_.size()) {
size_t readable_size = buffer_.size() - offset;
size_t writable_size = buffer.size();
size_t copyable_size = std::min(readable_size, writable_size);
memcpy(buffer.data(), &buffer_[offset], copyable_size);
result.bytes_read = copyable_size;
} else {
NOTREACHED();
result.result = MOJO_RESULT_OUT_OF_RANGE;
}
return result;
}
std::vector<char> buffer_;
};
class JSChunkedDataPipeGetter : public gin::Wrappable<JSChunkedDataPipeGetter>,
public network::mojom::ChunkedDataPipeGetter {
public:
static gin::Handle<JSChunkedDataPipeGetter> Create(
v8::Isolate* isolate,
v8::Local<v8::Function> body_func,
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
chunked_data_pipe_getter) {
return gin::CreateHandle(
isolate, new JSChunkedDataPipeGetter(
isolate, body_func, std::move(chunked_data_pipe_getter)));
}
// gin::Wrappable
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override {
return gin::Wrappable<JSChunkedDataPipeGetter>::GetObjectTemplateBuilder(
isolate)
.SetMethod("write", &JSChunkedDataPipeGetter::WriteChunk)
.SetMethod("done", &JSChunkedDataPipeGetter::Done);
}
static gin::WrapperInfo kWrapperInfo;
~JSChunkedDataPipeGetter() override = default;
private:
JSChunkedDataPipeGetter(
v8::Isolate* isolate,
v8::Local<v8::Function> body_func,
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
chunked_data_pipe_getter)
: isolate_(isolate), body_func_(isolate, body_func) {
receiver_.Bind(std::move(chunked_data_pipe_getter));
}
// network::mojom::ChunkedDataPipeGetter:
void GetSize(GetSizeCallback callback) override {
size_callback_ = std::move(callback);
}
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (body_func_.IsEmpty()) {
LOG(ERROR) << "Tried to read twice from a JSChunkedDataPipeGetter";
// Drop the handle on the floor.
return;
}
data_producer_ = std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
v8::HandleScope handle_scope(isolate_);
v8::MicrotasksScope script_scope(isolate_,
v8::MicrotasksScope::kRunMicrotasks);
auto maybe_wrapper = GetWrapper(isolate_);
v8::Local<v8::Value> wrapper;
if (!maybe_wrapper.ToLocal(&wrapper)) {
return;
}
v8::Local<v8::Value> argv[] = {wrapper};
node::Environment* env = node::Environment::GetCurrent(isolate_);
auto global = env->context()->Global();
node::MakeCallback(isolate_, global, body_func_.Get(isolate_),
node::arraysize(argv), argv, {0, 0});
}
v8::Local<v8::Promise> WriteChunk(v8::Local<v8::Value> buffer_val) {
electron::util::Promise promise(isolate_);
v8::Local<v8::Promise> handle = promise.GetHandle();
if (!buffer_val->IsArrayBufferView()) {
promise.RejectWithErrorMessage("Expected an ArrayBufferView");
return handle;
}
if (is_writing_) {
promise.RejectWithErrorMessage("Only one write can be pending at a time");
return handle;
}
if (!size_callback_) {
promise.RejectWithErrorMessage("Can't write after calling done()");
return handle;
}
auto buffer = buffer_val.As<v8::ArrayBufferView>();
is_writing_ = true;
bytes_written_ += buffer->ByteLength();
auto contents = buffer->Buffer()->GetContents();
auto buffer_span = base::make_span(
static_cast<char*>(contents.Data()) + buffer->ByteOffset(),
buffer->ByteLength());
auto buffer_source = std::make_unique<BufferDataSource>(buffer_span);
data_producer_->Write(
std::move(buffer_source),
base::BindOnce(&JSChunkedDataPipeGetter::OnWriteChunkComplete,
// We're OK to use Unretained here because we own
// |data_producer_|.
base::Unretained(this), std::move(promise)));
return handle;
}
void OnWriteChunkComplete(electron::util::Promise promise,
MojoResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
is_writing_ = false;
if (result == MOJO_RESULT_OK) {
promise.Resolve();
} else {
promise.RejectWithErrorMessage("mojo result not ok");
Finished();
}
}
// TODO(nornagon): accept a net error here to allow the data provider to
// cancel the request with an error.
void Done() {
if (size_callback_) {
std::move(size_callback_).Run(net::OK, bytes_written_);
Finished();
}
}
void Finished() {
size_callback_.Reset();
body_func_.Reset();
receiver_.reset();
data_producer_.reset();
}
GetSizeCallback size_callback_;
mojo::Receiver<network::mojom::ChunkedDataPipeGetter> receiver_{this};
std::unique_ptr<mojo::DataPipeProducer> data_producer_;
bool is_writing_ = false;
uint64_t bytes_written_ = 0;
v8::Isolate* isolate_;
v8::Global<v8::Function> body_func_;
};
gin::WrapperInfo JSChunkedDataPipeGetter::kWrapperInfo = {
gin::kEmbedderNativeGin};
namespace electron {
namespace api {
namespace {
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
semantics {
sender: "Electron Net module"
description:
"Issue HTTP/HTTPS requests using Chromium's native networking "
"library."
trigger: "Using the Net module"
data: "Anything the user wants to send."
destination: OTHER
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature cannot be disabled."
})");
base::IDMap<SimpleURLLoaderWrapper*>& GetAllRequests() {
static base::NoDestructor<base::IDMap<SimpleURLLoaderWrapper*>>
s_all_requests;
return *s_all_requests;
}
} // namespace
SimpleURLLoaderWrapper::SimpleURLLoaderWrapper(
std::unique_ptr<network::ResourceRequest> request,
network::mojom::URLLoaderFactory* url_loader_factory)
: id_(GetAllRequests().Add(this)) {
// We slightly abuse the |render_frame_id| field in ResourceRequest so that
// we can correlate any authentication events that arrive with this request.
request->render_frame_id = id_;
// SimpleURLLoader wants to control the request body itself. We have other
// ideas.
auto request_body = std::move(request->request_body);
auto* request_ref = request.get();
loader_ =
network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
if (request_body) {
request_ref->request_body = std::move(request_body);
}
loader_->SetAllowHttpErrorResults(true);
loader_->SetOnResponseStartedCallback(base::BindOnce(
&SimpleURLLoaderWrapper::OnResponseStarted, base::Unretained(this)));
loader_->SetOnRedirectCallback(base::BindRepeating(
&SimpleURLLoaderWrapper::OnRedirect, base::Unretained(this)));
loader_->SetOnUploadProgressCallback(base::BindRepeating(
&SimpleURLLoaderWrapper::OnUploadProgress, base::Unretained(this)));
loader_->SetOnDownloadProgressCallback(base::BindRepeating(
&SimpleURLLoaderWrapper::OnDownloadProgress, base::Unretained(this)));
loader_->DownloadAsStream(url_loader_factory, this);
}
void SimpleURLLoaderWrapper::Pin() {
// Prevent ourselves from being GC'd until the request is complete.
// Must be called after InitWithArgs(), otherwise GetWrapper() isn't
// initialized.
pinned_wrapper_.Reset(isolate(), GetWrapper());
}
void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local<v8::Value> body_getter) {
pinned_chunk_pipe_getter_.Reset(isolate(), body_getter);
}
SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() {
GetAllRequests().Remove(id_);
}
// static
SimpleURLLoaderWrapper* SimpleURLLoaderWrapper::FromID(uint32_t id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return GetAllRequests().Lookup(id);
}
void SimpleURLLoaderWrapper::OnAuthRequired(
const GURL& url,
bool first_auth_attempt,
net::AuthChallengeInfo auth_info,
network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
std::move(auth_challenge_responder));
// WeakPtr because if we're Cancel()ed while waiting for auth, and the
// network service also decides to cancel at the same time and kill this
// pipe, we might end up trying to call Cancel again on dead memory.
auth_responder.set_disconnect_handler(base::BindOnce(
&SimpleURLLoaderWrapper::Cancel, weak_factory_.GetWeakPtr()));
auto cb = base::BindOnce(
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
mate::Arguments* args) {
base::string16 username_str, password_str;
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
auth_responder->OnAuthCredentials(base::nullopt);
return;
}
auth_responder->OnAuthCredentials(
net::AuthCredentials(username_str, password_str));
},
std::move(auth_responder));
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
}
void SimpleURLLoaderWrapper::Cancel() {
loader_.reset();
pinned_wrapper_.Reset();
pinned_chunk_pipe_getter_.Reset();
// This ensures that no further callbacks will be called, so there's no need
// for additional guards.
}
// static
mate::WrappableBase* SimpleURLLoaderWrapper::New(mate::Arguments* args) {
mate::Dictionary opts;
if (!args->GetNext(&opts)) {
args->ThrowTypeError("Expected a dictionary");
return nullptr;
}
auto request = std::make_unique<network::ResourceRequest>();
opts.Get("method", &request->method);
opts.Get("url", &request->url);
std::map<std::string, std::string> extra_headers;
if (opts.Get("extraHeaders", &extra_headers)) {
for (const auto& it : extra_headers) {
if (!net::HttpUtil::IsValidHeaderName(it.first) ||
!net::HttpUtil::IsValidHeaderValue(it.second)) {
args->ThrowTypeError("Invalid header name or value");
return nullptr;
}
request->headers.SetHeader(it.first, it.second);
}
}
v8::Local<v8::Value> body;
v8::Local<v8::Value> chunk_pipe_getter;
if (opts.Get("body", &body)) {
if (body->IsArrayBufferView()) {
auto buffer_body = body.As<v8::ArrayBufferView>();
auto contents = buffer_body->Buffer()->GetContents();
request->request_body = network::ResourceRequestBody::CreateFromBytes(
static_cast<char*>(contents.Data()) + buffer_body->ByteOffset(),
buffer_body->ByteLength());
} else if (body->IsFunction()) {
auto body_func = body.As<v8::Function>();
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
data_pipe_getter;
chunk_pipe_getter = JSChunkedDataPipeGetter::Create(
args->isolate(), body_func,
data_pipe_getter.InitWithNewPipeAndPassReceiver())
.ToV8();
request->request_body = new network::ResourceRequestBody();
request->request_body->SetToChunkedDataPipe(
network::mojom::ChunkedDataPipeGetterPtr(
std::move(data_pipe_getter)));
}
}
std::string partition;
mate::Handle<Session> session;
if (!opts.Get("session", &session)) {
if (opts.Get("partition", &partition))
session = Session::FromPartition(args->isolate(), partition);
else // default session
session = Session::FromPartition(args->isolate(), "");
}
auto url_loader_factory = session->browser_context()->GetURLLoaderFactory();
auto* ret =
new SimpleURLLoaderWrapper(std::move(request), url_loader_factory.get());
ret->InitWith(args->isolate(), args->GetThis());
ret->Pin();
if (!chunk_pipe_getter.IsEmpty()) {
ret->PinBodyGetter(chunk_pipe_getter);
}
return ret;
}
void SimpleURLLoaderWrapper::OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
v8::HandleScope handle_scope(isolate());
auto array_buffer = v8::ArrayBuffer::New(isolate(), string_piece.size());
auto contents = array_buffer->GetContents();
memcpy(contents.Data(), string_piece.data(), string_piece.size());
Emit("data", array_buffer.As<v8::Value>());
std::move(resume).Run();
}
void SimpleURLLoaderWrapper::OnComplete(bool success) {
if (success) {
Emit("complete");
} else {
Emit("error", net::ErrorToString(loader_->NetError()));
}
loader_.reset();
pinned_wrapper_.Reset();
pinned_chunk_pipe_getter_.Reset();
}
void SimpleURLLoaderWrapper::OnRetry(base::OnceClosure start_retry) {}
void SimpleURLLoaderWrapper::OnResponseStarted(
const GURL& final_url,
const network::ResourceResponseHead& response_head) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate());
dict.Set("statusCode", response_head.headers->response_code());
dict.Set("statusMessage", response_head.headers->GetStatusText());
dict.Set("headers", response_head.headers.get());
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
Emit("response-started", final_url, dict);
}
void SimpleURLLoaderWrapper::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* removed_headers) {
Emit("redirect", redirect_info, response_head.headers.get());
}
void SimpleURLLoaderWrapper::OnUploadProgress(uint64_t position,
uint64_t total) {
Emit("upload-progress", position, total);
}
void SimpleURLLoaderWrapper::OnDownloadProgress(uint64_t current) {
Emit("download-progress", current);
}
// static
void SimpleURLLoaderWrapper::BuildPrototype(
v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(gin::StringToV8(isolate, "SimpleURLLoaderWrapper"));
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("cancel", &SimpleURLLoaderWrapper::Cancel);
}
} // namespace api
} // namespace electron

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
#define SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
#include <memory>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "net/base/auth.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "shell/browser/api/event_emitter.h"
#include "url/gurl.h"
#include "v8/include/v8.h"
namespace mate {
class Arguments;
}
namespace network {
class SimpleURLLoader;
struct ResourceRequest;
} // namespace network
namespace electron {
namespace api {
/** Wraps a SimpleURLLoader to make it usable from JavaScript */
class SimpleURLLoaderWrapper
: public mate::EventEmitter<SimpleURLLoaderWrapper>,
public network::SimpleURLLoaderStreamConsumer {
public:
~SimpleURLLoaderWrapper() override;
static mate::WrappableBase* New(mate::Arguments* args);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
static SimpleURLLoaderWrapper* FromID(uint32_t id);
void OnAuthRequired(
const GURL& url,
bool first_auth_attempt,
net::AuthChallengeInfo auth_info,
network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder);
void Cancel();
private:
SimpleURLLoaderWrapper(std::unique_ptr<network::ResourceRequest> loader,
network::mojom::URLLoaderFactory* url_loader_factory);
// SimpleURLLoaderStreamConsumer:
void OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) override;
void OnComplete(bool success) override;
void OnRetry(base::OnceClosure start_retry) override;
// SimpleURLLoader callbacks
void OnResponseStarted(const GURL& final_url,
const network::ResourceResponseHead& response_head);
void OnRedirect(const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* removed_headers);
void OnUploadProgress(uint64_t position, uint64_t total);
void OnDownloadProgress(uint64_t current);
void Start();
void Pin();
void PinBodyGetter(v8::Local<v8::Value>);
uint32_t id_;
std::unique_ptr<network::SimpleURLLoader> loader_;
v8::Global<v8::Value> pinned_wrapper_;
v8::Global<v8::Value> pinned_chunk_pipe_getter_;
base::WeakPtrFactory<SimpleURLLoaderWrapper> weak_factory_{this};
};
} // namespace api
} // namespace electron
#endif // SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_

View File

@@ -1,544 +0,0 @@
// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/atom_api_url_request_ns.h"
#include <utility>
#include "mojo/public/cpp/system/string_data_source.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "net/http/http_util.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
#include "shell/browser/api/atom_api_session.h"
#include "shell/browser/atom_browser_context.h"
#include "shell/common/native_mate_converters/gurl_converter.h"
#include "shell/common/native_mate_converters/net_converter.h"
#include "shell/common/node_includes.h"
namespace mate {
template <>
struct Converter<network::mojom::RedirectMode> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
network::mojom::RedirectMode* out) {
std::string mode;
if (!ConvertFromV8(isolate, val, &mode))
return false;
if (mode == "follow")
*out = network::mojom::RedirectMode::kFollow;
else if (mode == "error")
*out = network::mojom::RedirectMode::kError;
else if (mode == "manual")
*out = network::mojom::RedirectMode::kManual;
else
return false;
return true;
}
};
} // namespace mate
namespace electron {
namespace api {
namespace {
// Network state for request and response.
enum State {
STATE_STARTED = 1 << 0,
STATE_FINISHED = 1 << 1,
STATE_CANCELED = 1 << 2,
STATE_FAILED = 1 << 3,
STATE_CLOSED = 1 << 4,
STATE_ERROR = STATE_CANCELED | STATE_FAILED | STATE_CLOSED,
};
// Annotation tag passed to NetworkService.
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
semantics {
sender: "Electron Net module"
description:
"Issue HTTP/HTTPS requests using Chromium's native networking "
"library."
trigger: "Using the Net module"
data: "Anything the user wants to send."
destination: OTHER
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature cannot be disabled."
})");
} // namespace
// Common class for streaming data.
class UploadDataPipeGetter {
public:
explicit UploadDataPipeGetter(URLRequestNS* request) : request_(request) {}
virtual ~UploadDataPipeGetter() = default;
virtual void AttachToRequestBody(network::ResourceRequestBody* body) = 0;
protected:
void SetCallback(network::mojom::DataPipeGetter::ReadCallback callback) {
request_->size_callback_ = std::move(callback);
}
void SetPipe(mojo::ScopedDataPipeProducerHandle pipe) {
request_->producer_ =
std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
request_->StartWriting();
}
private:
URLRequestNS* request_;
DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter);
};
// Streaming multipart data to NetworkService.
class MultipartDataPipeGetter : public UploadDataPipeGetter,
public network::mojom::DataPipeGetter {
public:
explicit MultipartDataPipeGetter(URLRequestNS* request)
: UploadDataPipeGetter(request) {}
~MultipartDataPipeGetter() override = default;
void AttachToRequestBody(network::ResourceRequestBody* body) override {
network::mojom::DataPipeGetterPtr data_pipe_getter;
binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
body->AppendDataPipe(std::move(data_pipe_getter));
}
private:
// network::mojom::DataPipeGetter:
void Read(mojo::ScopedDataPipeProducerHandle pipe,
ReadCallback callback) override {
SetCallback(std::move(callback));
SetPipe(std::move(pipe));
}
void Clone(network::mojom::DataPipeGetterRequest request) override {
binding_set_.AddBinding(this, std::move(request));
}
mojo::BindingSet<network::mojom::DataPipeGetter> binding_set_;
};
// Streaming chunked data to NetworkService.
class ChunkedDataPipeGetter : public UploadDataPipeGetter,
public network::mojom::ChunkedDataPipeGetter {
public:
explicit ChunkedDataPipeGetter(URLRequestNS* request)
: UploadDataPipeGetter(request) {}
~ChunkedDataPipeGetter() override = default;
void AttachToRequestBody(network::ResourceRequestBody* body) override {
network::mojom::ChunkedDataPipeGetterPtr data_pipe_getter;
binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
body->SetToChunkedDataPipe(std::move(data_pipe_getter));
}
private:
// network::mojom::ChunkedDataPipeGetter:
void GetSize(GetSizeCallback callback) override {
SetCallback(std::move(callback));
}
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
SetPipe(std::move(pipe));
}
mojo::BindingSet<network::mojom::ChunkedDataPipeGetter> binding_set_;
};
URLRequestNS::URLRequestNS(mate::Arguments* args) : weak_factory_(this) {
request_ = std::make_unique<network::ResourceRequest>();
mate::Dictionary dict;
if (args->GetNext(&dict)) {
dict.Get("method", &request_->method);
dict.Get("url", &request_->url);
dict.Get("redirect", &redirect_mode_);
request_->redirect_mode = redirect_mode_;
}
std::string partition;
mate::Handle<api::Session> session;
if (!dict.Get("session", &session)) {
if (dict.Get("partition", &partition))
session = Session::FromPartition(args->isolate(), partition);
else // default session
session = Session::FromPartition(args->isolate(), "");
}
url_loader_factory_ = session->browser_context()->GetURLLoaderFactory();
InitWith(args->isolate(), args->GetThis());
}
URLRequestNS::~URLRequestNS() {}
bool URLRequestNS::NotStarted() const {
return request_state_ == 0;
}
bool URLRequestNS::Finished() const {
return request_state_ & STATE_FINISHED;
}
void URLRequestNS::Cancel() {
// Cancel only once.
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
return;
// Mark as canceled.
request_state_ |= STATE_CANCELED;
EmitEvent(EventType::kRequest, true, "abort");
if ((response_state_ & STATE_STARTED) && !(response_state_ & STATE_FINISHED))
EmitEvent(EventType::kResponse, true, "aborted");
Close();
}
void URLRequestNS::Close() {
if (!(request_state_ & STATE_CLOSED)) {
request_state_ |= STATE_CLOSED;
if (response_state_ & STATE_STARTED) {
// Emit a close event if we really have a response object.
EmitEvent(EventType::kResponse, true, "close");
}
EmitEvent(EventType::kRequest, true, "close");
}
Unpin();
loader_.reset();
}
bool URLRequestNS::Write(v8::Local<v8::Value> data, bool is_last) {
if (request_state_ & (STATE_FINISHED | STATE_ERROR))
return false;
size_t length = node::Buffer::Length(data);
if (!loader_) {
// Pin on first write.
request_state_ = STATE_STARTED;
Pin();
// Create the loader.
network::ResourceRequest* request_ref = request_.get();
loader_ = network::SimpleURLLoader::Create(std::move(request_),
kTrafficAnnotation);
loader_->SetOnResponseStartedCallback(base::Bind(
&URLRequestNS::OnResponseStarted, weak_factory_.GetWeakPtr()));
loader_->SetOnRedirectCallback(
base::Bind(&URLRequestNS::OnRedirect, weak_factory_.GetWeakPtr()));
loader_->SetOnUploadProgressCallback(base::Bind(
&URLRequestNS::OnUploadProgress, weak_factory_.GetWeakPtr()));
// Create upload data pipe if we have data to write.
if (length > 0) {
request_ref->request_body = new network::ResourceRequestBody();
if (is_chunked_upload_)
data_pipe_getter_ = std::make_unique<ChunkedDataPipeGetter>(this);
else
data_pipe_getter_ = std::make_unique<MultipartDataPipeGetter>(this);
data_pipe_getter_->AttachToRequestBody(request_ref->request_body.get());
}
// Start downloading.
loader_->DownloadAsStream(url_loader_factory_.get(), this);
}
if (length > 0)
pending_writes_.emplace_back(node::Buffer::Data(data), length);
if (is_last) {
// The ElementsUploadDataStream requires the knowledge of content length
// before doing upload, while Node's stream does not give us any size
// information. So the only option left for us is to keep all the write
// data in memory and flush them after the write is done.
//
// While this looks frustrating, it is actually the behavior of the non-
// NetworkService implementation, and we are not breaking anything.
if (!pending_writes_.empty()) {
last_chunk_written_ = true;
StartWriting();
}
request_state_ |= STATE_FINISHED;
EmitEvent(EventType::kRequest, true, "finish");
}
return true;
}
void URLRequestNS::FollowRedirect() {
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
return;
follow_redirect_ = true;
}
bool URLRequestNS::SetExtraHeader(const std::string& name,
const std::string& value) {
if (!request_)
return false;
if (!net::HttpUtil::IsValidHeaderName(name))
return false;
if (!net::HttpUtil::IsValidHeaderValue(value))
return false;
request_->headers.SetHeader(name, value);
return true;
}
void URLRequestNS::RemoveExtraHeader(const std::string& name) {
if (request_)
request_->headers.RemoveHeader(name);
}
void URLRequestNS::SetChunkedUpload(bool is_chunked_upload) {
if (request_)
is_chunked_upload_ = is_chunked_upload;
}
mate::Dictionary URLRequestNS::GetUploadProgress() {
mate::Dictionary progress = mate::Dictionary::CreateEmpty(isolate());
if (loader_) {
if (request_)
progress.Set("started", false);
else
progress.Set("started", true);
progress.Set("current", upload_position_);
progress.Set("total", upload_total_);
progress.Set("active", true);
} else {
progress.Set("active", false);
}
return progress;
}
int URLRequestNS::StatusCode() const {
if (response_headers_)
return response_headers_->response_code();
return -1;
}
std::string URLRequestNS::StatusMessage() const {
if (response_headers_)
return response_headers_->GetStatusText();
return "";
}
net::HttpResponseHeaders* URLRequestNS::RawResponseHeaders() const {
return response_headers_.get();
}
uint32_t URLRequestNS::ResponseHttpVersionMajor() const {
if (response_headers_)
return response_headers_->GetHttpVersion().major_value();
return 0;
}
uint32_t URLRequestNS::ResponseHttpVersionMinor() const {
if (response_headers_)
return response_headers_->GetHttpVersion().minor_value();
return 0;
}
void URLRequestNS::OnDataReceived(base::StringPiece data,
base::OnceClosure resume) {
// In case we received an unexpected event from Chromium net, don't emit any
// data event after request cancel/error/close.
if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Value> buffer;
auto maybe = node::Buffer::Copy(isolate(), data.data(), data.size());
if (maybe.ToLocal(&buffer))
Emit("data", buffer);
}
std::move(resume).Run();
}
void URLRequestNS::OnRetry(base::OnceClosure start_retry) {}
void URLRequestNS::OnComplete(bool success) {
if (success) {
// In case we received an unexpected event from Chromium net, don't emit any
// data event after request cancel/error/close.
if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
response_state_ |= STATE_FINISHED;
Emit("end");
}
} else { // failed
// If response is started then emit response event, else emit request error.
//
// Error is only emitted when there is no previous failure. This is to align
// with the behavior of non-NetworkService implementation.
std::string error = net::ErrorToString(loader_->NetError());
if (response_state_ & STATE_STARTED) {
if (!(response_state_ & STATE_FAILED))
EmitError(EventType::kResponse, error);
} else {
if (!(request_state_ & STATE_FAILED))
EmitError(EventType::kRequest, error);
}
}
Close();
}
void URLRequestNS::OnResponseStarted(
const GURL& final_url,
const network::ResourceResponseHead& response_head) {
// Don't emit any event after request cancel.
if (request_state_ & STATE_ERROR)
return;
response_headers_ = response_head.headers;
response_state_ |= STATE_STARTED;
Emit("response");
}
void URLRequestNS::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers) {
if (!loader_)
return;
if (request_state_ & (STATE_CLOSED | STATE_CANCELED)) {
NOTREACHED();
Cancel();
return;
}
switch (redirect_mode_) {
case network::mojom::RedirectMode::kError:
Cancel();
EmitError(
EventType::kRequest,
"Request cannot follow redirect with the current redirect mode");
break;
case network::mojom::RedirectMode::kManual:
// When redirect mode is "manual", the user has to explicitly call the
// FollowRedirect method to continue redirecting, otherwise the request
// would be cancelled.
//
// Note that the SimpleURLLoader always calls FollowRedirect and does not
// provide a formal way for us to cancel redirection, we have to cancel
// the request to prevent the redirection.
follow_redirect_ = false;
EmitEvent(EventType::kRequest, false, "redirect",
redirect_info.status_code, redirect_info.new_method,
redirect_info.new_url, response_head.headers.get());
if (!follow_redirect_)
Cancel();
break;
case network::mojom::RedirectMode::kFollow:
EmitEvent(EventType::kRequest, false, "redirect",
redirect_info.status_code, redirect_info.new_method,
redirect_info.new_url, response_head.headers.get());
break;
}
}
void URLRequestNS::OnUploadProgress(uint64_t position, uint64_t total) {
upload_position_ = position;
upload_total_ = total;
}
void URLRequestNS::OnWrite(MojoResult result) {
if (result != MOJO_RESULT_OK)
return;
// Continue the pending writes.
pending_writes_.pop_front();
if (!pending_writes_.empty())
DoWrite();
}
void URLRequestNS::DoWrite() {
DCHECK(producer_);
DCHECK(!pending_writes_.empty());
producer_->Write(
std::make_unique<mojo::StringDataSource>(
pending_writes_.front(), mojo::StringDataSource::AsyncWritingMode::
STRING_STAYS_VALID_UNTIL_COMPLETION),
base::BindOnce(&URLRequestNS::OnWrite, weak_factory_.GetWeakPtr()));
}
void URLRequestNS::StartWriting() {
if (!last_chunk_written_ || size_callback_.is_null())
return;
size_t size = 0;
for (const auto& data : pending_writes_)
size += data.size();
std::move(size_callback_).Run(net::OK, size);
DoWrite();
}
void URLRequestNS::Pin() {
if (wrapper_.IsEmpty()) {
wrapper_.Reset(isolate(), GetWrapper());
}
}
void URLRequestNS::Unpin() {
wrapper_.Reset();
}
void URLRequestNS::EmitError(EventType type, base::StringPiece message) {
if (type == EventType::kRequest)
request_state_ |= STATE_FAILED;
else
response_state_ |= STATE_FAILED;
v8::HandleScope handle_scope(isolate());
auto error = v8::Exception::Error(mate::StringToV8(isolate(), message));
EmitEvent(type, false, "error", error);
}
template <typename... Args>
void URLRequestNS::EmitEvent(EventType type, Args... args) {
const char* method =
type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent";
v8::HandleScope handle_scope(isolate());
mate::CustomEmit(isolate(), GetWrapper(), method, args...);
}
// static
mate::WrappableBase* URLRequestNS::New(mate::Arguments* args) {
return new URLRequestNS(args);
}
// static
void URLRequestNS::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(mate::StringToV8(isolate, "URLRequest"));
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.MakeDestroyable()
.SetMethod("write", &URLRequestNS::Write)
.SetMethod("cancel", &URLRequestNS::Cancel)
.SetMethod("setExtraHeader", &URLRequestNS::SetExtraHeader)
.SetMethod("removeExtraHeader", &URLRequestNS::RemoveExtraHeader)
.SetMethod("setChunkedUpload", &URLRequestNS::SetChunkedUpload)
.SetMethod("followRedirect", &URLRequestNS::FollowRedirect)
.SetMethod("getUploadProgress", &URLRequestNS::GetUploadProgress)
.SetProperty("notStarted", &URLRequestNS::NotStarted)
.SetProperty("finished", &URLRequestNS::Finished)
.SetProperty("statusCode", &URLRequestNS::StatusCode)
.SetProperty("statusMessage", &URLRequestNS::StatusMessage)
.SetProperty("rawResponseHeaders", &URLRequestNS::RawResponseHeaders)
.SetProperty("httpVersionMajor", &URLRequestNS::ResponseHttpVersionMajor)
.SetProperty("httpVersionMinor", &URLRequestNS::ResponseHttpVersionMinor);
}
} // namespace api
} // namespace electron

View File

@@ -1,143 +0,0 @@
// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_
#define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "native_mate/dictionary.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/mojom/data_pipe_getter.mojom.h"
#include "shell/browser/api/event_emitter.h"
namespace electron {
namespace api {
class UploadDataPipeGetter;
class URLRequestNS : public mate::EventEmitter<URLRequestNS>,
public network::SimpleURLLoaderStreamConsumer {
public:
static mate::WrappableBase* New(mate::Arguments* args);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
protected:
explicit URLRequestNS(mate::Arguments* args);
~URLRequestNS() override;
bool NotStarted() const;
bool Finished() const;
void Cancel();
void Close();
bool Write(v8::Local<v8::Value> data, bool is_last);
void FollowRedirect();
bool SetExtraHeader(const std::string& name, const std::string& value);
void RemoveExtraHeader(const std::string& name);
void SetChunkedUpload(bool is_chunked_upload);
mate::Dictionary GetUploadProgress();
int StatusCode() const;
std::string StatusMessage() const;
net::HttpResponseHeaders* RawResponseHeaders() const;
uint32_t ResponseHttpVersionMajor() const;
uint32_t ResponseHttpVersionMinor() const;
// SimpleURLLoaderStreamConsumer:
void OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) override;
void OnComplete(bool success) override;
void OnRetry(base::OnceClosure start_retry) override;
private:
friend class UploadDataPipeGetter;
void OnResponseStarted(const GURL& final_url,
const network::ResourceResponseHead& response_head);
void OnRedirect(const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers);
void OnUploadProgress(uint64_t position, uint64_t total);
void OnWrite(MojoResult result);
// Write the first data of |pending_writes_|.
void DoWrite();
// Start streaming.
void StartWriting();
// Manage lifetime of wrapper.
void Pin();
void Unpin();
// Emit events.
enum class EventType {
kRequest,
kResponse,
};
void EmitError(EventType type, base::StringPiece error);
template <typename... Args>
void EmitEvent(EventType type, Args... args);
std::unique_ptr<network::ResourceRequest> request_;
std::unique_ptr<network::SimpleURLLoader> loader_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;
// Redirect mode.
//
// Note that we store it ourselves instead of reading from the one stored in
// |request_|, this is because with multiple redirections, the original one
// might be modified.
network::mojom::RedirectMode redirect_mode_ =
network::mojom::RedirectMode::kFollow;
// The DataPipeGetter passed to reader.
bool is_chunked_upload_ = false;
std::unique_ptr<UploadDataPipeGetter> data_pipe_getter_;
// Passed from DataPipeGetter for streaming data.
network::mojom::DataPipeGetter::ReadCallback size_callback_;
std::unique_ptr<mojo::DataPipeProducer> producer_;
// Whether request.end() has been called.
bool last_chunk_written_ = false;
// Whether the redirect should be followed.
bool follow_redirect_ = true;
// Upload progress.
uint64_t upload_position_ = 0;
uint64_t upload_total_ = 0;
// Current status.
int request_state_ = 0;
int response_state_ = 0;
// Pending writes that not yet sent to NetworkService.
std::list<std::string> pending_writes_;
// Used by pin/unpin to manage lifetime.
v8::Global<v8::Object> wrapper_;
base::WeakPtrFactory<URLRequestNS> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(URLRequestNS);
};
} // namespace api
} // namespace electron
#endif // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_

View File

@@ -365,7 +365,10 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options)
// Whether to enable DevTools.
options.Get("devTools", &enable_devtools_);
bool initially_shown = true;
// BrowserViews are not attached to a window initially so they should start
// off as hidden. This is also important for compositor recycling. See:
// https://github.com/electron/electron/pull/21372
bool initially_shown = type_ != Type::BROWSER_VIEW;
options.Get(options::kShow, &initially_shown);
// Obtain the session.
@@ -595,6 +598,25 @@ void WebContents::WebContentsCreated(content::WebContents* source_contents,
tracker->frame_name = frame_name;
}
bool WebContents::ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
if (Emit("-will-add-new-contents", target_url, frame_name)) {
return false;
}
return true;
}
void WebContents::AddNewContents(
content::WebContents* source,
std::unique_ptr<content::WebContents> new_contents,
@@ -837,10 +859,20 @@ void WebContents::BeforeUnloadFired(bool proceed,
}
void WebContents::RenderViewCreated(content::RenderViewHost* render_view_host) {
auto* impl = static_cast<content::RenderWidgetHostImpl*>(
render_view_host->GetWidget());
if (impl)
impl->disable_hidden_ = !background_throttling_;
if (!background_throttling_)
render_view_host->SetSchedulerThrottling(false);
}
void WebContents::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
auto* rwhv = render_frame_host->GetView();
if (!rwhv)
return;
auto* rwh_impl =
static_cast<content::RenderWidgetHostImpl*>(rwhv->GetRenderWidgetHost());
if (rwh_impl)
rwh_impl->disable_hidden_ = !background_throttling_;
}
void WebContents::RenderViewHostChanged(content::RenderViewHost* old_host,
@@ -1259,31 +1291,24 @@ void WebContents::NavigationEntryCommitted(
void WebContents::SetBackgroundThrottling(bool allowed) {
background_throttling_ = allowed;
auto* contents = web_contents();
if (!contents) {
auto* rfh = web_contents()->GetMainFrame();
if (!rfh)
return;
}
auto* render_view_host = contents->GetRenderViewHost();
if (!render_view_host) {
auto* rwhv = rfh->GetView();
if (!rwhv)
return;
}
auto* render_process_host = render_view_host->GetProcess();
if (!render_process_host) {
auto* rwh_impl =
static_cast<content::RenderWidgetHostImpl*>(rwhv->GetRenderWidgetHost());
if (!rwh_impl)
return;
}
auto* render_widget_host_impl = content::RenderWidgetHostImpl::FromID(
render_process_host->GetID(), render_view_host->GetRoutingID());
if (!render_widget_host_impl) {
return;
}
rwh_impl->disable_hidden_ = !background_throttling_;
web_contents()->GetRenderViewHost()->SetSchedulerThrottling(allowed);
render_widget_host_impl->disable_hidden_ = !background_throttling_;
if (render_widget_host_impl->is_hidden()) {
render_widget_host_impl->WasShown(base::nullopt);
if (rwh_impl->is_hidden()) {
rwh_impl->WasShown(base::nullopt);
}
}
@@ -1355,6 +1380,12 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) {
params.load_type = content::NavigationController::LOAD_TYPE_DATA;
}
bool reload_ignoring_cache = false;
if (options.Get("reloadIgnoringCache", &reload_ignoring_cache) &&
reload_ignoring_cache) {
params.reload_type = content::ReloadType::BYPASSING_CACHE;
}
params.transition_type = ui::PAGE_TRANSITION_TYPED;
params.should_clear_history_list = true;
params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE;

View File

@@ -346,6 +346,19 @@ class WebContents : public mate::TrackableObject<WebContents>,
const base::string16& message,
int32_t line_no,
const base::string16& source_id) override;
bool ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) override;
void WebContentsCreated(content::WebContents* source_contents,
int opener_render_process_id,
int opener_render_frame_id,
@@ -417,7 +430,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
// content::WebContentsObserver:
void BeforeUnloadFired(bool proceed,
const base::TimeTicks& proceed_time) override;
void RenderViewCreated(content::RenderViewHost*) override;
void RenderViewCreated(content::RenderViewHost* render_view_host) override;
void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
void RenderViewHostChanged(content::RenderViewHost* old_host,
content::RenderViewHost* new_host) override;
void RenderViewDeleted(content::RenderViewHost*) override;

View File

@@ -26,9 +26,12 @@
#include "content/browser/blob_storage/chrome_blob_storage_context.h" // nogncheck
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.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 "services/network/public/mojom/network_context.mojom.h"
#include "shell/browser/api/atom_api_url_loader.h"
#include "shell/browser/atom_browser_client.h"
#include "shell/browser/atom_browser_main_parts.h"
#include "shell/browser/atom_download_manager_delegate.h"
@@ -283,6 +286,7 @@ AtomBrowserContext::GetURLLoaderFactory() {
network::mojom::URLLoaderFactoryParamsPtr params =
network::mojom::URLLoaderFactoryParams::New();
params->header_client = std::move(header_client);
params->auth_client = auth_client_.BindNewPipeAndPassRemote();
params->process_id = network::mojom::kBrowserProcessId;
params->is_corb_enabled = false;
// The tests of net module would fail if this setting is true, it seems that
@@ -299,6 +303,40 @@ AtomBrowserContext::GetURLLoaderFactory() {
return url_loader_factory_;
}
class AuthResponder : public network::mojom::TrustedAuthClient {
public:
AuthResponder() {}
~AuthResponder() override = default;
private:
void OnAuthRequired(
const base::Optional<::base::UnguessableToken>& window_id,
uint32_t process_id,
uint32_t routing_id,
uint32_t request_id,
const ::GURL& url,
bool first_auth_attempt,
const ::net::AuthChallengeInfo& auth_info,
::network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder) override {
api::SimpleURLLoaderWrapper* url_loader =
api::SimpleURLLoaderWrapper::FromID(routing_id);
if (url_loader) {
url_loader->OnAuthRequired(url, first_auth_attempt, auth_info,
std::move(head),
std::move(auth_challenge_responder));
}
}
};
void AtomBrowserContext::OnLoaderCreated(
int32_t request_id,
mojo::PendingReceiver<network::mojom::TrustedAuthClient> auth_client) {
mojo::MakeSelfOwnedReceiver(std::make_unique<AuthResponder>(),
std::move(auth_client));
}
content::PushMessagingService* AtomBrowserContext::GetPushMessagingService() {
return nullptr;
}

View File

@@ -17,6 +17,8 @@
#include "content/public/browser/browser_context.h"
#include "content/public/browser/resource_context.h"
#include "electron/buildflags/buildflags.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "shell/browser/media/media_device_id_salt.h"
@@ -50,7 +52,8 @@ class WebViewManager;
class AtomBrowserContext
: public base::RefCountedDeleteOnSequence<AtomBrowserContext>,
public content::BrowserContext {
public content::BrowserContext,
public network::mojom::TrustedURLLoaderAuthClient {
public:
// partition_id => browser_context
struct PartitionKey {
@@ -148,6 +151,10 @@ class AtomBrowserContext
friend class base::RefCountedDeleteOnSequence<AtomBrowserContext>;
friend class base::DeleteHelper<AtomBrowserContext>;
void OnLoaderCreated(int32_t request_id,
mojo::PendingReceiver<network::mojom::TrustedAuthClient>
header_client) override;
// Initialize pref registry.
void InitPrefs();
@@ -184,6 +191,7 @@ class AtomBrowserContext
// Shared URLLoaderFactory.
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
mojo::Receiver<network::mojom::TrustedURLLoaderAuthClient> auth_client_{this};
base::WeakPtrFactory<AtomBrowserContext> weak_factory_;

View File

@@ -30,6 +30,7 @@
#include "services/device/public/mojom/constants.mojom.h"
#include "services/network/public/cpp/features.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
#include "shell/app/atom_main_delegate.h"
#include "shell/browser/api/atom_api_app.h"
#include "shell/browser/api/trackable_object.h"
@@ -383,6 +384,12 @@ int AtomBrowserMainParts::PreCreateThreads() {
return 0;
}
void AtomBrowserMainParts::PostCreateThreads() {
base::PostTask(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&tracing::TracingSamplerProfiler::CreateOnChildThread));
}
void AtomBrowserMainParts::PostDestroyThreads() {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
extensions_browser_client_.reset();

View File

@@ -92,6 +92,7 @@ class AtomBrowserMainParts : public content::BrowserMainParts {
void PostMainMessageLoopStart() override;
void PostMainMessageLoopRun() override;
void PreMainMessageLoopStart() override;
void PostCreateThreads() override;
void PostDestroyThreads() override;
private:

View File

@@ -25,6 +25,21 @@
namespace electron {
namespace {
// Call |quit| after Chromium is fully started.
//
// This is important for quitting immediately in the "ready" event, when
// certain initialization task may still be pending, and quitting at that time
// could end up with crash on exit.
void RunQuitClosure(base::OnceClosure quit) {
// On Linux/Windows the "ready" event is emitted in "PreMainMessageLoopRun",
// make sure we quit after message loop has run for once.
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(quit));
}
} // namespace
Browser::LoginItemSettings::LoginItemSettings() = default;
Browser::LoginItemSettings::~LoginItemSettings() = default;
Browser::LoginItemSettings::LoginItemSettings(const LoginItemSettings& other) =
@@ -93,7 +108,7 @@ void Browser::Shutdown() {
observer.OnQuit();
if (quit_main_message_loop_) {
std::move(quit_main_message_loop_).Run();
RunQuitClosure(std::move(quit_main_message_loop_));
} else {
// There is no message loop available so we are in early stage, wait until
// the quit_main_message_loop_ is available.
@@ -188,7 +203,7 @@ void Browser::PreMainMessageLoopRun() {
void Browser::SetMainMessageLoopQuitClosure(base::OnceClosure quit_closure) {
if (is_shutdown_)
std::move(quit_closure).Run();
RunQuitClosure(std::move(quit_closure));
else
quit_main_message_loop_ = std::move(quit_closure);
}

View File

@@ -10,11 +10,14 @@
#include <vector>
#include "base/callback.h"
#include "base/strings/string16.h"
#include "native_mate/arguments.h"
#include "native_mate/dictionary.h"
#include "shell/browser/api/atom_api_web_contents.h"
#include "shell/common/gin_converters/callback_converter_gin_adapter.h"
#include "shell/common/gin_converters/net_converter_gin_adapter.h"
#include "shell/common/gin_converters/value_converter_gin_adapter.h"
#include "shell/common/native_mate_converters/callback.h"
#include "shell/common/native_mate_converters/gurl_converter.h"
#include "shell/common/native_mate_converters/net_converter.h"
#include "shell/common/native_mate_converters/value_converter.h"
using content::BrowserThread;
@@ -57,7 +60,7 @@ void LoginHandler::EmitEvent(
v8::HandleScope scope(isolate);
auto details = mate::Dictionary::CreateEmpty(isolate);
details.Set("url", url.spec());
details.Set("url", url);
// These parameters aren't documented, and I'm not sure that they're useful,
// but we might as well stick 'em on the details object. If it turns out they
@@ -77,9 +80,13 @@ void LoginHandler::EmitEvent(
LoginHandler::~LoginHandler() = default;
void LoginHandler::CallbackFromJS(base::string16 username,
base::string16 password) {
void LoginHandler::CallbackFromJS(mate::Arguments* args) {
if (auth_required_callback_) {
base::string16 username, password;
if (!args->GetNext(&username) || !args->GetNext(&password)) {
std::move(auth_required_callback_).Run(base::nullopt);
return;
}
std::move(auth_required_callback_)
.Run(net::AuthCredentials(username, password));
}

View File

@@ -5,7 +5,6 @@
#ifndef SHELL_BROWSER_LOGIN_HANDLER_H_
#define SHELL_BROWSER_LOGIN_HANDLER_H_
#include "base/strings/string16.h"
#include "base/values.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/login_delegate.h"
@@ -15,6 +14,10 @@ namespace content {
class WebContents;
}
namespace mate {
class Arguments;
}
namespace electron {
// Handles HTTP basic auth.
@@ -36,7 +39,7 @@ class LoginHandler : public content::LoginDelegate,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers,
bool first_auth_attempt);
void CallbackFromJS(base::string16 username, base::string16 password);
void CallbackFromJS(mate::Arguments* args);
LoginAuthRequiredCallback auth_required_callback_;

View File

@@ -964,20 +964,22 @@ void NativeWindowViews::SetFocusable(bool focusable) {
void NativeWindowViews::SetMenu(AtomMenuModel* menu_model) {
#if defined(USE_X11)
if (menu_model == nullptr) {
// Remove global menu bar.
if (global_menu_bar_ && menu_model == nullptr) {
global_menu_bar_.reset();
root_view_->UnregisterAcceleratorsWithFocusManager();
return;
}
if (!global_menu_bar_ && ShouldUseGlobalMenuBar())
global_menu_bar_.reset(new GlobalMenuBarX11(this));
// Use global application menu bar when possible.
if (global_menu_bar_ && global_menu_bar_->IsServerStarted()) {
root_view_->RegisterAcceleratorsWithFocusManager(menu_model);
global_menu_bar_->SetMenu(menu_model);
return;
if (ShouldUseGlobalMenuBar()) {
if (!global_menu_bar_)
global_menu_bar_.reset(new GlobalMenuBarX11(this));
if (global_menu_bar_->IsServerStarted()) {
root_view_->RegisterAcceleratorsWithFocusManager(menu_model);
global_menu_bar_->SetMenu(menu_model);
return;
}
}
#endif

View File

@@ -6,12 +6,16 @@
#include <utility>
#include "base/command_line.h"
#include "base/strings/string_util.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "net/base/completion_repeating_callback.h"
#include "net/base/load_flags.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/features.h"
#include "shell/browser/net/asar/asar_url_loader.h"
#include "shell/common/options_switches.h"
namespace electron {
@@ -682,18 +686,40 @@ ProxyingURLLoaderFactory::ProxyingURLLoaderFactory(
if (header_client_receiver)
url_loader_header_client_receiver_.Bind(std::move(header_client_receiver));
ignore_connections_limit_domains_ = base::SplitString(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kIgnoreConnectionsLimit),
",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}
ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
bool ProxyingURLLoaderFactory::ShouldIgnoreConnectionsLimit(
const network::ResourceRequest& request) {
for (const auto& domain : ignore_connections_limit_domains_) {
if (request.url.DomainIs(domain)) {
return true;
}
}
return false;
}
void ProxyingURLLoaderFactory::CreateLoaderAndStart(
network::mojom::URLLoaderRequest loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
const network::ResourceRequest& original_request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
// Take a copy so we can mutate the request.
network::ResourceRequest request = original_request;
if (ShouldIgnoreConnectionsLimit(request)) {
request.load_flags |= net::LOAD_IGNORE_LIMITS;
}
// Check if user has intercepted this scheme.
auto it = intercepted_handlers_.find(request.url.scheme());
if (it != intercepted_handlers_.end()) {

View File

@@ -237,6 +237,8 @@ class ProxyingURLLoaderFactory
void RemoveRequest(int32_t network_service_request_id, uint64_t request_id);
void MaybeDeleteThis();
bool ShouldIgnoreConnectionsLimit(const network::ResourceRequest& request);
// Passed from api::WebRequestNS.
WebRequestAPI* web_request_api_;
@@ -265,6 +267,8 @@ class ProxyingURLLoaderFactory
// internally generated request ID for the same request.
std::map<int32_t, uint64_t> network_request_id_to_web_request_id_;
std::vector<std::string> ignore_connections_limit_domains_;
DISALLOW_COPY_AND_ASSIGN(ProxyingURLLoaderFactory);
};

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 7,1,2,0
PRODUCTVERSION 7,1,2,0
FILEVERSION 7,1,8,0
PRODUCTVERSION 7,1,8,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron"
VALUE "FileVersion", "7.1.2"
VALUE "FileVersion", "7.1.8"
VALUE "InternalName", "electron.exe"
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "electron.exe"
VALUE "ProductName", "Electron"
VALUE "ProductVersion", "7.1.2"
VALUE "ProductVersion", "7.1.8"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -28,7 +28,7 @@ class AtomMenuModel;
base::scoped_nsobject<NSMenu> menu_;
BOOL isMenuOpen_;
BOOL useDefaultAccelerator_;
base::Callback<void()> closeCallback;
base::OnceClosure closeCallback;
}
@property(nonatomic, assign) electron::AtomMenuModel* model;
@@ -38,7 +38,7 @@ class AtomMenuModel;
- (id)initWithModel:(electron::AtomMenuModel*)model
useDefaultAccelerator:(BOOL)use;
- (void)setCloseCallback:(const base::Callback<void()>&)callback;
- (void)setCloseCallback:(base::OnceClosure)callback;
// Populate current NSMenu with |model|.
- (void)populateWithModel:(electron::AtomMenuModel*)model;

View File

@@ -118,8 +118,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
[super dealloc];
}
- (void)setCloseCallback:(const base::Callback<void()>&)callback {
closeCallback = callback;
- (void)setCloseCallback:(base::OnceClosure)callback {
closeCallback = std::move(callback);
}
- (void)populateWithModel:(electron::AtomMenuModel*)model {
@@ -153,7 +153,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
isMenuOpen_ = NO;
model_->MenuWillClose();
if (!closeCallback.is_null()) {
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
std::move(closeCallback));
}
}
}
@@ -385,7 +386,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// Post async task so that itemSelected runs before the close callback
// deletes the controller from the map which deallocates it
if (!closeCallback.is_null()) {
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
std::move(closeCallback));
}
}
}

View File

@@ -103,8 +103,6 @@ bool ScopedDisableResize::disable_resize_ = false;
}
- (id)accessibilityAttributeValue:(NSString*)attribute {
if ([attribute isEqual:NSAccessibilityTitleAttribute])
return base::SysUTF8ToNSString(shell_->GetTitle());
if ([attribute isEqual:NSAccessibilityEnabledAttribute])
return [NSNumber numberWithBool:YES];
if (![attribute isEqualToString:@"AXChildren"])
@@ -125,6 +123,10 @@ bool ScopedDisableResize::disable_resize_ = false;
return [children filteredArrayUsingPredicate:predicate];
}
- (NSString*)accessibilityTitle {
return base::SysUTF8ToNSString(shell_->GetTitle());
}
- (BOOL)canBecomeMainWindow {
return !self.disableKeyOrMainWindow;
}

View File

@@ -27,10 +27,12 @@
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/file_url_loader.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/shared_cors_origin_access_list.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/user_agent.h"
#include "ipc/ipc_channel.h"
@@ -61,7 +63,7 @@ const char kChromeUIDevToolsURL[] =
"textColor=rgba(0,0,0,1)&"
"experiments=true";
const char kChromeUIDevToolsRemoteFrontendBase[] =
"https://devtools-frontend.appspot.com/";
"https://chrome-devtools-frontend.appspot.com/";
const char kChromeUIDevToolsRemoteFrontendPath[] = "serve_file";
const char kDevToolsBoundsPref[] = "electron.devtools.bounds";
@@ -149,29 +151,87 @@ GURL GetDevToolsURL(bool can_dock) {
return GURL(url_string);
}
constexpr base::TimeDelta kInitialBackoffDelay =
base::TimeDelta::FromMilliseconds(250);
constexpr base::TimeDelta kMaxBackoffDelay = base::TimeDelta::FromSeconds(10);
} // namespace
class InspectableWebContentsImpl::NetworkResourceLoader
: public network::SimpleURLLoaderStreamConsumer {
public:
NetworkResourceLoader(int stream_id,
InspectableWebContentsImpl* bindings,
std::unique_ptr<network::SimpleURLLoader> loader,
network::mojom::URLLoaderFactory* url_loader_factory,
const DispatchCallback& callback)
class URLLoaderFactoryHolder {
public:
network::mojom::URLLoaderFactory* get() {
return ptr_.get() ? ptr_.get() : refptr_.get();
}
void operator=(std::unique_ptr<network::mojom::URLLoaderFactory>&& ptr) {
ptr_ = std::move(ptr);
}
void operator=(scoped_refptr<network::SharedURLLoaderFactory>&& refptr) {
refptr_ = std::move(refptr);
}
private:
std::unique_ptr<network::mojom::URLLoaderFactory> ptr_;
scoped_refptr<network::SharedURLLoaderFactory> refptr_;
};
static void Create(int stream_id,
InspectableWebContentsImpl* bindings,
const network::ResourceRequest& resource_request,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
URLLoaderFactoryHolder url_loader_factory,
const DispatchCallback& callback,
base::TimeDelta retry_delay = base::TimeDelta()) {
auto resource_loader =
std::make_unique<InspectableWebContentsImpl::NetworkResourceLoader>(
stream_id, bindings, resource_request, traffic_annotation,
std::move(url_loader_factory), callback, retry_delay);
bindings->loaders_.insert(std::move(resource_loader));
}
NetworkResourceLoader(
int stream_id,
InspectableWebContentsImpl* bindings,
const network::ResourceRequest& resource_request,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
URLLoaderFactoryHolder url_loader_factory,
const DispatchCallback& callback,
base::TimeDelta delay)
: stream_id_(stream_id),
bindings_(bindings),
loader_(std::move(loader)),
callback_(callback) {
resource_request_(resource_request),
traffic_annotation_(traffic_annotation),
loader_(network::SimpleURLLoader::Create(
std::make_unique<network::ResourceRequest>(resource_request),
traffic_annotation)),
url_loader_factory_(std::move(url_loader_factory)),
callback_(callback),
retry_delay_(delay) {
loader_->SetOnResponseStartedCallback(base::BindOnce(
&NetworkResourceLoader::OnResponseStarted, base::Unretained(this)));
loader_->DownloadAsStream(url_loader_factory, this);
timer_.Start(FROM_HERE, delay,
base::BindRepeating(&NetworkResourceLoader::DownloadAsStream,
base::Unretained(this)));
}
NetworkResourceLoader(const NetworkResourceLoader&) = delete;
NetworkResourceLoader& operator=(const NetworkResourceLoader&) = delete;
private:
void DownloadAsStream() {
loader_->DownloadAsStream(url_loader_factory_.get(), this);
}
base::TimeDelta GetNextExponentialBackoffDelay(const base::TimeDelta& delta) {
if (delta.is_zero()) {
return kInitialBackoffDelay;
} else {
return delta * 1.3;
}
}
void OnResponseStarted(const GURL& final_url,
const network::ResourceResponseHead& response_head) {
response_headers_ = response_head.headers;
@@ -198,21 +258,34 @@ class InspectableWebContentsImpl::NetworkResourceLoader
}
void OnComplete(bool success) override {
base::DictionaryValue response;
response.SetInteger("statusCode", response_headers_
? response_headers_->response_code()
: 200);
if (!success && loader_->NetError() == net::ERR_INSUFFICIENT_RESOURCES &&
retry_delay_ < kMaxBackoffDelay) {
const base::TimeDelta delay =
GetNextExponentialBackoffDelay(retry_delay_);
LOG(WARNING) << "InspectableWebContentsImpl::NetworkResourceLoader id = "
<< stream_id_
<< " failed with insufficient resources, retrying in "
<< delay << "." << std::endl;
NetworkResourceLoader::Create(
stream_id_, bindings_, resource_request_, traffic_annotation_,
std::move(url_loader_factory_), callback_, delay);
} else {
base::DictionaryValue response;
response.SetInteger("statusCode", response_headers_
? response_headers_->response_code()
: 200);
auto headers = std::make_unique<base::DictionaryValue>();
size_t iterator = 0;
std::string name;
std::string value;
while (response_headers_ &&
response_headers_->EnumerateHeaderLines(&iterator, &name, &value))
headers->SetString(name, value);
auto headers = std::make_unique<base::DictionaryValue>();
size_t iterator = 0;
std::string name;
std::string value;
while (response_headers_ &&
response_headers_->EnumerateHeaderLines(&iterator, &name, &value))
headers->SetString(name, value);
response.Set("headers", std::move(headers));
callback_.Run(&response);
response.Set("headers", std::move(headers));
callback_.Run(&response);
}
bindings_->loaders_.erase(bindings_->loaders_.find(this));
}
@@ -221,9 +294,14 @@ class InspectableWebContentsImpl::NetworkResourceLoader
const int stream_id_;
InspectableWebContentsImpl* const bindings_;
const network::ResourceRequest resource_request_;
const net::NetworkTrafficAnnotationTag traffic_annotation_;
std::unique_ptr<network::SimpleURLLoader> loader_;
URLLoaderFactoryHolder url_loader_factory_;
DispatchCallback callback_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;
base::OneShotTimer timer_;
base::TimeDelta retry_delay_;
};
// Implemented separately on each platform.
@@ -527,19 +605,45 @@ void InspectableWebContentsImpl::LoadNetworkResource(
return;
}
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = gurl;
resource_request->headers.AddHeadersFromString(headers);
// Create traffic annotation tag.
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("devtools_network_resource", R"(
semantics {
sender: "Developer Tools"
description:
"When user opens Developer Tools, the browser may fetch additional "
"resources from the network to enrich the debugging experience "
"(e.g. source map resources)."
trigger: "User opens Developer Tools to debug a web page."
data: "Any resources requested by Developer Tools."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"It's not possible to disable this feature from settings."
})");
auto* partition = content::BrowserContext::GetDefaultStoragePartition(
GetDevToolsWebContents()->GetBrowserContext());
auto factory = partition->GetURLLoaderFactoryForBrowserProcess();
network::ResourceRequest resource_request;
resource_request.url = gurl;
resource_request.site_for_cookies = gurl;
resource_request.headers.AddHeadersFromString(headers);
auto simple_url_loader = network::SimpleURLLoader::Create(
std::move(resource_request), MISSING_TRAFFIC_ANNOTATION);
auto resource_loader = std::make_unique<NetworkResourceLoader>(
stream_id, this, std::move(simple_url_loader), factory.get(), callback);
loaders_.insert(std::move(resource_loader));
NetworkResourceLoader::URLLoaderFactoryHolder url_loader_factory;
if (gurl.SchemeIsFile()) {
url_loader_factory = content::CreateFileURLLoaderFactory(
base::FilePath() /* profile_path */,
nullptr /* shared_cors_origin_access_list */);
} else {
auto* partition = content::BrowserContext::GetDefaultStoragePartition(
GetDevToolsWebContents()->GetBrowserContext());
url_loader_factory = partition->GetURLLoaderFactoryForBrowserProcess();
}
NetworkResourceLoader::Create(stream_id, this, resource_request,
traffic_annotation,
std::move(url_loader_factory), callback);
}
void InspectableWebContentsImpl::SetIsDocked(const DispatchCallback& callback,

View File

@@ -6,6 +6,7 @@
#define SHELL_BROWSER_UI_MESSAGE_BOX_H_
#include <string>
#include <utility>
#include <vector>
#include "base/callback_forward.h"
@@ -24,10 +25,7 @@ enum class MessageBoxType {
kQuestion,
};
enum MessageBoxOptions {
MESSAGE_BOX_NONE = 0,
MESSAGE_BOX_NO_LINK = 1 << 0,
};
using DialogResult = std::pair<int, bool>;
struct MessageBoxSettings {
electron::NativeWindow* parent_window = nullptr;
@@ -35,7 +33,7 @@ struct MessageBoxSettings {
std::vector<std::string> buttons;
int default_id;
int cancel_id;
int options = electron::MessageBoxOptions::MESSAGE_BOX_NONE;
bool no_link = false;
std::string title;
std::string message;
std::string detail;

View File

@@ -59,9 +59,11 @@ NSAlert* CreateNSAlert(const MessageBoxSettings& settings) {
int button_count = static_cast<int>([ns_buttons count]);
if (settings.default_id >= 0 && settings.default_id < button_count) {
// Focus the button at default_id if the user opted to do so.
// The first button added gets set as the default selected.
// So remove that default, and make the requested button the default.
// Highlight the button at default_id
[[ns_buttons objectAtIndex:settings.default_id] highlight:YES];
// The first button added gets set as the default selected, so remove
// that and set the button @ default_id to be default.
[[ns_buttons objectAtIndex:0] setKeyEquivalent:@""];
[[ns_buttons objectAtIndex:settings.default_id] setKeyEquivalent:@"\r"];
}

View File

@@ -78,18 +78,18 @@ void MapToCommonID(const std::vector<base::string16>& buttons,
}
}
int ShowTaskDialogUTF16(NativeWindow* parent,
MessageBoxType type,
const std::vector<base::string16>& buttons,
int default_id,
int cancel_id,
int options,
const base::string16& title,
const base::string16& message,
const base::string16& detail,
const base::string16& checkbox_label,
bool* checkbox_checked,
const gfx::ImageSkia& icon) {
DialogResult ShowTaskDialogUTF16(NativeWindow* parent,
MessageBoxType type,
const std::vector<base::string16>& buttons,
int default_id,
int cancel_id,
bool no_link,
const base::string16& title,
const base::string16& message,
const base::string16& detail,
const base::string16& checkbox_label,
bool checkbox_checked,
const gfx::ImageSkia& icon) {
TASKDIALOG_FLAGS flags =
TDF_SIZE_TO_CONTENT | // Show all content.
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
@@ -149,16 +149,15 @@ int ShowTaskDialogUTF16(NativeWindow* parent,
if (!checkbox_label.empty()) {
config.pszVerificationText = checkbox_label.c_str();
if (checkbox_checked && *checkbox_checked) {
if (checkbox_checked)
config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED;
}
}
// Iterate through the buttons, put common buttons in dwCommonButtons
// and custom buttons in pButtons.
std::map<int, int> id_map;
std::vector<TASKDIALOG_BUTTON> dialog_buttons;
if (options & MESSAGE_BOX_NO_LINK) {
if (no_link) {
for (size_t i = 0; i < buttons.size(); ++i)
dialog_buttons.push_back(
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
@@ -168,26 +167,28 @@ int ShowTaskDialogUTF16(NativeWindow* parent,
if (dialog_buttons.size() > 0) {
config.pButtons = &dialog_buttons.front();
config.cButtons = dialog_buttons.size();
if (!(options & MESSAGE_BOX_NO_LINK))
if (!no_link)
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
}
int button_id;
int id = 0;
BOOL verificationFlagChecked = FALSE;
TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
if (checkbox_checked) {
*checkbox_checked = verificationFlagChecked;
}
if (id_map.find(id) != id_map.end()) // common button.
return id_map[id];
button_id = id_map[id];
else if (id >= kIDStart) // custom button.
return id - kIDStart;
button_id = id - kIDStart;
else
return cancel_id;
button_id = cancel_id;
return std::make_pair(button_id,
checkbox_checked ? verificationFlagChecked : false);
}
int ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
std::vector<base::string16> utf16_buttons;
for (const auto& button : settings.buttons)
utf16_buttons.push_back(base::UTF8ToUTF16(button));
@@ -197,21 +198,20 @@ int ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
const base::string16 detail_16 = base::UTF8ToUTF16(settings.detail);
const base::string16 checkbox_label_16 =
base::UTF8ToUTF16(settings.checkbox_label);
bool cb_checked = settings.checkbox_checked;
return ShowTaskDialogUTF16(
settings.parent_window, settings.type, utf16_buttons, settings.default_id,
settings.cancel_id, settings.options, title_16, message_16, detail_16,
checkbox_label_16, &cb_checked, settings.icon);
settings.cancel_id, settings.no_link, title_16, message_16, detail_16,
checkbox_label_16, settings.checkbox_checked, settings.icon);
}
void RunMessageBoxInNewThread(base::Thread* thread,
const MessageBoxSettings& settings,
MessageBoxCallback callback) {
int result = ShowTaskDialogUTF8(settings);
DialogResult result = ShowTaskDialogUTF8(settings);
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(std::move(callback), result, settings.checkbox_checked));
base::BindOnce(std::move(callback), result.first, result.second));
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
thread);
}
@@ -220,7 +220,8 @@ void RunMessageBoxInNewThread(base::Thread* thread,
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
electron::UnresponsiveSuppressor suppressor;
return ShowTaskDialogUTF8(settings);
DialogResult result = ShowTaskDialogUTF8(settings);
return result.first;
}
void ShowMessageBox(const MessageBoxSettings& settings,
@@ -242,8 +243,8 @@ void ShowMessageBox(const MessageBoxSettings& settings,
void ShowErrorBox(const base::string16& title, const base::string16& content) {
electron::UnresponsiveSuppressor suppressor;
ShowTaskDialogUTF16(nullptr, MessageBoxType::kError, {}, -1, 0, 0, L"Error",
title, content, L"", nullptr, gfx::ImageSkia());
ShowTaskDialogUTF16(nullptr, MessageBoxType::kError, {}, -1, 0, false,
L"Error", title, content, L"", false, gfx::ImageSkia());
}
} // namespace electron

View File

@@ -71,6 +71,8 @@ class MenuBar : public views::AccessiblePaneView,
// views::AccessiblePaneView:
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
bool SetPaneFocus(views::View* initial_focus) override;
void RemovePaneFocus() override;
protected:
// views::View:
@@ -82,9 +84,6 @@ class MenuBar : public views::AccessiblePaneView,
const ui::Event* event) override;
void OnThemeChanged() override;
bool SetPaneFocus(views::View* initial_focus);
void RemovePaneFocus();
private:
friend class MenuBarColorUpdater;

View File

@@ -1,508 +0,0 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/crash_reporter/win/crash_service.h"
#include <windows.h>
#include <sddl.h>
#include <fstream> // NOLINT
#include <map>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/win/windows_version.h"
#include "breakpad/src/client/windows/crash_generation/client_info.h"
#include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
#include "breakpad/src/client/windows/sender/crash_report_sender.h"
namespace breakpad {
namespace {
const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent";
const wchar_t kClassNameFormat[] = L"$1CrashServiceWindow";
const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
const wchar_t kGoogleReportURL[] = L"https://clients2.google.com/cr/report";
const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
typedef std::map<std::wstring, std::wstring> CrashMap;
bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info,
const std::wstring& reporter_tag,
CrashMap* map) {
google_breakpad::CustomClientInfo info = client_info->GetCustomInfo();
for (uintptr_t i = 0; i < info.count; ++i) {
(*map)[info.entries[i].name] = info.entries[i].value;
}
(*map)[L"rept"] = reporter_tag;
return !map->empty();
}
bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) {
std::wstring file_path(dump_path);
size_t last_dot = file_path.rfind(L'.');
if (last_dot == std::wstring::npos)
return false;
file_path.resize(last_dot);
file_path += L".txt";
std::wofstream file(file_path.c_str(), std::ios_base::out |
std::ios_base::app |
std::ios::binary);
if (!file.is_open())
return false;
CrashMap::const_iterator pos;
for (pos = map.begin(); pos != map.end(); ++pos) {
std::wstring line = pos->first;
line += L':';
line += pos->second;
line += L'\n';
file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
}
return true;
}
bool WriteReportIDToFile(const std::wstring& dump_path,
const std::wstring& report_id) {
std::wstring file_path(dump_path);
size_t last_slash = file_path.rfind(L'\\');
if (last_slash == std::wstring::npos)
return false;
file_path.resize(last_slash);
file_path += L"\\uploads.log";
std::wofstream file(file_path.c_str(), std::ios_base::out |
std::ios_base::app |
std::ios::binary);
if (!file.is_open())
return false;
int64_t seconds_since_epoch =
(base::Time::Now() - base::Time::UnixEpoch()).InSeconds();
std::wstring line = base::NumberToString16(seconds_since_epoch);
line += L',';
line += report_id;
line += L'\n';
file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
return true;
}
// The window procedure task is to handle when a) the user logs off.
// b) the system shuts down or c) when the user closes the window.
LRESULT __stdcall CrashSvcWndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
switch (message) {
case WM_CLOSE:
case WM_ENDSESSION:
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
return 0;
}
// This is the main and only application window.
HWND g_top_window = NULL;
bool CreateTopWindow(HINSTANCE instance,
const base::string16& application_name,
bool visible) {
base::string16 class_name =
base::ReplaceStringPlaceholders(kClassNameFormat, application_name, NULL);
WNDCLASSEXW wcx = {0};
wcx.cbSize = sizeof(wcx);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = CrashSvcWndProc;
wcx.hInstance = instance;
wcx.lpszClassName = class_name.c_str();
::RegisterClassExW(&wcx);
DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
// The window size is zero but being a popup window still shows in the
// task bar and can be closed using the system menu or using task manager.
HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style,
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL,
instance, NULL);
if (!window)
return false;
::UpdateWindow(window);
VLOG(1) << "window handle is " << window;
g_top_window = window;
return true;
}
// Simple helper class to keep the process alive until the current request
// finishes.
class ProcessingLock {
public:
ProcessingLock() { ::InterlockedIncrement(&op_count_); }
~ProcessingLock() { ::InterlockedDecrement(&op_count_); }
static bool IsWorking() { return (op_count_ != 0); }
private:
static volatile LONG op_count_;
};
volatile LONG ProcessingLock::op_count_ = 0;
// This structure contains the information that the worker thread needs to
// send a crash dump to the server.
struct DumpJobInfo {
DWORD pid;
CrashService* self;
CrashMap map;
std::wstring dump_path;
DumpJobInfo(DWORD process_id,
CrashService* service,
const CrashMap& crash_map,
const std::wstring& path)
: pid(process_id), self(service), map(crash_map), dump_path(path) {}
};
} // namespace
// Command line switches:
const char CrashService::kMaxReports[] = "max-reports";
const char CrashService::kNoWindow[] = "no-window";
const char CrashService::kReporterTag[] = "reporter";
const char CrashService::kDumpsDir[] = "dumps-dir";
const char CrashService::kPipeName[] = "pipe-name";
const char CrashService::kReporterURL[] = "reporter-url";
CrashService::CrashService() {}
CrashService::~CrashService() {
base::AutoLock lock(sending_);
delete dumper_;
delete sender_;
}
bool CrashService::Initialize(const base::string16& application_name,
const base::FilePath& operating_dir,
const base::FilePath& dumps_path) {
using google_breakpad::CrashGenerationServer;
using google_breakpad::CrashReportSender;
std::wstring pipe_name = kTestPipeName;
int max_reports = -1;
// The checkpoint file allows CrashReportSender to enforce the maximum
// reports per day quota. Does not seem to serve any other purpose.
base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile);
base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
base::FilePath dumps_path_to_use = dumps_path;
if (cmd_line.HasSwitch(kDumpsDir)) {
dumps_path_to_use =
base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
}
// We can override the send reports quota with a command line switch.
if (cmd_line.HasSwitch(kMaxReports))
max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str());
// Allow the global pipe name to be overridden for better testability.
if (cmd_line.HasSwitch(kPipeName))
pipe_name = cmd_line.GetSwitchValueNative(kPipeName);
if (max_reports > 0) {
// Create the http sender object.
sender_ = new CrashReportSender(checkpoint_path.value());
sender_->set_max_reports_per_day(max_reports);
}
SECURITY_ATTRIBUTES security_attributes = {0};
SECURITY_DESCRIPTOR* security_descriptor =
reinterpret_cast<SECURITY_DESCRIPTOR*>(
GetSecurityDescriptorForLowIntegrity());
DCHECK(security_descriptor != NULL);
security_attributes.nLength = sizeof(security_attributes);
security_attributes.lpSecurityDescriptor = security_descriptor;
security_attributes.bInheritHandle = FALSE;
// Create the OOP crash generator object.
dumper_ = new CrashGenerationServer(
pipe_name, &security_attributes, &CrashService::OnClientConnected, this,
&CrashService::OnClientDumpRequest, this, &CrashService::OnClientExited,
this, NULL, NULL, true, &dumps_path_to_use.value());
if (!dumper_) {
LOG(ERROR) << "could not create dumper";
if (security_attributes.lpSecurityDescriptor)
LocalFree(security_attributes.lpSecurityDescriptor);
return false;
}
if (!CreateTopWindow(::GetModuleHandleW(NULL), application_name,
!cmd_line.HasSwitch(kNoWindow))) {
LOG(ERROR) << "could not create window";
if (security_attributes.lpSecurityDescriptor)
LocalFree(security_attributes.lpSecurityDescriptor);
return false;
}
reporter_tag_ = L"crash svc";
if (cmd_line.HasSwitch(kReporterTag))
reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
reporter_url_ = kGoogleReportURL;
if (cmd_line.HasSwitch(kReporterURL))
reporter_url_ = cmd_line.GetSwitchValueNative(kReporterURL);
// Log basic information.
VLOG(1) << "pipe name is " << pipe_name << "\ndumps at "
<< dumps_path_to_use.value();
if (sender_) {
VLOG(1) << "checkpoint is " << checkpoint_path.value() << "\nserver is "
<< reporter_url_ << "\nmaximum " << sender_->max_reports_per_day()
<< " reports/day"
<< "\nreporter is " << reporter_tag_;
}
// Start servicing clients.
if (!dumper_->Start()) {
LOG(ERROR) << "could not start dumper";
if (security_attributes.lpSecurityDescriptor)
LocalFree(security_attributes.lpSecurityDescriptor);
return false;
}
if (security_attributes.lpSecurityDescriptor)
LocalFree(security_attributes.lpSecurityDescriptor);
// Create or open an event to signal the browser process that the crash
// service is initialized.
base::string16 wait_name =
base::ReplaceStringPlaceholders(kWaitEventFormat, application_name, NULL);
HANDLE wait_event = ::CreateEventW(NULL, TRUE, TRUE, wait_name.c_str());
::SetEvent(wait_event);
return true;
}
void CrashService::OnClientConnected(
void* context,
const google_breakpad::ClientInfo* client_info) {
ProcessingLock lock;
VLOG(1) << "client start. pid = " << client_info->pid();
CrashService* self = static_cast<CrashService*>(context);
::InterlockedIncrement(&self->clients_connected_);
}
void CrashService::OnClientExited(
void* context,
const google_breakpad::ClientInfo* client_info) {
ProcessingLock processing_lock;
VLOG(1) << "client end. pid = " << client_info->pid();
CrashService* self = static_cast<CrashService*>(context);
::InterlockedIncrement(&self->clients_terminated_);
if (!self->sender_)
return;
// When we are instructed to send reports we need to exit if there are
// no more clients to service. The next client that runs will start us.
// Only chrome.exe starts crash_service with a non-zero max_reports.
if (self->clients_connected_ > self->clients_terminated_)
return;
if (self->sender_->max_reports_per_day() > 0) {
// Wait for the other thread to send crashes, if applicable. The sender
// thread takes the sending_ lock, so the sleep is just to give it a
// chance to start.
::Sleep(1000);
base::AutoLock lock(self->sending_);
// Some people can restart chrome very fast, check again if we have
// a new client before exiting for real.
if (self->clients_connected_ == self->clients_terminated_) {
VLOG(1) << "zero clients. exiting";
::PostMessage(g_top_window, WM_CLOSE, 0, 0);
}
}
}
void CrashService::OnClientDumpRequest(
void* context,
const google_breakpad::ClientInfo* client_info,
const std::wstring* file_path) {
ProcessingLock lock;
if (!file_path) {
LOG(ERROR) << "dump with no file path";
return;
}
if (!client_info) {
LOG(ERROR) << "dump with no client info";
return;
}
CrashService* self = static_cast<CrashService*>(context);
if (!self) {
LOG(ERROR) << "dump with no context";
return;
}
CrashMap map;
CustomInfoToMap(client_info, self->reporter_tag_, &map);
// Move dump file to the directory under client breakpad dump location.
base::FilePath dump_location = base::FilePath(*file_path);
CrashMap::const_iterator it = map.find(L"breakpad-dump-location");
if (it != map.end()) {
base::FilePath alternate_dump_location = base::FilePath(it->second);
base::CreateDirectoryW(alternate_dump_location);
alternate_dump_location =
alternate_dump_location.Append(dump_location.BaseName());
base::Move(dump_location, alternate_dump_location);
dump_location = alternate_dump_location;
}
DWORD pid = client_info->pid();
VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
if (!WriteCustomInfoToFile(dump_location.value(), map)) {
LOG(ERROR) << "could not write custom info file";
}
if (!self->sender_ || map.find(L"skip_upload") != map.end())
return;
// Send the crash dump using a worker thread. This operation has retry
// logic in case there is no internet connection at the time.
DumpJobInfo* dump_job =
new DumpJobInfo(pid, self, map, dump_location.value());
if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, dump_job,
WT_EXECUTELONGFUNCTION)) {
LOG(ERROR) << "could not queue job";
}
}
// We are going to try sending the report several times. If we can't send,
// we sleep from one minute to several hours depending on the retry round.
DWORD CrashService::AsyncSendDump(void* context) {
if (!context)
return 0;
DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
std::wstring report_id = L"<unsent>";
const DWORD kOneMinute = 60 * 1000;
const DWORD kOneHour = 60 * kOneMinute;
const DWORD kSleepSchedule[] = {24 * kOneHour, 8 * kOneHour, 4 * kOneHour,
kOneHour, 15 * kOneMinute, 0};
int retry_round = base::size(kSleepSchedule) - 1;
do {
::Sleep(kSleepSchedule[retry_round]);
{
// Take the server lock while sending. This also prevent early
// termination of the service object.
base::AutoLock lock(info->self->sending_);
VLOG(1) << "trying to send report for pid = " << info->pid;
std::map<std::wstring, std::wstring> file_map;
file_map[L"upload_file_minidump"] = info->dump_path;
google_breakpad::ReportResult send_result =
info->self->sender_->SendCrashReport(info->self->reporter_url_,
info->map, file_map, &report_id);
switch (send_result) {
case google_breakpad::RESULT_FAILED:
report_id = L"<network issue>";
break;
case google_breakpad::RESULT_REJECTED:
report_id = L"<rejected>";
++info->self->requests_handled_;
retry_round = 0;
break;
case google_breakpad::RESULT_SUCCEEDED:
++info->self->requests_sent_;
++info->self->requests_handled_;
retry_round = 0;
WriteReportIDToFile(info->dump_path, report_id);
break;
case google_breakpad::RESULT_THROTTLED:
report_id = L"<throttled>";
break;
default:
report_id = L"<unknown>";
break;
}
}
VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
--retry_round;
} while (retry_round >= 0);
if (!::DeleteFileW(info->dump_path.c_str()))
LOG(WARNING) << "could not delete " << info->dump_path;
delete info;
return 0;
}
int CrashService::ProcessingLoop() {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
VLOG(1) << "session ending..";
while (ProcessingLock::IsWorking()) {
::Sleep(50);
}
VLOG(1) << "clients connected :" << clients_connected_
<< "\nclients terminated :" << clients_terminated_
<< "\ndumps serviced :" << requests_handled_
<< "\ndumps reported :" << requests_sent_;
return static_cast<int>(msg.wParam);
}
PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() {
// Build the SDDL string for the label.
std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)";
PSECURITY_DESCRIPTOR sec_desc = NULL;
PACL sacl = NULL;
BOOL sacl_present = FALSE;
BOOL sacl_defaulted = FALSE;
if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl.c_str(), SDDL_REVISION, &sec_desc, NULL)) {
if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
&sacl_defaulted)) {
return sec_desc;
}
}
return NULL;
}
} // namespace breakpad

View File

@@ -1,130 +0,0 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_
#define SHELL_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_
#include <string>
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
#if defined(OS_WIN)
#include <windows.h>
#endif // defined(OS_WIN)
namespace google_breakpad {
class CrashReportSender;
class CrashGenerationServer;
class ClientInfo;
} // namespace google_breakpad
namespace breakpad {
// This class implements an out-of-process crash server. It uses breakpad's
// CrashGenerationServer and CrashReportSender to generate and then send the
// crash dumps. Internally, it uses OS specific pipe to allow applications to
// register for crash dumps and later on when a registered application crashes
// it will signal an event that causes this code to wake up and perform a
// crash dump on the signaling process. The dump is then stored on disk and
// possibly sent to the crash2 servers.
class CrashService {
public:
CrashService();
~CrashService();
// Starts servicing crash dumps. Returns false if it failed. Do not use
// other members in that case. |operating_dir| is where the CrashService
// should store breakpad's checkpoint file. |dumps_path| is the directory
// where the crash dumps should be stored.
bool Initialize(const base::string16& application_name,
const base::FilePath& operating_dir,
const base::FilePath& dumps_path);
// Command line switches:
//
// --max-reports=<number>
// Allows to override the maximum number for reports per day. Normally
// the crash dumps are never sent so if you want to send any you must
// specify a positive number here.
static const char kMaxReports[];
// --no-window
// Does not create a visible window on the desktop. The window does not have
// any other functionality other than allowing the crash service to be
// gracefully closed.
static const char kNoWindow[];
// --reporter=<string>
// Allows to specify a custom string that appears on the detail crash report
// page in the crash server. This should be a 25 chars or less string.
// The default tag if not specified is 'crash svc'.
static const char kReporterTag[];
// --dumps-dir=<directory-path>
// Override the directory to which crash dump files will be written.
static const char kDumpsDir[];
// --pipe-name=<string>
// Override the name of the Windows named pipe on which we will
// listen for crash dump request messages.
static const char kPipeName[];
// --reporter-url=<string>
// Override the URL to which crash reports will be sent to.
static const char kReporterURL[];
// Returns number of crash dumps handled.
int requests_handled() const { return requests_handled_; }
// Returns number of crash clients registered.
int clients_connected() const { return clients_connected_; }
// Returns number of crash clients terminated.
int clients_terminated() const { return clients_terminated_; }
// Starts the processing loop. This function does not return unless the
// user is logging off or the user closes the crash service window. The
// return value is a good number to pass in ExitProcess().
int ProcessingLoop();
private:
static void OnClientConnected(void* context,
const google_breakpad::ClientInfo* client_info);
static void OnClientDumpRequest(
void* context,
const google_breakpad::ClientInfo* client_info,
const std::wstring* file_path);
static void OnClientExited(void* context,
const google_breakpad::ClientInfo* client_info);
// This routine sends the crash dump to the server. It takes the sending_
// lock when it is performing the send.
static DWORD __stdcall AsyncSendDump(void* context);
// Returns the security descriptor which access to low integrity processes
// The caller is supposed to free the security descriptor by calling
// LocalFree.
PSECURITY_DESCRIPTOR GetSecurityDescriptorForLowIntegrity();
google_breakpad::CrashGenerationServer* dumper_ = nullptr;
google_breakpad::CrashReportSender* sender_ = nullptr;
// the extra tag sent to the server with each dump.
std::wstring reporter_tag_;
// receiver URL of crash reports.
std::wstring reporter_url_;
// clients serviced statistics:
int requests_handled_ = 0;
int requests_sent_ = 0;
volatile LONG clients_connected_ = 0;
volatile LONG clients_terminated_ = 0;
base::Lock sending_;
DISALLOW_COPY_AND_ASSIGN(CrashService);
};
} // namespace breakpad
#endif // SHELL_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_

View File

@@ -13,7 +13,6 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "shell/common/crash_reporter/crash_reporter.h"
#include "shell/common/crash_reporter/win/crash_service.h"
#include "third_party/crashpad/crashpad/handler/handler_main.h"
namespace crash_service {

View File

@@ -25,11 +25,11 @@ bool Converter<electron::MessageBoxSettings>::FromV8(
dict.Get("buttons", &out->buttons);
dict.Get("defaultId", &out->default_id);
dict.Get("cancelId", &out->cancel_id);
dict.Get("options", &out->options);
dict.Get("title", &out->title);
dict.Get("message", &out->message);
dict.Get("detail", &out->detail);
dict.Get("checkboxLabel", &out->checkbox_label);
dict.Get("noLink", &out->no_link);
dict.Get("checkboxChecked", &out->checkbox_checked);
dict.Get("icon", &out->icon);
return true;

View File

@@ -20,6 +20,8 @@
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_version.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "shell/browser/api/atom_api_data_pipe_holder.h"
#include "shell/browser/net/cert_verifier_client.h"
@@ -295,6 +297,34 @@ v8::Local<v8::Value> Converter<electron::VerifyRequestParams>::ToV8(
return dict.GetHandle();
}
// static
v8::Local<v8::Value> Converter<net::HttpVersion>::ToV8(
v8::Isolate* isolate,
const net::HttpVersion& val) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
dict.Set("major", static_cast<uint32_t>(val.major_value()));
dict.Set("minor", static_cast<uint32_t>(val.minor_value()));
return ConvertToV8(isolate, dict);
}
// static
v8::Local<v8::Value> Converter<net::RedirectInfo>::ToV8(
v8::Isolate* isolate,
const net::RedirectInfo& val) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
dict.Set("statusCode", val.status_code);
dict.Set("newMethod", val.new_method);
dict.Set("newUrl", val.new_url);
dict.Set("newSiteForCookies", val.new_site_for_cookies);
dict.Set("newReferrer", val.new_referrer);
dict.Set("insecureSchemeWasUpgraded", val.insecure_scheme_was_upgraded);
dict.Set("isSignedExchangeFallbackRedirect",
val.is_signed_exchange_fallback_redirect);
return ConvertToV8(isolate, dict);
}
} // namespace mate
namespace electron {

View File

@@ -24,6 +24,8 @@ class X509Certificate;
class HttpResponseHeaders;
class HttpRequestHeaders;
struct CertPrincipal;
class HttpVersion;
struct RedirectInfo;
} // namespace net
namespace network {
@@ -89,6 +91,18 @@ struct Converter<electron::VerifyRequestParams> {
electron::VerifyRequestParams val);
};
template <>
struct Converter<net::HttpVersion> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const net::HttpVersion& val);
};
template <>
struct Converter<net::RedirectInfo> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const net::RedirectInfo& val);
};
} // namespace mate
namespace electron {

View File

@@ -34,20 +34,12 @@ content::RenderFrame* GetRenderFrame(const v8::Local<v8::Object>& value) {
return content::RenderFrame::FromWebFrame(frame);
}
std::map<content::RenderFrame*, context_bridge::RenderFramePersistenceStore*>&
GetStoreMap() {
static base::NoDestructor<std::map<
content::RenderFrame*, context_bridge::RenderFramePersistenceStore*>>
store_map;
return *store_map;
}
context_bridge::RenderFramePersistenceStore* GetOrCreateStore(
content::RenderFrame* render_frame) {
auto it = GetStoreMap().find(render_frame);
if (it == GetStoreMap().end()) {
auto it = context_bridge::GetStoreMap().find(render_frame->GetRoutingID());
if (it == context_bridge::GetStoreMap().end()) {
auto* store = new context_bridge::RenderFramePersistenceStore(render_frame);
GetStoreMap().emplace(render_frame, store);
context_bridge::GetStoreMap().emplace(render_frame->GetRoutingID(), store);
return store;
}
return it->second;

View File

@@ -20,7 +20,7 @@ class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
RenderFramePersistenceStore* store,
base::WeakPtr<RenderFramePersistenceStore> store,
WeakGlobalPairNode* node,
int hash) {
new CachedProxyLifeMonitor(isolate, target, store, node, hash);
@@ -29,7 +29,7 @@ class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
protected:
CachedProxyLifeMonitor(v8::Isolate* isolate,
v8::Local<v8::Object> target,
RenderFramePersistenceStore* store,
base::WeakPtr<RenderFramePersistenceStore> store,
WeakGlobalPairNode* node,
int hash)
: ObjectLifeMonitor(isolate, target),
@@ -38,8 +38,12 @@ class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
hash_(hash) {}
void RunDestructor() override {
if (!store_)
return;
if (node_->detached) {
delete node_;
return;
}
if (node_->prev) {
node_->prev->next = node_->next;
@@ -55,13 +59,19 @@ class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
}
private:
RenderFramePersistenceStore* store_;
base::WeakPtr<RenderFramePersistenceStore> store_;
WeakGlobalPairNode* node_;
int hash_;
};
} // namespace
std::map<int32_t, RenderFramePersistenceStore*>& GetStoreMap() {
static base::NoDestructor<std::map<int32_t, RenderFramePersistenceStore*>>
store_map;
return *store_map;
}
WeakGlobalPairNode::WeakGlobalPairNode(WeakGlobalPair pair) {
this->pair = std::move(pair);
}
@@ -74,11 +84,13 @@ WeakGlobalPairNode::~WeakGlobalPairNode() {
RenderFramePersistenceStore::RenderFramePersistenceStore(
content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame) {}
: content::RenderFrameObserver(render_frame),
routing_id_(render_frame->GetRoutingID()) {}
RenderFramePersistenceStore::~RenderFramePersistenceStore() = default;
void RenderFramePersistenceStore::OnDestruct() {
GetStoreMap().erase(routing_id_);
delete this;
}
@@ -97,11 +109,11 @@ void RenderFramePersistenceStore::CacheProxiedObject(
auto iter = proxy_map_.find(hash);
auto* node = new WeakGlobalPairNode(
std::make_tuple(std::move(global_from), std::move(global_proxy)));
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(), obj, this, node,
hash);
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(), obj,
weak_factory_.GetWeakPtr(), node, hash);
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(),
v8::Local<v8::Object>::Cast(proxy_value),
this, node, hash);
weak_factory_.GetWeakPtr(), node, hash);
if (iter == proxy_map_.end()) {
proxy_map_.emplace(hash, node);
} else {

View File

@@ -58,10 +58,15 @@ class RenderFramePersistenceStore final : public content::RenderFrameObserver {
// proxy maps are weak globals, i.e. these are not retained beyond
// there normal JS lifetime. You must check IsEmpty()
const int32_t routing_id_;
// object_identity ==> [from_value, proxy_value]
std::map<int, WeakGlobalPairNode*> proxy_map_;
base::WeakPtrFactory<RenderFramePersistenceStore> weak_factory_{this};
};
std::map<int32_t, RenderFramePersistenceStore*>& GetStoreMap();
} // namespace context_bridge
} // namespace api

View File

@@ -69,7 +69,7 @@ void InvokeIpcCallback(v8::Local<v8::Context> context,
.ToLocalChecked();
auto callback_value = ipcNative->Get(context, callback_key).ToLocalChecked();
DCHECK(callback_value->IsFunction()); // set by init.ts
auto callback = v8::Local<v8::Function>::Cast(callback_value);
auto callback = callback_value.As<v8::Function>();
ignore_result(callback->Call(context, ipcNative, args.size(), args.data()));
}

View File

@@ -1519,6 +1519,37 @@ describe('BrowserWindow module', () => {
sandbox: true,
contextIsolation: true
})
it('does not leak any node globals on the window object with nodeIntegration is disabled', async () => {
let w = new BrowserWindow({
webPreferences: {
contextIsolation: false,
nodeIntegration: false,
preload: path.resolve(fixtures, 'module', 'empty.js')
},
show: false
})
w.loadFile(path.join(fixtures, 'api', 'globals.html'))
const [, notIsolated] = await emittedOnce(ipcMain, 'leak-result')
expect(notIsolated).to.have.property('globals')
w.destroy()
w = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload: path.resolve(fixtures, 'module', 'empty.js')
},
show: false
})
w.loadFile(path.join(fixtures, 'api', 'globals.html'))
const [, isolated] = await emittedOnce(ipcMain, 'leak-result')
expect(isolated).to.have.property('globals')
const notIsolatedGlobals = new Set(notIsolated.globals)
for (const isolatedGlobal of isolated.globals) {
notIsolatedGlobals.delete(isolatedGlobal)
}
expect([...notIsolatedGlobals]).to.deep.equal([], 'non-isoalted renderer should have no additional globals')
})
it('loads the script before other scripts in window', async () => {
const preload = path.join(fixtures, 'module', 'set-global.js')

View File

@@ -1,8 +1,14 @@
import * as cp from 'child_process'
import * as path from 'path'
import { expect } from 'chai'
import { BrowserWindow, globalShortcut, Menu, MenuItem } from 'electron'
import { sortMenuItems } from '../lib/browser/api/menu-utils'
import { emittedOnce } from './events-helpers'
import { ifit } from './spec-helpers'
import { closeWindow } from './window-helpers'
const fixturesPath = path.resolve(__dirname, 'fixtures')
describe('Menu module', function () {
this.timeout(5000)
describe('Menu.buildFromTemplate', () => {
@@ -822,6 +828,31 @@ describe('Menu module', function () {
menu.closePopup()
})
})
it('prevents menu from getting garbage-collected when popuping', (done) => {
let menu = Menu.buildFromTemplate([{role: 'paste'}])
menu.popup({ window: w })
// Keep a weak reference to the menu.
const v8Util = process.electronBinding('v8_util')
const map = (v8Util as any).createIDWeakMap() as any
map.set(0, menu)
setTimeout(() => {
// Do garbage collection, since |menu| is not referenced in this closure
// it would be gone after next call.
v8Util.requestGarbageCollectionForTesting()
setTimeout(() => {
// Try to receive menu from weak reference.
if (map.has(0)) {
map.get(0).closePopup()
done()
} else {
done('Menu is garbage-collected while popuping')
}
})
})
})
})
describe('Menu.setApplicationMenu', () => {
@@ -839,6 +870,28 @@ describe('Menu module', function () {
Menu.setApplicationMenu(null)
expect(Menu.getApplicationMenu()).to.be.null('application menu')
})
ifit(process.platform !== 'darwin')('does not override menu visibility on startup', async () => {
const appPath = path.join(fixturesPath, 'api', 'test-menu-visibility')
const appProcess = cp.spawn(process.execPath, [appPath])
let output = ''
appProcess.stdout.on('data', data => { output += data })
await emittedOnce(appProcess, 'close')
expect(output).to.include('Window has no menu')
})
ifit(process.platform !== 'darwin')('does not override null menu on startup', async () => {
const appPath = path.join(fixturesPath, 'api', 'test-menu-null')
const appProcess = cp.spawn(process.execPath, [appPath])
let output = ''
appProcess.stdout.on('data', data => { output += data })
await emittedOnce(appProcess, 'close')
expect(output).to.include('Window has no menu')
})
})
describe('menu accelerators', async () => {

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,15 @@ describe('session module', () => {
const value = '0'
afterEach(closeAllWindows)
// Clear cookie of defaultSession after each test.
afterEach(async () => {
const { cookies } = session.defaultSession
const cs = await cookies.get({ url })
for (const c of cs) {
await cookies.remove(url, c.name)
}
})
it('should get cookies', async () => {
const server = http.createServer((req, res) => {
res.setHeader('Set-Cookie', [`${name}=${value}`])
@@ -79,9 +88,33 @@ describe('session module', () => {
const name = '1'
const value = '1'
await cookies.set({ url, name, value, expirationDate: (+new Date) / 1000 + 120 })
const cs = await cookies.get({ url })
expect(cs.some(c => c.name === name && c.value === value)).to.equal(true)
await cookies.set({ url, name, value, expirationDate: (+new Date()) / 1000 + 120 })
const c = (await cookies.get({ url }))[0]
expect(c.name).to.equal(name)
expect(c.value).to.equal(value)
expect(c.session).to.equal(false)
})
it('sets session cookies', async () => {
const { cookies } = session.defaultSession
const name = '2'
const value = '1'
await cookies.set({ url, name, value })
const c = (await cookies.get({ url }))[0]
expect(c.name).to.equal(name)
expect(c.value).to.equal(value)
expect(c.session).to.equal(true)
})
it('sets cookies without name', async () => {
const { cookies } = session.defaultSession
const value = '3'
await cookies.set({ url, value })
const c = (await cookies.get({ url }))[0]
expect(c.name).to.be.empty()
expect(c.value).to.equal(value)
})
it('gets cookies without url', async () => {
@@ -517,15 +550,23 @@ describe('session module', () => {
const fetch = (url) => new Promise((resolve, reject) => {
const request = net.request({ url, session: ses })
request.on('response', (response) => {
let data = ''
let data = null
response.on('data', (chunk) => {
if (!data) {
data = ''
}
data += chunk
})
response.on('end', () => {
resolve(data)
if (!data) {
reject(new Error('Empty response'))
} else {
resolve(data)
}
})
response.on('error', (error) => { reject(new Error(error)) })
});
request.on('error', (error) => { reject(new Error(error)) })
request.end()
})
// the first time should throw due to unauthenticated

View File

@@ -244,7 +244,7 @@ describe('webContents module', () => {
}
response
.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' })
.end()
.end('401')
}).listen(0, '127.0.0.1', () => {
serverPort = (server.address() as AddressInfo).port
serverUrl = `http://127.0.0.1:${serverPort}`
@@ -267,6 +267,10 @@ describe('webContents module', () => {
})
})
afterEach(async () => {
await session.defaultSession.clearAuthCache({ type: 'password' })
})
after(() => {
server.close()
proxyServer.close()
@@ -317,5 +321,38 @@ describe('webContents module', () => {
expect(eventAuthInfo.port).to.equal(proxyServerPort)
expect(eventAuthInfo.realm).to.equal('Foo')
})
it('cancels authentication when callback is called with no arguments', async () => {
const w = new BrowserWindow({ show: false })
w.webContents.on('login', (event, request, authInfo, cb) => {
event.preventDefault()
cb()
})
await w.loadURL(serverUrl)
const body = await w.webContents.executeJavaScript(`document.documentElement.textContent`)
expect(body).to.equal('401')
})
})
it('emits a cancelable event before creating a child webcontents', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true
}
})
w.webContents.on('-will-add-new-contents' as any, (event: any, url: any) => {
expect(url).to.equal('about:blank')
event.preventDefault()
})
let wasCalled = false
w.webContents.on('new-window' as any, () => {
wasCalled = true
})
await w.loadURL('about:blank')
await w.webContents.executeJavaScript(`window.open('about:blank')`)
await new Promise((resolve) => { process.nextTick(resolve) })
expect(wasCalled).to.equal(false)
await closeAllWindows()
})
})

View File

@@ -0,0 +1,17 @@
const { app, BrowserWindow } = require('electron')
let win
app.on('ready', function () {
win = new BrowserWindow({})
win.loadURL('about:blank')
win.setMenu(null)
setTimeout(() => {
if (win.isMenuBarVisible()) {
console.log('Window has a menu')
} else {
console.log('Window has no menu')
}
app.quit()
})
})

View File

@@ -0,0 +1,4 @@
{
"name": "electron-test-menu",
"main": "main.js"
}

View File

@@ -0,0 +1,17 @@
const { app, BrowserWindow } = require('electron')
let win
app.on('ready', function () {
win = new BrowserWindow({})
win.loadURL('about:blank')
win.setMenuBarVisibility(false)
setTimeout(() => {
if (win.isMenuBarVisible()) {
console.log('Window has a menu')
} else {
console.log('Window has no menu')
}
app.quit()
})
})

View File

@@ -0,0 +1,4 @@
{
"name": "electron-test-menu",
"main": "main.js"
}

View File

@@ -9,7 +9,5 @@ app.on('ready', () => {
process.stdout.write(JSON.stringify(payload))
process.stdout.end()
setImmediate(() => {
app.quit()
})
app.quit()
})

View File

@@ -42,8 +42,6 @@ app.on('ready', async function () {
} finally {
process.stdout.end()
setImmediate(() => {
app.quit()
})
app.quit()
}
})

13
spec/fixtures/api/globals.html vendored Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<script>
window.postMessage({
globals: Object.keys(Object.getOwnPropertyDescriptors(window))
})
</script>
</body>
</html>

View File

@@ -4,7 +4,5 @@ app.on('ready', () => {
process.stdout.write(JSON.stringify(ipcMain.eventNames()))
process.stdout.end()
setImmediate(() => {
app.quit()
})
app.quit()
})

View File

@@ -2,5 +2,5 @@ const { BrowserView, app } = require('electron')
app.on('ready', function () {
new BrowserView({}) // eslint-disable-line
process.nextTick(() => app.quit())
app.quit()
})

View File

@@ -2,5 +2,5 @@ const { app, webContents } = require('electron')
app.on('ready', function () {
webContents.create({})
process.nextTick(() => app.quit())
app.quit()
})

Some files were not shown because too many files have changed in this diff Show More