mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
19 Commits
v25.0.0-ni
...
v24.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1255ae20f | ||
|
|
de5ee7e60e | ||
|
|
85802682b5 | ||
|
|
f04ad15b65 | ||
|
|
38ca7c0860 | ||
|
|
309349a020 | ||
|
|
f3d50c674c | ||
|
|
8e74a37505 | ||
|
|
426c23446c | ||
|
|
52adfe2137 | ||
|
|
4911476787 | ||
|
|
7a62411ad1 | ||
|
|
99d648faaa | ||
|
|
91776c5484 | ||
|
|
1a48e36313 | ||
|
|
97c66e5985 | ||
|
|
4ce69207b0 | ||
|
|
cf4f6285c8 | ||
|
|
253a60f8ae |
@@ -1054,8 +1054,6 @@ commands:
|
||||
parameters:
|
||||
artifact-key:
|
||||
type: string
|
||||
build-type:
|
||||
type: string
|
||||
build-nonproprietary-ffmpeg:
|
||||
type: boolean
|
||||
default: true
|
||||
@@ -1063,7 +1061,6 @@ commands:
|
||||
- *step-gn-gen-default
|
||||
- ninja_build_electron:
|
||||
clean-prebuilt-snapshot: false
|
||||
build-type: << parameters.build-type >>
|
||||
- *step-maybe-electron-dist-strip
|
||||
- step-electron-dist-build:
|
||||
additional-targets: shell_browser_ui_unittests third_party/electron_node:headers third_party/electron_node:overlapped-checker electron:hunspell_dictionaries_zip
|
||||
@@ -1225,12 +1222,9 @@ commands:
|
||||
clean-prebuilt-snapshot:
|
||||
type: boolean
|
||||
default: true
|
||||
build-type:
|
||||
type: string
|
||||
|
||||
steps:
|
||||
- run:
|
||||
name: Electron << parameters.build-type >> build
|
||||
name: Electron build
|
||||
no_output_timeout: 60m
|
||||
command: |
|
||||
cd src
|
||||
@@ -1298,8 +1292,6 @@ commands:
|
||||
default: true
|
||||
artifact-key:
|
||||
type: string
|
||||
build-type:
|
||||
type: string
|
||||
after-build-and-save:
|
||||
type: steps
|
||||
default: []
|
||||
@@ -1426,7 +1418,6 @@ commands:
|
||||
steps:
|
||||
- build_and_save_artifacts:
|
||||
artifact-key: << parameters.artifact-key >>
|
||||
build-type: << parameters.build-type >>
|
||||
build-nonproprietary-ffmpeg: << parameters.build-nonproprietary-ffmpeg >>
|
||||
- steps: << parameters.after-build-and-save >>
|
||||
|
||||
@@ -1572,8 +1563,6 @@ commands:
|
||||
checkout:
|
||||
type: boolean
|
||||
default: true
|
||||
build-type:
|
||||
type: string
|
||||
steps:
|
||||
- when:
|
||||
condition: << parameters.attach >>
|
||||
@@ -1605,8 +1594,7 @@ commands:
|
||||
- *step-gn-gen-default
|
||||
|
||||
# Electron app
|
||||
- ninja_build_electron:
|
||||
build-type: << parameters.build-type >>
|
||||
- ninja_build_electron
|
||||
- *step-show-goma-stats
|
||||
- *step-maybe-generate-breakpad-symbols
|
||||
- *step-maybe-electron-dist-strip
|
||||
@@ -1669,7 +1657,6 @@ jobs:
|
||||
save-git-cache: true
|
||||
checkout-to-create-src-cache: true
|
||||
artifact-key: 'nil'
|
||||
build-type: 'nil'
|
||||
|
||||
mac-checkout:
|
||||
executor:
|
||||
@@ -1688,7 +1675,6 @@ jobs:
|
||||
persist-checkout: true
|
||||
restore-src-cache: false
|
||||
artifact-key: 'nil'
|
||||
build-type: 'nil'
|
||||
|
||||
mac-make-src-cache:
|
||||
executor:
|
||||
@@ -1707,7 +1693,6 @@ jobs:
|
||||
save-git-cache: true
|
||||
checkout-to-create-src-cache: true
|
||||
artifact-key: 'nil'
|
||||
build-type: 'nil'
|
||||
|
||||
# Layer 2: Builds.
|
||||
linux-x64-testing:
|
||||
@@ -1725,7 +1710,6 @@ jobs:
|
||||
checkout: false
|
||||
checkout-and-assume-cache: true
|
||||
artifact-key: 'linux-x64'
|
||||
build-type: 'Linux'
|
||||
|
||||
linux-x64-testing-asan:
|
||||
executor:
|
||||
@@ -1744,7 +1728,6 @@ jobs:
|
||||
checkout: true
|
||||
build-nonproprietary-ffmpeg: false
|
||||
artifact-key: 'linux-x64-asan'
|
||||
build-type: 'Linux'
|
||||
|
||||
linux-x64-testing-no-run-as-node:
|
||||
executor:
|
||||
@@ -1761,7 +1744,6 @@ jobs:
|
||||
persist: false
|
||||
checkout: true
|
||||
artifact-key: 'linux-x64-no-run-as-node'
|
||||
build-type: 'Linux'
|
||||
|
||||
linux-x64-testing-gn-check:
|
||||
executor:
|
||||
@@ -1793,7 +1775,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: false
|
||||
checkout: true
|
||||
build-type: 'Linux'
|
||||
|
||||
|
||||
linux-arm-testing:
|
||||
@@ -1814,7 +1795,6 @@ jobs:
|
||||
checkout: false
|
||||
checkout-and-assume-cache: true
|
||||
artifact-key: 'linux-arm'
|
||||
build-type: 'Linux ARM'
|
||||
|
||||
linux-arm-publish:
|
||||
executor:
|
||||
@@ -1839,7 +1819,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: false
|
||||
checkout: true
|
||||
build-type: 'Linux ARM'
|
||||
|
||||
linux-arm64-testing:
|
||||
executor:
|
||||
@@ -1859,7 +1838,6 @@ jobs:
|
||||
checkout: false
|
||||
checkout-and-assume-cache: true
|
||||
artifact-key: 'linux-arm64'
|
||||
build-type: 'Linux ARM64'
|
||||
|
||||
linux-arm64-testing-gn-check:
|
||||
executor:
|
||||
@@ -1894,7 +1872,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: false
|
||||
checkout: true
|
||||
build-type: 'Linux ARM64'
|
||||
|
||||
osx-testing-x64:
|
||||
executor:
|
||||
@@ -1913,7 +1890,6 @@ jobs:
|
||||
checkout-and-assume-cache: true
|
||||
attach: true
|
||||
artifact-key: 'darwin-x64'
|
||||
build-type: 'Darwin'
|
||||
after-build-and-save:
|
||||
- run:
|
||||
name: Configuring MAS build
|
||||
@@ -1924,7 +1900,6 @@ jobs:
|
||||
rm -rf src/out/Default/Electron*.app
|
||||
- build_and_save_artifacts:
|
||||
artifact-key: 'mas-x64'
|
||||
build-type: 'MAS'
|
||||
after-persist:
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
@@ -1961,7 +1936,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: true
|
||||
checkout: false
|
||||
build-type: 'Darwin'
|
||||
|
||||
osx-publish-arm64:
|
||||
executor:
|
||||
@@ -1984,7 +1958,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: true
|
||||
checkout: false
|
||||
build-type: 'Darwin ARM64'
|
||||
|
||||
osx-testing-arm64:
|
||||
executor:
|
||||
@@ -2005,7 +1978,6 @@ jobs:
|
||||
checkout-and-assume-cache: true
|
||||
attach: true
|
||||
artifact-key: 'darwin-arm64'
|
||||
build-type: 'Darwin ARM64'
|
||||
after-build-and-save:
|
||||
- run:
|
||||
name: Configuring MAS build
|
||||
@@ -2016,7 +1988,6 @@ jobs:
|
||||
rm -rf src/out/Default/Electron*.app
|
||||
- build_and_save_artifacts:
|
||||
artifact-key: 'mas-arm64'
|
||||
build-type: 'MAS ARM64'
|
||||
after-persist:
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
@@ -2043,7 +2014,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: true
|
||||
checkout: false
|
||||
build-type: 'MAS'
|
||||
|
||||
mas-publish-arm64:
|
||||
executor:
|
||||
@@ -2066,7 +2036,6 @@ jobs:
|
||||
- electron-publish:
|
||||
attach: true
|
||||
checkout: false
|
||||
build-type: 'MAS ARM64'
|
||||
|
||||
# Layer 3: Tests.
|
||||
linux-x64-testing-tests:
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -8,7 +8,6 @@
|
||||
DEPS @electron/wg-upgrades
|
||||
|
||||
# Releases WG
|
||||
/docs/breaking-changes.md @electron/wg-releases
|
||||
/npm/ @electron/wg-releases
|
||||
/script/release @electron/wg-releases
|
||||
|
||||
|
||||
14
.github/config.yml
vendored
14
.github/config.yml
vendored
@@ -25,3 +25,17 @@ newPRWelcomeComment: |
|
||||
# Comment to be posted to on pull requests merged by a first time user
|
||||
firstPRMergeComment: >
|
||||
Congrats on merging your first pull request! 🎉🎉🎉
|
||||
|
||||
# Users authorized to run manual trop backports
|
||||
authorizedUsers:
|
||||
- alexeykuzmin
|
||||
- ckerr
|
||||
- codebytere
|
||||
- deepak1556
|
||||
- jkleinsc
|
||||
- loc
|
||||
- MarshallOfSound
|
||||
- miniak
|
||||
- mlaurencin
|
||||
- nornagon
|
||||
- zcbenz
|
||||
|
||||
2
DEPS
2
DEPS
@@ -4,7 +4,7 @@ vars = {
|
||||
'chromium_version':
|
||||
'111.0.5560.0',
|
||||
'node_version':
|
||||
'v18.14.2',
|
||||
'v18.14.0',
|
||||
'nan_version':
|
||||
'16fa32231e2ccd89d2804b3f765319128b20c4ac',
|
||||
'squirrel.mac_version':
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# - "GN_CONFIG" Build type. One of {'testing', 'release'}.
|
||||
# - "GN_EXTRA_ARGS" Additional gn arguments for a build config,
|
||||
# e.g. 'target_cpu="x86"' to build for a 32bit platform.
|
||||
# https://gn.googlesource.com/gn/+/main/docs/reference.md#var_target_cpu
|
||||
# https://gn.googlesource.com/gn/+/master/docs/reference.md#target_cpu
|
||||
# Don't forget to set up "NPM_CONFIG_ARCH" and "TARGET_ARCH" accordingly
|
||||
# if you pass a custom value for 'target_cpu'.
|
||||
# - "ELECTRON_RELEASE" Set it to '1' upload binaries on success.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# - "GN_CONFIG" Build type. One of {'testing', 'release'}.
|
||||
# - "GN_EXTRA_ARGS" Additional gn arguments for a build config,
|
||||
# e.g. 'target_cpu="x86"' to build for a 32bit platform.
|
||||
# https://gn.googlesource.com/gn/+/main/docs/reference.md#var_target_cpu
|
||||
# https://gn.googlesource.com/gn/+/master/docs/reference.md#target_cpu
|
||||
# Don't forget to set up "NPM_CONFIG_ARCH" and "TARGET_ARCH" accordingly
|
||||
# if you pass a custom value for 'target_cpu'.
|
||||
# - "ELECTRON_RELEASE" Set it to '1' upload binaries on success.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
is_electron_build = true
|
||||
root_extra_deps = [ "//electron" ]
|
||||
|
||||
# Registry of NMVs --> https://github.com/nodejs/node/blob/main/doc/abi_version_registry.json
|
||||
# Registry of NMVs --> https://github.com/nodejs/node/blob/master/doc/abi_version_registry.json
|
||||
node_module_version = 114
|
||||
|
||||
v8_promise_internal_field_count = 1
|
||||
|
||||
@@ -1842,36 +1842,16 @@ will remove the vibrancy effect on the window.
|
||||
Note that `appearance-based`, `light`, `dark`, `medium-light`, and `ultra-dark` have been
|
||||
deprecated and will be removed in an upcoming version of macOS.
|
||||
|
||||
#### `win.setWindowButtonPosition(position)` _macOS_
|
||||
|
||||
* `position` [Point](structures/point.md) | null
|
||||
|
||||
Set a custom position for the traffic light buttons in frameless window.
|
||||
Passing `null` will reset the position to default.
|
||||
|
||||
#### `win.getWindowButtonPosition()` _macOS_
|
||||
|
||||
Returns `Point | null` - The custom position for the traffic light buttons in
|
||||
frameless window, `null` will be returned when there is no custom position.
|
||||
|
||||
#### `win.setTrafficLightPosition(position)` _macOS_ _Deprecated_
|
||||
#### `win.setTrafficLightPosition(position)` _macOS_
|
||||
|
||||
* `position` [Point](structures/point.md)
|
||||
|
||||
Set a custom position for the traffic light buttons in frameless window.
|
||||
Passing `{ x: 0, y: 0 }` will reset the position to default.
|
||||
|
||||
> **Note**
|
||||
> This function is deprecated. Use [setWindowButtonPosition](#winsetwindowbuttonpositionposition-macos) instead.
|
||||
|
||||
#### `win.getTrafficLightPosition()` _macOS_ _Deprecated_
|
||||
#### `win.getTrafficLightPosition()` _macOS_
|
||||
|
||||
Returns `Point` - The custom position for the traffic light buttons in
|
||||
frameless window, `{ x: 0, y: 0 }` will be returned when there is no custom
|
||||
position.
|
||||
|
||||
> **Note**
|
||||
> This function is deprecated. Use [getWindowButtonPosition](#wingetwindowbuttonposition-macos) instead.
|
||||
frameless window.
|
||||
|
||||
#### `win.setTouchBar(touchBar)` _macOS_
|
||||
|
||||
|
||||
@@ -23,14 +23,12 @@ following properties:
|
||||
with which the request is associated. Defaults to the empty string. The
|
||||
`session` option supersedes `partition`. Thus if a `session` is explicitly
|
||||
specified, `partition` is ignored.
|
||||
* `credentials` string (optional) - Can be `include`, `omit` or
|
||||
`same-origin`. Whether to send
|
||||
[credentials](https://fetch.spec.whatwg.org/#credentials) with this
|
||||
* `credentials` string (optional) - Can be `include` or `omit`. Whether to
|
||||
send [credentials](https://fetch.spec.whatwg.org/#credentials) with this
|
||||
request. If set to `include`, credentials from the session associated with
|
||||
the request will be used. If set to `omit`, credentials will not be sent
|
||||
with the request (and the `'login'` event will not be triggered in the
|
||||
event of a 401). If set to `same-origin`, `origin` must also be specified.
|
||||
This matches the behavior of the
|
||||
event of a 401). This matches the behavior of the
|
||||
[fetch](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
|
||||
option of the same name. If this option is not specified, authentication
|
||||
data from the session will be sent, and cookies will not be sent (unless
|
||||
@@ -51,13 +49,6 @@ following properties:
|
||||
[`request.followRedirect`](#requestfollowredirect) is invoked synchronously
|
||||
during the [`redirect`](#event-redirect) event. Defaults to `follow`.
|
||||
* `origin` string (optional) - The origin URL of the request.
|
||||
* `referrerPolicy` string (optional) - can be `""`, `no-referrer`,
|
||||
`no-referrer-when-downgrade`, `origin`, `origin-when-cross-origin`,
|
||||
`unsafe-url`, `same-origin`, `strict-origin`, or
|
||||
`strict-origin-when-cross-origin`. Defaults to
|
||||
`strict-origin-when-cross-origin`.
|
||||
* `cache` string (optional) - can be `default`, `no-store`, `reload`,
|
||||
`no-cache`, `force-cache` or `only-if-cached`.
|
||||
|
||||
`options` properties such as `protocol`, `host`, `hostname`, `port` and `path`
|
||||
strictly follow the Node.js model as described in the
|
||||
|
||||
@@ -78,7 +78,6 @@ The following methods are available on instances of `Cookies`:
|
||||
* `path` string (optional) - Retrieves cookies whose path matches `path`.
|
||||
* `secure` boolean (optional) - Filters cookies by their Secure property.
|
||||
* `session` boolean (optional) - Filters out session or persistent cookies.
|
||||
* `httpOnly` boolean (optional) - Filters cookies by httpOnly.
|
||||
|
||||
Returns `Promise<Cookie[]>` - A promise which resolves an array of cookie objects.
|
||||
|
||||
|
||||
@@ -63,44 +63,6 @@ Creates a [`ClientRequest`](./client-request.md) instance using the provided
|
||||
The `net.request` method would be used to issue both secure and insecure HTTP
|
||||
requests according to the specified protocol scheme in the `options` object.
|
||||
|
||||
### `net.fetch(input[, init])`
|
||||
|
||||
* `input` string | [Request](https://nodejs.org/api/globals.html#request)
|
||||
* `init` [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) (optional)
|
||||
|
||||
Returns `Promise<GlobalResponse>` - see [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||
|
||||
Sends a request, similarly to how `fetch()` works in the renderer, using
|
||||
Chrome's network stack. This differs from Node's `fetch()`, which uses
|
||||
Node.js's HTTP stack.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
async function example () {
|
||||
const response = await net.fetch('https://my.app')
|
||||
if (response.ok) {
|
||||
const body = await response.json()
|
||||
// ... use the result.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This method will issue requests from the [default
|
||||
session](session.md#sessiondefaultsession). To send a `fetch` request from
|
||||
another session, use [ses.fetch()](session.md#sesfetchinput-init).
|
||||
|
||||
See the MDN documentation for
|
||||
[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) for more
|
||||
details.
|
||||
|
||||
Limitations:
|
||||
|
||||
* `net.fetch()` does not support the `data:` or `blob:` schemes.
|
||||
* The value of the `integrity` option is ignored.
|
||||
* The `.type` and `.url` values of the returned `Response` object are
|
||||
incorrect.
|
||||
|
||||
### `net.isOnline()`
|
||||
|
||||
Returns `boolean` - Whether there is currently internet connection.
|
||||
|
||||
@@ -49,11 +49,8 @@ beginning to load the web page or the main script.
|
||||
|
||||
### `process.defaultApp` _Readonly_
|
||||
|
||||
A `boolean`. When the app is started by being passed as parameter to the default Electron executable, this
|
||||
A `boolean`. When app is started by being passed as parameter to the default app, this
|
||||
property is `true` in the main process, otherwise it is `undefined`.
|
||||
For example when running the app with `electron .`, it is `true`,
|
||||
even if the app is packaged ([`isPackaged`](app.md#appispackaged-readonly)) is `true`.
|
||||
This can be useful to determine how many arguments will need to be sliced off from `process.argv`.
|
||||
|
||||
### `process.isMainFrame` _Readonly_
|
||||
|
||||
|
||||
@@ -731,43 +731,6 @@ Returns `Promise<void>` - Resolves when all connections are closed.
|
||||
|
||||
**Note:** It will terminate / fail all requests currently in flight.
|
||||
|
||||
#### `ses.fetch(input[, init])`
|
||||
|
||||
* `input` string | [GlobalRequest](https://nodejs.org/api/globals.html#request)
|
||||
* `init` [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) (optional)
|
||||
|
||||
Returns `Promise<GlobalResponse>` - see [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||
|
||||
Sends a request, similarly to how `fetch()` works in the renderer, using
|
||||
Chrome's network stack. This differs from Node's `fetch()`, which uses
|
||||
Node.js's HTTP stack.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
async function example () {
|
||||
const response = await net.fetch('https://my.app')
|
||||
if (response.ok) {
|
||||
const body = await response.json()
|
||||
// ... use the result.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See also [`net.fetch()`](net.md#netfetchinput-init), a convenience method which
|
||||
issues requests from the [default session](#sessiondefaultsession).
|
||||
|
||||
See the MDN documentation for
|
||||
[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) for more
|
||||
details.
|
||||
|
||||
Limitations:
|
||||
|
||||
* `net.fetch()` does not support the `data:` or `blob:` schemes.
|
||||
* The value of the `integrity` option is ignored.
|
||||
* The `.type` and `.url` values of the returned `Response` object are
|
||||
incorrect.
|
||||
|
||||
#### `ses.disableNetworkEmulation()`
|
||||
|
||||
Disables any network emulation already active for the `session`. Resets to
|
||||
|
||||
@@ -40,8 +40,6 @@ Open the given file in the desktop's default manner.
|
||||
* `options` Object (optional)
|
||||
* `activate` boolean (optional) _macOS_ - `true` to bring the opened application to the foreground. The default is `true`.
|
||||
* `workingDirectory` string (optional) _Windows_ - The working directory.
|
||||
* `logUsage` boolean (optional) _Windows_ - Indicates a user initiated launch that enables tracking of frequently used programs and other behaviors.
|
||||
The default is `false`.
|
||||
|
||||
Returns `Promise<void>`
|
||||
|
||||
|
||||
3
docs/api/structures/event.md
Normal file
3
docs/api/structures/event.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Event Object extends `GlobalEvent`
|
||||
|
||||
* `preventDefault` VoidFunction
|
||||
@@ -1,4 +1,3 @@
|
||||
# WebRequestFilter Object
|
||||
|
||||
* `urls` string[] - Array of [URL patterns](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns) that will be used to filter out the requests that do not match the URL patterns.
|
||||
* `types` String[] (optional) - Array of types that will be used to filter out the requests that do not match the types. When not specified, all types will be matched. Can be `mainFrame`, `subFrame`, `stylesheet`, `script`, `image`, `font`, `object`, `xhr`, `ping`, `cspReport`, `media` or `webSocket`.
|
||||
|
||||
@@ -831,7 +831,7 @@ Emitted when the preload script `preloadPath` throws an unhandled exception `err
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` [IpcMainEvent](structures/ipc-main-event.md)
|
||||
* `event` Event
|
||||
* `channel` string
|
||||
* `...args` any[]
|
||||
|
||||
@@ -843,7 +843,7 @@ See also [`webContents.ipc`](#contentsipc-readonly), which provides an [`IpcMain
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` [IpcMainEvent](structures/ipc-main-event.md)
|
||||
* `event` Event
|
||||
* `channel` string
|
||||
* `...args` any[]
|
||||
|
||||
|
||||
@@ -12,56 +12,8 @@ This document uses the following convention to categorize breaking changes:
|
||||
* **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
|
||||
* **Removed:** An API or feature was removed, and is no longer supported by Electron.
|
||||
|
||||
## Planned Breaking API Changes (24.0)
|
||||
|
||||
### Deprecated: `BrowserWindow.setTrafficLightPosition(position)`
|
||||
|
||||
`BrowserWindow.setTrafficLightPosition(position)` has been deprecated, the
|
||||
`BrowserWindow.setWindowButtonPosition(position)` API should be used instead
|
||||
which accepts `null` instead of `{ x: 0, y: 0 }` to reset the position to
|
||||
system default.
|
||||
|
||||
```js
|
||||
// Removed in Electron 24
|
||||
win.setTrafficLightPosition({ x: 10, y: 10 })
|
||||
win.setTrafficLightPosition({ x: 0, y: 0 })
|
||||
|
||||
// Replace with
|
||||
win.setWindowButtonPosition({ x: 10, y: 10 })
|
||||
win.setWindowButtonPosition(null)
|
||||
```
|
||||
|
||||
### Deprecated: `BrowserWindow.getTrafficLightPosition()`
|
||||
|
||||
`BrowserWindow.getTrafficLightPosition()` has been deprecated, the
|
||||
`BrowserWindow.getWindowButtonPosition()` API should be used instead
|
||||
which returns `null` instead of `{ x: 0, y: 0 }` when there is no custom
|
||||
position.
|
||||
|
||||
```js
|
||||
// Removed in Electron 24
|
||||
const pos = win.getTrafficLightPosition()
|
||||
if (pos.x === 0 && pos.y === 0) {
|
||||
// No custom position.
|
||||
}
|
||||
|
||||
// Replace with
|
||||
const ret = win.getWindowButtonPosition()
|
||||
if (ret === null) {
|
||||
// No custom position.
|
||||
}
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (23.0)
|
||||
|
||||
### Behavior Changed: Draggable Regions on macOS
|
||||
|
||||
The implementation of draggable regions (using the CSS property `-webkit-app-region: drag`) has changed on macOS to bring it in line with Windows and Linux. Previously, when a region with `-webkit-app-region: no-drag` overlapped a region with `-webkit-app-region: drag`, the `no-drag` region would always take precedence on macOS, regardless of CSS layering. That is, if a `drag` region was above a `no-drag` region, it would be ignored. Beginning in Electron 23, a `drag` region on top of a `no-drag` region will correctly cause the region to be draggable.
|
||||
|
||||
Additionally, the `customButtonsOnHover` BrowserWindow property previously created a draggable region which ignored the `-webkit-app-region` CSS property. This has now been fixed (see [#37210](https://github.com/electron/electron/issues/37210#issuecomment-1440509592) for discussion).
|
||||
|
||||
As a result, if your app uses a frameless window with draggable regions on macOS, the regions which are draggable in your app may change in Electron 23.
|
||||
|
||||
### Removed: Windows 7 / 8 / 8.1 support
|
||||
|
||||
[Windows 7, Windows 8, and Windows 8.1 are no longer supported](https://www.electronjs.org/blog/windows-7-to-8-1-deprecation-notice). Electron follows the planned Chromium deprecation policy, which will [deprecate Windows 7 support beginning in Chromium 109](https://support.google.com/chrome/thread/185534985/sunsetting-support-for-windows-7-8-8-1-in-early-2023?hl=en).
|
||||
|
||||
@@ -79,6 +79,7 @@ auto_filenames = {
|
||||
"docs/api/structures/custom-scheme.md",
|
||||
"docs/api/structures/desktop-capturer-source.md",
|
||||
"docs/api/structures/display.md",
|
||||
"docs/api/structures/event.md",
|
||||
"docs/api/structures/extension-info.md",
|
||||
"docs/api/structures/extension.md",
|
||||
"docs/api/structures/file-filter.md",
|
||||
@@ -208,8 +209,6 @@ auto_filenames = {
|
||||
"lib/browser/api/message-channel.ts",
|
||||
"lib/browser/api/module-list.ts",
|
||||
"lib/browser/api/native-theme.ts",
|
||||
"lib/browser/api/net-client-request.ts",
|
||||
"lib/browser/api/net-fetch.ts",
|
||||
"lib/browser/api/net-log.ts",
|
||||
"lib/browser/api/net.ts",
|
||||
"lib/browser/api/notification.ts",
|
||||
|
||||
@@ -153,7 +153,7 @@ filenames = {
|
||||
"shell/browser/notifications/mac/notification_presenter_mac.mm",
|
||||
"shell/browser/relauncher_mac.cc",
|
||||
"shell/browser/ui/certificate_trust_mac.mm",
|
||||
"shell/browser/ui/cocoa/delayed_native_view_host.mm",
|
||||
"shell/browser/ui/cocoa/delayed_native_view_host.cc",
|
||||
"shell/browser/ui/cocoa/delayed_native_view_host.h",
|
||||
"shell/browser/ui/cocoa/electron_bundle_mover.h",
|
||||
"shell/browser/ui/cocoa/electron_bundle_mover.mm",
|
||||
@@ -267,6 +267,7 @@ filenames = {
|
||||
"shell/browser/api/electron_api_dialog.cc",
|
||||
"shell/browser/api/electron_api_download_item.cc",
|
||||
"shell/browser/api/electron_api_download_item.h",
|
||||
"shell/browser/api/electron_api_event.cc",
|
||||
"shell/browser/api/electron_api_event_emitter.cc",
|
||||
"shell/browser/api/electron_api_event_emitter.h",
|
||||
"shell/browser/api/electron_api_global_shortcut.cc",
|
||||
@@ -319,6 +320,8 @@ filenames = {
|
||||
"shell/browser/api/electron_api_web_request.cc",
|
||||
"shell/browser/api/electron_api_web_request.h",
|
||||
"shell/browser/api/electron_api_web_view_manager.cc",
|
||||
"shell/browser/api/event.cc",
|
||||
"shell/browser/api/event.h",
|
||||
"shell/browser/api/frame_subscriber.cc",
|
||||
"shell/browser/api/frame_subscriber.h",
|
||||
"shell/browser/api/gpu_info_enumerator.cc",
|
||||
@@ -379,6 +382,7 @@ filenames = {
|
||||
"shell/browser/electron_web_contents_utility_handler_impl.h",
|
||||
"shell/browser/electron_web_ui_controller_factory.cc",
|
||||
"shell/browser/electron_web_ui_controller_factory.h",
|
||||
"shell/browser/event_emitter_mixin.cc",
|
||||
"shell/browser/event_emitter_mixin.h",
|
||||
"shell/browser/extended_web_contents_observer.h",
|
||||
"shell/browser/feature_list.cc",
|
||||
@@ -582,7 +586,6 @@ filenames = {
|
||||
"shell/common/gin_converters/native_window_converter.h",
|
||||
"shell/common/gin_converters/net_converter.cc",
|
||||
"shell/common/gin_converters/net_converter.h",
|
||||
"shell/common/gin_converters/optional_converter.h",
|
||||
"shell/common/gin_converters/serial_port_info_converter.h",
|
||||
"shell/common/gin_converters/std_converter.h",
|
||||
"shell/common/gin_converters/time_converter.cc",
|
||||
@@ -603,13 +606,10 @@ filenames = {
|
||||
"shell/common/gin_helper/dictionary.h",
|
||||
"shell/common/gin_helper/error_thrower.cc",
|
||||
"shell/common/gin_helper/error_thrower.h",
|
||||
"shell/common/gin_helper/event.cc",
|
||||
"shell/common/gin_helper/event.h",
|
||||
"shell/common/gin_helper/event_emitter.cc",
|
||||
"shell/common/gin_helper/event_emitter.h",
|
||||
"shell/common/gin_helper/event_emitter_caller.cc",
|
||||
"shell/common/gin_helper/event_emitter_caller.h",
|
||||
"shell/common/gin_helper/event_emitter_template.cc",
|
||||
"shell/common/gin_helper/event_emitter_template.h",
|
||||
"shell/common/gin_helper/function_template.cc",
|
||||
"shell/common/gin_helper/function_template.h",
|
||||
"shell/common/gin_helper/function_template_extensions.h",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import type { BaseWindow as TLWT } from 'electron/main';
|
||||
import * as deprecate from '@electron/internal/common/deprecate';
|
||||
const { BaseWindow } = process._linkedBinding('electron_browser_base_window') as { BaseWindow: typeof TLWT };
|
||||
|
||||
Object.setPrototypeOf(BaseWindow.prototype, EventEmitter.prototype);
|
||||
@@ -16,25 +15,6 @@ BaseWindow.prototype._init = function () {
|
||||
}
|
||||
};
|
||||
|
||||
// Deprecation.
|
||||
const setTrafficLightPositionDeprecated = deprecate.warnOnce('setTrafficLightPosition', 'setWindowButtonPosition');
|
||||
// Converting to any as the methods are defined under BrowserWindow in our docs.
|
||||
(BaseWindow as any).prototype.setTrafficLightPosition = function (pos: Electron.Point) {
|
||||
setTrafficLightPositionDeprecated();
|
||||
if (typeof pos === 'object' && pos.x === 0 && pos.y === 0) {
|
||||
this.setWindowButtonPosition(null);
|
||||
} else {
|
||||
this.setWindowButtonPosition(pos);
|
||||
}
|
||||
};
|
||||
|
||||
const getTrafficLightPositionDeprecated = deprecate.warnOnce('getTrafficLightPosition', 'getWindowButtonPosition');
|
||||
(BaseWindow as any).prototype.getTrafficLightPosition = function () {
|
||||
getTrafficLightPositionDeprecated();
|
||||
const pos = this.getWindowButtonPosition();
|
||||
return pos === null ? { x: 0, y: 0 } : pos;
|
||||
};
|
||||
|
||||
// Properties
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'autoHideMenuBar', {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseWindow, WebContents, BrowserView, TouchBar } from 'electron/main';
|
||||
import { BaseWindow, WebContents, Event, BrowserView, TouchBar } from 'electron/main';
|
||||
import type { BrowserWindow as BWT } from 'electron/main';
|
||||
import * as deprecate from '@electron/internal/common/deprecate';
|
||||
const { BrowserWindow } = process._linkedBinding('electron_browser_window') as { BrowserWindow: typeof BWT };
|
||||
@@ -22,10 +22,10 @@ BrowserWindow.prototype._init = function (this: BWT) {
|
||||
};
|
||||
|
||||
// Redirect focus/blur event to app instance too.
|
||||
this.on('blur', (event: Electron.Event) => {
|
||||
this.on('blur', (event: Event) => {
|
||||
app.emit('browser-window-blur', event, this);
|
||||
});
|
||||
this.on('focus', (event: Electron.Event) => {
|
||||
this.on('focus', (event: Event) => {
|
||||
app.emit('browser-window-focus', event, this);
|
||||
});
|
||||
|
||||
@@ -68,7 +68,8 @@ BrowserWindow.prototype._init = function (this: BWT) {
|
||||
});
|
||||
|
||||
// Notify the creation of the window.
|
||||
app.emit('browser-window-created', { preventDefault () {} }, this);
|
||||
const event = process._linkedBinding('electron_browser_event').createEmpty();
|
||||
app.emit('browser-window-created', event, this);
|
||||
|
||||
Object.defineProperty(this, 'devToolsWebContents', {
|
||||
enumerable: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as roles from '@electron/internal/browser/api/menu-item-roles';
|
||||
import { Menu, BrowserWindow, WebContents, KeyboardEvent } from 'electron/main';
|
||||
import { Menu, Event, BrowserWindow, WebContents } from 'electron/main';
|
||||
|
||||
let nextCommandId = 0;
|
||||
|
||||
@@ -53,7 +53,7 @@ const MenuItem = function (this: any, options: any) {
|
||||
});
|
||||
|
||||
const click = options.click;
|
||||
this.click = (event: KeyboardEvent, focusedWindow: BrowserWindow, focusedWebContents: WebContents) => {
|
||||
this.click = (event: Event, focusedWindow: BrowserWindow, focusedWebContents: WebContents) => {
|
||||
// Manually flip the checked flags when clicked.
|
||||
if (!roles.shouldOverrideCheckStatus(this.role) &&
|
||||
(this.type === 'checkbox' || this.type === 'radio')) {
|
||||
|
||||
@@ -1,522 +0,0 @@
|
||||
import * as url from 'url';
|
||||
import { Readable, Writable } from 'stream';
|
||||
import { app } from 'electron/main';
|
||||
import type { ClientRequestConstructorOptions, UploadProgress } from 'electron/main';
|
||||
|
||||
const {
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue,
|
||||
createURLLoader
|
||||
} = process._linkedBinding('electron_browser_net');
|
||||
const { Session } = process._linkedBinding('electron_browser_session');
|
||||
|
||||
const kSupportedProtocols = new Set(['http:', 'https:']);
|
||||
|
||||
// set of headers that Node.js discards duplicates for
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
const discardableDuplicateHeaders = new Set([
|
||||
'content-type',
|
||||
'content-length',
|
||||
'user-agent',
|
||||
'referer',
|
||||
'host',
|
||||
'authorization',
|
||||
'proxy-authorization',
|
||||
'if-modified-since',
|
||||
'if-unmodified-since',
|
||||
'from',
|
||||
'location',
|
||||
'max-forwards',
|
||||
'retry-after',
|
||||
'etag',
|
||||
'last-modified',
|
||||
'server',
|
||||
'age',
|
||||
'expires'
|
||||
]);
|
||||
|
||||
class IncomingMessage extends Readable {
|
||||
_shouldPush: boolean = false;
|
||||
_data: (Buffer | null)[] = [];
|
||||
_responseHead: NodeJS.ResponseHead;
|
||||
_resume: (() => void) | null = null;
|
||||
|
||||
constructor (responseHead: NodeJS.ResponseHead) {
|
||||
super();
|
||||
this._responseHead = responseHead;
|
||||
}
|
||||
|
||||
get statusCode () {
|
||||
return this._responseHead.statusCode;
|
||||
}
|
||||
|
||||
get statusMessage () {
|
||||
return this._responseHead.statusMessage;
|
||||
}
|
||||
|
||||
get headers () {
|
||||
const filteredHeaders: Record<string, string | string[]> = {};
|
||||
const { headers, rawHeaders } = this._responseHead;
|
||||
for (const [name, values] of Object.entries(headers)) {
|
||||
filteredHeaders[name] = discardableDuplicateHeaders.has(name) ? values[0] : values.join(', ');
|
||||
}
|
||||
const cookies = rawHeaders.filter(({ key }) => key.toLowerCase() === 'set-cookie').map(({ value }) => value);
|
||||
// keep set-cookie as an array per Node.js rules
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
if (cookies.length) { filteredHeaders['set-cookie'] = cookies; }
|
||||
return filteredHeaders;
|
||||
}
|
||||
|
||||
get rawHeaders () {
|
||||
const rawHeadersArr: string[] = [];
|
||||
const { rawHeaders } = this._responseHead;
|
||||
rawHeaders.forEach(header => {
|
||||
rawHeadersArr.push(header.key, header.value);
|
||||
});
|
||||
return rawHeadersArr;
|
||||
}
|
||||
|
||||
get httpVersion () {
|
||||
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
|
||||
}
|
||||
|
||||
get httpVersionMajor () {
|
||||
return this._responseHead.httpVersion.major;
|
||||
}
|
||||
|
||||
get httpVersionMinor () {
|
||||
return this._responseHead.httpVersion.minor;
|
||||
}
|
||||
|
||||
get rawTrailers () {
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
get trailers () {
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
_storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
|
||||
// save the network callback for use in _pushInternalData
|
||||
this._resume = resume;
|
||||
this._data.push(chunk);
|
||||
this._pushInternalData();
|
||||
}
|
||||
|
||||
_pushInternalData () {
|
||||
while (this._shouldPush && this._data.length > 0) {
|
||||
const chunk = this._data.shift();
|
||||
this._shouldPush = this.push(chunk);
|
||||
}
|
||||
if (this._shouldPush && this._resume) {
|
||||
// Reset the callback, so that a new one is used for each
|
||||
// batch of throttled data. Do this before calling resume to avoid a
|
||||
// potential race-condition
|
||||
const resume = this._resume;
|
||||
this._resume = null;
|
||||
|
||||
resume();
|
||||
}
|
||||
}
|
||||
|
||||
_read () {
|
||||
this._shouldPush = true;
|
||||
this._pushInternalData();
|
||||
}
|
||||
}
|
||||
|
||||
/** Writable stream that buffers up everything written to it. */
|
||||
class SlurpStream extends Writable {
|
||||
_data: Buffer;
|
||||
constructor () {
|
||||
super();
|
||||
this._data = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_write (chunk: Buffer, encoding: string, callback: () => void) {
|
||||
this._data = Buffer.concat([this._data, chunk]);
|
||||
callback();
|
||||
}
|
||||
|
||||
data () { return this._data; }
|
||||
}
|
||||
|
||||
class ChunkedBodyStream extends Writable {
|
||||
_pendingChunk: Buffer | undefined;
|
||||
_downstream?: NodeJS.DataPipe;
|
||||
_pendingCallback?: (error?: Error) => void;
|
||||
_clientRequest: ClientRequest;
|
||||
|
||||
constructor (clientRequest: ClientRequest) {
|
||||
super();
|
||||
this._clientRequest = clientRequest;
|
||||
}
|
||||
|
||||
_write (chunk: Buffer, encoding: string, callback: () => void) {
|
||||
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: () => void) {
|
||||
this._downstream!.done();
|
||||
callback();
|
||||
}
|
||||
|
||||
startReading (pipe: NodeJS.DataPipe) {
|
||||
if (this._downstream) {
|
||||
throw new Error('two startReading calls???');
|
||||
}
|
||||
this._downstream = pipe;
|
||||
if (this._pendingChunk) {
|
||||
const doneWriting = (maybeError: Error | void) => {
|
||||
// If the underlying request has been aborted, we honestly don't care about the error
|
||||
// all work should cease as soon as we abort anyway, this error is probably a
|
||||
// "mojo pipe disconnected" error (code=9)
|
||||
if (this._clientRequest._aborted) return;
|
||||
|
||||
const cb = this._pendingCallback!;
|
||||
delete this._pendingCallback;
|
||||
delete this._pendingChunk;
|
||||
cb(maybeError || undefined);
|
||||
};
|
||||
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RedirectPolicy = 'manual' | 'follow' | 'error';
|
||||
|
||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } {
|
||||
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
|
||||
|
||||
let urlStr: string = options.url;
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj: url.UrlObject = {};
|
||||
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: NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } = {
|
||||
method: (options.method || 'GET').toUpperCase(),
|
||||
url: urlStr,
|
||||
redirectPolicy,
|
||||
headers: {},
|
||||
body: null as any,
|
||||
useSessionCookies: options.useSessionCookies,
|
||||
credentials: options.credentials,
|
||||
origin: options.origin,
|
||||
referrerPolicy: options.referrerPolicy,
|
||||
cache: options.cache
|
||||
};
|
||||
const headers: Record<string, string | string[]> = options.headers || {};
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
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();
|
||||
urlLoaderOptions.headers[key] = { name, value };
|
||||
}
|
||||
if (options.session) {
|
||||
if (!(options.session instanceof Session)) { throw new TypeError('`session` should be an instance of the Session class'); }
|
||||
urlLoaderOptions.session = options.session;
|
||||
} else if (options.partition) {
|
||||
if (typeof options.partition === 'string') {
|
||||
urlLoaderOptions.partition = options.partition;
|
||||
} else {
|
||||
throw new TypeError('`partition` should be a string');
|
||||
}
|
||||
}
|
||||
return urlLoaderOptions;
|
||||
}
|
||||
|
||||
export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
_started: boolean = false;
|
||||
_firstWrite: boolean = false;
|
||||
_aborted: boolean = false;
|
||||
_chunkedEncoding: boolean | undefined;
|
||||
_body: Writable | undefined;
|
||||
_urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { headers: Record<string, { name: string, value: string | string[] }> };
|
||||
_redirectPolicy: RedirectPolicy;
|
||||
_followRedirectCb?: () => void;
|
||||
_uploadProgress?: { active: boolean, started: boolean, current: number, total: number };
|
||||
_urlLoader?: NodeJS.URLLoader;
|
||||
_response?: IncomingMessage;
|
||||
|
||||
constructor (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
super({ autoDestroy: true });
|
||||
|
||||
if (!app.isReady()) {
|
||||
throw new Error('net module can only be used after app is ready');
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
this.once('response', callback);
|
||||
}
|
||||
|
||||
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
|
||||
if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) { throw new Error('credentials: same-origin requires origin to be set'); }
|
||||
this._urlLoaderOptions = urlLoaderOptions;
|
||||
this._redirectPolicy = redirectPolicy;
|
||||
}
|
||||
|
||||
get chunkedEncoding () {
|
||||
return this._chunkedEncoding || false;
|
||||
}
|
||||
|
||||
set chunkedEncoding (value: boolean) {
|
||||
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: NodeJS.DataPipe) => {
|
||||
(this._body! as ChunkedBodyStream).startReading(pipe);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setHeader (name: string, value: string) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)');
|
||||
}
|
||||
if (value == null) {
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)');
|
||||
}
|
||||
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._urlLoaderOptions.headers[key] = { name, value };
|
||||
}
|
||||
|
||||
getHeader (name: string) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for getHeader(name)');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase();
|
||||
const header = this._urlLoaderOptions.headers[key];
|
||||
return header && header.value as any;
|
||||
}
|
||||
|
||||
removeHeader (name: string) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for removeHeader(name)');
|
||||
}
|
||||
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t remove headers after they are sent');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase();
|
||||
delete this._urlLoaderOptions.headers[key];
|
||||
}
|
||||
|
||||
_write (chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
|
||||
this._firstWrite = true;
|
||||
if (!this._body) {
|
||||
this._body = new SlurpStream();
|
||||
this._body.on('finish', () => {
|
||||
this._urlLoaderOptions.body = (this._body as SlurpStream).data();
|
||||
this._startRequest();
|
||||
});
|
||||
}
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.write(chunk, encoding, callback);
|
||||
}
|
||||
|
||||
_final (callback: () => void) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
_startRequest () {
|
||||
this._started = true;
|
||||
const stringifyValues = (obj: Record<string, { name: string, value: string | string[] }>) => {
|
||||
const ret: Record<string, string> = {};
|
||||
for (const k of Object.keys(obj)) {
|
||||
const kv = obj[k];
|
||||
ret[kv.name] = kv.value.toString();
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
this._urlLoaderOptions.referrer = this.getHeader('referer') || '';
|
||||
this._urlLoaderOptions.origin = this._urlLoaderOptions.origin || this.getHeader('origin') || '';
|
||||
this._urlLoaderOptions.hasUserActivation = this.getHeader('sec-fetch-user') === '?1';
|
||||
this._urlLoaderOptions.mode = this.getHeader('sec-fetch-mode') || '';
|
||||
this._urlLoaderOptions.destination = this.getHeader('sec-fetch-dest') || '';
|
||||
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.headers) };
|
||||
this._urlLoader = createURLLoader(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, resume) => {
|
||||
this._response!._storeInternalData(Buffer.from(data), resume);
|
||||
});
|
||||
this._urlLoader.on('complete', () => {
|
||||
if (this._response) { this._response._storeInternalData(null, null); }
|
||||
});
|
||||
this._urlLoader.on('error', (event, netErrorString) => {
|
||||
const error = new Error(netErrorString);
|
||||
if (this._response) this._response.destroy(error);
|
||||
this._die(error);
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
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 = undefined;
|
||||
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 = undefined;
|
||||
}
|
||||
} else {
|
||||
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
|
||||
}
|
||||
});
|
||||
|
||||
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 () {
|
||||
if (this._followRedirectCb) {
|
||||
this._followRedirectCb();
|
||||
} else {
|
||||
throw new Error('followRedirect() called, but was not waiting for a redirect');
|
||||
}
|
||||
}
|
||||
|
||||
abort () {
|
||||
if (!this._aborted) {
|
||||
process.nextTick(() => { this.emit('abort'); });
|
||||
}
|
||||
this._aborted = true;
|
||||
this._die();
|
||||
}
|
||||
|
||||
_die (err?: Error) {
|
||||
// Node.js assumes that any stream which is ended is no longer capable of emitted events
|
||||
// which is a faulty assumption for the case of an object that is acting like a stream
|
||||
// (our urlRequest). If we don't emit here, this causes errors since we *do* expect
|
||||
// that error events can be emitted after urlRequest.end().
|
||||
if ((this as any)._writableState.destroyed && err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
|
||||
this.destroy(err);
|
||||
if (this._urlLoader) {
|
||||
this._urlLoader.cancel();
|
||||
if (this._response) this._response.destroy(err);
|
||||
}
|
||||
}
|
||||
|
||||
getUploadProgress (): UploadProgress {
|
||||
return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 };
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import { net, IncomingMessage, Session as SessionT } from 'electron/main';
|
||||
import { Readable, Writable, isReadable } from 'stream';
|
||||
|
||||
function createDeferredPromise<T, E extends Error = Error> (): { promise: Promise<T>; resolve: (x: T) => void; reject: (e: E) => void; } {
|
||||
let res: (x: T) => void;
|
||||
let rej: (e: E) => void;
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
res = resolve;
|
||||
rej = reject;
|
||||
});
|
||||
|
||||
return { promise, resolve: res!, reject: rej! };
|
||||
}
|
||||
|
||||
export function fetchWithSession (input: RequestInfo, init: RequestInit | undefined, session: SessionT): Promise<Response> {
|
||||
const p = createDeferredPromise<Response>();
|
||||
let req: Request;
|
||||
try {
|
||||
req = new Request(input, init);
|
||||
} catch (e: any) {
|
||||
p.reject(e);
|
||||
return p.promise;
|
||||
}
|
||||
|
||||
if (req.signal.aborted) {
|
||||
// 1. Abort the fetch() call with p, request, null, and
|
||||
// requestObject’s signal’s abort reason.
|
||||
const error = (req.signal as any).reason ?? new DOMException('The operation was aborted.', 'AbortError');
|
||||
p.reject(error);
|
||||
|
||||
if (req.body != null && isReadable(req.body as unknown as NodeJS.ReadableStream)) {
|
||||
req.body.cancel(error).catch((err) => {
|
||||
if (err.code === 'ERR_INVALID_STATE') {
|
||||
// Node bug?
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Return p.
|
||||
return p.promise;
|
||||
}
|
||||
|
||||
let locallyAborted = false;
|
||||
req.signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
// 1. Set locallyAborted to true.
|
||||
locallyAborted = true;
|
||||
|
||||
// 2. Abort the fetch() call with p, request, responseObject,
|
||||
// and requestObject’s signal’s abort reason.
|
||||
const error = (req.signal as any).reason ?? new DOMException('The operation was aborted.', 'AbortError');
|
||||
p.reject(error);
|
||||
if (req.body != null && isReadable(req.body as unknown as NodeJS.ReadableStream)) {
|
||||
req.body.cancel(error).catch((err) => {
|
||||
if (err.code === 'ERR_INVALID_STATE') {
|
||||
// Node bug?
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
r.abort();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
const origin = req.headers.get('origin') ?? undefined;
|
||||
// We can't set credentials to same-origin unless there's an origin set.
|
||||
const credentials = req.credentials === 'same-origin' && !origin ? 'include' : req.credentials;
|
||||
|
||||
const r = net.request({
|
||||
session,
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
origin,
|
||||
credentials,
|
||||
cache: req.cache,
|
||||
referrerPolicy: req.referrerPolicy,
|
||||
redirect: req.redirect
|
||||
});
|
||||
|
||||
// cors is the default mode, but we can't set mode=cors without an origin.
|
||||
if (req.mode && (req.mode !== 'cors' || origin)) {
|
||||
r.setHeader('Sec-Fetch-Mode', req.mode);
|
||||
}
|
||||
|
||||
for (const [k, v] of req.headers) {
|
||||
r.setHeader(k, v);
|
||||
}
|
||||
|
||||
r.on('response', (resp: IncomingMessage) => {
|
||||
if (locallyAborted) return;
|
||||
const headers = new Headers();
|
||||
for (const [k, v] of Object.entries(resp.headers)) { headers.set(k, Array.isArray(v) ? v.join(', ') : v); }
|
||||
const nullBodyStatus = [101, 204, 205, 304];
|
||||
const body = nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD' ? null : Readable.toWeb(resp as unknown as Readable) as ReadableStream;
|
||||
const rResp = new Response(body, {
|
||||
headers,
|
||||
status: resp.statusCode,
|
||||
statusText: resp.statusMessage
|
||||
});
|
||||
p.resolve(rResp);
|
||||
});
|
||||
|
||||
r.on('error', (err) => {
|
||||
p.reject(err);
|
||||
});
|
||||
|
||||
if (!req.body?.pipeTo(Writable.toWeb(r as unknown as Writable)).then(() => r.end())) { r.end(); }
|
||||
|
||||
return p.promise;
|
||||
}
|
||||
@@ -1,17 +1,531 @@
|
||||
import { IncomingMessage, session } from 'electron/main';
|
||||
import type { ClientRequestConstructorOptions } from 'electron/main';
|
||||
import { ClientRequest } from '@electron/internal/browser/api/net-client-request';
|
||||
import * as url from 'url';
|
||||
import { Readable, Writable } from 'stream';
|
||||
import { app } from 'electron/main';
|
||||
import type { ClientRequestConstructorOptions, UploadProgress } from 'electron/main';
|
||||
|
||||
const { isOnline } = process._linkedBinding('electron_browser_net');
|
||||
const {
|
||||
isOnline,
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue,
|
||||
createURLLoader
|
||||
} = process._linkedBinding('electron_browser_net');
|
||||
|
||||
const kSupportedProtocols = new Set(['http:', 'https:']);
|
||||
|
||||
// set of headers that Node.js discards duplicates for
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
const discardableDuplicateHeaders = new Set([
|
||||
'content-type',
|
||||
'content-length',
|
||||
'user-agent',
|
||||
'referer',
|
||||
'host',
|
||||
'authorization',
|
||||
'proxy-authorization',
|
||||
'if-modified-since',
|
||||
'if-unmodified-since',
|
||||
'from',
|
||||
'location',
|
||||
'max-forwards',
|
||||
'retry-after',
|
||||
'etag',
|
||||
'last-modified',
|
||||
'server',
|
||||
'age',
|
||||
'expires'
|
||||
]);
|
||||
|
||||
class IncomingMessage extends Readable {
|
||||
_shouldPush: boolean = false;
|
||||
_data: (Buffer | null)[] = [];
|
||||
_responseHead: NodeJS.ResponseHead;
|
||||
_resume: (() => void) | null = null;
|
||||
|
||||
constructor (responseHead: NodeJS.ResponseHead) {
|
||||
super();
|
||||
this._responseHead = responseHead;
|
||||
}
|
||||
|
||||
get statusCode () {
|
||||
return this._responseHead.statusCode;
|
||||
}
|
||||
|
||||
get statusMessage () {
|
||||
return this._responseHead.statusMessage;
|
||||
}
|
||||
|
||||
get headers () {
|
||||
const filteredHeaders: Record<string, string | string[]> = {};
|
||||
const { headers, rawHeaders } = this._responseHead;
|
||||
for (const [name, values] of Object.entries(headers)) {
|
||||
filteredHeaders[name] = discardableDuplicateHeaders.has(name) ? values[0] : values.join(', ');
|
||||
}
|
||||
const cookies = rawHeaders.filter(({ key }) => key.toLowerCase() === 'set-cookie').map(({ value }) => value);
|
||||
// keep set-cookie as an array per Node.js rules
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
if (cookies.length) { filteredHeaders['set-cookie'] = cookies; }
|
||||
return filteredHeaders;
|
||||
}
|
||||
|
||||
get rawHeaders () {
|
||||
const rawHeadersArr: string[] = [];
|
||||
const { rawHeaders } = this._responseHead;
|
||||
rawHeaders.forEach(header => {
|
||||
rawHeadersArr.push(header.key, header.value);
|
||||
});
|
||||
return rawHeadersArr;
|
||||
}
|
||||
|
||||
get httpVersion () {
|
||||
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
|
||||
}
|
||||
|
||||
get httpVersionMajor () {
|
||||
return this._responseHead.httpVersion.major;
|
||||
}
|
||||
|
||||
get httpVersionMinor () {
|
||||
return this._responseHead.httpVersion.minor;
|
||||
}
|
||||
|
||||
get rawTrailers () {
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
get trailers () {
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
_storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
|
||||
// save the network callback for use in _pushInternalData
|
||||
this._resume = resume;
|
||||
this._data.push(chunk);
|
||||
this._pushInternalData();
|
||||
}
|
||||
|
||||
_pushInternalData () {
|
||||
while (this._shouldPush && this._data.length > 0) {
|
||||
const chunk = this._data.shift();
|
||||
this._shouldPush = this.push(chunk);
|
||||
}
|
||||
if (this._shouldPush && this._resume) {
|
||||
// Reset the callback, so that a new one is used for each
|
||||
// batch of throttled data. Do this before calling resume to avoid a
|
||||
// potential race-condition
|
||||
const resume = this._resume;
|
||||
this._resume = null;
|
||||
|
||||
resume();
|
||||
}
|
||||
}
|
||||
|
||||
_read () {
|
||||
this._shouldPush = true;
|
||||
this._pushInternalData();
|
||||
}
|
||||
}
|
||||
|
||||
/** Writable stream that buffers up everything written to it. */
|
||||
class SlurpStream extends Writable {
|
||||
_data: Buffer;
|
||||
constructor () {
|
||||
super();
|
||||
this._data = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_write (chunk: Buffer, encoding: string, callback: () => void) {
|
||||
this._data = Buffer.concat([this._data, chunk]);
|
||||
callback();
|
||||
}
|
||||
|
||||
data () { return this._data; }
|
||||
}
|
||||
|
||||
class ChunkedBodyStream extends Writable {
|
||||
_pendingChunk: Buffer | undefined;
|
||||
_downstream?: NodeJS.DataPipe;
|
||||
_pendingCallback?: (error?: Error) => void;
|
||||
_clientRequest: ClientRequest;
|
||||
|
||||
constructor (clientRequest: ClientRequest) {
|
||||
super();
|
||||
this._clientRequest = clientRequest;
|
||||
}
|
||||
|
||||
_write (chunk: Buffer, encoding: string, callback: () => void) {
|
||||
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: () => void) {
|
||||
this._downstream!.done();
|
||||
callback();
|
||||
}
|
||||
|
||||
startReading (pipe: NodeJS.DataPipe) {
|
||||
if (this._downstream) {
|
||||
throw new Error('two startReading calls???');
|
||||
}
|
||||
this._downstream = pipe;
|
||||
if (this._pendingChunk) {
|
||||
const doneWriting = (maybeError: Error | void) => {
|
||||
// If the underlying request has been aborted, we honestly don't care about the error
|
||||
// all work should cease as soon as we abort anyway, this error is probably a
|
||||
// "mojo pipe disconnected" error (code=9)
|
||||
if (this._clientRequest._aborted) return;
|
||||
|
||||
const cb = this._pendingCallback!;
|
||||
delete this._pendingCallback;
|
||||
delete this._pendingChunk;
|
||||
cb(maybeError || undefined);
|
||||
};
|
||||
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RedirectPolicy = 'manual' | 'follow' | 'error';
|
||||
|
||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } {
|
||||
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
|
||||
|
||||
let urlStr: string = options.url;
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj: url.UrlObject = {};
|
||||
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: NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } = {
|
||||
method: (options.method || 'GET').toUpperCase(),
|
||||
url: urlStr,
|
||||
redirectPolicy,
|
||||
headers: {},
|
||||
body: null as any,
|
||||
useSessionCookies: options.useSessionCookies,
|
||||
credentials: options.credentials,
|
||||
origin: options.origin
|
||||
};
|
||||
const headers: Record<string, string | string[]> = options.headers || {};
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
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();
|
||||
urlLoaderOptions.headers[key] = { name, value };
|
||||
}
|
||||
if (options.session) {
|
||||
// Weak check, but it should be enough to catch 99% of accidental misuses.
|
||||
if (options.session.constructor && options.session.constructor.name === '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;
|
||||
}
|
||||
|
||||
export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
_started: boolean = false;
|
||||
_firstWrite: boolean = false;
|
||||
_aborted: boolean = false;
|
||||
_chunkedEncoding: boolean | undefined;
|
||||
_body: Writable | undefined;
|
||||
_urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { headers: Record<string, { name: string, value: string | string[] }> };
|
||||
_redirectPolicy: RedirectPolicy;
|
||||
_followRedirectCb?: () => void;
|
||||
_uploadProgress?: { active: boolean, started: boolean, current: number, total: number };
|
||||
_urlLoader?: NodeJS.URLLoader;
|
||||
_response?: IncomingMessage;
|
||||
|
||||
constructor (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
super({ autoDestroy: true });
|
||||
|
||||
if (!app.isReady()) {
|
||||
throw new Error('net module can only be used after app is ready');
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
this.once('response', callback);
|
||||
}
|
||||
|
||||
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
|
||||
this._urlLoaderOptions = urlLoaderOptions;
|
||||
this._redirectPolicy = redirectPolicy;
|
||||
}
|
||||
|
||||
get chunkedEncoding () {
|
||||
return this._chunkedEncoding || false;
|
||||
}
|
||||
|
||||
set chunkedEncoding (value: boolean) {
|
||||
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: NodeJS.DataPipe) => {
|
||||
(this._body! as ChunkedBodyStream).startReading(pipe);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setHeader (name: string, value: string) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)');
|
||||
}
|
||||
if (value == null) {
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)');
|
||||
}
|
||||
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._urlLoaderOptions.headers[key] = { name, value };
|
||||
}
|
||||
|
||||
getHeader (name: string) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for getHeader(name)');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase();
|
||||
const header = this._urlLoaderOptions.headers[key];
|
||||
return header && header.value as any;
|
||||
}
|
||||
|
||||
removeHeader (name: string) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for removeHeader(name)');
|
||||
}
|
||||
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t remove headers after they are sent');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase();
|
||||
delete this._urlLoaderOptions.headers[key];
|
||||
}
|
||||
|
||||
_write (chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
|
||||
this._firstWrite = true;
|
||||
if (!this._body) {
|
||||
this._body = new SlurpStream();
|
||||
this._body.on('finish', () => {
|
||||
this._urlLoaderOptions.body = (this._body as SlurpStream).data();
|
||||
this._startRequest();
|
||||
});
|
||||
}
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.write(chunk, encoding, callback);
|
||||
}
|
||||
|
||||
_final (callback: () => void) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
_startRequest () {
|
||||
this._started = true;
|
||||
const stringifyValues = (obj: Record<string, { name: string, value: string | string[] }>) => {
|
||||
const ret: Record<string, string> = {};
|
||||
for (const k of Object.keys(obj)) {
|
||||
const kv = obj[k];
|
||||
ret[kv.name] = kv.value.toString();
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
this._urlLoaderOptions.referrer = this.getHeader('referer') || '';
|
||||
this._urlLoaderOptions.origin = this._urlLoaderOptions.origin || this.getHeader('origin') || '';
|
||||
this._urlLoaderOptions.hasUserActivation = this.getHeader('sec-fetch-user') === '?1';
|
||||
this._urlLoaderOptions.mode = this.getHeader('sec-fetch-mode') || '';
|
||||
this._urlLoaderOptions.destination = this.getHeader('sec-fetch-dest') || '';
|
||||
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.headers) };
|
||||
this._urlLoader = createURLLoader(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, resume) => {
|
||||
this._response!._storeInternalData(Buffer.from(data), resume);
|
||||
});
|
||||
this._urlLoader.on('complete', () => {
|
||||
if (this._response) { this._response._storeInternalData(null, null); }
|
||||
});
|
||||
this._urlLoader.on('error', (event, netErrorString) => {
|
||||
const error = new Error(netErrorString);
|
||||
if (this._response) this._response.destroy(error);
|
||||
this._die(error);
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
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 = undefined;
|
||||
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 = undefined;
|
||||
}
|
||||
} else {
|
||||
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
|
||||
}
|
||||
});
|
||||
|
||||
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 () {
|
||||
if (this._followRedirectCb) {
|
||||
this._followRedirectCb();
|
||||
} else {
|
||||
throw new Error('followRedirect() called, but was not waiting for a redirect');
|
||||
}
|
||||
}
|
||||
|
||||
abort () {
|
||||
if (!this._aborted) {
|
||||
process.nextTick(() => { this.emit('abort'); });
|
||||
}
|
||||
this._aborted = true;
|
||||
this._die();
|
||||
}
|
||||
|
||||
_die (err?: Error) {
|
||||
// Node.js assumes that any stream which is ended is no longer capable of emitted events
|
||||
// which is a faulty assumption for the case of an object that is acting like a stream
|
||||
// (our urlRequest). If we don't emit here, this causes errors since we *do* expect
|
||||
// that error events can be emitted after urlRequest.end().
|
||||
if ((this as any)._writableState.destroyed && err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
|
||||
this.destroy(err);
|
||||
if (this._urlLoader) {
|
||||
this._urlLoader.cancel();
|
||||
if (this._response) this._response.destroy(err);
|
||||
}
|
||||
}
|
||||
|
||||
getUploadProgress (): UploadProgress {
|
||||
return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
return new ClientRequest(options, callback);
|
||||
}
|
||||
|
||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
return session.defaultSession.fetch(input, init);
|
||||
}
|
||||
|
||||
exports.isOnline = isOnline;
|
||||
|
||||
Object.defineProperty(exports, 'online', {
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { fetchWithSession } from '@electron/internal/browser/api/net-fetch';
|
||||
const { fromPartition, Session } = process._linkedBinding('electron_browser_session');
|
||||
|
||||
Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) {
|
||||
return fetchWithSession(input, init, this);
|
||||
};
|
||||
const { fromPartition } = process._linkedBinding('electron_browser_session');
|
||||
|
||||
export default {
|
||||
fromPartition,
|
||||
|
||||
@@ -418,6 +418,10 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
||||
};
|
||||
|
||||
WebContents.prototype.loadURL = function (url, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const p = new Promise<void>((resolve, reject) => {
|
||||
const resolveAndCleanup = () => {
|
||||
removeListeners();
|
||||
@@ -484,7 +488,7 @@ WebContents.prototype.loadURL = function (url, options) {
|
||||
});
|
||||
// Add a no-op rejection handler to silence the unhandled rejection error.
|
||||
p.catch(() => {});
|
||||
this._loadURL(url, options ?? {});
|
||||
this._loadURL(url, options);
|
||||
return p;
|
||||
};
|
||||
|
||||
@@ -544,8 +548,7 @@ const addReplyToEvent = (event: Electron.IpcMainEvent) => {
|
||||
};
|
||||
};
|
||||
|
||||
const addSenderToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent, sender: Electron.WebContents) => {
|
||||
event.sender = sender;
|
||||
const addSenderFrameToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent) => {
|
||||
const { processId, frameId } = event;
|
||||
Object.defineProperty(event, 'senderFrame', {
|
||||
get: () => webFrameMain.fromId(processId, frameId)
|
||||
@@ -554,7 +557,7 @@ const addSenderToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeE
|
||||
|
||||
const addReturnValueToEvent = (event: Electron.IpcMainEvent) => {
|
||||
Object.defineProperty(event, 'returnValue', {
|
||||
set: (value) => event._replyChannel.sendReply(value),
|
||||
set: (value) => event.sendReply(value),
|
||||
get: () => {}
|
||||
});
|
||||
};
|
||||
@@ -595,7 +598,7 @@ WebContents.prototype._init = function () {
|
||||
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) {
|
||||
addSenderToEvent(event, this);
|
||||
addSenderFrameToEvent(event);
|
||||
if (internal) {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
@@ -608,30 +611,25 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
});
|
||||
|
||||
this.on('-ipc-invoke' as any, async function (this: Electron.WebContents, event: Electron.IpcMainInvokeEvent, internal: boolean, channel: string, args: any[]) {
|
||||
addSenderToEvent(event, this);
|
||||
const replyWithResult = (result: any) => event._replyChannel.sendReply({ result });
|
||||
const replyWithError = (error: Error) => {
|
||||
this.on('-ipc-invoke' as any, function (event: Electron.IpcMainInvokeEvent, internal: boolean, channel: string, args: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
event._reply = (result: any) => event.sendReply({ result });
|
||||
event._throw = (error: Error) => {
|
||||
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||
event._replyChannel.sendReply({ error: error.toString() });
|
||||
event.sendReply({ error: error.toString() });
|
||||
};
|
||||
const maybeWebFrame = getWebFrameForEvent(event);
|
||||
const targets: (ElectronInternal.IpcMainInternal| undefined)[] = internal ? [ipcMainInternal] : [maybeWebFrame?.ipc, ipc, ipcMain];
|
||||
const target = targets.find(target => target && (target as any)._invokeHandlers.has(channel));
|
||||
if (target) {
|
||||
const handler = (target as any)._invokeHandlers.get(channel);
|
||||
try {
|
||||
replyWithResult(await Promise.resolve(handler(event, ...args)));
|
||||
} catch (err) {
|
||||
replyWithError(err as Error);
|
||||
}
|
||||
(target as any)._invokeHandlers.get(channel)(event, ...args);
|
||||
} else {
|
||||
replyWithError(new Error(`No handler registered for '${channel}'`));
|
||||
event._throw(`No handler registered for '${channel}'`);
|
||||
}
|
||||
});
|
||||
|
||||
this.on('-ipc-message-sync' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) {
|
||||
addSenderToEvent(event, this);
|
||||
addSenderFrameToEvent(event);
|
||||
addReturnValueToEvent(event);
|
||||
if (internal) {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
@@ -648,8 +646,8 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
});
|
||||
|
||||
this.on('-ipc-ports' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) {
|
||||
addSenderToEvent(event, this);
|
||||
this.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
event.ports = ports.map(p => new MessagePortMain(p));
|
||||
const maybeWebFrame = getWebFrameForEvent(event);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, message);
|
||||
@@ -677,7 +675,7 @@ WebContents.prototype._init = function () {
|
||||
|
||||
if (this.getType() !== 'remote') {
|
||||
// Make new windows requested by links behave like "window.open".
|
||||
this.on('-new-window' as any, (event: Electron.Event, url: string, frameName: string, disposition: Electron.HandlerDetails['disposition'],
|
||||
this.on('-new-window' as any, (event: ElectronInternal.Event, url: string, frameName: string, disposition: Electron.HandlerDetails['disposition'],
|
||||
rawFeatures: string, referrer: Electron.Referrer, postData: PostData) => {
|
||||
const postBody = postData ? {
|
||||
data: postData,
|
||||
@@ -703,7 +701,7 @@ WebContents.prototype._init = function () {
|
||||
const options = result.browserWindowConstructorOptions;
|
||||
if (!event.defaultPrevented) {
|
||||
openGuestWindow({
|
||||
embedder: this,
|
||||
embedder: event.sender,
|
||||
disposition,
|
||||
referrer,
|
||||
postData,
|
||||
@@ -716,7 +714,7 @@ WebContents.prototype._init = function () {
|
||||
|
||||
let windowOpenOverriddenOptions: BrowserWindowConstructorOptions | null = null;
|
||||
let windowOpenOutlivesOpenerOption: boolean = false;
|
||||
this.on('-will-add-new-contents' as any, (event: Electron.Event, url: string, frameName: string, rawFeatures: string, disposition: Electron.HandlerDetails['disposition'], referrer: Electron.Referrer, postData: PostData) => {
|
||||
this.on('-will-add-new-contents' as any, (event: ElectronInternal.Event, url: string, frameName: string, rawFeatures: string, disposition: Electron.HandlerDetails['disposition'], referrer: Electron.Referrer, postData: PostData) => {
|
||||
const postBody = postData ? {
|
||||
data: postData,
|
||||
...parseContentTypeFormat(postData)
|
||||
@@ -751,7 +749,7 @@ WebContents.prototype._init = function () {
|
||||
} : undefined;
|
||||
const { webPreferences: parsedWebPreferences } = parseFeatures(rawFeatures);
|
||||
const webPreferences = makeWebPreferences({
|
||||
embedder: this,
|
||||
embedder: event.sender,
|
||||
insecureParsedWebPreferences: parsedWebPreferences,
|
||||
secureOverrideWebPreferences
|
||||
});
|
||||
@@ -764,7 +762,7 @@ WebContents.prototype._init = function () {
|
||||
});
|
||||
|
||||
// Create a new browser window for "window.open"
|
||||
this.on('-add-new-contents' as any, (event: Electron.Event, webContents: Electron.WebContents, disposition: string,
|
||||
this.on('-add-new-contents' as any, (event: ElectronInternal.Event, webContents: Electron.WebContents, disposition: string,
|
||||
_userGesture: boolean, _left: number, _top: number, _width: number, _height: number, url: string, frameName: string,
|
||||
referrer: Electron.Referrer, rawFeatures: string, postData: PostData) => {
|
||||
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
||||
@@ -780,7 +778,7 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
|
||||
openGuestWindow({
|
||||
embedder: this,
|
||||
embedder: event.sender,
|
||||
guest: webContents,
|
||||
overrideBrowserWindowOptions: overriddenOptions,
|
||||
disposition,
|
||||
@@ -817,7 +815,8 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
});
|
||||
|
||||
app.emit('web-contents-created', { sender: this, preventDefault () {}, get defaultPrevented () { return false; } }, this);
|
||||
const event = process._linkedBinding('electron_browser_event').createEmpty();
|
||||
app.emit('web-contents-created', event, this);
|
||||
|
||||
// Properties
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ interface GuestInstance {
|
||||
}
|
||||
|
||||
const webViewManager = process._linkedBinding('electron_browser_web_view_manager');
|
||||
const eventBinding = process._linkedBinding('electron_browser_event');
|
||||
const netBinding = process._linkedBinding('electron_browser_net');
|
||||
|
||||
const supportedWebViewEvents = Object.keys(webViewEvents);
|
||||
@@ -81,13 +82,7 @@ function makeLoadURLOptions (params: Record<string, any>) {
|
||||
// Create a new guest instance.
|
||||
const createGuest = function (embedder: Electron.WebContents, embedderFrameId: number, elementInstanceId: number, params: Record<string, any>) {
|
||||
const webPreferences = makeWebPreferences(embedder, params);
|
||||
const event = {
|
||||
sender: embedder,
|
||||
preventDefault () {
|
||||
this.defaultPrevented = true;
|
||||
},
|
||||
defaultPrevented: false
|
||||
};
|
||||
const event = eventBinding.createWithSender(embedder);
|
||||
|
||||
const { instanceId } = params;
|
||||
|
||||
|
||||
@@ -18,7 +18,13 @@ export class IpcMainImpl extends EventEmitter {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`);
|
||||
}
|
||||
this._invokeHandlers.set(method, fn);
|
||||
this._invokeHandlers.set(method, async (e, ...args) => {
|
||||
try {
|
||||
e._reply(await Promise.resolve(fn(e, ...args)));
|
||||
} catch (err) {
|
||||
e._throw(err as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => {
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
"@azure/storage-blob": "^12.9.0",
|
||||
"@dsanders11/vscode-markdown-languageservice": "^0.3.0-alpha.4",
|
||||
"@electron/asar": "^3.2.1",
|
||||
"@electron/docs-parser": "^1.1.0",
|
||||
"@electron/docs-parser": "^1.0.0",
|
||||
"@electron/fiddle-core": "^1.0.4",
|
||||
"@electron/github-app-auth": "^1.5.0",
|
||||
"@electron/typescript-definitions": "^8.14.0",
|
||||
"@electron/typescript-definitions": "^8.10.0",
|
||||
"@octokit/rest": "^19.0.7",
|
||||
"@primer/octicons": "^10.0.0",
|
||||
"@types/basic-auth": "^1.1.3",
|
||||
|
||||
@@ -2118,7 +2118,7 @@ index 0000000000000000000000000000000000000000..d1d6b51e8c0c5bc6a5d09e217eb30483
|
||||
+ args = rebase_path(inputs + outputs, root_build_dir)
|
||||
+}
|
||||
diff --git a/src/node_version.h b/src/node_version.h
|
||||
index 46e039093716576d3c382dea0cb4bafd42d99799..ff9296f5abac9afda03e457c107f9666e792c21f 100644
|
||||
index c42b070a3367283155f9ff6b861b9f0160b5e9ed..ff8a64174e6f09af533b2fe05b2fe6e600624910 100644
|
||||
--- a/src/node_version.h
|
||||
+++ b/src/node_version.h
|
||||
@@ -89,7 +89,10 @@
|
||||
|
||||
@@ -40,7 +40,7 @@ index b661dc4a1b3149e780eef46033e70e68038417b0..15b6155017233ed9d4c60c2453f498ba
|
||||
'defines': [
|
||||
'V8_COMPRESS_POINTERS',
|
||||
diff --git a/configure.py b/configure.py
|
||||
index bfa20f5fc7a64b30b464327f4086a027e9a23359..f9d849260c3d7b1368d375125ae587eaa396c49e 100755
|
||||
index 171afd04030e6933da054db866d44428ae808acf..363bf746101c85630a6c52146303986e7e0dcdce 100755
|
||||
--- a/configure.py
|
||||
+++ b/configure.py
|
||||
@@ -1517,6 +1517,7 @@ def configure_library(lib, output, pkgname=None):
|
||||
|
||||
@@ -42,7 +42,7 @@ index 2c2f3218a8ae387802af3d154ede601aedcb52dd..b661dc4a1b3149e780eef46033e70e68
|
||||
'defines': ['V8_31BIT_SMIS_ON_64BIT_ARCH'],
|
||||
}],
|
||||
diff --git a/configure.py b/configure.py
|
||||
index 62c01aaf6a386d24e82289554520140f03699c95..bfa20f5fc7a64b30b464327f4086a027e9a23359 100755
|
||||
index 0a45c07f587ed8d21dff45d523b7074606c586ac..171afd04030e6933da054db866d44428ae808acf 100755
|
||||
--- a/configure.py
|
||||
+++ b/configure.py
|
||||
@@ -1530,6 +1530,7 @@ def configure_v8(o):
|
||||
|
||||
@@ -8,10 +8,10 @@ they use themselves as the entry point. We should try to upstream some form
|
||||
of this.
|
||||
|
||||
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
|
||||
index c19cd5e5d8a818c52406c78b95d96bae1b1f41bd..88df0c4b6f995c993c330963ff0c730e00c5b8ec 100644
|
||||
index 207142322bba89e94f4e3052a22bef6ffebcdc39..c83d8b0db630d3fc26fb273170945c872bede091 100644
|
||||
--- a/lib/internal/modules/cjs/loader.js
|
||||
+++ b/lib/internal/modules/cjs/loader.js
|
||||
@@ -1228,6 +1228,13 @@ Module.prototype._compile = function(content, filename) {
|
||||
@@ -1200,6 +1200,13 @@ Module.prototype._compile = function(content, filename) {
|
||||
if (getOptionValue('--inspect-brk') && process._eval == null) {
|
||||
if (!resolvedArgv) {
|
||||
// We enter the repl if we're not given a filename argument.
|
||||
|
||||
@@ -715,10 +715,10 @@ index 008ab129f0e019c659eecf5a76b7eb412c947fe3..6688f5d916f50e1e4fcfff1619c8634a
|
||||
|
||||
cipher.end('Papaya!'); // Should not cause an unhandled exception.
|
||||
diff --git a/test/parallel/test-crypto-x509.js b/test/parallel/test-crypto-x509.js
|
||||
index 2f2e443a7f6482c26f3a065520c8764d8402a91f..ccfe79ac65a223139f293f7dd034096fcdbc81ab 100644
|
||||
index 6d92e97115fd537b0469f69132e591d8b1c6a43f..cdeed19099c351e3c3ec3c2d78e12b64098ddc6a 100644
|
||||
--- a/test/parallel/test-crypto-x509.js
|
||||
+++ b/test/parallel/test-crypto-x509.js
|
||||
@@ -111,7 +111,7 @@ const der = Buffer.from(
|
||||
@@ -110,7 +110,7 @@ const der = Buffer.from(
|
||||
'5A:42:63:E0:21:2F:D6:70:63:07:96:6F:27:A7:78:12:08:02:7A:8B'
|
||||
);
|
||||
assert.strictEqual(x509.keyUsage, undefined);
|
||||
@@ -727,7 +727,7 @@ index 2f2e443a7f6482c26f3a065520c8764d8402a91f..ccfe79ac65a223139f293f7dd034096f
|
||||
|
||||
assert.deepStrictEqual(x509.raw, der);
|
||||
|
||||
@@ -253,6 +253,16 @@ oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI=
|
||||
@@ -209,6 +209,16 @@ const der = Buffer.from(
|
||||
});
|
||||
mc.port2.postMessage(x509);
|
||||
|
||||
@@ -744,7 +744,7 @@ index 2f2e443a7f6482c26f3a065520c8764d8402a91f..ccfe79ac65a223139f293f7dd034096f
|
||||
// Verify that legacy encoding works
|
||||
const legacyObjectCheck = {
|
||||
subject: Object.assign(Object.create(null), {
|
||||
@@ -277,16 +287,8 @@ oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI=
|
||||
@@ -233,16 +243,8 @@ const der = Buffer.from(
|
||||
'OCSP - URI': ['http://ocsp.nodejs.org/'],
|
||||
'CA Issuers - URI': ['http://ca.nodejs.org/ca.cert']
|
||||
}),
|
||||
@@ -762,7 +762,7 @@ index 2f2e443a7f6482c26f3a065520c8764d8402a91f..ccfe79ac65a223139f293f7dd034096f
|
||||
exponent: '0x10001',
|
||||
valid_from: 'Sep 3 21:40:37 2022 GMT',
|
||||
valid_to: 'Jun 17 21:40:37 2296 GMT',
|
||||
@@ -298,7 +300,7 @@ oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI=
|
||||
@@ -254,7 +256,7 @@ const der = Buffer.from(
|
||||
'51:62:18:39:E2:E2:77:F5:86:11:E8:C0:CA:54:43:7C:76:83:19:05:D0:03:' +
|
||||
'24:21:B8:EB:14:61:FB:24:16:EB:BD:51:1A:17:91:04:30:03:EB:68:5F:DC:' +
|
||||
'86:E1:D1:7C:FB:AF:78:ED:63:5F:29:9C:32:AF:A1:8E:22:96:D1:02',
|
||||
@@ -771,7 +771,7 @@ index 2f2e443a7f6482c26f3a065520c8764d8402a91f..ccfe79ac65a223139f293f7dd034096f
|
||||
};
|
||||
|
||||
const legacyObject = x509.toLegacyObject();
|
||||
@@ -307,7 +309,7 @@ oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI=
|
||||
@@ -263,7 +265,7 @@ const der = Buffer.from(
|
||||
assert.deepStrictEqual(legacyObject.subject, legacyObjectCheck.subject);
|
||||
assert.deepStrictEqual(legacyObject.issuer, legacyObjectCheck.issuer);
|
||||
assert.deepStrictEqual(legacyObject.infoAccess, legacyObjectCheck.infoAccess);
|
||||
@@ -780,7 +780,7 @@ index 2f2e443a7f6482c26f3a065520c8764d8402a91f..ccfe79ac65a223139f293f7dd034096f
|
||||
assert.strictEqual(legacyObject.bits, legacyObjectCheck.bits);
|
||||
assert.strictEqual(legacyObject.exponent, legacyObjectCheck.exponent);
|
||||
assert.strictEqual(legacyObject.valid_from, legacyObjectCheck.valid_from);
|
||||
@@ -316,7 +318,7 @@ oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI=
|
||||
@@ -272,7 +274,7 @@ const der = Buffer.from(
|
||||
assert.strictEqual(
|
||||
legacyObject.fingerprint256,
|
||||
legacyObjectCheck.fingerprint256);
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: Pass all globals through "require"
|
||||
(cherry picked from commit 7d015419cb7a0ecfe6728431a4ed2056cd411d62)
|
||||
|
||||
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
|
||||
index 42e5cc50105560174b3106898d20ef11ea37be85..6b9aef981cfa934e1321916828d4f921008134d6 100644
|
||||
index b942b37dd90498432728bf897fb6f694b7041ba5..9da60135ac79a2034a646c0a1380923f7fbfacab 100644
|
||||
--- a/lib/internal/modules/cjs/loader.js
|
||||
+++ b/lib/internal/modules/cjs/loader.js
|
||||
@@ -142,6 +142,13 @@ const {
|
||||
@@ -137,6 +137,13 @@ const {
|
||||
CHAR_FORWARD_SLASH,
|
||||
} = require('internal/constants');
|
||||
|
||||
@@ -23,7 +23,7 @@ index 42e5cc50105560174b3106898d20ef11ea37be85..6b9aef981cfa934e1321916828d4f921
|
||||
const {
|
||||
isProxy
|
||||
} = require('internal/util/types');
|
||||
@@ -1249,10 +1256,12 @@ Module.prototype._compile = function(content, filename) {
|
||||
@@ -1221,10 +1228,12 @@ Module.prototype._compile = function(content, filename) {
|
||||
if (requireDepth === 0) statCache = new SafeMap();
|
||||
if (inspectorWrapper) {
|
||||
result = inspectorWrapper(compiledWrapper, thisValue, exports,
|
||||
|
||||
@@ -22,7 +22,7 @@ index 81b441a554e34556fc41066fc8761c713acb0ced..7dd89d5f134b09da2678dd54fa913946
|
||||
// release cycle, remove the Proxy and setter and update the
|
||||
// getter to either return a read-only object or always return
|
||||
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
|
||||
index 6b9aef981cfa934e1321916828d4f921008134d6..c19cd5e5d8a818c52406c78b95d96bae1b1f41bd 100644
|
||||
index 9da60135ac79a2034a646c0a1380923f7fbfacab..207142322bba89e94f4e3052a22bef6ffebcdc39 100644
|
||||
--- a/lib/internal/modules/cjs/loader.js
|
||||
+++ b/lib/internal/modules/cjs/loader.js
|
||||
@@ -94,7 +94,7 @@ const fs = require('fs');
|
||||
@@ -34,7 +34,7 @@ index 6b9aef981cfa934e1321916828d4f921008134d6..c19cd5e5d8a818c52406c78b95d96bae
|
||||
const packageJsonReader = require('internal/modules/package_json_reader');
|
||||
const { safeGetenv } = internalBinding('credentials');
|
||||
const {
|
||||
@@ -190,7 +190,7 @@ function stat(filename) {
|
||||
@@ -171,7 +171,7 @@ function stat(filename) {
|
||||
const result = statCache.get(filename);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ process.env.PATH = `${process.env.PATH}${path.delimiter}${DEPOT_TOOLS}`;
|
||||
const IGNORELIST = new Set([
|
||||
['shell', 'browser', 'resources', 'win', 'resource.h'],
|
||||
['shell', 'common', 'node_includes.h'],
|
||||
['spec', 'fixtures', 'pages', 'jquery-3.6.0.min.js']
|
||||
['spec', 'fixtures', 'pages', 'jquery-3.6.0.min.js'],
|
||||
['spec', 'ts-smoke', 'electron', 'main.ts'],
|
||||
['spec', 'ts-smoke', 'electron', 'renderer.ts'],
|
||||
['spec', 'ts-smoke', 'runner.js']
|
||||
].map(tokens => path.join(ELECTRON_ROOT, ...tokens)));
|
||||
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
|
||||
@@ -318,15 +318,12 @@ function saveShaSumFile (checksums, fileName) {
|
||||
}
|
||||
|
||||
async function publishRelease (release) {
|
||||
let makeLatest = false;
|
||||
if (!release.prerelease) {
|
||||
const currentLatest = await octokit.repos.getLatestRelease({
|
||||
owner: 'electron',
|
||||
repo: targetRepo
|
||||
});
|
||||
const currentLatest = await octokit.repos.getLatestRelease({
|
||||
owner: 'electron',
|
||||
repo: targetRepo
|
||||
});
|
||||
|
||||
makeLatest = semver.gte(release.tag_name, currentLatest.data.tag_name);
|
||||
}
|
||||
const makeLatest = !release.prerelease && semver.gte(release.tag_name, currentLatest.data.tag_name);
|
||||
|
||||
return octokit.repos.updateRelease({
|
||||
owner: 'electron',
|
||||
|
||||
@@ -17,8 +17,7 @@ sys.path.append(
|
||||
|
||||
from zipfile import ZipFile
|
||||
from lib.config import PLATFORM, get_target_arch, \
|
||||
get_zip_name, enable_verbose_mode, \
|
||||
is_verbose_mode, get_platform_key
|
||||
get_zip_name, enable_verbose_mode, get_platform_key
|
||||
from lib.util import get_electron_branding, execute, get_electron_version, \
|
||||
store_artifact, get_electron_exec, get_out_dir, \
|
||||
SRC_DIR, ELECTRON_DIR, TS_NODE
|
||||
@@ -384,14 +383,7 @@ def upload_sha256_checksum(version, file_path, key_prefix=None):
|
||||
def get_release(version):
|
||||
script_path = os.path.join(
|
||||
ELECTRON_DIR, 'script', 'release', 'find-github-release.js')
|
||||
|
||||
# Strip warnings from stdout to ensure the only output is the desired object
|
||||
release_env = os.environ.copy()
|
||||
release_env['NODE_NO_WARNINGS'] = '1'
|
||||
release_info = execute(['node', script_path, version], release_env)
|
||||
if is_verbose_mode():
|
||||
print('Release info for version: {}:\n'.format(version))
|
||||
print(release_info)
|
||||
release_info = execute(['node', script_path, version])
|
||||
release = json.loads(release_info)
|
||||
return release
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "shell/common/gin_converters/gfx_converter.h"
|
||||
#include "shell/common/gin_converters/image_converter.h"
|
||||
#include "shell/common/gin_converters/native_window_converter.h"
|
||||
#include "shell/common/gin_converters/optional_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
@@ -877,12 +876,17 @@ bool BaseWindow::GetWindowButtonVisibility() const {
|
||||
return window_->GetWindowButtonVisibility();
|
||||
}
|
||||
|
||||
void BaseWindow::SetWindowButtonPosition(absl::optional<gfx::Point> position) {
|
||||
window_->SetWindowButtonPosition(std::move(position));
|
||||
void BaseWindow::SetTrafficLightPosition(const gfx::Point& position) {
|
||||
// For backward compatibility we treat (0, 0) as resetting to default.
|
||||
if (position.IsOrigin())
|
||||
window_->SetTrafficLightPosition(absl::nullopt);
|
||||
else
|
||||
window_->SetTrafficLightPosition(position);
|
||||
}
|
||||
|
||||
absl::optional<gfx::Point> BaseWindow::GetWindowButtonPosition() const {
|
||||
return window_->GetWindowButtonPosition();
|
||||
gfx::Point BaseWindow::GetTrafficLightPosition() const {
|
||||
// For backward compatibility we treat default value as (0, 0).
|
||||
return window_->GetTrafficLightPosition().value_or(gfx::Point());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1267,6 +1271,12 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("setAutoHideCursor", &BaseWindow::SetAutoHideCursor)
|
||||
#endif
|
||||
.SetMethod("setVibrancy", &BaseWindow::SetVibrancy)
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
.SetMethod("setTrafficLightPosition",
|
||||
&BaseWindow::SetTrafficLightPosition)
|
||||
.SetMethod("getTrafficLightPosition",
|
||||
&BaseWindow::GetTrafficLightPosition)
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
.SetMethod("isHiddenInMissionControl",
|
||||
@@ -1289,10 +1299,6 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
&BaseWindow::SetWindowButtonVisibility)
|
||||
.SetMethod("_getWindowButtonVisibility",
|
||||
&BaseWindow::GetWindowButtonVisibility)
|
||||
.SetMethod("setWindowButtonPosition",
|
||||
&BaseWindow::SetWindowButtonPosition)
|
||||
.SetMethod("getWindowButtonPosition",
|
||||
&BaseWindow::GetWindowButtonPosition)
|
||||
.SetProperty("excludedFromShownWindowsMenu",
|
||||
&BaseWindow::IsExcludedFromShownWindowsMenu,
|
||||
&BaseWindow::SetExcludedFromShownWindowsMenu)
|
||||
|
||||
@@ -195,8 +195,8 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||
std::string GetAlwaysOnTopLevel();
|
||||
void SetWindowButtonVisibility(bool visible);
|
||||
bool GetWindowButtonVisibility() const;
|
||||
void SetWindowButtonPosition(absl::optional<gfx::Point> position);
|
||||
absl::optional<gfx::Point> GetWindowButtonPosition() const;
|
||||
void SetTrafficLightPosition(const gfx::Point& position);
|
||||
gfx::Point GetTrafficLightPosition() const;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
|
||||
@@ -133,9 +133,6 @@ bool MatchesCookie(const base::Value::Dict& filter,
|
||||
absl::optional<bool> session_filter = filter.FindBool("session");
|
||||
if (session_filter && *session_filter == cookie.IsPersistent())
|
||||
return false;
|
||||
absl::optional<bool> httpOnly_filter = filter.FindBool("httpOnly");
|
||||
if (httpOnly_filter && *httpOnly_filter != cookie.IsHttpOnly())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
28
shell/browser/api/electron_api_event.cc
Normal file
28
shell/browser/api/electron_api_event.cc
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2018 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/event.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event_emitter.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
v8::Local<v8::Object> CreateWithSender(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender) {
|
||||
return gin_helper::internal::CreateCustomEvent(isolate, sender);
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
gin_helper::Dictionary dict(context->GetIsolate(), exports);
|
||||
dict.SetMethod("createWithSender", &CreateWithSender);
|
||||
dict.SetMethod("createEmpty", &gin_helper::Event::Create);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_event, Initialize)
|
||||
@@ -1178,7 +1178,8 @@ gin::Handle<Session> Session::CreateFrom(
|
||||
// to use partition strings, instead of using the Session object directly.
|
||||
handle->Pin(isolate);
|
||||
|
||||
App::Get()->EmitWithoutEvent("session-created", handle);
|
||||
App::Get()->EmitCustomEvent("session-created",
|
||||
handle.ToV8().As<v8::Object>());
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -1203,16 +1204,10 @@ gin::Handle<Session> Session::FromPartition(v8::Isolate* isolate,
|
||||
return CreateFrom(isolate, browser_context);
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<Session> Session::New() {
|
||||
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
|
||||
.ThrowError("Session objects cannot be created with 'new'");
|
||||
return gin::Handle<Session>();
|
||||
}
|
||||
|
||||
void Session::FillObjectTemplate(v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> templ) {
|
||||
gin::ObjectTemplateBuilder(isolate, "Session", templ)
|
||||
gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin_helper::EventEmitterMixin<Session>::GetObjectTemplateBuilder(
|
||||
isolate)
|
||||
.SetMethod("resolveProxy", &Session::ResolveProxy)
|
||||
.SetMethod("getCacheSize", &Session::GetCacheSize)
|
||||
.SetMethod("clearCache", &Session::ClearCache)
|
||||
@@ -1282,8 +1277,7 @@ void Session::FillObjectTemplate(v8::Isolate* isolate,
|
||||
.SetProperty("protocol", &Session::Protocol)
|
||||
.SetProperty("serviceWorkers", &Session::ServiceWorkerContext)
|
||||
.SetProperty("webRequest", &Session::WebRequest)
|
||||
.SetProperty("storagePath", &Session::GetPath)
|
||||
.Build();
|
||||
.SetProperty("storagePath", &Session::GetPath);
|
||||
}
|
||||
|
||||
const char* Session::GetTypeName() {
|
||||
@@ -1314,7 +1308,6 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("Session", Session::GetConstructor(context));
|
||||
dict.SetMethod("fromPartition", &FromPartition);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
#include "shell/browser/net/resolve_proxy_helper.h"
|
||||
#include "shell/common/gin_helper/cleaned_up_at_exit.h"
|
||||
#include "shell/common/gin_helper/constructible.h"
|
||||
#include "shell/common/gin_helper/error_thrower.h"
|
||||
#include "shell/common/gin_helper/function_template_extensions.h"
|
||||
#include "shell/common/gin_helper/pinnable.h"
|
||||
@@ -58,7 +57,6 @@ namespace api {
|
||||
|
||||
class Session : public gin::Wrappable<Session>,
|
||||
public gin_helper::Pinnable<Session>,
|
||||
public gin_helper::Constructible<Session>,
|
||||
public gin_helper::EventEmitterMixin<Session>,
|
||||
public gin_helper::CleanedUpAtExit,
|
||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||
@@ -73,7 +71,6 @@ class Session : public gin::Wrappable<Session>,
|
||||
static gin::Handle<Session> CreateFrom(
|
||||
v8::Isolate* isolate,
|
||||
ElectronBrowserContext* browser_context);
|
||||
static gin::Handle<Session> New(); // Dummy, do not use!
|
||||
|
||||
static Session* FromBrowserContext(content::BrowserContext* context);
|
||||
|
||||
@@ -86,7 +83,8 @@ class Session : public gin::Wrappable<Session>,
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
const char* GetTypeName() override;
|
||||
|
||||
// Methods.
|
||||
|
||||
@@ -97,19 +97,19 @@ void Tray::OnClicked(const gfx::Rect& bounds,
|
||||
int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("click", CreateEventFromFlags(modifiers), bounds, location);
|
||||
EmitCustomEvent("click", CreateEventFromFlags(modifiers), bounds, location);
|
||||
}
|
||||
|
||||
void Tray::OnDoubleClicked(const gfx::Rect& bounds, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("double-click", CreateEventFromFlags(modifiers), bounds);
|
||||
EmitCustomEvent("double-click", CreateEventFromFlags(modifiers), bounds);
|
||||
}
|
||||
|
||||
void Tray::OnRightClicked(const gfx::Rect& bounds, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("right-click", CreateEventFromFlags(modifiers), bounds);
|
||||
EmitCustomEvent("right-click", CreateEventFromFlags(modifiers), bounds);
|
||||
}
|
||||
|
||||
void Tray::OnBalloonShow() {
|
||||
@@ -139,31 +139,31 @@ void Tray::OnDropText(const std::string& text) {
|
||||
void Tray::OnMouseEntered(const gfx::Point& location, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("mouse-enter", CreateEventFromFlags(modifiers), location);
|
||||
EmitCustomEvent("mouse-enter", CreateEventFromFlags(modifiers), location);
|
||||
}
|
||||
|
||||
void Tray::OnMouseExited(const gfx::Point& location, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("mouse-leave", CreateEventFromFlags(modifiers), location);
|
||||
EmitCustomEvent("mouse-leave", CreateEventFromFlags(modifiers), location);
|
||||
}
|
||||
|
||||
void Tray::OnMouseMoved(const gfx::Point& location, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("mouse-move", CreateEventFromFlags(modifiers), location);
|
||||
EmitCustomEvent("mouse-move", CreateEventFromFlags(modifiers), location);
|
||||
}
|
||||
|
||||
void Tray::OnMouseUp(const gfx::Point& location, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("mouse-up", CreateEventFromFlags(modifiers), location);
|
||||
EmitCustomEvent("mouse-up", CreateEventFromFlags(modifiers), location);
|
||||
}
|
||||
|
||||
void Tray::OnMouseDown(const gfx::Point& location, int modifiers) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
EmitWithoutEvent("mouse-down", CreateEventFromFlags(modifiers), location);
|
||||
EmitCustomEvent("mouse-down", CreateEventFromFlags(modifiers), location);
|
||||
}
|
||||
|
||||
void Tray::OnDragEntered() {
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "third_party/blink/public/common/loader/referrer_utils.h"
|
||||
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
|
||||
|
||||
namespace gin {
|
||||
|
||||
@@ -61,84 +59,15 @@ struct Converter<network::mojom::CredentialsMode> {
|
||||
*out = network::mojom::CredentialsMode::kOmit;
|
||||
else if (mode == "include")
|
||||
*out = network::mojom::CredentialsMode::kInclude;
|
||||
else if (mode == "same-origin")
|
||||
// Note: This only makes sense if the request specifies the "origin"
|
||||
// option.
|
||||
*out = network::mojom::CredentialsMode::kSameOrigin;
|
||||
else
|
||||
// "same-origin" is technically a member of this enum as well, but it
|
||||
// doesn't make sense in the context of `net.request()`, so don't convert
|
||||
// it.
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<blink::mojom::FetchCacheMode> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
blink::mojom::FetchCacheMode* out) {
|
||||
std::string cache;
|
||||
if (!ConvertFromV8(isolate, val, &cache))
|
||||
return false;
|
||||
if (cache == "default") {
|
||||
*out = blink::mojom::FetchCacheMode::kDefault;
|
||||
} else if (cache == "no-store") {
|
||||
*out = blink::mojom::FetchCacheMode::kNoStore;
|
||||
} else if (cache == "reload") {
|
||||
*out = blink::mojom::FetchCacheMode::kBypassCache;
|
||||
} else if (cache == "no-cache") {
|
||||
*out = blink::mojom::FetchCacheMode::kValidateCache;
|
||||
} else if (cache == "force-cache") {
|
||||
*out = blink::mojom::FetchCacheMode::kForceCache;
|
||||
} else if (cache == "only-if-cached") {
|
||||
*out = blink::mojom::FetchCacheMode::kOnlyIfCached;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<net::ReferrerPolicy> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
net::ReferrerPolicy* out) {
|
||||
std::string referrer_policy;
|
||||
if (!ConvertFromV8(isolate, val, &referrer_policy))
|
||||
return false;
|
||||
if (base::CompareCaseInsensitiveASCII(referrer_policy, "no-referrer") ==
|
||||
0) {
|
||||
*out = net::ReferrerPolicy::NO_REFERRER;
|
||||
} else if (base::CompareCaseInsensitiveASCII(
|
||||
referrer_policy, "no-referrer-when-downgrade") == 0) {
|
||||
*out = net::ReferrerPolicy::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
|
||||
} else if (base::CompareCaseInsensitiveASCII(referrer_policy, "origin") ==
|
||||
0) {
|
||||
*out = net::ReferrerPolicy::ORIGIN;
|
||||
} else if (base::CompareCaseInsensitiveASCII(
|
||||
referrer_policy, "origin-when-cross-origin") == 0) {
|
||||
*out = net::ReferrerPolicy::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN;
|
||||
} else if (base::CompareCaseInsensitiveASCII(referrer_policy,
|
||||
"unsafe-url") == 0) {
|
||||
*out = net::ReferrerPolicy::NEVER_CLEAR;
|
||||
} else if (base::CompareCaseInsensitiveASCII(referrer_policy,
|
||||
"same-origin") == 0) {
|
||||
*out = net::ReferrerPolicy::CLEAR_ON_TRANSITION_CROSS_ORIGIN;
|
||||
} else if (base::CompareCaseInsensitiveASCII(referrer_policy,
|
||||
"strict-origin") == 0) {
|
||||
*out = net::ReferrerPolicy::
|
||||
ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
|
||||
} else if (referrer_policy == "" ||
|
||||
base::CompareCaseInsensitiveASCII(
|
||||
referrer_policy, "strict-origin-when-cross-origin") == 0) {
|
||||
*out = net::ReferrerPolicy::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
namespace electron::api {
|
||||
@@ -472,9 +401,6 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
|
||||
opts.Get("url", &request->url);
|
||||
request->site_for_cookies = net::SiteForCookies::FromUrl(request->url);
|
||||
opts.Get("referrer", &request->referrer);
|
||||
request->referrer_policy =
|
||||
blink::ReferrerUtils::GetDefaultNetReferrerPolicy();
|
||||
opts.Get("referrerPolicy", &request->referrer_policy);
|
||||
std::string origin;
|
||||
opts.Get("origin", &origin);
|
||||
if (!origin.empty()) {
|
||||
@@ -558,36 +484,6 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
|
||||
}
|
||||
}
|
||||
|
||||
blink::mojom::FetchCacheMode cache_mode =
|
||||
blink::mojom::FetchCacheMode::kDefault;
|
||||
opts.Get("cache", &cache_mode);
|
||||
switch (cache_mode) {
|
||||
case blink::mojom::FetchCacheMode::kNoStore:
|
||||
request->load_flags |= net::LOAD_DISABLE_CACHE;
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kValidateCache:
|
||||
request->load_flags |= net::LOAD_VALIDATE_CACHE;
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kBypassCache:
|
||||
request->load_flags |= net::LOAD_BYPASS_CACHE;
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kForceCache:
|
||||
request->load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kOnlyIfCached:
|
||||
request->load_flags |=
|
||||
net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kUnspecifiedOnlyIfCachedStrict:
|
||||
request->load_flags |= net::LOAD_ONLY_FROM_CACHE;
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kDefault:
|
||||
break;
|
||||
case blink::mojom::FetchCacheMode::kUnspecifiedForceCacheMiss:
|
||||
request->load_flags |= net::LOAD_ONLY_FROM_CACHE | net::LOAD_BYPASS_CACHE;
|
||||
break;
|
||||
}
|
||||
|
||||
bool use_session_cookies = false;
|
||||
opts.Get("useSessionCookies", &use_session_cookies);
|
||||
int options = 0;
|
||||
|
||||
@@ -203,13 +203,13 @@ void UtilityProcessWrapper::OnServiceProcessLaunched(
|
||||
pid_ = process.Pid();
|
||||
GetAllUtilityProcessWrappers().AddWithID(this, pid_);
|
||||
if (stdout_read_fd_ != -1) {
|
||||
EmitWithoutEvent("stdout", stdout_read_fd_);
|
||||
EmitWithoutCustomEvent("stdout", stdout_read_fd_);
|
||||
}
|
||||
if (stderr_read_fd_ != -1) {
|
||||
EmitWithoutEvent("stderr", stderr_read_fd_);
|
||||
EmitWithoutCustomEvent("stderr", stderr_read_fd_);
|
||||
}
|
||||
// Emit 'spawn' event
|
||||
EmitWithoutEvent("spawn");
|
||||
EmitWithoutCustomEvent("spawn");
|
||||
}
|
||||
|
||||
void UtilityProcessWrapper::OnServiceProcessDisconnected(
|
||||
@@ -219,7 +219,7 @@ void UtilityProcessWrapper::OnServiceProcessDisconnected(
|
||||
GetAllUtilityProcessWrappers().Remove(pid_);
|
||||
CloseConnectorPort();
|
||||
// Emit 'exit' event
|
||||
EmitWithoutEvent("exit", error_code);
|
||||
EmitWithoutCustomEvent("exit", error_code);
|
||||
Unpin();
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ void UtilityProcessWrapper::Shutdown(int exit_code) {
|
||||
node_service_remote_.reset();
|
||||
CloseConnectorPort();
|
||||
// Emit 'exit' event
|
||||
EmitWithoutEvent("exit", exit_code);
|
||||
EmitWithoutCustomEvent("exit", exit_code);
|
||||
Unpin();
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ bool UtilityProcessWrapper::Accept(mojo::Message* mojo_message) {
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Local<v8::Value> message_value =
|
||||
electron::DeserializeV8Value(isolate, message);
|
||||
EmitWithoutEvent("message", message_value);
|
||||
EmitWithoutCustomEvent("message", message_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1817,81 +1817,6 @@ void WebContents::OnFirstNonEmptyLayout(
|
||||
}
|
||||
}
|
||||
|
||||
// This object wraps the InvokeCallback so that if it gets GC'd by V8, we can
|
||||
// still call the callback and send an error. Not doing so causes a Mojo DCHECK,
|
||||
// since Mojo requires callbacks to be called before they are destroyed.
|
||||
class ReplyChannel : public gin::Wrappable<ReplyChannel> {
|
||||
public:
|
||||
using InvokeCallback = electron::mojom::ElectronApiIPC::InvokeCallback;
|
||||
static gin::Handle<ReplyChannel> Create(v8::Isolate* isolate,
|
||||
InvokeCallback callback) {
|
||||
return gin::CreateHandle(isolate, new ReplyChannel(std::move(callback)));
|
||||
}
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<ReplyChannel>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("sendReply", &ReplyChannel::SendReply);
|
||||
}
|
||||
const char* GetTypeName() override { return "ReplyChannel"; }
|
||||
|
||||
private:
|
||||
explicit ReplyChannel(InvokeCallback callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
~ReplyChannel() override {
|
||||
if (callback_) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
// If there's no current context, it means we're shutting down, so we
|
||||
// don't need to send an event.
|
||||
if (!isolate->GetCurrentContext().IsEmpty()) {
|
||||
v8::HandleScope scope(isolate);
|
||||
auto message = gin::DataObjectBuilder(isolate)
|
||||
.Set("error", "reply was never sent")
|
||||
.Build();
|
||||
SendReply(isolate, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> arg) {
|
||||
if (!callback_)
|
||||
return false;
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, arg, &message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::move(callback_).Run(std::move(message));
|
||||
return true;
|
||||
}
|
||||
|
||||
InvokeCallback callback_;
|
||||
};
|
||||
|
||||
gin::WrapperInfo ReplyChannel::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> WebContents::MakeEventWithSender(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* frame,
|
||||
electron::mojom::ElectronApiIPC::InvokeCallback callback) {
|
||||
v8::Local<v8::Object> wrapper;
|
||||
if (!GetWrapper(isolate).ToLocal(&wrapper))
|
||||
return gin::Handle<gin_helper::internal::Event>();
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
gin_helper::internal::Event::New(isolate);
|
||||
gin_helper::Dictionary dict(isolate, event.ToV8().As<v8::Object>());
|
||||
if (callback)
|
||||
dict.Set("_replyChannel",
|
||||
ReplyChannel::Create(isolate, std::move(callback)));
|
||||
if (frame) {
|
||||
dict.Set("frameId", frame->GetRoutingID());
|
||||
dict.Set("processId", frame->GetProcess()->GetID());
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
void WebContents::ReceivePostMessage(
|
||||
const std::string& channel,
|
||||
blink::TransferableMessage message,
|
||||
|
||||
@@ -352,26 +352,20 @@ class WebContents : public ExclusiveAccessContext,
|
||||
// this.emit(name, new Event(sender, message), args...);
|
||||
template <typename... Args>
|
||||
bool EmitWithSender(base::StringPiece name,
|
||||
content::RenderFrameHost* frame,
|
||||
content::RenderFrameHost* sender,
|
||||
electron::mojom::ElectronApiIPC::InvokeCallback callback,
|
||||
Args&&... args) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
MakeEventWithSender(isolate, frame, std::move(callback));
|
||||
if (event.IsEmpty())
|
||||
v8::Local<v8::Object> wrapper;
|
||||
if (!GetWrapper(isolate).ToLocal(&wrapper))
|
||||
return false;
|
||||
EmitWithoutEvent(name, event, std::forward<Args>(args)...);
|
||||
return event->GetDefaultPrevented();
|
||||
v8::Local<v8::Object> event = gin_helper::internal::CreateNativeEvent(
|
||||
isolate, wrapper, sender, std::move(callback));
|
||||
return EmitCustomEvent(name, event, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> MakeEventWithSender(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* frame,
|
||||
electron::mojom::ElectronApiIPC::InvokeCallback callback);
|
||||
|
||||
WebContents* embedder() { return embedder_; }
|
||||
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
@@ -442,13 +436,7 @@ class WebContents : public ExclusiveAccessContext,
|
||||
// content::RenderWidgetHost::InputEventObserver:
|
||||
void OnInputEvent(const blink::WebInputEvent& event) override;
|
||||
|
||||
SkRegion* draggable_region() {
|
||||
return force_non_draggable_ ? nullptr : draggable_region_.get();
|
||||
}
|
||||
|
||||
void SetForceNonDraggable(bool force_non_draggable) {
|
||||
force_non_draggable_ = force_non_draggable;
|
||||
}
|
||||
SkRegion* draggable_region() { return draggable_region_.get(); }
|
||||
|
||||
// disable copy
|
||||
WebContents(const WebContents&) = delete;
|
||||
@@ -832,8 +820,6 @@ class WebContents : public ExclusiveAccessContext,
|
||||
|
||||
std::unique_ptr<SkRegion> draggable_region_;
|
||||
|
||||
bool force_non_draggable_ = false;
|
||||
|
||||
base::WeakPtrFactory<WebContents> weak_factory_{this};
|
||||
};
|
||||
|
||||
|
||||
@@ -94,34 +94,17 @@ struct UserData : public base::SupportsUserData::Data {
|
||||
WebRequest* data;
|
||||
};
|
||||
|
||||
extensions::WebRequestResourceType ParseResourceType(const std::string& value) {
|
||||
if (value == "mainFrame") {
|
||||
return extensions::WebRequestResourceType::MAIN_FRAME;
|
||||
} else if (value == "subFrame") {
|
||||
return extensions::WebRequestResourceType::SUB_FRAME;
|
||||
} else if (value == "stylesheet") {
|
||||
return extensions::WebRequestResourceType::STYLESHEET;
|
||||
} else if (value == "script") {
|
||||
return extensions::WebRequestResourceType::SCRIPT;
|
||||
} else if (value == "image") {
|
||||
return extensions::WebRequestResourceType::IMAGE;
|
||||
} else if (value == "font") {
|
||||
return extensions::WebRequestResourceType::FONT;
|
||||
} else if (value == "object") {
|
||||
return extensions::WebRequestResourceType::OBJECT;
|
||||
} else if (value == "xhr") {
|
||||
return extensions::WebRequestResourceType::XHR;
|
||||
} else if (value == "ping") {
|
||||
return extensions::WebRequestResourceType::PING;
|
||||
} else if (value == "cspReport") {
|
||||
return extensions::WebRequestResourceType::CSP_REPORT;
|
||||
} else if (value == "media") {
|
||||
return extensions::WebRequestResourceType::MEDIA;
|
||||
} else if (value == "webSocket") {
|
||||
return extensions::WebRequestResourceType::WEB_SOCKET;
|
||||
} else {
|
||||
return extensions::WebRequestResourceType::OTHER;
|
||||
// Test whether the URL of |request| matches |patterns|.
|
||||
bool MatchesFilterCondition(extensions::WebRequestInfo* info,
|
||||
const std::set<URLPattern>& patterns) {
|
||||
if (patterns.empty())
|
||||
return true;
|
||||
|
||||
for (const auto& pattern : patterns) {
|
||||
if (pattern.MatchesURL(info->url))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert HttpResponseHeaders to V8.
|
||||
@@ -264,54 +247,17 @@ void ReadFromResponse(v8::Isolate* isolate,
|
||||
|
||||
gin::WrapperInfo WebRequest::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
WebRequest::RequestFilter::RequestFilter(
|
||||
std::set<URLPattern> url_patterns,
|
||||
std::set<extensions::WebRequestResourceType> types)
|
||||
: url_patterns_(std::move(url_patterns)), types_(std::move(types)) {}
|
||||
WebRequest::RequestFilter::RequestFilter(const RequestFilter&) = default;
|
||||
WebRequest::RequestFilter::RequestFilter() = default;
|
||||
WebRequest::RequestFilter::~RequestFilter() = default;
|
||||
|
||||
void WebRequest::RequestFilter::AddUrlPattern(URLPattern pattern) {
|
||||
url_patterns_.emplace(std::move(pattern));
|
||||
}
|
||||
|
||||
void WebRequest::RequestFilter::AddType(
|
||||
extensions::WebRequestResourceType type) {
|
||||
types_.insert(type);
|
||||
}
|
||||
|
||||
bool WebRequest::RequestFilter::MatchesURL(const GURL& url) const {
|
||||
if (url_patterns_.empty())
|
||||
return true;
|
||||
|
||||
for (const auto& pattern : url_patterns_) {
|
||||
if (pattern.MatchesURL(url))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WebRequest::RequestFilter::MatchesType(
|
||||
extensions::WebRequestResourceType type) const {
|
||||
return types_.empty() || types_.find(type) != types_.end();
|
||||
}
|
||||
|
||||
bool WebRequest::RequestFilter::MatchesRequest(
|
||||
extensions::WebRequestInfo* info) const {
|
||||
return MatchesURL(info->url) && MatchesType(info->web_request_type);
|
||||
}
|
||||
|
||||
WebRequest::SimpleListenerInfo::SimpleListenerInfo(RequestFilter filter_,
|
||||
SimpleListener listener_)
|
||||
: filter(std::move(filter_)), listener(listener_) {}
|
||||
WebRequest::SimpleListenerInfo::SimpleListenerInfo(
|
||||
std::set<URLPattern> patterns_,
|
||||
SimpleListener listener_)
|
||||
: url_patterns(std::move(patterns_)), listener(listener_) {}
|
||||
WebRequest::SimpleListenerInfo::SimpleListenerInfo() = default;
|
||||
WebRequest::SimpleListenerInfo::~SimpleListenerInfo() = default;
|
||||
|
||||
WebRequest::ResponseListenerInfo::ResponseListenerInfo(
|
||||
RequestFilter filter_,
|
||||
std::set<URLPattern> patterns_,
|
||||
ResponseListener listener_)
|
||||
: filter(std::move(filter_)), listener(listener_) {}
|
||||
: url_patterns(std::move(patterns_)), listener(listener_) {}
|
||||
WebRequest::ResponseListenerInfo::ResponseListenerInfo() = default;
|
||||
WebRequest::ResponseListenerInfo::~ResponseListenerInfo() = default;
|
||||
|
||||
@@ -446,8 +392,8 @@ void WebRequest::SetListener(Event event,
|
||||
gin::Arguments* args) {
|
||||
v8::Local<v8::Value> arg;
|
||||
|
||||
// { urls, types }.
|
||||
std::set<std::string> filter_patterns, filter_types;
|
||||
// { urls }.
|
||||
std::set<std::string> filter_patterns;
|
||||
gin::Dictionary dict(args->isolate());
|
||||
if (args->GetNext(&arg) && !arg->IsFunction()) {
|
||||
// Note that gin treats Function as Dictionary when doing conversions, so we
|
||||
@@ -458,18 +404,16 @@ void WebRequest::SetListener(Event event,
|
||||
args->ThrowTypeError("Parameter 'filter' must have property 'urls'.");
|
||||
return;
|
||||
}
|
||||
dict.Get("types", &filter_types);
|
||||
args->GetNext(&arg);
|
||||
}
|
||||
}
|
||||
|
||||
RequestFilter filter;
|
||||
|
||||
std::set<URLPattern> patterns;
|
||||
for (const std::string& filter_pattern : filter_patterns) {
|
||||
URLPattern pattern(URLPattern::SCHEME_ALL);
|
||||
const URLPattern::ParseResult result = pattern.Parse(filter_pattern);
|
||||
if (result == URLPattern::ParseResult::kSuccess) {
|
||||
filter.AddUrlPattern(std::move(pattern));
|
||||
patterns.insert(pattern);
|
||||
} else {
|
||||
const char* error_type = URLPattern::GetParseResultString(result);
|
||||
args->ThrowTypeError("Invalid url pattern " + filter_pattern + ": " +
|
||||
@@ -478,16 +422,6 @@ void WebRequest::SetListener(Event event,
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::string& filter_type : filter_types) {
|
||||
auto type = ParseResourceType(filter_type);
|
||||
if (type != extensions::WebRequestResourceType::OTHER) {
|
||||
filter.AddType(type);
|
||||
} else {
|
||||
args->ThrowTypeError("Invalid type " + filter_type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Function or null.
|
||||
Listener listener;
|
||||
if (arg.IsEmpty() ||
|
||||
@@ -499,7 +433,7 @@ void WebRequest::SetListener(Event event,
|
||||
if (listener.is_null())
|
||||
listeners->erase(event);
|
||||
else
|
||||
(*listeners)[event] = {std::move(filter), std::move(listener)};
|
||||
(*listeners)[event] = {std::move(patterns), std::move(listener)};
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@@ -511,7 +445,7 @@ void WebRequest::HandleSimpleEvent(SimpleEvent event,
|
||||
return;
|
||||
|
||||
const auto& info = iter->second;
|
||||
if (!info.filter.MatchesRequest(request_info))
|
||||
if (!MatchesFilterCondition(request_info, info.url_patterns))
|
||||
return;
|
||||
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
@@ -532,7 +466,7 @@ int WebRequest::HandleResponseEvent(ResponseEvent event,
|
||||
return net::OK;
|
||||
|
||||
const auto& info = iter->second;
|
||||
if (!info.filter.MatchesRequest(request_info))
|
||||
if (!MatchesFilterCondition(request_info, info.url_patterns))
|
||||
return net::OK;
|
||||
|
||||
callbacks_[request_info->id] = std::move(callback);
|
||||
|
||||
@@ -123,41 +123,20 @@ class WebRequest : public gin::Wrappable<WebRequest>, public WebRequestAPI {
|
||||
template <typename T>
|
||||
void OnListenerResult(uint64_t id, T out, v8::Local<v8::Value> response);
|
||||
|
||||
class RequestFilter {
|
||||
public:
|
||||
RequestFilter(std::set<URLPattern>,
|
||||
std::set<extensions::WebRequestResourceType>);
|
||||
RequestFilter(const RequestFilter&);
|
||||
RequestFilter();
|
||||
~RequestFilter();
|
||||
|
||||
void AddUrlPattern(URLPattern pattern);
|
||||
void AddType(extensions::WebRequestResourceType type);
|
||||
|
||||
bool MatchesRequest(extensions::WebRequestInfo* info) const;
|
||||
|
||||
private:
|
||||
bool MatchesURL(const GURL& url) const;
|
||||
bool MatchesType(extensions::WebRequestResourceType type) const;
|
||||
|
||||
std::set<URLPattern> url_patterns_;
|
||||
std::set<extensions::WebRequestResourceType> types_;
|
||||
};
|
||||
|
||||
struct SimpleListenerInfo {
|
||||
RequestFilter filter;
|
||||
std::set<URLPattern> url_patterns;
|
||||
SimpleListener listener;
|
||||
|
||||
SimpleListenerInfo(RequestFilter, SimpleListener);
|
||||
SimpleListenerInfo(std::set<URLPattern>, SimpleListener);
|
||||
SimpleListenerInfo();
|
||||
~SimpleListenerInfo();
|
||||
};
|
||||
|
||||
struct ResponseListenerInfo {
|
||||
RequestFilter filter;
|
||||
std::set<URLPattern> url_patterns;
|
||||
ResponseListener listener;
|
||||
|
||||
ResponseListenerInfo(RequestFilter, ResponseListener);
|
||||
ResponseListenerInfo(std::set<URLPattern>, ResponseListener);
|
||||
ResponseListenerInfo();
|
||||
~ResponseListenerInfo();
|
||||
};
|
||||
|
||||
77
shell/browser/api/event.cc
Normal file
77
shell/browser/api/event.cc
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2014 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/event.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "gin/data_object_builder.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_converters/std_converter.h"
|
||||
|
||||
namespace gin_helper {
|
||||
|
||||
gin::WrapperInfo Event::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
Event::Event() = default;
|
||||
|
||||
Event::~Event() {
|
||||
if (callback_) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
// If there's no current context, it means we're shutting down, so we don't
|
||||
// need to send an event.
|
||||
if (!isolate->GetCurrentContext().IsEmpty()) {
|
||||
v8::HandleScope scope(isolate);
|
||||
auto message = gin::DataObjectBuilder(isolate)
|
||||
.Set("error", "reply was never sent")
|
||||
.Build();
|
||||
SendReply(isolate, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Event::SetCallback(InvokeCallback callback) {
|
||||
DCHECK(!callback_);
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void Event::PreventDefault(v8::Isolate* isolate) {
|
||||
v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
|
||||
self->Set(isolate->GetCurrentContext(),
|
||||
gin::StringToV8(isolate, "defaultPrevented"), v8::True(isolate))
|
||||
.Check();
|
||||
}
|
||||
|
||||
bool Event::SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result) {
|
||||
if (!callback_)
|
||||
return false;
|
||||
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, result, &message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::move(callback_).Run(std::move(message));
|
||||
return true;
|
||||
}
|
||||
|
||||
gin::ObjectTemplateBuilder Event::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::Wrappable<Event>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("preventDefault", &Event::PreventDefault)
|
||||
.SetMethod("sendReply", &Event::SendReply);
|
||||
}
|
||||
|
||||
const char* Event::GetTypeName() {
|
||||
return "Event";
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<Event> Event::Create(v8::Isolate* isolate) {
|
||||
return gin::CreateHandle(isolate, new Event());
|
||||
}
|
||||
|
||||
} // namespace gin_helper
|
||||
52
shell/browser/api/event.h
Normal file
52
shell/browser/api/event.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_API_EVENT_H_
|
||||
#define ELECTRON_SHELL_BROWSER_API_EVENT_H_
|
||||
|
||||
#include "electron/shell/common/api/api.mojom.h"
|
||||
#include "gin/handle.h"
|
||||
#include "gin/wrappable.h"
|
||||
|
||||
namespace gin_helper {
|
||||
|
||||
class Event : public gin::Wrappable<Event> {
|
||||
public:
|
||||
using InvokeCallback = electron::mojom::ElectronApiIPC::InvokeCallback;
|
||||
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
|
||||
static gin::Handle<Event> Create(v8::Isolate* isolate);
|
||||
|
||||
// Pass the callback to be invoked.
|
||||
void SetCallback(InvokeCallback callback);
|
||||
|
||||
// event.PreventDefault().
|
||||
void PreventDefault(v8::Isolate* isolate);
|
||||
|
||||
// event.sendReply(value), used for replying to synchronous messages and
|
||||
// `invoke` calls.
|
||||
bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result);
|
||||
|
||||
// disable copy
|
||||
Event(const Event&) = delete;
|
||||
Event& operator=(const Event&) = delete;
|
||||
|
||||
protected:
|
||||
Event();
|
||||
~Event() override;
|
||||
|
||||
// gin::Wrappable:
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
const char* GetTypeName() override;
|
||||
|
||||
private:
|
||||
// Replier for the synchronous messages.
|
||||
InvokeCallback callback_;
|
||||
};
|
||||
|
||||
} // namespace gin_helper
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_API_EVENT_H_
|
||||
@@ -61,7 +61,7 @@ class MessagePort : public gin::Wrappable<MessagePort>, mojo::MessageReceiver {
|
||||
// The blink version of MessagePort uses the very nice "ActiveScriptWrapper"
|
||||
// class, which keeps the object alive through the V8 embedder hooks into the
|
||||
// GC lifecycle: see
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/heap/thread_state.cc;l=258;drc=b892cf58e162a8f66cd76d7472f129fe0fb6a7d1
|
||||
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state.cc;l=258;drc=b892cf58e162a8f66cd76d7472f129fe0fb6a7d1
|
||||
// We do not have that luxury, so we brutishly use v8::Global to accomplish
|
||||
// something similar. Critically, whenever the value of
|
||||
// "HasPendingActivity()" changes, we must call Pin() or Unpin() as
|
||||
|
||||
@@ -270,7 +270,7 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
|
||||
|
||||
v8::HandleScope scope(js_env_->isolate());
|
||||
|
||||
node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
|
||||
node_bindings_->Initialize();
|
||||
// Create the global environment.
|
||||
node::Environment* env = node_bindings_->CreateEnvironment(
|
||||
js_env_->isolate()->GetCurrentContext(), js_env_->platform());
|
||||
@@ -561,7 +561,7 @@ void ElectronBrowserMainParts::PostCreateMainMessageLoop() {
|
||||
config->main_thread_runner =
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault();
|
||||
// c.f.
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/chrome_switches.cc;l=689;drc=9d82515060b9b75fa941986f5db7390299669ef1
|
||||
// https://source.chromium.org/chromium/chromium/src/+/master:chrome/common/chrome_switches.cc;l=689;drc=9d82515060b9b75fa941986f5db7390299669ef1
|
||||
config->should_use_preference =
|
||||
command_line.HasSwitch(::switches::kEnableEncryptionSelection);
|
||||
base::PathService::Get(DIR_SESSION_DATA, &config->user_data_path);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright (c) 2023 Salesforce, Inc.
|
||||
// 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/common/gin_helper/event_emitter_template.h"
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
|
||||
#include "gin/converter.h"
|
||||
#include "gin/per_isolate_data.h"
|
||||
#include "gin/public/wrapper_info.h"
|
||||
#include "shell/browser/api/electron_api_event_emitter.h"
|
||||
#include "v8/include/v8-function.h"
|
||||
#include "v8/include/v8-template.h"
|
||||
|
||||
namespace gin_helper::internal {
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "gin/handle.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
#include "shell/common/gin_helper/event_emitter.h"
|
||||
|
||||
namespace gin_helper {
|
||||
|
||||
namespace internal {
|
||||
v8::Local<v8::FunctionTemplate> GetEventEmitterTemplate(v8::Isolate* isolate);
|
||||
} // namespace internal
|
||||
|
||||
template <typename T>
|
||||
class EventEmitterMixin {
|
||||
public:
|
||||
@@ -31,15 +33,14 @@ class EventEmitterMixin {
|
||||
v8::Local<v8::Object> wrapper;
|
||||
if (!static_cast<T*>(this)->GetWrapper(isolate).ToLocal(&wrapper))
|
||||
return false;
|
||||
gin::Handle<internal::Event> event = internal::Event::New(isolate);
|
||||
gin_helper::EmitEvent(isolate, wrapper, name, event,
|
||||
std::forward<Args>(args)...);
|
||||
return event->GetDefaultPrevented();
|
||||
v8::Local<v8::Object> event = internal::CreateCustomEvent(isolate, wrapper);
|
||||
return EmitWithEvent(isolate, wrapper, name, event,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// this.emit(name, args...);
|
||||
template <typename... Args>
|
||||
void EmitWithoutEvent(base::StringPiece name, Args&&... args) {
|
||||
void EmitWithoutCustomEvent(base::StringPiece name, Args&&... args) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Local<v8::Object> wrapper;
|
||||
@@ -48,6 +49,20 @@ class EventEmitterMixin {
|
||||
gin_helper::EmitEvent(isolate, wrapper, name, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// this.emit(name, event, args...);
|
||||
template <typename... Args>
|
||||
bool EmitCustomEvent(base::StringPiece name,
|
||||
v8::Local<v8::Object> custom_event,
|
||||
Args&&... args) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> wrapper;
|
||||
if (!static_cast<T*>(this)->GetWrapper(isolate).ToLocal(&wrapper))
|
||||
return false;
|
||||
return EmitWithEvent(isolate, wrapper, name, custom_event,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
protected:
|
||||
EventEmitterMixin() = default;
|
||||
|
||||
@@ -67,6 +82,25 @@ class EventEmitterMixin {
|
||||
static_cast<T*>(this)->GetTypeName(),
|
||||
constructor->InstanceTemplate());
|
||||
}
|
||||
|
||||
private:
|
||||
// this.emit(name, event, args...);
|
||||
template <typename... Args>
|
||||
static bool EmitWithEvent(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> wrapper,
|
||||
base::StringPiece name,
|
||||
v8::Local<v8::Object> event,
|
||||
Args&&... args) {
|
||||
auto context = isolate->GetCurrentContext();
|
||||
gin_helper::EmitEvent(isolate, wrapper, name, event,
|
||||
std::forward<Args>(args)...);
|
||||
v8::Local<v8::Value> defaultPrevented;
|
||||
if (event->Get(context, gin::StringToV8(isolate, "defaultPrevented"))
|
||||
.ToLocal(&defaultPrevented)) {
|
||||
return defaultPrevented->BooleanValue(isolate);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin_helper
|
||||
|
||||
@@ -219,8 +219,8 @@ class NativeWindow : public base::SupportsUserData,
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
virtual void SetWindowButtonVisibility(bool visible) = 0;
|
||||
virtual bool GetWindowButtonVisibility() const = 0;
|
||||
virtual void SetWindowButtonPosition(absl::optional<gfx::Point> position) = 0;
|
||||
virtual absl::optional<gfx::Point> GetWindowButtonPosition() const = 0;
|
||||
virtual void SetTrafficLightPosition(absl::optional<gfx::Point> position) = 0;
|
||||
virtual absl::optional<gfx::Point> GetTrafficLightPosition() const = 0;
|
||||
virtual void RedrawTrafficLights() = 0;
|
||||
virtual void UpdateFrame() = 0;
|
||||
#endif
|
||||
|
||||
@@ -129,8 +129,8 @@ class NativeWindowMac : public NativeWindow,
|
||||
void SetVibrancy(const std::string& type) override;
|
||||
void SetWindowButtonVisibility(bool visible) override;
|
||||
bool GetWindowButtonVisibility() const override;
|
||||
void SetWindowButtonPosition(absl::optional<gfx::Point> position) override;
|
||||
absl::optional<gfx::Point> GetWindowButtonPosition() const override;
|
||||
void SetTrafficLightPosition(absl::optional<gfx::Point> position) override;
|
||||
absl::optional<gfx::Point> GetTrafficLightPosition() const override;
|
||||
void RedrawTrafficLights() override;
|
||||
void UpdateFrame() override;
|
||||
void SetTouchBar(
|
||||
|
||||
@@ -1215,7 +1215,7 @@ content::DesktopMediaID NativeWindowMac::GetDesktopMediaID() const {
|
||||
auto desktop_media_id = content::DesktopMediaID(
|
||||
content::DesktopMediaID::TYPE_WINDOW, GetAcceleratedWidget());
|
||||
// c.f.
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/media/webrtc/native_desktop_media_list.cc;l=775-780;drc=79502ab47f61bff351426f57f576daef02b1a8dc
|
||||
// https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/media/webrtc/native_desktop_media_list.cc;l=372?q=kWindowCaptureMacV2&ss=chromium
|
||||
// Refs https://github.com/electron/electron/pull/30507
|
||||
// TODO(deepak1556): Match upstream for `kWindowCaptureMacV2`
|
||||
#if 0
|
||||
@@ -1474,7 +1474,7 @@ bool NativeWindowMac::GetWindowButtonVisibility() const {
|
||||
![window_ standardWindowButton:NSWindowCloseButton].hidden;
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetWindowButtonPosition(
|
||||
void NativeWindowMac::SetTrafficLightPosition(
|
||||
absl::optional<gfx::Point> position) {
|
||||
traffic_light_position_ = std::move(position);
|
||||
if (buttons_proxy_) {
|
||||
@@ -1483,7 +1483,7 @@ void NativeWindowMac::SetWindowButtonPosition(
|
||||
}
|
||||
}
|
||||
|
||||
absl::optional<gfx::Point> NativeWindowMac::GetWindowButtonPosition() const {
|
||||
absl::optional<gfx::Point> NativeWindowMac::GetTrafficLightPosition() const {
|
||||
return traffic_light_position_;
|
||||
}
|
||||
|
||||
|
||||
179
shell/browser/net/electron_url_loader_factory.cc
Normal file → Executable file
179
shell/browser/net/electron_url_loader_factory.cc
Normal file → Executable file
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "shell/browser/net/electron_url_loader_factory.h"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -82,19 +81,6 @@ bool ResponseMustBeObject(ProtocolType type) {
|
||||
}
|
||||
}
|
||||
|
||||
bool LooksLikeStream(v8::Isolate* isolate, v8::Local<v8::Value> v) {
|
||||
// the stream loader can handle null and undefined as "empty body". Could
|
||||
// probably be more efficient here but this works.
|
||||
if (v->IsNullOrUndefined())
|
||||
return true;
|
||||
if (!v->IsObject())
|
||||
return false;
|
||||
gin_helper::Dictionary dict(isolate, v.As<v8::Object>());
|
||||
v8::Local<v8::Value> method;
|
||||
return dict.Get("on", &method) && method->IsFunction() &&
|
||||
dict.Get("removeListener", &method) && method->IsFunction();
|
||||
}
|
||||
|
||||
// Helper to convert value to Dictionary.
|
||||
gin::Dictionary ToDict(v8::Isolate* isolate, v8::Local<v8::Value> value) {
|
||||
if (!value->IsFunction() && value->IsObject())
|
||||
@@ -404,94 +390,36 @@ void ElectronURLLoaderFactory::StartLoading(
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
// DEPRECATED: Soon only |kFree| will be supported!
|
||||
case ProtocolType::kBuffer:
|
||||
if (response->IsArrayBufferView())
|
||||
StartLoadingBuffer(std::move(client), std::move(head),
|
||||
response.As<v8::ArrayBufferView>());
|
||||
else if (v8::Local<v8::Value> data; !dict.IsEmpty() &&
|
||||
dict.Get("data", &data) &&
|
||||
data->IsArrayBufferView())
|
||||
StartLoadingBuffer(std::move(client), std::move(head),
|
||||
data.As<v8::ArrayBufferView>());
|
||||
else
|
||||
OnComplete(std::move(client), request_id,
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
StartLoadingBuffer(std::move(client), std::move(head), dict);
|
||||
break;
|
||||
case ProtocolType::kString: {
|
||||
std::string data;
|
||||
if (gin::ConvertFromV8(args->isolate(), response, &data))
|
||||
SendContents(std::move(client), std::move(head), data);
|
||||
else if (!dict.IsEmpty() && dict.Get("data", &data))
|
||||
SendContents(std::move(client), std::move(head), data);
|
||||
else
|
||||
OnComplete(std::move(client), request_id,
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
case ProtocolType::kString:
|
||||
StartLoadingString(std::move(client), std::move(head), dict,
|
||||
args->isolate(), response);
|
||||
break;
|
||||
}
|
||||
case ProtocolType::kFile: {
|
||||
base::FilePath path;
|
||||
if (gin::ConvertFromV8(args->isolate(), response, &path))
|
||||
StartLoadingFile(std::move(client), std::move(loader), std::move(head),
|
||||
request, path, dict);
|
||||
else if (!dict.IsEmpty() && dict.Get("path", &path))
|
||||
StartLoadingFile(std::move(client), std::move(loader), std::move(head),
|
||||
request, path, dict);
|
||||
else
|
||||
OnComplete(std::move(client), request_id,
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
case ProtocolType::kFile:
|
||||
StartLoadingFile(std::move(loader), request, std::move(client),
|
||||
std::move(head), dict, args->isolate(), response);
|
||||
break;
|
||||
}
|
||||
case ProtocolType::kHttp:
|
||||
if (GURL url; !dict.IsEmpty() && dict.Get("url", &url) && url.is_valid())
|
||||
StartLoadingHttp(std::move(client), std::move(loader), request,
|
||||
traffic_annotation, dict);
|
||||
else
|
||||
OnComplete(std::move(client), request_id,
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
StartLoadingHttp(std::move(loader), request, std::move(client),
|
||||
traffic_annotation, dict);
|
||||
break;
|
||||
case ProtocolType::kStream:
|
||||
StartLoadingStream(std::move(client), std::move(loader), std::move(head),
|
||||
StartLoadingStream(std::move(loader), std::move(client), std::move(head),
|
||||
dict);
|
||||
break;
|
||||
|
||||
case ProtocolType::kFree: {
|
||||
// Infer the type based on the object given
|
||||
v8::Local<v8::Value> data;
|
||||
if (!dict.IsEmpty() && dict.Has("data"))
|
||||
dict.Get("data", &data);
|
||||
else
|
||||
data = response;
|
||||
|
||||
// |data| can be either a string, a buffer or a stream.
|
||||
if (data->IsArrayBufferView()) {
|
||||
StartLoadingBuffer(std::move(client), std::move(head),
|
||||
data.As<v8::ArrayBufferView>());
|
||||
} else if (data->IsString()) {
|
||||
SendContents(std::move(client), std::move(head),
|
||||
gin::V8ToString(args->isolate(), data));
|
||||
} else if (LooksLikeStream(args->isolate(), data)) {
|
||||
StartLoadingStream(std::move(client), std::move(loader),
|
||||
std::move(head), dict);
|
||||
} else if (!dict.IsEmpty()) {
|
||||
// |data| wasn't specified, so look for |response.url| or
|
||||
// |response.path|.
|
||||
if (GURL url; dict.Get("url", &url))
|
||||
StartLoadingHttp(std::move(client), std::move(loader), request,
|
||||
traffic_annotation, dict);
|
||||
else if (base::FilePath path; dict.Get("path", &path))
|
||||
StartLoadingFile(std::move(client), std::move(loader),
|
||||
std::move(head), request, path, dict);
|
||||
else
|
||||
// Don't know what kind of response this is, so fail.
|
||||
OnComplete(std::move(client), request_id,
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
} else {
|
||||
case ProtocolType::kFree:
|
||||
ProtocolType protocol_type;
|
||||
if (!gin::ConvertFromV8(args->isolate(), response, &protocol_type)) {
|
||||
OnComplete(std::move(client), request_id,
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
return;
|
||||
}
|
||||
StartLoading(std::move(loader), request_id, options, request,
|
||||
std::move(client), traffic_annotation,
|
||||
std::move(target_factory), protocol_type, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,25 +427,68 @@ void ElectronURLLoaderFactory::StartLoading(
|
||||
void ElectronURLLoaderFactory::StartLoadingBuffer(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
v8::Local<v8::ArrayBufferView> buffer) {
|
||||
SendContents(std::move(client), std::move(head),
|
||||
std::string(node::Buffer::Data(buffer.As<v8::Value>()),
|
||||
node::Buffer::Length(buffer.As<v8::Value>())));
|
||||
const gin_helper::Dictionary& dict) {
|
||||
v8::Local<v8::Value> buffer = dict.GetHandle();
|
||||
dict.Get("data", &buffer);
|
||||
if (!node::Buffer::HasInstance(buffer)) {
|
||||
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
|
||||
std::move(client));
|
||||
client_remote->OnComplete(
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
return;
|
||||
}
|
||||
|
||||
SendContents(
|
||||
std::move(client), std::move(head),
|
||||
std::string(node::Buffer::Data(buffer), node::Buffer::Length(buffer)));
|
||||
}
|
||||
|
||||
// static
|
||||
void ElectronURLLoaderFactory::StartLoadingString(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
const gin_helper::Dictionary& dict,
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> response) {
|
||||
std::string contents;
|
||||
if (response->IsString()) {
|
||||
contents = gin::V8ToString(isolate, response);
|
||||
} else if (!dict.IsEmpty()) {
|
||||
dict.Get("data", &contents);
|
||||
} else {
|
||||
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
|
||||
std::move(client));
|
||||
client_remote->OnComplete(
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
return;
|
||||
}
|
||||
|
||||
SendContents(std::move(client), std::move(head), std::move(contents));
|
||||
}
|
||||
|
||||
// static
|
||||
void ElectronURLLoaderFactory::StartLoadingFile(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
network::ResourceRequest request,
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
const network::ResourceRequest& original_request,
|
||||
const base::FilePath& path,
|
||||
const gin_helper::Dictionary& opts) {
|
||||
network::ResourceRequest request = original_request;
|
||||
request.url = net::FilePathToFileURL(path);
|
||||
if (!opts.IsEmpty()) {
|
||||
opts.Get("referrer", &request.referrer);
|
||||
opts.Get("method", &request.method);
|
||||
const gin_helper::Dictionary& dict,
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> response) {
|
||||
base::FilePath path;
|
||||
if (gin::ConvertFromV8(isolate, response, &path)) {
|
||||
request.url = net::FilePathToFileURL(path);
|
||||
} else if (!dict.IsEmpty()) {
|
||||
dict.Get("referrer", &request.referrer);
|
||||
dict.Get("method", &request.method);
|
||||
if (dict.Get("path", &path))
|
||||
request.url = net::FilePathToFileURL(path);
|
||||
} else {
|
||||
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
|
||||
std::move(client));
|
||||
client_remote->OnComplete(
|
||||
network::URLLoaderCompletionStatus(net::ERR_FAILED));
|
||||
return;
|
||||
}
|
||||
|
||||
// Add header to ignore CORS.
|
||||
@@ -528,9 +499,9 @@ void ElectronURLLoaderFactory::StartLoadingFile(
|
||||
|
||||
// static
|
||||
void ElectronURLLoaderFactory::StartLoadingHttp(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
const network::ResourceRequest& original_request,
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
|
||||
const gin_helper::Dictionary& dict) {
|
||||
auto request = std::make_unique<network::ResourceRequest>();
|
||||
@@ -572,8 +543,8 @@ void ElectronURLLoaderFactory::StartLoadingHttp(
|
||||
|
||||
// static
|
||||
void ElectronURLLoaderFactory::StartLoadingStream(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
const gin_helper::Dictionary& dict) {
|
||||
v8::Local<v8::Value> stream;
|
||||
|
||||
@@ -135,27 +135,33 @@ class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
int32_t request_id,
|
||||
const network::URLLoaderCompletionStatus& status);
|
||||
|
||||
static void StartLoadingBuffer(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
v8::Local<v8::ArrayBufferView> buffer);
|
||||
static void StartLoadingFile(
|
||||
const gin_helper::Dictionary& dict);
|
||||
static void StartLoadingString(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
const network::ResourceRequest& original_request,
|
||||
const base::FilePath& path,
|
||||
const gin_helper::Dictionary& opts);
|
||||
static void StartLoadingHttp(
|
||||
const gin_helper::Dictionary& dict,
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> response);
|
||||
static void StartLoadingFile(
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
network::ResourceRequest request,
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
const gin_helper::Dictionary& dict,
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> response);
|
||||
static void StartLoadingHttp(
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
const network::ResourceRequest& original_request,
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
|
||||
const gin_helper::Dictionary& dict);
|
||||
static void StartLoadingStream(
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
const gin_helper::Dictionary& dict);
|
||||
|
||||
|
||||
21
shell/browser/ui/cocoa/delayed_native_view_host.cc
Normal file
21
shell/browser/ui/cocoa/delayed_native_view_host.cc
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view)
|
||||
: native_view_(native_view) {}
|
||||
|
||||
DelayedNativeViewHost::~DelayedNativeViewHost() = default;
|
||||
|
||||
void DelayedNativeViewHost::ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) {
|
||||
NativeViewHost::ViewHierarchyChanged(details);
|
||||
if (details.is_add && GetWidget())
|
||||
Attach(native_view_);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -23,7 +23,6 @@ class DelayedNativeViewHost : public views::NativeViewHost {
|
||||
// views::View:
|
||||
void ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) override;
|
||||
bool OnMousePressed(const ui::MouseEvent& event) override;
|
||||
|
||||
private:
|
||||
gfx::NativeView native_view_;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
#include "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view)
|
||||
: native_view_(native_view) {}
|
||||
|
||||
DelayedNativeViewHost::~DelayedNativeViewHost() = default;
|
||||
|
||||
void DelayedNativeViewHost::ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) {
|
||||
NativeViewHost::ViewHierarchyChanged(details);
|
||||
if (details.is_add && GetWidget())
|
||||
Attach(native_view_);
|
||||
}
|
||||
|
||||
bool DelayedNativeViewHost::OnMousePressed(const ui::MouseEvent& ui_event) {
|
||||
// NativeViewHost::OnMousePressed normally isn't called, but
|
||||
// NativeWidgetMacNSWindow specifically carves out an event here for
|
||||
// right-mouse-button clicks. We want to forward them to the web content, so
|
||||
// handle them here.
|
||||
// See:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm;l=415-421;drc=a5af91924bafb85426e091c6035801990a6dc697
|
||||
ElectronInspectableWebContentsView* inspectable_web_contents_view =
|
||||
(ElectronInspectableWebContentsView*)native_view_.GetNativeNSView();
|
||||
[inspectable_web_contents_view
|
||||
redispatchContextMenuEvent:ui_event.native_event()];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
@@ -46,8 +46,6 @@ using electron::InspectableWebContentsViewMac;
|
||||
(const DevToolsContentsResizingStrategy&)strategy;
|
||||
- (void)setTitle:(NSString*)title;
|
||||
|
||||
- (void)redispatchContextMenuEvent:(NSEvent*)theEvent;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
#include "shell/browser/ui/cocoa/electron_inspectable_web_contents_view.h"
|
||||
|
||||
#include "content/public/browser/render_widget_host_view.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/ui/cocoa/event_dispatching_window.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_mac.h"
|
||||
#include "ui/base/cocoa/base_view.h"
|
||||
#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
|
||||
|
||||
@implementation ElectronInspectableWebContentsView
|
||||
@@ -275,27 +273,6 @@
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
- (void)redispatchContextMenuEvent:(NSEvent*)event {
|
||||
DCHECK(event.type == NSEventTypeRightMouseDown ||
|
||||
(event.type == NSEventTypeLeftMouseDown &&
|
||||
(event.modifierFlags & NSEventModifierFlagControl)));
|
||||
content::WebContents* contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()->GetWebContents();
|
||||
electron::api::WebContents* api_contents =
|
||||
electron::api::WebContents::From(contents);
|
||||
if (api_contents) {
|
||||
// Temporarily pretend that the WebContents is fully non-draggable while we
|
||||
// re-send the mouse event. This allows the re-dispatched event to "land"
|
||||
// on the WebContents, instead of "falling through" back to the window.
|
||||
api_contents->SetForceNonDraggable(true);
|
||||
BaseView* contentsView = (BaseView*)contents->GetRenderWidgetHostView()
|
||||
->GetNativeView()
|
||||
.GetNativeNSView();
|
||||
[contentsView mouseEvent:event];
|
||||
api_contents->SetForceNonDraggable(false);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notification {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "ui/gtk/gtk_compat.h" // nogncheck
|
||||
|
||||
// The following utilities are pulled from
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gtk/select_file_dialog_linux_gtk.cc;l=44-75;drc=a03ba4ca94f75531207c3ea832d6a605cde77394
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gtk/select_file_dialog_impl_gtk.cc;l=43-74
|
||||
namespace gtk_util {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -64,7 +64,6 @@ v8::Local<v8::Promise> OpenExternal(const GURL& url, gin::Arguments* args) {
|
||||
if (args->GetNext(&obj)) {
|
||||
obj.Get("activate", &options.activate);
|
||||
obj.Get("workingDirectory", &options.working_dir);
|
||||
obj.Get("logUsage", &options.log_usage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2023 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_COMMON_GIN_CONVERTERS_OPTIONAL_CONVERTER_H_
|
||||
#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_OPTIONAL_CONVERTER_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "gin/converter.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
|
||||
namespace gin {
|
||||
|
||||
template <typename T>
|
||||
struct Converter<absl::optional<T>> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const absl::optional<T>& val) {
|
||||
if (val)
|
||||
return Converter<T>::ToV8(isolate, val.value());
|
||||
else
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
absl::optional<T>* out) {
|
||||
T converted;
|
||||
if (Converter<T>::FromV8(isolate, val, &converted))
|
||||
out->emplace(std::move(converted));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_OPTIONAL_CONVERTER_H_
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "gin/per_isolate_data.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/common/gin_helper/event_emitter_template.h"
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
#include "shell/common/gin_helper/function_template_extensions.h"
|
||||
|
||||
namespace gin_helper {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2023 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
|
||||
namespace gin_helper::internal {
|
||||
|
||||
// static
|
||||
gin::Handle<Event> Event::New(v8::Isolate* isolate) {
|
||||
return gin::CreateHandle(isolate, new Event());
|
||||
}
|
||||
// static
|
||||
v8::Local<v8::ObjectTemplate> Event::FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> templ) {
|
||||
return gin::ObjectTemplateBuilder(isolate, "Event", templ)
|
||||
.SetMethod("preventDefault", &Event::PreventDefault)
|
||||
.SetProperty("defaultPrevented", &Event::GetDefaultPrevented)
|
||||
.Build();
|
||||
}
|
||||
|
||||
Event::Event() = default;
|
||||
|
||||
Event::~Event() = default;
|
||||
|
||||
gin::WrapperInfo Event::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
} // namespace gin_helper::internal
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2023 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_H_
|
||||
#define ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_H_
|
||||
|
||||
#include "gin/handle.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/common/gin_helper/constructible.h"
|
||||
|
||||
namespace v8 {
|
||||
class Isolate;
|
||||
template <typename T>
|
||||
class Local;
|
||||
class Object;
|
||||
class ObjectTemplate;
|
||||
} // namespace v8
|
||||
|
||||
namespace gin_helper::internal {
|
||||
|
||||
class Event : public gin::Wrappable<Event>,
|
||||
public gin_helper::Constructible<Event> {
|
||||
public:
|
||||
// gin_helper::Constructible
|
||||
static gin::Handle<Event> New(v8::Isolate* isolate);
|
||||
static v8::Local<v8::ObjectTemplate> FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> prototype);
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
|
||||
~Event() override;
|
||||
|
||||
void PreventDefault() { default_prevented_ = true; }
|
||||
|
||||
bool GetDefaultPrevented() { return default_prevented_; }
|
||||
|
||||
private:
|
||||
Event();
|
||||
|
||||
bool default_prevented_ = false;
|
||||
};
|
||||
|
||||
} // namespace gin_helper::internal
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_H_
|
||||
76
shell/common/gin_helper/event_emitter.cc
Normal file
76
shell/common/gin_helper/event_emitter.cc
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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/common/gin_helper/event_emitter.h"
|
||||
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "shell/browser/api/event.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
|
||||
namespace gin_helper::internal {
|
||||
|
||||
namespace {
|
||||
|
||||
v8::Persistent<v8::ObjectTemplate> event_template;
|
||||
|
||||
void PreventDefault(gin_helper::Arguments* args) {
|
||||
Dictionary self;
|
||||
if (args->GetHolder(&self))
|
||||
self.Set("defaultPrevented", true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
v8::Local<v8::Object> CreateCustomEvent(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender,
|
||||
v8::Local<v8::Object> custom_event) {
|
||||
if (event_template.IsEmpty()) {
|
||||
event_template.Reset(
|
||||
isolate,
|
||||
ObjectTemplateBuilder(isolate, v8::ObjectTemplate::New(isolate))
|
||||
.SetMethod("preventDefault", &PreventDefault)
|
||||
.Build());
|
||||
}
|
||||
|
||||
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
||||
CHECK(!context.IsEmpty());
|
||||
v8::Local<v8::Object> event =
|
||||
v8::Local<v8::ObjectTemplate>::New(isolate, event_template)
|
||||
->NewInstance(context)
|
||||
.ToLocalChecked();
|
||||
if (!sender.IsEmpty())
|
||||
Dictionary(isolate, event).Set("sender", sender);
|
||||
if (!custom_event.IsEmpty())
|
||||
event->SetPrototype(context, custom_event).IsJust();
|
||||
return event;
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> CreateNativeEvent(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender,
|
||||
content::RenderFrameHost* frame,
|
||||
electron::mojom::ElectronApiIPC::MessageSyncCallback callback) {
|
||||
v8::Local<v8::Object> event;
|
||||
if (frame && callback) {
|
||||
gin::Handle<Event> native_event = Event::Create(isolate);
|
||||
native_event->SetCallback(std::move(callback));
|
||||
event = native_event.ToV8().As<v8::Object>();
|
||||
} else {
|
||||
// No need to create native event if we do not need to send reply.
|
||||
event = CreateCustomEvent(isolate);
|
||||
}
|
||||
|
||||
Dictionary dict(isolate, event);
|
||||
dict.Set("sender", sender);
|
||||
// Should always set frameId even when callback is null.
|
||||
if (frame) {
|
||||
dict.Set("frameId", frame->GetRoutingID());
|
||||
dict.Set("processId", frame->GetProcess()->GetID());
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
} // namespace gin_helper::internal
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "electron/shell/common/api/api.mojom.h"
|
||||
#include "gin/handle.h"
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "shell/common/gin_helper/wrappable.h"
|
||||
|
||||
@@ -21,6 +19,20 @@ class RenderFrameHost;
|
||||
|
||||
namespace gin_helper {
|
||||
|
||||
namespace internal {
|
||||
|
||||
v8::Local<v8::Object> CreateCustomEvent(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender = v8::Local<v8::Object>(),
|
||||
v8::Local<v8::Object> custom_event = v8::Local<v8::Object>());
|
||||
v8::Local<v8::Object> CreateNativeEvent(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> sender,
|
||||
content::RenderFrameHost* frame,
|
||||
electron::mojom::ElectronApiIPC::MessageSyncCallback callback);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// Provide helperers to emit event in JavaScript.
|
||||
template <typename T>
|
||||
class EventEmitter : public gin_helper::Wrappable<T> {
|
||||
@@ -36,6 +48,16 @@ class EventEmitter : public gin_helper::Wrappable<T> {
|
||||
return Base::GetWrapper(isolate);
|
||||
}
|
||||
|
||||
// this.emit(name, event, args...);
|
||||
template <typename... Args>
|
||||
bool EmitCustomEvent(base::StringPiece name,
|
||||
v8::Local<v8::Object> event,
|
||||
Args&&... args) {
|
||||
return EmitWithEvent(
|
||||
name, internal::CreateCustomEvent(isolate(), GetWrapper(), event),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// this.emit(name, new Event(), args...);
|
||||
template <typename... Args>
|
||||
bool Emit(base::StringPiece name, Args&&... args) {
|
||||
@@ -43,8 +65,8 @@ class EventEmitter : public gin_helper::Wrappable<T> {
|
||||
v8::Local<v8::Object> wrapper = GetWrapper();
|
||||
if (wrapper.IsEmpty())
|
||||
return false;
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
internal::Event::New(isolate());
|
||||
v8::Local<v8::Object> event =
|
||||
internal::CreateCustomEvent(isolate(), wrapper);
|
||||
return EmitWithEvent(name, event, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -59,14 +81,20 @@ class EventEmitter : public gin_helper::Wrappable<T> {
|
||||
// this.emit(name, event, args...);
|
||||
template <typename... Args>
|
||||
bool EmitWithEvent(base::StringPiece name,
|
||||
gin::Handle<gin_helper::internal::Event> event,
|
||||
v8::Local<v8::Object> event,
|
||||
Args&&... args) {
|
||||
// It's possible that |this| will be deleted by EmitEvent, so save anything
|
||||
// we need from |this| before calling EmitEvent.
|
||||
auto* isolate = this->isolate();
|
||||
auto context = isolate->GetCurrentContext();
|
||||
gin_helper::EmitEvent(isolate, GetWrapper(), name, event,
|
||||
std::forward<Args>(args)...);
|
||||
return event->GetDefaultPrevented();
|
||||
v8::Local<v8::Value> defaultPrevented;
|
||||
if (event->Get(context, gin::StringToV8(isolate, "defaultPrevented"))
|
||||
.ToLocal(&defaultPrevented)) {
|
||||
return defaultPrevented->BooleanValue(isolate);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) 2023 Salesforce, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_EMITTER_TEMPLATE_H_
|
||||
#define ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_EMITTER_TEMPLATE_H_
|
||||
|
||||
namespace v8 {
|
||||
class Isolate;
|
||||
template <typename T>
|
||||
class Local;
|
||||
class FunctionTemplate;
|
||||
} // namespace v8
|
||||
|
||||
namespace gin_helper::internal {
|
||||
v8::Local<v8::FunctionTemplate> GetEventEmitterTemplate(v8::Isolate* isolate);
|
||||
}
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_EMITTER_TEMPLATE_H_
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "shell/common/electron_command_line.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "shell/common/gin_helper/locker.h"
|
||||
#include "shell/common/gin_helper/microtasks_scope.h"
|
||||
@@ -51,6 +50,7 @@
|
||||
V(electron_browser_content_tracing) \
|
||||
V(electron_browser_crash_reporter) \
|
||||
V(electron_browser_dialog) \
|
||||
V(electron_browser_event) \
|
||||
V(electron_browser_event_emitter) \
|
||||
V(electron_browser_global_shortcut) \
|
||||
V(electron_browser_in_app_purchase) \
|
||||
@@ -415,7 +415,7 @@ void NodeBindings::SetNodeCliFlags() {
|
||||
}
|
||||
}
|
||||
|
||||
void NodeBindings::Initialize(v8::Local<v8::Context> context) {
|
||||
void NodeBindings::Initialize() {
|
||||
TRACE_EVENT0("electron", "NodeBindings::Initialize");
|
||||
// Open node's error reporting system for browser process.
|
||||
|
||||
@@ -463,8 +463,6 @@ void NodeBindings::Initialize(v8::Local<v8::Context> context) {
|
||||
SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX);
|
||||
#endif
|
||||
|
||||
gin_helper::internal::Event::GetConstructor(context);
|
||||
|
||||
g_is_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class NodeBindings {
|
||||
virtual ~NodeBindings();
|
||||
|
||||
// Setup V8, libuv.
|
||||
void Initialize(v8::Local<v8::Context> context);
|
||||
void Initialize();
|
||||
|
||||
void SetNodeCliFlags();
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ void OpenPath(const base::FilePath& full_path, OpenCallback callback);
|
||||
struct OpenExternalOptions {
|
||||
bool activate = true;
|
||||
base::FilePath working_dir;
|
||||
bool log_usage = false;
|
||||
};
|
||||
|
||||
// Open the given external protocol URL in the desktop's default manner.
|
||||
|
||||
@@ -246,19 +246,10 @@ std::string OpenExternalOnWorkerThread(
|
||||
L"\"";
|
||||
std::wstring working_dir = options.working_dir.value();
|
||||
|
||||
SHELLEXECUTEINFO info = {};
|
||||
info.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
info.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
|
||||
info.lpVerb = L"open";
|
||||
info.lpFile = escaped_url.c_str();
|
||||
info.lpDirectory = working_dir.empty() ? nullptr : working_dir.c_str();
|
||||
info.nShow = SW_SHOWNORMAL;
|
||||
|
||||
if (options.log_usage) {
|
||||
info.fMask |= SEE_MASK_FLAG_LOG_USAGE;
|
||||
}
|
||||
|
||||
if (!ShellExecuteEx(&info)) {
|
||||
if (reinterpret_cast<ULONG_PTR>(
|
||||
ShellExecuteW(nullptr, L"open", escaped_url.c_str(), nullptr,
|
||||
working_dir.empty() ? nullptr : working_dir.c_str(),
|
||||
SW_SHOWNORMAL)) <= 32) {
|
||||
return "Failed to open: " +
|
||||
logging::SystemErrorCodeToString(logging::GetLastSystemErrorCode());
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ void ElectronRendererClient::DidCreateScriptContext(
|
||||
|
||||
if (!node_integration_initialized_) {
|
||||
node_integration_initialized_ = true;
|
||||
node_bindings_->Initialize(renderer_context);
|
||||
node_bindings_->Initialize();
|
||||
node_bindings_->PrepareEmbedThread();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ void NodeService::Initialize(node::mojom::NodeServiceParamsPtr params) {
|
||||
|
||||
v8::HandleScope scope(js_env_->isolate());
|
||||
|
||||
node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
|
||||
node_bindings_->Initialize();
|
||||
|
||||
// Append program path for process.argv0
|
||||
auto program = base::CommandLine::ForCurrentProcess()->GetProgram();
|
||||
|
||||
@@ -7,9 +7,9 @@ import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { app, BrowserWindow, Menu, session, net as electronNet } from 'electron/main';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
import { closeWindow, closeAllWindows } from './lib/window-helpers';
|
||||
import { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers';
|
||||
import { once } from 'events';
|
||||
import { ifdescribe, ifit, waitUntil } from './lib/spec-helpers';
|
||||
import split = require('split')
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
@@ -33,7 +33,7 @@ describe('app module', () => {
|
||||
let secureUrl: string;
|
||||
const certPath = path.join(fixturesPath, 'certificates');
|
||||
|
||||
before(async () => {
|
||||
before((done) => {
|
||||
const options = {
|
||||
key: fs.readFileSync(path.join(certPath, 'server.key')),
|
||||
cert: fs.readFileSync(path.join(certPath, 'server.pem')),
|
||||
@@ -55,7 +55,11 @@ describe('app module', () => {
|
||||
}
|
||||
});
|
||||
|
||||
secureUrl = (await listen(server)).url;
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const port = (server.address() as net.AddressInfo).port;
|
||||
secureUrl = `https://127.0.0.1:${port}`;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(done => {
|
||||
@@ -169,7 +173,7 @@ describe('app module', () => {
|
||||
if (appProcess && appProcess.stdout) {
|
||||
appProcess.stdout.on('data', data => { output += data; });
|
||||
}
|
||||
const [code] = await once(appProcess, 'exit');
|
||||
const [code] = await emittedOnce(appProcess, 'exit');
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
expect(output).to.include('Exit event with code: 123');
|
||||
@@ -182,7 +186,7 @@ describe('app module', () => {
|
||||
const electronPath = process.execPath;
|
||||
|
||||
appProcess = cp.spawn(electronPath, [appPath]);
|
||||
const [code, signal] = await once(appProcess, 'exit');
|
||||
const [code, signal] = await emittedOnce(appProcess, 'exit');
|
||||
|
||||
expect(signal).to.equal(null, 'exit signal should be null, if you see this please tag @MarshallOfSound');
|
||||
expect(code).to.equal(123, 'exit code should be 123, if you see this please tag @MarshallOfSound');
|
||||
@@ -203,7 +207,7 @@ describe('app module', () => {
|
||||
if (appProcess && appProcess.stdout) {
|
||||
appProcess.stdout.on('data', () => appProcess!.kill());
|
||||
}
|
||||
const [code, signal] = await once(appProcess, 'exit');
|
||||
const [code, signal] = await emittedOnce(appProcess, 'exit');
|
||||
|
||||
const message = `code:\n${code}\nsignal:\n${signal}`;
|
||||
expect(code).to.equal(0, message);
|
||||
@@ -229,37 +233,37 @@ describe('app module', () => {
|
||||
this.timeout(120000);
|
||||
const appPath = path.join(fixturesPath, 'api', 'singleton-data');
|
||||
const first = cp.spawn(process.execPath, [appPath]);
|
||||
await once(first.stdout, 'data');
|
||||
await emittedOnce(first.stdout, 'data');
|
||||
// Start second app when received output.
|
||||
const second = cp.spawn(process.execPath, [appPath]);
|
||||
const [code2] = await once(second, 'exit');
|
||||
const [code2] = await emittedOnce(second, 'exit');
|
||||
expect(code2).to.equal(1);
|
||||
const [code1] = await once(first, 'exit');
|
||||
const [code1] = await emittedOnce(first, 'exit');
|
||||
expect(code1).to.equal(0);
|
||||
});
|
||||
|
||||
it('returns true when setting non-existent user data folder', async function () {
|
||||
const appPath = path.join(fixturesPath, 'api', 'singleton-userdata');
|
||||
const instance = cp.spawn(process.execPath, [appPath]);
|
||||
const [code] = await once(instance, 'exit');
|
||||
const [code] = await emittedOnce(instance, 'exit');
|
||||
expect(code).to.equal(0);
|
||||
});
|
||||
|
||||
async function testArgumentPassing (testArgs: SingleInstanceLockTestArgs) {
|
||||
const appPath = path.join(fixturesPath, 'api', 'singleton-data');
|
||||
const first = cp.spawn(process.execPath, [appPath, ...testArgs.args]);
|
||||
const firstExited = once(first, 'exit');
|
||||
const firstExited = emittedOnce(first, 'exit');
|
||||
|
||||
// Wait for the first app to boot.
|
||||
const firstStdoutLines = first.stdout.pipe(split());
|
||||
while ((await once(firstStdoutLines, 'data')).toString() !== 'started') {
|
||||
while ((await emittedOnce(firstStdoutLines, 'data')).toString() !== 'started') {
|
||||
// wait.
|
||||
}
|
||||
const additionalDataPromise = once(firstStdoutLines, 'data');
|
||||
const additionalDataPromise = emittedOnce(firstStdoutLines, 'data');
|
||||
|
||||
const secondInstanceArgs = [process.execPath, appPath, ...testArgs.args, '--some-switch', 'some-arg'];
|
||||
const second = cp.spawn(secondInstanceArgs[0], secondInstanceArgs.slice(1));
|
||||
const secondExited = once(second, 'exit');
|
||||
const secondExited = emittedOnce(second, 'exit');
|
||||
|
||||
const [code2] = await secondExited;
|
||||
expect(code2).to.equal(1);
|
||||
@@ -427,7 +431,7 @@ describe('app module', () => {
|
||||
it('is emitted when visiting a server with a self-signed cert', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.loadURL(secureUrl);
|
||||
await once(app, 'certificate-error');
|
||||
await emittedOnce(app, 'certificate-error');
|
||||
});
|
||||
|
||||
describe('when denied', () => {
|
||||
@@ -444,7 +448,7 @@ describe('app module', () => {
|
||||
it('causes did-fail-load', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.loadURL(secureUrl);
|
||||
await once(w.webContents, 'did-fail-load');
|
||||
await emittedOnce(w.webContents, 'did-fail-load');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -506,7 +510,7 @@ describe('app module', () => {
|
||||
afterEach(() => closeWindow(w).then(() => { w = null as any; }));
|
||||
|
||||
it('should emit browser-window-focus event when window is focused', async () => {
|
||||
const emitted = once(app, 'browser-window-focus');
|
||||
const emitted = emittedOnce(app, 'browser-window-focus');
|
||||
w = new BrowserWindow({ show: false });
|
||||
w.emit('focus');
|
||||
const [, window] = await emitted;
|
||||
@@ -514,7 +518,7 @@ describe('app module', () => {
|
||||
});
|
||||
|
||||
it('should emit browser-window-blur event when window is blurred', async () => {
|
||||
const emitted = once(app, 'browser-window-blur');
|
||||
const emitted = emittedOnce(app, 'browser-window-blur');
|
||||
w = new BrowserWindow({ show: false });
|
||||
w.emit('blur');
|
||||
const [, window] = await emitted;
|
||||
@@ -522,14 +526,14 @@ describe('app module', () => {
|
||||
});
|
||||
|
||||
it('should emit browser-window-created event when window is created', async () => {
|
||||
const emitted = once(app, 'browser-window-created');
|
||||
const emitted = emittedOnce(app, 'browser-window-created');
|
||||
w = new BrowserWindow({ show: false });
|
||||
const [, window] = await emitted;
|
||||
expect(window.id).to.equal(w.id);
|
||||
});
|
||||
|
||||
it('should emit web-contents-created event when a webContents is created', async () => {
|
||||
const emitted = once(app, 'web-contents-created');
|
||||
const emitted = emittedOnce(app, 'web-contents-created');
|
||||
w = new BrowserWindow({ show: false });
|
||||
const [, webContents] = await emitted;
|
||||
expect(webContents.id).to.equal(w.webContents.id);
|
||||
@@ -546,7 +550,7 @@ describe('app module', () => {
|
||||
});
|
||||
await w.loadURL('about:blank');
|
||||
|
||||
const emitted = once(app, 'renderer-process-crashed');
|
||||
const emitted = emittedOnce(app, 'renderer-process-crashed');
|
||||
w.webContents.executeJavaScript('process.crash()');
|
||||
|
||||
const [, webContents] = await emitted;
|
||||
@@ -564,7 +568,7 @@ describe('app module', () => {
|
||||
});
|
||||
await w.loadURL('about:blank');
|
||||
|
||||
const emitted = once(app, 'render-process-gone');
|
||||
const emitted = emittedOnce(app, 'render-process-gone');
|
||||
w.webContents.executeJavaScript('process.crash()');
|
||||
|
||||
const [, webContents, details] = await emitted;
|
||||
@@ -890,7 +894,7 @@ describe('app module', () => {
|
||||
ifit(process.platform === 'win32')('detects disabled by TaskManager', async function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: true, args: ['arg1'] });
|
||||
const appProcess = cp.spawn('reg', [...regAddArgs, '030000000000000000000000']);
|
||||
await once(appProcess, 'exit');
|
||||
await emittedOnce(appProcess, 'exit');
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: false,
|
||||
openAsHidden: false,
|
||||
@@ -927,12 +931,12 @@ describe('app module', () => {
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false, args: ['arg1'] });
|
||||
let appProcess = cp.spawn('reg', [...regAddArgs, '020000000000000000000000']);
|
||||
await once(appProcess, 'exit');
|
||||
await emittedOnce(appProcess, 'exit');
|
||||
expect(app.getLoginItemSettings()).to.deep.equal(expectation);
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false, args: ['arg1'] });
|
||||
appProcess = cp.spawn('reg', [...regAddArgs, '000000000000000000000000']);
|
||||
await once(appProcess, 'exit');
|
||||
await emittedOnce(appProcess, 'exit');
|
||||
expect(app.getLoginItemSettings()).to.deep.equal(expectation);
|
||||
});
|
||||
});
|
||||
@@ -1290,7 +1294,7 @@ describe('app module', () => {
|
||||
const appPath = path.join(fixturesPath, 'api', 'quit-app');
|
||||
// App should exit with non 123 code.
|
||||
const first = cp.spawn(process.execPath, [appPath, 'electron-test:?', 'abc']);
|
||||
const [code] = await once(first, 'exit');
|
||||
const [code] = await emittedOnce(first, 'exit');
|
||||
expect(code).to.not.equal(123);
|
||||
});
|
||||
|
||||
@@ -1298,7 +1302,7 @@ describe('app module', () => {
|
||||
const appPath = path.join(fixturesPath, 'api', 'quit-app');
|
||||
// App should exit with code 123.
|
||||
const first = cp.spawn(process.execPath, [appPath, 'e:\\abc', 'abc']);
|
||||
const [code] = await once(first, 'exit');
|
||||
const [code] = await emittedOnce(first, 'exit');
|
||||
expect(code).to.equal(123);
|
||||
});
|
||||
|
||||
@@ -1306,7 +1310,7 @@ describe('app module', () => {
|
||||
const appPath = path.join(fixturesPath, 'api', 'quit-app');
|
||||
// App should exit with code 123.
|
||||
const first = cp.spawn(process.execPath, [appPath, '--', 'http://electronjs.org', 'electron-test://testdata']);
|
||||
const [code] = await once(first, 'exit');
|
||||
const [code] = await emittedOnce(first, 'exit');
|
||||
expect(code).to.equal(123);
|
||||
});
|
||||
});
|
||||
@@ -1432,7 +1436,7 @@ describe('app module', () => {
|
||||
appProcess.stderr.on('data', (data) => {
|
||||
errorData += data;
|
||||
});
|
||||
const [exitCode] = await once(appProcess, 'exit');
|
||||
const [exitCode] = await emittedOnce(appProcess, 'exit');
|
||||
if (exitCode === 0) {
|
||||
try {
|
||||
const [, json] = /HERE COMES THE JSON: (.+) AND THERE IT WAS/.exec(gpuInfoData)!;
|
||||
@@ -1933,7 +1937,7 @@ describe('default behavior', () => {
|
||||
let server: http.Server;
|
||||
let serverUrl: string;
|
||||
|
||||
before(async () => {
|
||||
before((done) => {
|
||||
server = http.createServer((request, response) => {
|
||||
if (request.headers.authorization) {
|
||||
return response.end('ok');
|
||||
@@ -1941,15 +1945,16 @@ describe('default behavior', () => {
|
||||
response
|
||||
.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' })
|
||||
.end();
|
||||
}).listen(0, '127.0.0.1', () => {
|
||||
serverUrl = 'http://127.0.0.1:' + (server.address() as net.AddressInfo).port;
|
||||
done();
|
||||
});
|
||||
|
||||
serverUrl = (await listen(server)).url;
|
||||
});
|
||||
|
||||
it('should emit a login event on app when a WebContents hits a 401', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.loadURL(serverUrl);
|
||||
const [, webContents] = await once(app, 'login');
|
||||
const [, webContents] = await emittedOnce(app, 'login');
|
||||
expect(webContents).to.equal(w.webContents);
|
||||
});
|
||||
});
|
||||
@@ -1976,7 +1981,7 @@ async function runTestApp (name: string, ...args: any[]) {
|
||||
let output = '';
|
||||
appProcess.stdout.on('data', (data) => { output += data; });
|
||||
|
||||
await once(appProcess.stdout, 'end');
|
||||
await emittedOnce(appProcess.stdout, 'end');
|
||||
|
||||
return JSON.parse(output);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { autoUpdater } from 'electron/main';
|
||||
import { expect } from 'chai';
|
||||
import { ifit, ifdescribe } from './lib/spec-helpers';
|
||||
import { once } from 'events';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
|
||||
ifdescribe(!process.mas)('autoUpdater module', function () {
|
||||
describe('checkForUpdates', function () {
|
||||
ifit(process.platform === 'win32')('emits an error on Windows if the feed URL is not set', async function () {
|
||||
const errorEvent = once(autoUpdater, 'error');
|
||||
const errorEvent = emittedOnce(autoUpdater, 'error');
|
||||
autoUpdater.setFeedURL({ url: '' });
|
||||
autoUpdater.checkForUpdates();
|
||||
const [error] = await errorEvent;
|
||||
@@ -56,7 +56,7 @@ ifdescribe(!process.mas)('autoUpdater module', function () {
|
||||
|
||||
ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64')('on Mac', function () {
|
||||
it('emits an error when the application is unsigned', async () => {
|
||||
const errorEvent = once(autoUpdater, 'error');
|
||||
const errorEvent = emittedOnce(autoUpdater, 'error');
|
||||
autoUpdater.setFeedURL({ url: '' });
|
||||
const [error] = await errorEvent;
|
||||
expect(error.message).equal('Could not get code signature for running application');
|
||||
@@ -80,7 +80,7 @@ ifdescribe(!process.mas)('autoUpdater module', function () {
|
||||
|
||||
describe('quitAndInstall', () => {
|
||||
ifit(process.platform === 'win32')('emits an error on Windows when no update is available', async function () {
|
||||
const errorEvent = once(autoUpdater, 'error');
|
||||
const errorEvent = emittedOnce(autoUpdater, 'error');
|
||||
autoUpdater.quitAndInstall();
|
||||
const [error] = await errorEvent;
|
||||
expect(error.message).to.equal('No update available, can\'t quit and install');
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
import { BrowserView, BrowserWindow, screen, webContents } from 'electron/main';
|
||||
import { closeWindow } from './lib/window-helpers';
|
||||
import { defer, ifit, startRemoteControlApp } from './lib/spec-helpers';
|
||||
import { areColorsSimilar, captureScreen, getPixelColor } from './lib/screen-helpers';
|
||||
import { once } from 'events';
|
||||
|
||||
describe('BrowserView module', () => {
|
||||
const fixtures = path.resolve(__dirname, 'fixtures');
|
||||
@@ -25,13 +25,13 @@ describe('BrowserView module', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const p = once(w.webContents, 'destroyed');
|
||||
const p = emittedOnce(w.webContents, 'destroyed');
|
||||
await closeWindow(w);
|
||||
w = null as any;
|
||||
await p;
|
||||
|
||||
if (view && view.webContents) {
|
||||
const p = once(view.webContents, 'destroyed');
|
||||
const p = emittedOnce(view.webContents, 'destroyed');
|
||||
view.webContents.destroy();
|
||||
view = null as any;
|
||||
await p;
|
||||
@@ -231,7 +231,7 @@ describe('BrowserView module', () => {
|
||||
|
||||
w.addBrowserView(view);
|
||||
view.webContents.loadURL('about:blank');
|
||||
await once(view.webContents, 'did-finish-load');
|
||||
await emittedOnce(view.webContents, 'did-finish-load');
|
||||
|
||||
const w2 = new BrowserWindow({ show: false });
|
||||
w2.addBrowserView(view);
|
||||
@@ -239,7 +239,7 @@ describe('BrowserView module', () => {
|
||||
w.close();
|
||||
|
||||
view.webContents.loadURL(`file://${fixtures}/pages/blank.html`);
|
||||
await once(view.webContents, 'did-finish-load');
|
||||
await emittedOnce(view.webContents, 'did-finish-load');
|
||||
|
||||
// Clean up - the afterEach hook assumes the webContents on w is still alive.
|
||||
w = new BrowserWindow({ show: false });
|
||||
@@ -326,7 +326,7 @@ describe('BrowserView module', () => {
|
||||
app.quit();
|
||||
});
|
||||
});
|
||||
const [code] = await once(rc.process, 'exit');
|
||||
const [code] = await emittedOnce(rc.process, 'exit');
|
||||
expect(code).to.equal(0);
|
||||
});
|
||||
|
||||
@@ -342,7 +342,7 @@ describe('BrowserView module', () => {
|
||||
app.quit();
|
||||
});
|
||||
});
|
||||
const [code] = await once(rc.process, 'exit');
|
||||
const [code] = await emittedOnce(rc.process, 'exit');
|
||||
expect(code).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,7 @@ import { expect } from 'chai';
|
||||
import { app, contentTracing, TraceConfig, TraceCategoriesAndOptions } from 'electron/main';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import { ifdescribe } from './lib/spec-helpers';
|
||||
import { ifdescribe, delay } from './lib/spec-helpers';
|
||||
|
||||
// FIXME: The tests are skipped on arm/arm64 and ia32.
|
||||
ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing', () => {
|
||||
@@ -11,7 +10,7 @@ ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing',
|
||||
await app.whenReady();
|
||||
|
||||
await contentTracing.startRecording(options);
|
||||
await setTimeout(recordTimeInMilliseconds);
|
||||
await delay(recordTimeInMilliseconds);
|
||||
const resultFilePath = await contentTracing.stopRecording(outputFilePath);
|
||||
|
||||
return resultFilePath;
|
||||
@@ -132,7 +131,7 @@ ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing',
|
||||
let n = 0;
|
||||
const f = () => {};
|
||||
while (+new Date() - start < 200 || n < 500) {
|
||||
await setTimeout(0);
|
||||
await delay(0);
|
||||
f();
|
||||
n++;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
|
||||
import { closeWindow } from './lib/window-helpers';
|
||||
import { listen } from './lib/spec-helpers';
|
||||
import { once } from 'events';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge');
|
||||
|
||||
@@ -17,14 +17,13 @@ describe('contextBridge', () => {
|
||||
let w: BrowserWindow;
|
||||
let dir: string;
|
||||
let server: http.Server;
|
||||
let serverUrl: string;
|
||||
|
||||
before(async () => {
|
||||
server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end('');
|
||||
});
|
||||
serverUrl = (await listen(server)).url;
|
||||
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
@@ -45,8 +44,7 @@ describe('contextBridge', () => {
|
||||
preload: path.resolve(fixturesPath, 'can-bind-preload.js')
|
||||
}
|
||||
});
|
||||
w.loadFile(path.resolve(fixturesPath, 'empty.html'));
|
||||
const [, bound] = await once(ipcMain, 'context-bridge-bound');
|
||||
const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')));
|
||||
expect(bound).to.equal(false);
|
||||
});
|
||||
|
||||
@@ -58,8 +56,7 @@ describe('contextBridge', () => {
|
||||
preload: path.resolve(fixturesPath, 'can-bind-preload.js')
|
||||
}
|
||||
});
|
||||
w.loadFile(path.resolve(fixturesPath, 'empty.html'));
|
||||
const [, bound] = await once(ipcMain, 'context-bridge-bound');
|
||||
const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')));
|
||||
expect(bound).to.equal(true);
|
||||
});
|
||||
|
||||
@@ -98,7 +95,7 @@ describe('contextBridge', () => {
|
||||
additionalArguments: ['--unsafely-expose-electron-internals-for-testing']
|
||||
}
|
||||
});
|
||||
await w.loadURL(serverUrl);
|
||||
await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`);
|
||||
};
|
||||
|
||||
const callWithBindings = (fn: Function, worldId: number = 0) =>
|
||||
@@ -107,8 +104,7 @@ describe('contextBridge', () => {
|
||||
const getGCInfo = async (): Promise<{
|
||||
trackedValues: number;
|
||||
}> => {
|
||||
w.webContents.send('get-gc-info');
|
||||
const [, info] = await once(ipcMain, 'gc-info');
|
||||
const [, info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'));
|
||||
return info;
|
||||
};
|
||||
|
||||
@@ -689,7 +685,7 @@ describe('contextBridge', () => {
|
||||
});
|
||||
require('electron').ipcRenderer.send('window-ready-for-tasking');
|
||||
});
|
||||
const loadPromise = once(ipcMain, 'window-ready-for-tasking');
|
||||
const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking');
|
||||
expect((await getGCInfo()).trackedValues).to.equal(0);
|
||||
await callWithBindings((root: any) => {
|
||||
root.example.track(root.example.getFunction());
|
||||
@@ -1266,7 +1262,7 @@ describe('ContextBridgeMutability', () => {
|
||||
|
||||
let output = '';
|
||||
appProcess.stdout.on('data', data => { output += data; });
|
||||
await once(appProcess, 'exit');
|
||||
await emittedOnce(appProcess, 'exit');
|
||||
|
||||
expect(output).to.include('some-modified-text');
|
||||
expect(output).to.include('obj-modified-prop');
|
||||
@@ -1279,7 +1275,7 @@ describe('ContextBridgeMutability', () => {
|
||||
|
||||
let output = '';
|
||||
appProcess.stdout.on('data', data => { output += data; });
|
||||
await once(appProcess, 'exit');
|
||||
await emittedOnce(appProcess, 'exit');
|
||||
|
||||
expect(output).to.include('some-text');
|
||||
expect(output).to.include('obj-prop');
|
||||
|
||||
@@ -3,13 +3,13 @@ import * as childProcess from 'child_process';
|
||||
import * as http from 'http';
|
||||
import * as Busboy from 'busboy';
|
||||
import * as path from 'path';
|
||||
import { ifdescribe, ifit, defer, startRemoteControlApp, repeatedly, listen } from './lib/spec-helpers';
|
||||
import { ifdescribe, ifit, defer, startRemoteControlApp, delay, repeatedly } from './lib/spec-helpers';
|
||||
import { app } from 'electron/main';
|
||||
import { crashReporter } from 'electron/common';
|
||||
import { AddressInfo } from 'net';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import * as uuid from 'uuid';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
|
||||
const isWindowsOnArm = process.platform === 'win32' && process.arch === 'arm64';
|
||||
const isLinuxOnArm = process.platform === 'linux' && process.arch.includes('arm');
|
||||
@@ -89,7 +89,11 @@ const startServer = async () => {
|
||||
req.pipe(busboy);
|
||||
});
|
||||
|
||||
const { port } = await listen(server);
|
||||
await new Promise<void>(resolve => {
|
||||
server.listen(0, '127.0.0.1', () => { resolve(); });
|
||||
});
|
||||
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
|
||||
defer(() => { server.close(); });
|
||||
|
||||
@@ -299,7 +303,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
|
||||
ignoreSystemCrashHandler: true,
|
||||
extra: { longParam: 'a'.repeat(100000) }
|
||||
});
|
||||
setTimeout().then(() => process.crash());
|
||||
setTimeout(() => process.crash());
|
||||
}, port);
|
||||
const crash = await waitForCrash();
|
||||
expect(stitchLongCrashParam(crash, 'longParam')).to.have.lengthOf(160 * 127, 'crash should have truncated longParam');
|
||||
@@ -321,7 +325,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
|
||||
}
|
||||
});
|
||||
require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value');
|
||||
setTimeout().then(() => process.crash());
|
||||
setTimeout(() => process.crash());
|
||||
}, port, kKeyLengthMax);
|
||||
const crash = await waitForCrash();
|
||||
expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10));
|
||||
@@ -376,7 +380,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
|
||||
waitForCrash().then(() => expect.fail('expected not to receive a dump'));
|
||||
await runCrashApp('renderer', port, ['--no-upload']);
|
||||
// wait a sec in case the crash reporter is about to upload a crash
|
||||
await setTimeout(1000);
|
||||
await delay(1000);
|
||||
expect(getCrashes()).to.have.length(0);
|
||||
});
|
||||
|
||||
@@ -503,7 +507,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
|
||||
function crash (processType: string, remotely: Function) {
|
||||
if (processType === 'main') {
|
||||
return remotely(() => {
|
||||
setTimeout().then(() => { process.crash(); });
|
||||
setTimeout(() => { process.crash(); });
|
||||
});
|
||||
} else if (processType === 'renderer') {
|
||||
return remotely(() => {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { expect } from 'chai';
|
||||
import * as http from 'http';
|
||||
import * as path from 'path';
|
||||
import { AddressInfo } from 'net';
|
||||
import { BrowserWindow } from 'electron/main';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { emittedUntil } from './lib/events-helpers';
|
||||
import { listen } from './lib/spec-helpers';
|
||||
import { once } from 'events';
|
||||
import { emittedOnce, emittedUntil } from './lib/events-helpers';
|
||||
|
||||
describe('debugger module', () => {
|
||||
const fixtures = path.resolve(__dirname, 'fixtures');
|
||||
@@ -46,7 +45,7 @@ describe('debugger module', () => {
|
||||
|
||||
describe('debugger.detach', () => {
|
||||
it('fires detach event', async () => {
|
||||
const detach = once(w.webContents.debugger, 'detach');
|
||||
const detach = emittedOnce(w.webContents.debugger, 'detach');
|
||||
w.webContents.debugger.attach();
|
||||
w.webContents.debugger.detach();
|
||||
const [, reason] = await detach;
|
||||
@@ -56,7 +55,7 @@ describe('debugger module', () => {
|
||||
|
||||
it('doesn\'t disconnect an active devtools session', async () => {
|
||||
w.webContents.loadURL('about:blank');
|
||||
const detach = once(w.webContents.debugger, 'detach');
|
||||
const detach = emittedOnce(w.webContents.debugger, 'detach');
|
||||
w.webContents.debugger.attach();
|
||||
w.webContents.openDevTools();
|
||||
w.webContents.once('devtools-opened', () => {
|
||||
@@ -95,7 +94,7 @@ describe('debugger module', () => {
|
||||
w.webContents.loadURL('about:blank');
|
||||
w.webContents.debugger.attach();
|
||||
|
||||
const opened = once(w.webContents, 'devtools-opened');
|
||||
const opened = emittedOnce(w.webContents, 'devtools-opened');
|
||||
w.webContents.openDevTools();
|
||||
await opened;
|
||||
|
||||
@@ -139,9 +138,9 @@ describe('debugger module', () => {
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.end('\u0024');
|
||||
});
|
||||
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
|
||||
const { url } = await listen(server);
|
||||
w.loadURL(url);
|
||||
w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`);
|
||||
// If we do this synchronously, it's fast enough to attach and enable
|
||||
// network capture before the load. If we do it before the loadURL, for
|
||||
// some reason network capture doesn't get enabled soon enough and we get
|
||||
@@ -156,17 +155,19 @@ describe('debugger module', () => {
|
||||
expect(body).to.equal('\u0024');
|
||||
});
|
||||
|
||||
it('does not crash for invalid unicode characters in message', async () => {
|
||||
w.webContents.debugger.attach();
|
||||
it('does not crash for invalid unicode characters in message', (done) => {
|
||||
try {
|
||||
w.webContents.debugger.attach();
|
||||
} catch (err) {
|
||||
done(`unexpected error : ${err}`);
|
||||
}
|
||||
|
||||
const loadingFinished = new Promise<void>(resolve => {
|
||||
w.webContents.debugger.on('message', (event, method) => {
|
||||
// loadingFinished indicates that page has been loaded and it did not
|
||||
// crash because of invalid UTF-8 data
|
||||
if (method === 'Network.loadingFinished') {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
w.webContents.debugger.on('message', (event, method) => {
|
||||
// loadingFinished indicates that page has been loaded and it did not
|
||||
// crash because of invalid UTF-8 data
|
||||
if (method === 'Network.loadingFinished') {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
server = http.createServer((req, res) => {
|
||||
@@ -174,17 +175,16 @@ describe('debugger module', () => {
|
||||
res.end('\uFFFF');
|
||||
});
|
||||
|
||||
const { url } = await listen(server);
|
||||
w.webContents.debugger.sendCommand('Network.enable');
|
||||
w.loadURL(url);
|
||||
|
||||
await loadingFinished;
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
w.webContents.debugger.sendCommand('Network.enable');
|
||||
w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses empty sessionId by default', async () => {
|
||||
w.webContents.loadURL('about:blank');
|
||||
w.webContents.debugger.attach();
|
||||
const onMessage = once(w.webContents.debugger, 'message');
|
||||
const onMessage = emittedOnce(w.webContents.debugger, 'message');
|
||||
await w.webContents.debugger.sendCommand('Target.setDiscoverTargets', { discover: true });
|
||||
const [, method, params, sessionId] = await onMessage;
|
||||
expect(method).to.equal('Target.targetCreated');
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import { screen, desktopCapturer, BrowserWindow } from 'electron/main';
|
||||
import { once } from 'events';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import { ifdescribe, ifit } from './lib/spec-helpers';
|
||||
import { delay, ifdescribe, ifit } from './lib/spec-helpers';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
|
||||
@@ -75,7 +74,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
|
||||
|
||||
it('disabling thumbnail should return empty images', async () => {
|
||||
const w2 = new BrowserWindow({ show: false, width: 200, height: 200, webPreferences: { contextIsolation: false } });
|
||||
const wShown = once(w2, 'show');
|
||||
const wShown = emittedOnce(w2, 'show');
|
||||
w2.show();
|
||||
await wShown;
|
||||
|
||||
@@ -91,8 +90,8 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
|
||||
|
||||
it('getMediaSourceId should match DesktopCapturerSource.id', async () => {
|
||||
const w = new BrowserWindow({ show: false, width: 100, height: 100, webPreferences: { contextIsolation: false } });
|
||||
const wShown = once(w, 'show');
|
||||
const wFocused = once(w, 'focus');
|
||||
const wShown = emittedOnce(w, 'show');
|
||||
const wFocused = emittedOnce(w, 'focus');
|
||||
w.show();
|
||||
w.focus();
|
||||
await wShown;
|
||||
@@ -122,8 +121,8 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
|
||||
|
||||
it('getSources should not incorrectly duplicate window_id', async () => {
|
||||
const w = new BrowserWindow({ show: false, width: 100, height: 100, webPreferences: { contextIsolation: false } });
|
||||
const wShown = once(w, 'show');
|
||||
const wFocused = once(w, 'focus');
|
||||
const wShown = emittedOnce(w, 'show');
|
||||
const wFocused = emittedOnce(w, 'focus');
|
||||
w.show();
|
||||
w.focus();
|
||||
await wShown;
|
||||
@@ -177,8 +176,8 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
|
||||
|
||||
// Show and focus all the windows.
|
||||
for (const w of wList) {
|
||||
const wShown = once(w, 'show');
|
||||
const wFocused = once(w, 'focus');
|
||||
const wShown = emittedOnce(w, 'show');
|
||||
const wFocused = emittedOnce(w, 'focus');
|
||||
|
||||
w.show();
|
||||
w.focus();
|
||||
@@ -228,7 +227,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
|
||||
w.focus();
|
||||
w.moveAbove(next.getMediaSourceId());
|
||||
// Ensure the window has time to move.
|
||||
await setTimeout(2000);
|
||||
await delay(2000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import { dialog, BrowserWindow } from 'electron/main';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { ifit } from './lib/spec-helpers';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import { ifit, delay } from './lib/spec-helpers';
|
||||
|
||||
describe('dialog module', () => {
|
||||
describe('showOpenDialog', () => {
|
||||
@@ -140,7 +139,7 @@ describe('dialog module', () => {
|
||||
const signal = controller.signal;
|
||||
const w = new BrowserWindow();
|
||||
const p = dialog.showMessageBox(w, { signal, message: 'i am message' });
|
||||
await setTimeout(500);
|
||||
await delay(500);
|
||||
controller.abort();
|
||||
const result = await p;
|
||||
expect(result.response).to.equal(0);
|
||||
@@ -171,7 +170,7 @@ describe('dialog module', () => {
|
||||
buttons: ['OK', 'Cancel'],
|
||||
cancelId: 1
|
||||
});
|
||||
await setTimeout(500);
|
||||
await delay(500);
|
||||
controller.abort();
|
||||
const result = await p;
|
||||
expect(result.response).to.equal(1);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
import { defer } from './lib/spec-helpers';
|
||||
import { ipcMain, BrowserWindow } from 'electron/main';
|
||||
import { once } from 'events';
|
||||
|
||||
describe('ipc main module', () => {
|
||||
const fixtures = path.join(__dirname, 'fixtures');
|
||||
@@ -57,7 +57,7 @@ describe('ipc main module', () => {
|
||||
let output = '';
|
||||
appProcess.stdout.on('data', (data) => { output += data; });
|
||||
|
||||
await once(appProcess.stdout, 'end');
|
||||
await emittedOnce(appProcess.stdout, 'end');
|
||||
|
||||
output = JSON.parse(output);
|
||||
expect(output).to.deep.equal(['error']);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
import { ipcMain, BrowserWindow, WebContents, WebPreferences, webContents } from 'electron/main';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
import { closeWindow } from './lib/window-helpers';
|
||||
import { once } from 'events';
|
||||
|
||||
describe('ipcRenderer module', () => {
|
||||
const fixtures = path.join(__dirname, 'fixtures');
|
||||
@@ -27,7 +27,7 @@ describe('ipcRenderer module', () => {
|
||||
const { ipcRenderer } = require('electron')
|
||||
ipcRenderer.send('message', ${JSON.stringify(obj)})
|
||||
}`);
|
||||
const [, received] = await once(ipcMain, 'message');
|
||||
const [, received] = await emittedOnce(ipcMain, 'message');
|
||||
expect(received).to.deep.equal(obj);
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('ipcRenderer module', () => {
|
||||
const { ipcRenderer } = require('electron')
|
||||
ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)}))
|
||||
}`);
|
||||
const [, received] = await once(ipcMain, 'message');
|
||||
const [, received] = await emittedOnce(ipcMain, 'message');
|
||||
expect(received.toISOString()).to.equal(isoDate);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('ipcRenderer module', () => {
|
||||
const { ipcRenderer } = require('electron')
|
||||
ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)}))
|
||||
}`);
|
||||
const [, received] = await once(ipcMain, 'message');
|
||||
const [, received] = await emittedOnce(ipcMain, 'message');
|
||||
expect(received).to.be.an.instanceOf(Uint8Array);
|
||||
expect(Buffer.from(data).equals(received)).to.be.true();
|
||||
});
|
||||
@@ -88,7 +88,7 @@ describe('ipcRenderer module', () => {
|
||||
const bar = { name: 'bar', child: child };
|
||||
const array = [foo, bar];
|
||||
|
||||
const [, arrayValue, fooValue, barValue, childValue] = await once(ipcMain, 'message');
|
||||
const [, arrayValue, fooValue, barValue, childValue] = await emittedOnce(ipcMain, 'message');
|
||||
expect(arrayValue).to.deep.equal(array);
|
||||
expect(fooValue).to.deep.equal(foo);
|
||||
expect(barValue).to.deep.equal(bar);
|
||||
@@ -106,7 +106,7 @@ describe('ipcRenderer module', () => {
|
||||
ipcRenderer.send('message', array, child)
|
||||
}`);
|
||||
|
||||
const [, arrayValue, childValue] = await once(ipcMain, 'message');
|
||||
const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message');
|
||||
expect(arrayValue[0]).to.equal(5);
|
||||
expect(arrayValue[1]).to.equal(arrayValue);
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { EventEmitter, once } from 'events';
|
||||
import { EventEmitter } from 'events';
|
||||
import { expect } from 'chai';
|
||||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { defer, listen } from './lib/spec-helpers';
|
||||
import { emittedOnce } from './lib/events-helpers';
|
||||
import { defer } from './lib/spec-helpers';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
|
||||
describe('ipc module', () => {
|
||||
describe('invoke', () => {
|
||||
let w: BrowserWindow;
|
||||
let w = (null as unknown as BrowserWindow);
|
||||
|
||||
before(async () => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
@@ -119,13 +121,13 @@ describe('ipc module', () => {
|
||||
/* never resolve */
|
||||
}));
|
||||
w.webContents.executeJavaScript(`(${rendererInvoke})()`);
|
||||
const [, { error }] = await once(ipcMain, 'result');
|
||||
const [, { error }] = await emittedOnce(ipcMain, 'result');
|
||||
expect(error).to.match(/reply was never sent/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ordering', () => {
|
||||
let w: BrowserWindow;
|
||||
let w = (null as unknown as BrowserWindow);
|
||||
|
||||
before(async () => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
@@ -207,7 +209,7 @@ describe('ipc module', () => {
|
||||
it('can send a port to the main process', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
const p = once(ipcMain, 'port');
|
||||
const p = emittedOnce(ipcMain, 'port');
|
||||
await w.webContents.executeJavaScript(`(${function () {
|
||||
const channel = new MessageChannel();
|
||||
require('electron').ipcRenderer.postMessage('port', 'hi', [channel.port1]);
|
||||
@@ -224,7 +226,7 @@ describe('ipc module', () => {
|
||||
it('can sent a message without a transfer', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
const p = once(ipcMain, 'port');
|
||||
const p = emittedOnce(ipcMain, 'port');
|
||||
await w.webContents.executeJavaScript(`(${function () {
|
||||
require('electron').ipcRenderer.postMessage('port', 'hi');
|
||||
}})()`);
|
||||
@@ -237,7 +239,7 @@ describe('ipc module', () => {
|
||||
it('can communicate between main and renderer', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
const p = once(ipcMain, 'port');
|
||||
const p = emittedOnce(ipcMain, 'port');
|
||||
await w.webContents.executeJavaScript(`(${function () {
|
||||
const channel = new MessageChannel();
|
||||
(channel.port2 as any).onmessage = (ev: any) => {
|
||||
@@ -251,7 +253,7 @@ describe('ipc module', () => {
|
||||
const [port] = ev.ports;
|
||||
port.start();
|
||||
port.postMessage(42);
|
||||
const [ev2] = await once(port, 'message');
|
||||
const [ev2] = await emittedOnce(port, 'message');
|
||||
expect(ev2.data).to.equal(84);
|
||||
});
|
||||
|
||||
@@ -266,11 +268,11 @@ describe('ipc module', () => {
|
||||
require('electron').ipcRenderer.postMessage('port', '', [channel1.port1]);
|
||||
}
|
||||
w.webContents.executeJavaScript(`(${fn})()`);
|
||||
const [{ ports: [port1] }] = await once(ipcMain, 'port');
|
||||
const [{ ports: [port1] }] = await emittedOnce(ipcMain, 'port');
|
||||
port1.start();
|
||||
const [{ ports: [port2] }] = await once(port1, 'message');
|
||||
const [{ ports: [port2] }] = await emittedOnce(port1, 'message');
|
||||
port2.start();
|
||||
const [{ data }] = await once(port2, 'message');
|
||||
const [{ data }] = await emittedOnce(port2, 'message');
|
||||
expect(data).to.equal('matryoshka');
|
||||
});
|
||||
|
||||
@@ -286,14 +288,14 @@ describe('ipc module', () => {
|
||||
};
|
||||
require('electron').ipcRenderer.postMessage('port', '', [channel.port1]);
|
||||
}})()`);
|
||||
const [{ ports: [port] }] = await once(ipcMain, 'port');
|
||||
const [{ ports: [port] }] = await emittedOnce(ipcMain, 'port');
|
||||
await w2.webContents.executeJavaScript(`(${function () {
|
||||
require('electron').ipcRenderer.on('port', ({ ports: [port] }: any) => {
|
||||
port.postMessage('a message');
|
||||
});
|
||||
}})()`);
|
||||
w2.webContents.postMessage('port', '', [port]);
|
||||
const [, data] = await once(ipcMain, 'message received');
|
||||
const [, data] = await emittedOnce(ipcMain, 'message received');
|
||||
expect(data).to.equal('a message');
|
||||
});
|
||||
|
||||
@@ -315,7 +317,7 @@ describe('ipc module', () => {
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
w.webContents.postMessage('port', null, [port2]);
|
||||
port1.close();
|
||||
await once(ipcMain, 'closed');
|
||||
await emittedOnce(ipcMain, 'closed');
|
||||
});
|
||||
|
||||
it('is emitted when the other end of a port is garbage-collected', async () => {
|
||||
@@ -359,7 +361,7 @@ describe('ipc module', () => {
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
port2.postMessage('hello');
|
||||
port1.start();
|
||||
const [ev] = await once(port1, 'message');
|
||||
const [ev] = await emittedOnce(port1, 'message');
|
||||
expect(ev.data).to.equal('hello');
|
||||
});
|
||||
|
||||
@@ -378,7 +380,7 @@ describe('ipc module', () => {
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
port1.postMessage('hello');
|
||||
w.webContents.postMessage('port', null, [port2]);
|
||||
await once(ipcMain, 'done');
|
||||
await emittedOnce(ipcMain, 'done');
|
||||
});
|
||||
|
||||
it('can be passed over another channel', async () => {
|
||||
@@ -399,7 +401,7 @@ describe('ipc module', () => {
|
||||
port1.postMessage(null, [port4]);
|
||||
port3.postMessage('hello');
|
||||
w.webContents.postMessage('port', null, [port2]);
|
||||
const [, message] = await once(ipcMain, 'done');
|
||||
const [, message] = await emittedOnce(ipcMain, 'done');
|
||||
expect(message).to.equal('hello');
|
||||
});
|
||||
|
||||
@@ -480,7 +482,7 @@ describe('ipc module', () => {
|
||||
});
|
||||
}})()`);
|
||||
postMessage(w.webContents)('foo', { some: 'message' });
|
||||
const [, msg] = await once(ipcMain, 'bar');
|
||||
const [, msg] = await emittedOnce(ipcMain, 'bar');
|
||||
expect(msg).to.deep.equal({ some: 'message' });
|
||||
});
|
||||
|
||||
@@ -574,7 +576,7 @@ describe('ipc module', () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
|
||||
const [, num] = await once(w.webContents.ipc, 'test');
|
||||
const [, num] = await emittedOnce(w.webContents.ipc, 'test');
|
||||
expect(num).to.equal(42);
|
||||
});
|
||||
|
||||
@@ -592,7 +594,7 @@ describe('ipc module', () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
|
||||
const [event] = await once(w.webContents.ipc, 'test');
|
||||
const [event] = await emittedOnce(w.webContents.ipc, 'test');
|
||||
expect(event.ports.length).to.equal(1);
|
||||
});
|
||||
|
||||
@@ -642,7 +644,8 @@ describe('ipc module', () => {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.end('');
|
||||
});
|
||||
const { port } = await listen(server);
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
defer(() => {
|
||||
server.close();
|
||||
});
|
||||
@@ -650,7 +653,7 @@ describe('ipc module', () => {
|
||||
// Preloads don't run in about:blank windows, and file:// urls can't be loaded in iframes, so use a blank http page.
|
||||
await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
|
||||
w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
|
||||
const [, arg] = await once(w.webContents.ipc, 'test');
|
||||
const [, arg] = await emittedOnce(w.webContents.ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
});
|
||||
@@ -661,7 +664,7 @@ describe('ipc module', () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
|
||||
const [, arg] = await once(w.webContents.mainFrame.ipc, 'test');
|
||||
const [, arg] = await emittedOnce(w.webContents.mainFrame.ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
|
||||
@@ -679,7 +682,7 @@ describe('ipc module', () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
|
||||
const [event] = await once(w.webContents.mainFrame.ipc, 'test');
|
||||
const [event] = await emittedOnce(w.webContents.mainFrame.ipc, 'test');
|
||||
expect(event.ports.length).to.equal(1);
|
||||
});
|
||||
|
||||
@@ -742,7 +745,8 @@ describe('ipc module', () => {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.end('');
|
||||
});
|
||||
const { port } = await listen(server);
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
defer(() => {
|
||||
server.close();
|
||||
});
|
||||
@@ -751,7 +755,7 @@ describe('ipc module', () => {
|
||||
await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
|
||||
w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
|
||||
w.webContents.mainFrame.ipc.on('test', () => { throw new Error('should not be called'); });
|
||||
const [, arg] = await once(w.webContents.mainFrame.frames[0].ipc, 'test');
|
||||
const [, arg] = await emittedOnce(w.webContents.mainFrame.frames[0].ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||
import { BrowserWindow, session, desktopCapturer } from 'electron/main';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import * as http from 'http';
|
||||
import { ifdescribe, ifit, listen } from './lib/spec-helpers';
|
||||
import { ifdescribe, ifit } from './lib/spec-helpers';
|
||||
|
||||
const features = process._linkedBinding('electron_common_features');
|
||||
|
||||
@@ -17,7 +17,8 @@ ifdescribe(features.isDesktopCapturerEnabled())('setDisplayMediaRequestHandler',
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end('');
|
||||
});
|
||||
serverUrl = (await listen(server)).url;
|
||||
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
serverUrl = `http://localhost:${(server.address() as any).port}`;
|
||||
});
|
||||
after(() => {
|
||||
server.close();
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { BrowserWindow, app, Menu, MenuItem, MenuItemConstructorOptions } from 'electron/main';
|
||||
import { expect } from 'chai';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
import { roleList, execute } from '../lib/browser/api/menu-item-roles';
|
||||
|
||||
function keys<Key extends string, Value> (record: Record<Key, Value>) {
|
||||
return Object.keys(record) as Key[];
|
||||
}
|
||||
const { roleList, execute } = require('../lib/browser/api/menu-item-roles');
|
||||
|
||||
describe('MenuItems', () => {
|
||||
describe('MenuItem instance properties', () => {
|
||||
@@ -183,7 +179,7 @@ describe('MenuItems', () => {
|
||||
const win = new BrowserWindow({ show: false, width: 200, height: 200 });
|
||||
const item = new MenuItem({ role: 'asdfghjkl' as any });
|
||||
|
||||
const canExecute = execute(item.role as any, win, win.webContents);
|
||||
const canExecute = execute(item.role, win, win.webContents);
|
||||
expect(canExecute).to.be.false('can execute');
|
||||
});
|
||||
|
||||
@@ -191,7 +187,7 @@ describe('MenuItems', () => {
|
||||
const win = new BrowserWindow({ show: false, width: 200, height: 200 });
|
||||
const item = new MenuItem({ role: 'reload' });
|
||||
|
||||
const canExecute = execute(item.role as any, win, win.webContents);
|
||||
const canExecute = execute(item.role, win, win.webContents);
|
||||
expect(canExecute).to.be.true('can execute');
|
||||
});
|
||||
|
||||
@@ -199,7 +195,7 @@ describe('MenuItems', () => {
|
||||
const win = new BrowserWindow({ show: false, width: 200, height: 200 });
|
||||
const item = new MenuItem({ role: 'resetZoom' });
|
||||
|
||||
const canExecute = execute(item.role as any, win, win.webContents);
|
||||
const canExecute = execute(item.role, win, win.webContents);
|
||||
expect(canExecute).to.be.true('can execute');
|
||||
});
|
||||
});
|
||||
@@ -241,7 +237,7 @@ describe('MenuItems', () => {
|
||||
|
||||
describe('MenuItem role', () => {
|
||||
it('returns undefined for items without default accelerator', () => {
|
||||
const list = keys(roleList).filter(key => !roleList[key].accelerator);
|
||||
const list = Object.keys(roleList).filter(key => !roleList[key].accelerator);
|
||||
|
||||
for (const role of list) {
|
||||
const item = new MenuItem({ role: role as any });
|
||||
@@ -250,7 +246,7 @@ describe('MenuItems', () => {
|
||||
});
|
||||
|
||||
it('returns the correct default label', () => {
|
||||
for (const role of keys(roleList)) {
|
||||
for (const role of Object.keys(roleList)) {
|
||||
const item = new MenuItem({ role: role as any });
|
||||
const label: string = roleList[role].label;
|
||||
expect(item.label).to.equal(label);
|
||||
@@ -258,11 +254,11 @@ describe('MenuItems', () => {
|
||||
});
|
||||
|
||||
it('returns the correct default accelerator', () => {
|
||||
const list = keys(roleList).filter(key => roleList[key].accelerator);
|
||||
const list = Object.keys(roleList).filter(key => roleList[key].accelerator);
|
||||
|
||||
for (const role of list) {
|
||||
const item = new MenuItem({ role: role as any });
|
||||
const accelerator = roleList[role].accelerator;
|
||||
const accelerator: string = roleList[role].accelerator;
|
||||
expect(item.getDefaultRoleAccelerator()).to.equal(accelerator);
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user