mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec61041d41 | ||
|
|
32b51bce75 | ||
|
|
e0576ef11a | ||
|
|
a889ec7957 | ||
|
|
bef0dd868b | ||
|
|
3d74cacefe | ||
|
|
0b1248c8e6 | ||
|
|
0569c929a1 | ||
|
|
2dc42971ce | ||
|
|
577d0483e9 | ||
|
|
a3fde67056 | ||
|
|
79aebdffb7 | ||
|
|
fc800837bd | ||
|
|
58469efd1c | ||
|
|
80c36602fd | ||
|
|
f910704838 | ||
|
|
f17dc63c16 | ||
|
|
04b0d61323 | ||
|
|
544b4c5209 | ||
|
|
b35f37ea5a | ||
|
|
7f0bbd27b1 | ||
|
|
3b557bcf2e | ||
|
|
ef2f0580d9 | ||
|
|
425d2fa6f1 | ||
|
|
1409056baf | ||
|
|
72cb493023 | ||
|
|
1e7f26e5fd | ||
|
|
383e75796a | ||
|
|
1edfffae25 | ||
|
|
96e7b443fc | ||
|
|
8f728af13a | ||
|
|
281b0741f7 | ||
|
|
38c43ab48f | ||
|
|
d1d3d1bd1f | ||
|
|
89a7bdb566 | ||
|
|
09d59f2b83 | ||
|
|
ebec22045d | ||
|
|
a2c4000b84 | ||
|
|
9010838f21 | ||
|
|
6fe40ca797 | ||
|
|
101b971f36 | ||
|
|
6156254886 | ||
|
|
d0e4bd3fc9 | ||
|
|
3e5213b4e9 | ||
|
|
7ba8855ac1 | ||
|
|
c3e0ae9646 | ||
|
|
4b456bf3cb | ||
|
|
2a4bdfff98 | ||
|
|
c4baee0dab | ||
|
|
15688c6d6e | ||
|
|
aa4d7f1799 | ||
|
|
0476eb67ab | ||
|
|
69aebc05e5 | ||
|
|
98d06f6e5c | ||
|
|
7d321a90aa | ||
|
|
cfddc0a125 | ||
|
|
ade70463c1 | ||
|
|
4cb983ed40 | ||
|
|
79b3fcb2ab | ||
|
|
4cfa7be79d | ||
|
|
67bcc2a972 | ||
|
|
449bdcfda2 | ||
|
|
e70adc8f83 | ||
|
|
a6ae1ca755 | ||
|
|
b6fb8d3a63 | ||
|
|
5462a2c197 | ||
|
|
de428e9a7a | ||
|
|
fbe36e2365 | ||
|
|
5ac12c9e44 | ||
|
|
8d11391cb4 |
@@ -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
2
DEPS
@@ -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':
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.1.2
|
||||
7.1.8
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()`
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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++.
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -188,6 +188,8 @@ if (nodeIntegration) {
|
||||
delete global.setImmediate
|
||||
delete global.clearImmediate
|
||||
delete global.global
|
||||
delete global.root
|
||||
delete global.GLOBAL
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
32
patches/chromium/accessible_pane_view.patch
Normal file
32
patches/chromium/accessible_pane_view.patch
Normal 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();
|
||||
158
patches/chromium/add_trustedauthclient_to_urlloaderfactory.patch
Normal file
158
patches/chromium/add_trustedauthclient_to_urlloaderfactory.patch
Normal 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_;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
@@ -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 ||
|
||||
@@ -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
66
script/add-debug-link.py
Executable 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
75
script/copy-debug-symbols.py
Executable 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())
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
454
shell/browser/api/atom_api_url_loader.cc
Normal file
454
shell/browser/api/atom_api_url_loader.cc
Normal 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
|
||||
94
shell/browser/api/atom_api_url_loader.h
Normal file
94
shell/browser/api/atom_api_url_loader.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
17
spec-main/fixtures/api/test-menu-null/main.js
Normal file
17
spec-main/fixtures/api/test-menu-null/main.js
Normal 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()
|
||||
})
|
||||
})
|
||||
4
spec-main/fixtures/api/test-menu-null/package.json
Normal file
4
spec-main/fixtures/api/test-menu-null/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "electron-test-menu",
|
||||
"main": "main.js"
|
||||
}
|
||||
17
spec-main/fixtures/api/test-menu-visibility/main.js
Normal file
17
spec-main/fixtures/api/test-menu-visibility/main.js
Normal 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()
|
||||
})
|
||||
})
|
||||
4
spec-main/fixtures/api/test-menu-visibility/package.json
Normal file
4
spec-main/fixtures/api/test-menu-visibility/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "electron-test-menu",
|
||||
"main": "main.js"
|
||||
}
|
||||
4
spec/fixtures/api/command-line/main.js
vendored
4
spec/fixtures/api/command-line/main.js
vendored
@@ -9,7 +9,5 @@ app.on('ready', () => {
|
||||
process.stdout.write(JSON.stringify(payload))
|
||||
process.stdout.end()
|
||||
|
||||
setImmediate(() => {
|
||||
app.quit()
|
||||
})
|
||||
app.quit()
|
||||
})
|
||||
|
||||
4
spec/fixtures/api/cookie-app/main.js
vendored
4
spec/fixtures/api/cookie-app/main.js
vendored
@@ -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
13
spec/fixtures/api/globals.html
vendored
Normal 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>
|
||||
4
spec/fixtures/api/ipc-main-listeners/main.js
vendored
4
spec/fixtures/api/ipc-main-listeners/main.js
vendored
@@ -4,7 +4,5 @@ app.on('ready', () => {
|
||||
process.stdout.write(JSON.stringify(ipcMain.eventNames()))
|
||||
process.stdout.end()
|
||||
|
||||
setImmediate(() => {
|
||||
app.quit()
|
||||
})
|
||||
app.quit()
|
||||
})
|
||||
|
||||
2
spec/fixtures/api/leak-exit-browserview.js
vendored
2
spec/fixtures/api/leak-exit-browserview.js
vendored
@@ -2,5 +2,5 @@ const { BrowserView, app } = require('electron')
|
||||
app.on('ready', function () {
|
||||
new BrowserView({}) // eslint-disable-line
|
||||
|
||||
process.nextTick(() => app.quit())
|
||||
app.quit()
|
||||
})
|
||||
|
||||
2
spec/fixtures/api/leak-exit-webcontents.js
vendored
2
spec/fixtures/api/leak-exit-webcontents.js
vendored
@@ -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
Reference in New Issue
Block a user