Compare commits

..

19 Commits

Author SHA1 Message Date
trop[bot]
a1255ae20f fix: guard more private API usage on MAS builds (#37364)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-21 17:54:48 +01:00
trop[bot]
de5ee7e60e fix: html fullscreen when window not fullscreenable (#37368)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-21 17:54:25 +01:00
trop[bot]
85802682b5 fix: restoring X11 window should not remove previous maximize state (#37358)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2023-02-21 12:50:40 +01:00
trop[bot]
f04ad15b65 build: remove unused python code (#37357)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-21 12:10:38 +01:00
trop[bot]
38ca7c0860 test: fix "crash cases" tests not failing properly (#37325)
* test: fix "crash cases" tests not failing properly

Co-authored-by: Alexey Kuzmin <alex.s.kuzmin@gmail.com>

* fixup! test: fix "crash cases" tests not failing properly

Co-authored-by: Alexey Kuzmin <alex.s.kuzmin@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Alexey Kuzmin <alex.s.kuzmin@gmail.com>
2023-02-20 12:53:09 +01:00
trop[bot]
309349a020 test: use render-process-gone event in tests (#37302)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <miburda@microsoft.com>
2023-02-19 01:29:53 -08:00
trop[bot]
f3d50c674c test: rename & split internal module tests (#37328)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <miburda@microsoft.com>
2023-02-19 01:26:08 -08:00
trop[bot]
8e74a37505 docs: only macOS 10.13+ is supported now (#37288)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <miburda@microsoft.com>
2023-02-16 15:56:01 -08:00
trop[bot]
426c23446c chore: remove redundant Node.js patch (#37307)
* chore: fix patch for other patch

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: update patches

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2023-02-16 15:49:05 -08:00
trop[bot]
52adfe2137 test: use webContents.create() in type-safe way (#37308)
test: use (webContents as typeof ElectronInternal.WebContents).create()

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <miburda@microsoft.com>
2023-02-16 15:48:38 -08:00
Keeley Hammond
4911476787 build: use node18.12 appveyor image (24-x-y) (#37283)
build: use node18.12 appveyor image
2023-02-15 14:22:45 -08:00
trop[bot]
7a62411ad1 feat: include all standard paper sizes for webContents.print() (#37265)
feat: include standard paper sizes for webContents.print()

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-15 19:58:49 +01:00
trop[bot]
99d648faaa fix: cookies filter secure invalid (#37246)
* fix: cookies filter secure and session invalid

Co-authored-by: Black-Hole1 <158blackhole@gmail.com>

* test: add test

Co-authored-by: Black-Hole1 <158blackhole@gmail.com>

* test: fix test failed

Co-authored-by: Black-Hole1 <158blackhole@gmail.com>

* test: fix test failed again

Co-authored-by: Black-Hole1 <158blackhole@gmail.com>

* fix: session check logic incorrect

Co-authored-by: Black-Hole1 <158blackhole@gmail.com>

* refactor: reset cookies filter session logic

Co-authored-by: Black-Hole1 <158blackhole@gmail.com>

* Update shell/browser/api/electron_api_cookies.cc

Co-authored-by: Robo <hop2deep@gmail.com>

Co-authored-by: Black-Hole <158blackhole@gmail.com>

* chore: re-enable worker spec failures (#37015)

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Black-Hole1 <158blackhole@gmail.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-15 12:38:01 +01:00
trop[bot]
91776c5484 docs: add win.isFocusable() return type (#37260)
docs: fix `win.isFocusable()` return type

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Andrew Ferreira <andrew.shien2@gmail.com>
2023-02-15 11:52:11 +01:00
trop[bot]
1a48e36313 fix: BrowserView crash when 'beforeunload' prevented (#37268)
fix: crash when beforeunload prevented

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-14 20:54:52 +01:00
trop[bot]
97c66e5985 refactor: simplify Node.js event loop with SpinEventLoop (#37258)
refactor: simplify Node.js event loop with SpinEventLoop

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-02-14 18:42:05 +01:00
trop[bot]
4ce69207b0 build: set make_latest correctly on published releases (#37242)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <marshallofsound@electronjs.org>
2023-02-13 14:47:26 +01:00
trop[bot]
cf4f6285c8 chore: update https://cs.chromium.org/ links to https://source.chromium.org/ (#37234)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <miburda@microsoft.com>
2023-02-13 16:32:32 +09:00
trop[bot]
253a60f8ae ci: update appveyor build agent (#37229)
ci update appveyor image

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2023-02-11 19:20:22 -05:00
140 changed files with 3355 additions and 4023 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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':

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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_

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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_

View File

@@ -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

View File

@@ -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>`

View File

@@ -0,0 +1,3 @@
# Event Object extends `GlobalEvent`
* `preventDefault` VoidFunction

View File

@@ -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`.

View File

@@ -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[]

View File

@@ -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).

View File

@@ -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",

View File

@@ -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",

View File

@@ -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', {

View File

@@ -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,

View File

@@ -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')) {

View File

@@ -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 };
}
}

View File

@@ -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
// requestObjects signals 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 requestObjects signals 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;
}

View File

@@ -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', {

View File

@@ -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,

View File

@@ -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

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -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",

View File

@@ -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 @@

View File

@@ -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):

View File

@@ -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):

View File

@@ -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.

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -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',

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;
}

View 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)

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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};
};

View File

@@ -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);

View File

@@ -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();
};

View 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
View 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_

View File

@@ -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

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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
View 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;

View File

@@ -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);

View 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

View File

@@ -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_;

View File

@@ -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

View File

@@ -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_

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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_

View File

@@ -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 {

View File

@@ -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

View File

@@ -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_

View 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

View File

@@ -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;
}
};

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -85,7 +85,7 @@ class NodeBindings {
virtual ~NodeBindings();
// Setup V8, libuv.
void Initialize(v8::Local<v8::Context> context);
void Initialize();
void SetNodeCliFlags();

View File

@@ -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.

View File

@@ -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());
}

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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');

View File

@@ -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

View File

@@ -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++;
}

View File

@@ -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');

View File

@@ -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(() => {

View File

@@ -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');

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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']);

View File

@@ -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);

View File

@@ -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);
});
});

View File

@@ -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();

View File

@@ -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